Envelopes Shape Time
A raw oscillator is a voltage that never changes its mind — press a key and it drones at full amplitude forever. Real notes have a shape: a hammer strike that blooms and dies, a bowed swell, a pad that breathes in slowly. In a modular synth that shape is itself a voltage. The ADSR envelope listens to a gate — the key going down — and answers with a contour from 0 to 10 V that other modules obey. Loudness is a contour, not a constant. Grab the curve below and bend it.
The primary interaction here is dragging the handles on the curve itself —
this is the envelope you would otherwise dial in with four knobs. The
violet curve is the module’s env output (its
internal 0–1 level times 10 V); the green bar is
the gate, high from the moment the key goes down until you let go —
0.80 s
later (scrub that number, or drag the marker on the bar). Drag the attack peak
and the release end left and right to set their times; drag the decay corner
sideways for decay time and up or down for the sustain level. Watch the
readouts: each stage shows both its time and the 0–1 knob CV that produces
it — the module’s time inputs sweep 1 ms to 10 s exponentially, so the first
few pixels of a drag move milliseconds and the last few move whole seconds.
The blue band underneath is the same envelope
multiplied into a saw wave at C3 — press ▶ hear it (or gate, which is
just pressing the key again) and that exact buffer plays. Below that, the
knob law strip plots all three time knobs on the module’s actual law,
\( T(\text{cv}) = 0.001 \cdot 10000^{\text{cv}} \), drawn on a log-time
axis where an exponential is a straight line — drag a time handle and watch
its marker slide along the very curve your finger is feeling. Finally, the
retrigger mid-release toggle simulates a second key press arriving halfway
through the release: the coral second pass shows
the module’s real behavior — the new attack climbs from the current level,
never resetting to zero.
Things to try
- Make a pluck. Drag the attack peak hard left (about 1 ms), the decay corner left to ~100 ms and all the way down so sustain is zero. The gate length now barely matters — the note is over before the key comes up. Hear it: that’s a mallet, a pizzicato, a bass stab.
- Make a pad. Drag the attack peak right until attack is 1–2 s and raise sustain near the top. The note now arrives instead of starting. Notice the time axis rescaling to keep the whole story on screen.
- Sustain is a level, not a time. With sustain at 0.6, scrub the gate length from 0.1 s to 4 s. Attack, decay, and release never change — only the flat sustain shelf stretches. The player owns that segment, not the module.
- Flip on exponential stages and compare releases. The linear ramp ends with an audible corner; the one-pole curve loses a constant fraction of its level per unit time — like a real string, a real RC circuit — and simply fades below hearing. That’s why exponential releases sound more natural.
- The release time is honest. Set sustain low (0.2) and release to ~1 s, and watch the release segment: it still takes the full labeled time to reach zero. The module scales the release rate by the level it captured at gate-off, so “release = 1 s” means one second from wherever the envelope was, not from an imaginary full-scale peak.
- Cut a stage short. Make the attack longer than the gate. The envelope never reaches the peak — the gate falls mid-climb and the release begins from the current level. Envelopes follow the key, not the plan.
- Retrigger mid-release. Flip on retrigger mid-release and hear it: the key comes up, the note starts dying, and a second key press lands halfway through the release. The coral pass climbs from wherever the level is — this is legato playing, and it is why envelopes continue from the current level. A zero-reset would snap the voltage to 0 in one sample and click on every fast repeated note.
- Watch the knob law. Drag the attack peak slowly from hard left to hard right while watching the A marker in the strip below. It rides a straight line on log-time paper — equal pixels of drag multiply the time by equal factors. That single law governs every time knob in the module.
What you just saw
The widget runs the same stage machine as Adsr::tick. In linear mode (the
shape input at 0 V) each segment is a constant per-sample rate, scaled by the
span it actually has to traverse:
\[ \text{attack\_rate} = \frac{1}{A \cdot f_s}, \qquad \text{decay\_rate} = \frac{1 - S}{D \cdot f_s}, \qquad \text{release\rate} = \frac{\ell{\text{gate-off}}}{R \cdot f_s} \]
where \( A, D, R \) are the stage times in seconds, \( S \) is the sustain level, and \( \ell_{\text{gate-off}} \) is the level captured the instant the gate falls — the scaling from “things to try” #5. In exponential mode each stage becomes a one-pole approach toward its target (1 for attack, \( S \) for decay, 0 for release), with the stage time as the time constant:
\[ c = e^{-1/(T f_s)}, \qquad \ell \leftarrow \ell + (\text{target} - \ell)(1 - c) \]
Every step closes a fixed fraction \( 1 - c \) of the remaining distance, which is exactly why the curve is steep at first and asymptotic at the end. And the knob law you felt while dragging — fine near the left, coarse near the right — is the exponential map from a 0–1 CV to seconds:
\[ T(\text{cv}) = 0.001 \cdot 10000^{\text{cv}} \]
so cv = 0 is 1 ms, cv = 0.5 is 100 ms, and cv = 1 is 10 s: each quarter turn of the knob multiplies the time by ten.
The Quiver code
The classic patch: a gate presses the envelope’s key, and the envelope’s 0–10 V contour drives a VCA sitting on the audio path — voltage shaping voltage, exactly what the widget draws.
use quiver::prelude::*;
use std::sync::Arc;
let sample_rate = 44100.0;
let mut patch = Patch::new(sample_rate);
// A key press, as voltage: this line goes 0 V -> +5 V -> 0 V.
let gate_cv = Arc::new(AtomicF64::new(0.0));
let gate = patch.add("gate", ExternalInput::gate(Arc::clone(&gate_cv)));
let vco = patch.add("vco", Vco::new(sample_rate));
let env = patch.add("env", Adsr::new(sample_rate));
let vca = patch.add("vca", Vca::new());
let out = patch.add("out", StereoOutput::new());
// The gate presses the envelope's key.
patch.connect(gate.out("out"), env.in_("gate")).unwrap();
// Audio path: raw saw through the VCA.
patch.connect(vco.out("saw"), vca.in_("in")).unwrap();
patch.connect(vca.out("out"), out.in_("left")).unwrap();
patch.connect(vca.out("out"), out.in_("right")).unwrap();
// The envelope's 0-10 V `env` output drives the VCA's `cv`:
// loudness IS the contour you just dragged.
patch.connect(env.out("env"), vca.in_("cv")).unwrap();
patch.set_output(out.id());
patch.compile().unwrap();
gate_cv.set(5.0); // key down: attack -> decay -> sustain...
// ... tick() for the length of the note ...
gate_cv.set(0.0); // key up: release, from the current level
The Adsr’s attack, decay, sustain, and release are themselves CV
inputs (0–1 through the knob law above), so anything — an LFO, a sequencer
row, another envelope — can reshape the shape. It also offers a retrig
trigger input (restart the attack from the current level without dropping the
gate — the same never-reset-to-zero behavior the retrigger toggle above
demonstrates), an inv output (the contour upside down, for ducking), and an eoc
end-of-cycle trigger for chaining. Set the shape input high (+5 V) for the
exponential stages you toggled above.
Go deeper
- Tutorial: Envelope Shaping builds this patch step by step and modulates the filter with the same contour.
- Reference: Modulators — the full
Adsrport list, plus the LFO and the other contour generators. - Next explorable: One Volt per Octave — the other CV convention every module agrees on: pitch as voltage.
Next: One Volt per Octave