7.12.9. STRUDEL-09 — Signals & Modulation
So far every effect parameter has been a constant — lpf(800),
gain(0.5). This tutorial introduces signals: patterns whose Haps
carry numbers instead of sounds, and the pattern-valued setters that
let a signal drive any effect parameter over time. A filter that sweeps, a
gain that swells, a pan that drifts — all of it is one idea: feed a signal
where you used to write a constant.
7.12.9.1. A signal is just a pattern
A signal is an ordinary Pattern; the difference is what its Haps carry.
Query one by hand and you get a number, not a drum hit. sine() is a
continuous signal — one query window yields one Hap holding the wave’s
value at the start of that window:
let sig <- sine()
var h <- invoke(sig, TimeSpan(start = 0.0lf, stop = 1.0lf))
print("value = {h[0].value.note}\n")
// output: value = 0.5
At phase 0 a strudel sine sits at its midpoint, 0.5 — it sweeps
0 -> 1 -> 0 across each cycle. run(n) is a discrete signal: n
equal steps valued 0, 1, ... n-1:
var hr <- invoke(run(4), TimeSpan(start = 0.0lf, stop = 1.0lf))
// four haps: 0 1 2 3 (one per quarter cycle)
The full signal zoo: continuous sine, cosine, saw, isaw,
tri, itri, square, perlin, rand (all running 0..1);
discrete run(n) and irand(n).
7.12.9.2. range: remap into a useful span
The continuous signals all run 0..1. range(lo, hi) stretches that
into the span you actually want — a cutoff in Hz, a gain, a pan position.
run() can drive pitch directly; run(8) |> add(48) is a chromatic
ramp from MIDI 48 (C3) upward:
let pat <- run(8) |> add(48.0) |> sound("sine") |> sustain(0.3)
play(pat, 0.5lf)
7.12.9.3. Pattern-valued setters: a moving filter
This is the headline. Every per-event setter (lpf, gain, pan,
speed, …) has an overload that takes a Pattern instead of a
constant. Feed it a signal and the parameter tracks that signal over time:
let pat <- (
atom("sawtooth") |> note(48.0) |> sustain(1.0)
|> lpf(sine() |> range(200.0, 4000.0) |> slow(4.0lf))
)
play(pat, 0.5lf, 8.0lf)
sine() |> range(200, 4000) |> slow(4) sweeps the cutoff from 200 Hz to
4 kHz and back over four cycles — the classic filter-sweep pad. Note the
surrounding parens: a multi-line pipe chain at statement level must be
wrapped in (...).
7.12.9.4. Modulating gain and pan
The same trick works for any setter. A slow sine on gain is a smooth
volume swell; a slow sine on pan drifts the sound across the stereo
field. Stack them and one sustained tone breathes and moves:
let pat <- (
atom("sine") |> note(48.0) |> sustain(1.0)
|> gain(sine() |> slow(2.0lf))
|> pan(sine() |> slow(4.0lf))
)
play(pat, 0.5lf, 8.0lf)
7.12.9.5. The noisy signals: perlin and rand
rand() is white-noise random — a fresh value every query, good for
scattering pan or velocity. perlin() is smooth/organic random — it
wanders instead of jumping, ideal for slow, living modulation:
// perlin drives a slow, unpredictable gain swell on a pad
let pad <- (
atom("sawtooth") |> note(48.0) |> sustain(1.0) |> lpf(800.0)
|> gain(perlin() |> range(0.1, 0.8) |> slow(4.0lf))
)
// rand scatters each note to a random stereo position
let mel <- note("c4 e4 g4 a4 g4 e4", "sine") |> sustain(0.5) |> pan(rand())
7.12.9.6. Where next
You now know:
A signal is a
Patternwhose Haps carry numbers (in thenotefield)range(lo, hi)remaps a0..1signal into any spanEvery setter has a pattern-valued overload — feed it a signal to make the parameter move over time
slow()stretches a signal’s motion across several cyclesperlinwanders smoothly;randjumps
Tutorial 10 covers ADSR envelopes — shaping the amplitude of each note over its lifetime, the other half of “sound that changes over time”.
See also
Full source: tutorials/daStrudel/daStrudel_09_signals_modulation.das
Previous tutorial: STRUDEL-08 — Effects & Filters
Next tutorial: STRUDEL-10 — ADSR & Envelope Shaping
Related: STRUDEL-08 — Effects & Filters — the constant-valued setters these signals replace