7.10.7. STRUDEL-07 — Per-Voice FX & Combinators
This tutorial covers the transforming combinators — those that take a
@(Pattern) => Pattern lambda and use it to derive a second copy of
the input — and the daslang-specific per-voice FX group (phaser,
tremolo, compressor, shape, crush).
7.10.7.1. Part A: jux — stereo splitting via a transform
jux(pat, fn) plays the original pattern in the left channel and
fn(pat) in the right. The classic move is to reverse the right
side, producing the trademark live-coding stereo wobble:
let pat <- jux(note("c4 e4 g4 c5", "sine") |> sustain(0.4), @(p) => rev(p))
Or transpose the right side up a fifth for instant harmony:
let pat <- jux(note("c4 e4 g4 c5", "sine") |> sustain(0.4),
@(p) => transpose(p, 7.0))
Any function Pattern → Pattern works as the transform — including
combinators built out of stack, cat or fast.
7.10.7.2. Part B: off — canon, delayed transformed copy
off(pat, time, fn) plays the original AND a copy of fn(pat)
delayed by time cycles. With time = 0.25 and an octave-up
transpose you get a textbook canon at the octave:
let pat <- off(note("c4 e4 g4 b4", "sine") |> sustain(0.4), 0.25lf,
@(p) => transpose(p, 12.0))
The delayed voice obeys whatever the transform does to it — try
@(p) => fast(p, 2.0lf) to layer a faster echo, or rev for a
retrograde answer.
7.10.7.3. Part C: chunk — apply a transform to one slice at a time
chunk(pat, n, fn) divides each cycle into n equal slices and
applies fn to slice 0 on cycle 0, slice 1 on cycle 1, and so on.
Use it to add evolving variation without rewriting the pattern:
let pat <- chunk(note("c4 e4 g4 c5", "sine") |> sustain(0.4), 4,
@(p) => fast(p, 2.0lf))
Each beat in turn doubles in speed for one cycle, then settles back.
7.10.7.4. Part D: per-voice FX (a daslang divergence)
Most live-coding systems treat phaser, tremolo, compressor,
shape, crush as effects on a shared bus. In daslang these are
per-voice — each playing voice carries its own FX chain, applied
before the voice mix is summed into the orbit. Two simultaneous
notes in a single stack can therefore use entirely different FX
parameters without any cross-talk:
let pat <- stack([
note("c4", "sawtooth") |> sustain(1.0) |> phaser(0.5) |> gain(0.4),
note("g4", "sawtooth") |> sustain(1.0) |> phaser(2.0) |> gain(0.4)
])
Above, the C voice carries a slow phaser sweep while the G voice carries a fast one. On a shared-bus system both voices would have to share the same rate.
The per-voice setters available today: lpf, hpf, phaser /
ph, tremolo / trem, compressor, shape, crush,
coarse. The shared-bus setters (room, delay, chorus)
are covered in the next tutorial.
See also
Full source: tutorials/daStrudel/daStrudel_07_per_voice_fx.das
Previous tutorial: STRUDEL-06 — Stacking & Combining
Next tutorial: STRUDEL-08 — Effects & Filters
Related: Lambdas and Closures, Function Pointers