7.10.9. STRUDEL-09 — ADSR & Envelope Shaping
An ADSR envelope shapes a note’s amplitude over time:
Attack |
time from key-down to peak amplitude |
Decay |
time to fall from the peak to the sustain level |
Sustain |
held level (0..1) while the note is on |
Release |
time to fade to silence after key-up |
daslang’s strudel uses tempo-aware defaults for ADSR. Instead of
fixed numeric defaults, each field defaults to -1 — a sentinel
meaning unset. At playback time a resolver fills in sensible values
based on which fields the user actually touched:
Nothing set |
held tone — sustain = 1, tiny attack/release |
Only |
percussion — sustain auto-zeroes |
|
always wins |
The same idea applies to delaytime: it defaults to -1 and is
derived from delaysync (in cycles) and the current cps so the echo
locks to tempo automatically.
7.10.9.1. Part A: default envelope — held tone
With no ADSR setters at all, the resolver picks a held-tone envelope: the note rings for its full scheduled duration, instead of decaying to silence in 50ms as in older defaults.
let pat <- note("c4 e4 g4 c5", "sine")
This is the unset → held branch of the resolver.
7.10.9.2. Part B: pluck — decay only
Set decay alone and the resolver assumes a percussive envelope:
sustain drops to 0 so each note plucks then decays away. This is the
idiom for short, rhythmic synth blips:
let pat <- note("c4 e4 g4 c5", "sine") |> decay(0.15)
No need to spell out sustain(0.0) — the resolver does it because
“only decay was set”.
7.10.9.3. Part C: pad — explicit ADSR
Setting all four fields gives full manual control. A pad-style envelope uses a long attack so the note swells in, plus a long release so it fades out gradually after key-up:
let pat <- note("c3", "triangle") |> attack(0.5) |> decay(0.2) |> sustain(0.6) |> release(0.5)
Because sustain was set explicitly, the resolver leaves all four
values untouched.
7.10.9.4. Part D: tempo-aware delay defaults
The “unset sentinel + resolver” idea also applies to delaytime.
Plain delay(0.6) without delaytime(...) derives delaytime
from delaysync (3/16 cycle by default) and the current cps. This
gives a dotted-eighth feel that automatically tracks tempo
changes:
let pat_slow <- note("c4 ~ e4 ~ g4 ~", "sine") |> decay(0.15) |> delay(0.6)
play(pat_slow, 0.5lf, 4.0lf)
let pat_fast <- note("c4 ~ e4 ~ g4 ~", "sine") |> decay(0.15) |> delay(0.6)
play(pat_fast, 1.0lf, 4.0lf)
Same pattern, two different cps — the echo timing follows the tempo.
Setting delaytime (or delaysync) explicitly bypasses the
resolver.
7.10.9.5. Part E: velocity via gain
gain multiplies the envelope output. Use it for a velocity curve
over a pattern: alternating loud and soft hits while the envelope shape
stays constant.
let pat <- stack([
note("c4", "sine") |> decay(0.15) |> gain(0.9),
note("e4", "sine") |> decay(0.15) |> gain(0.4),
note("g4", "sine") |> decay(0.15) |> gain(0.7),
note("c5", "sine") |> decay(0.15) |> gain(0.3)
])
Each voice keeps the same plucky envelope but a different peak level.
See also
Full source: tutorials/daStrudel/daStrudel_09_adsr_envelopes.das
Previous tutorial: STRUDEL-08 — Effects & Filters
Next tutorial: STRUDEL-10 — Scales & Music Theory