Envelope Shaping
An envelope generator shapes how a parameter changes over time. The classic ADSR (Attack, Decay, Sustain, Release) envelope is the heartbeat of synthesis.
graph LR
subgraph "ADSR Envelope"
A[Attack] --> D[Decay]
D --> S[Sustain]
S --> R[Release]
end
Anatomy of ADSR
│ ╱╲
│ ╱ ╲_______
│ ╱ ╲
│ ╱ ╲
│ ╱ ╲
────┴───────────────────────
A D S R
↑ ↑ ↑ ↑
Gate On Gate Off
| Stage | Description | Typical Range |
|---|---|---|
| Attack | Time to reach peak (0→5V) | 1ms - 10s |
| Decay | Time to fall to sustain level | 1ms - 10s |
| Sustain | Level held while gate is high | 0V - 5V |
| Release | Time to return to zero | 1ms - 10s |
The Mathematics
Each stage is typically an exponential curve:
Attack (exponential rise): $$v(t) = V_{max} \cdot (1 - e^{-t/\tau_a})$$
Decay/Release (exponential fall): $$v(t) = V_{start} \cdot e^{-t/\tau_d}$$
Where $\tau$ is the time constant. Analog envelopes have this natural exponential shape—it’s how capacitors charge and discharge.
Building the Example
//! Tutorial: Envelope Shaping
//!
//! Demonstrates the ADSR envelope generator and how it shapes sound over time.
//! This is fundamental to giving synthesized sounds their character.
//!
//! Run with: cargo run --example tutorial_envelope
use quiver::prelude::*;
use std::sync::Arc;
fn main() {
let sample_rate = 44100.0;
let mut patch = Patch::new(sample_rate);
// Gate control - simulates key press
let gate_cv = Arc::new(AtomicF64::new(0.0));
let gate = patch.add("gate", ExternalInput::gate(Arc::clone(&gate_cv)));
// Sound source
let vco = patch.add("vco", Vco::new(sample_rate));
// ADSR envelope generator
let env = patch.add("env", Adsr::new(sample_rate));
// Amplifier controlled by envelope
let vca = patch.add("vca", Vca::new());
// Output
let output = patch.add("output", StereoOutput::new());
// Connections
patch.connect(gate.out("out"), env.in_("gate")).unwrap();
patch.connect(vco.out("saw"), vca.in_("in")).unwrap();
patch.connect(env.out("env"), vca.in_("cv")).unwrap();
patch.connect(vca.out("out"), output.in_("left")).unwrap();
patch.set_output(output.id());
patch.compile().unwrap();
println!("=== ADSR Envelope Demo ===\n");
// Helper to get envelope level
fn run_samples(patch: &mut Patch, n: usize) -> f64 {
let mut last = 0.0;
for _ in 0..n {
let (left, _) = patch.tick();
last = left.abs();
}
last
}
// Start with gate off
println!("Initial state (gate off):");
let level = run_samples(&mut patch, 100);
println!(" Envelope level: {:.3}V\n", level);
// Gate ON - trigger attack
println!("Gate ON - Attack phase begins");
gate_cv.set(5.0);
// Sample the attack
for ms in [10, 25, 50, 100, 200] {
let samples = (sample_rate * ms as f64 / 1000.0) as usize;
let level = run_samples(&mut patch, samples);
println!(" {}ms: level = {:.2}V", ms, level * 5.0); // scale for display
}
// Let it reach sustain
println!("\nDecay → Sustain:");
let level = run_samples(&mut patch, (sample_rate * 0.5) as usize);
println!(" Sustain level: {:.2}V\n", level * 5.0);
// Gate OFF - trigger release
println!("Gate OFF - Release phase begins");
gate_cv.set(0.0);
for ms in [50, 100, 200, 500] {
let samples = (sample_rate * ms as f64 / 1000.0) as usize;
let level = run_samples(&mut patch, samples);
println!(" +{}ms: level = {:.3}V", ms, level * 5.0);
}
println!("\nThe envelope has completed its cycle.");
println!("Attack→Decay→Sustain (while held) →Release (when released)");
}
Envelope as Modulation Source
The envelope doesn’t just control volume. Route it to:
flowchart TD
ADSR[ADSR Envelope]
ADSR -->|brightness| VCF[Filter Cutoff]
ADSR -->|volume| VCA[Amplifier]
ADSR -->|depth| FM[FM Amount]
ADSR -->|color| PWM[Pulse Width]
Filter Envelope
Routing envelope to filter creates the classic “brightness sweep”:
- Plucky bass: Fast attack, fast decay, low sustain
- Brass stab: Medium attack, fast decay, medium sustain
- String pad: Slow attack, slow decay, high sustain
Dual Envelope Routing
Different amounts to different destinations:
| Destination | Amount | Effect |
|---|---|---|
| VCA | 100% | Full volume control |
| VCF | 50% | Subtle brightness sweep |
| Pitch | 5% | Pitch “blip” on attack |
Musical Applications
Plucky Synth Bass
Attack: 5ms (instant)
Decay: 200ms (quick fall)
Sustain: 30% (some body)
Release: 100ms (clean cutoff)
Swelling Pad
Attack: 2s (slow fade in)
Decay: 500ms (gentle settle)
Sustain: 80% (full and rich)
Release: 3s (long tail)
Percussive Hit
Attack: 1ms (instant)
Decay: 50ms (very fast)
Sustain: 0% (no sustain)
Release: 50ms (immediate)
Envelope Stages Visualization
sequenceDiagram
participant G as Gate
participant E as Envelope
Note over G,E: Note On
G->>E: Gate HIGH (+5V)
E->>E: Attack phase (rising)
E->>E: Decay phase (falling)
E->>E: Sustain phase (holding)
Note over G,E: Note Off
G->>E: Gate LOW (0V)
E->>E: Release phase (falling to 0)
Next: Filter Modulation