05_07_03_envelope - Envelope Generator Atoms¶
Overview¶
Envelope generators shape the amplitude, timbre, and other parameters of sound over time. This subsystem provides three fundamental envelope types used across all audio synthesis and processing:
- ADSREnvelope - Classic four-stage envelope (Attack-Decay-Sustain-Release)
- AREnvelope - Simple two-stage envelope (Attack-Release) for percussive sounds
- EnvelopeFollower - Tracks the amplitude envelope of incoming audio
π― Envelope Types¶
1. ADSREnvelope - The Synthesizer Workhorse¶
Purpose: The most common envelope in synthesis. Four stages provide complete control over sound evolution.
Stages: - Attack - Rise from 0 to peak (note on) - Decay - Fall from peak to sustain level - Sustain - Hold at sustain level (while key is held) - Release - Fall to zero (note off)
Features:
- Exponential and linear curve options
- Velocity sensitivity
- Retrigger support (legato mode)
- Multi-channel processing
- PercussiveEnvelope wrapper (preset for drums)
Typical Applications:
// Amplitude envelope for synth pad
ADSREnvelope<float> amp_env;
amp_env.set_attack_time(0.5f); // Slow attack (pad)
amp_env.set_decay_time(0.2f);
amp_env.set_sustain_level(0.7f);
amp_env.set_release_time(1.0f); // Long release (smooth)
// Filter envelope for pluck
ADSREnvelope<float> filter_env;
filter_env.set_attack_time(0.01f); // Fast attack
filter_env.set_decay_time(0.3f); // Medium decay
filter_env.set_sustain_level(0.3f); // Low sustain
filter_env.set_release_time(0.1f);
2. AREnvelope - Percussive Simplicity¶
Purpose: Two-stage envelope for percussive sounds that don't need sustain.
Stages: - Attack - Rise from 0 to peak - Release - Automatically fall to zero (no need for note_off)
Features:
- Simpler than ADSR (more CPU-efficient)
- Automatic completion (no sustain stage)
- trigger() vs retrigger() modes
- DrumEnvelope wrapper (ultra-fast attack)
- PluckEnvelope wrapper (instant attack, long decay)
Typical Applications:
// Drum synthesis
DrumEnvelope<float> kick_env;
kick_env.set_decay_time(0.15f); // 150ms decay
kick_env.trigger(1.0f);
// Plucked bass
PluckEnvelope<float> bass_env;
bass_env.set_decay_time(0.8f); // Long decay
bass_env.trigger(0.9f);
// Hi-hat
AREnvelope<float> hihat_env;
hihat_env.set_attack_time(0.0001f); // Instant
hihat_env.set_release_time(0.05f); // Very short
hihat_env.trigger(1.0f);
3. EnvelopeFollower - Audio-to-Control Conversion¶
Purpose: Tracks the amplitude envelope of an incoming audio signal.
How it works: 1. Rectify input signal (|x[n]|) 2. Apply asymmetric smoothing (fast attack, slow release) 3. Output smooth envelope curve
Features:
- Peak or RMS detection modes
- Independent attack/release times
- CompressorSidechain wrapper (optimized for dynamics)
- PeakMeter wrapper (VU-style metering)
Typical Applications:
// Compressor sidechain
CompressorSidechain<float> sidechain;
sidechain.set_attack_time(0.001f); // 1ms attack
sidechain.set_release_time(0.1f); // 100ms release
float envelope = sidechain.process_sample(input);
float gain_reduction = compute_compression(envelope, threshold, ratio);
output = input * gain_reduction;
// Auto-wah effect
EnvelopeFollower<float> follower;
follower.set_attack_time(0.01f);
follower.set_release_time(0.2f);
float envelope = follower.process_sample(input);
float cutoff = 200.0f + envelope * 1800.0f; // 200-2000 Hz
filter.set_cutoff(cutoff);
// Peak meter
PeakMeter<float> meter;
meter.process_sample(input);
float level_db = meter.get_level_db(); // For GUI display
π Quick Start Examples¶
Example 1: Basic ADSR Envelope¶
#include "adsr_envelope.h"
// Create envelope
ADSREnvelope<float> env;
env.set_sample_rate(44100.0f);
env.set_attack_time(0.01f); // 10ms
env.set_decay_time(0.1f); // 100ms
env.set_sustain_level(0.7f); // 70%
env.set_release_time(0.5f); // 500ms
// Trigger note
env.note_on(1.0f); // Full velocity
// Generate samples
for (size_t i = 0; i < buffer_size; ++i) {
float env_value = env.generate();
output[i] = oscillator.generate() * env_value;
}
// Release note
env.note_off();
// Continue generating (release stage)
for (size_t i = 0; i < buffer_size; ++i) {
float env_value = env.generate();
output[i] = oscillator.generate() * env_value;
}
Example 2: Drum Synthesis with AR Envelope¶
#include "ar_envelope.h"
// Create drum envelope
DrumEnvelope<float> env;
env.set_sample_rate(44100.0f);
env.set_decay_time(0.2f); // 200ms decay
// Trigger drum hit
env.trigger(1.0f);
// Generate drum sound (automatically completes)
while (env.is_active()) {
float env_value = env.generate();
float osc = sine_oscillator.generate();
output[i++] = osc * env_value;
}
Example 3: Envelope Follower for Dynamics¶
#include "envelope_follower.h"
// Create envelope follower
EnvelopeFollower<float> follower;
follower.set_sample_rate(44100.0f);
follower.set_attack_time(0.005f); // 5ms attack (capture transients)
follower.set_release_time(0.1f); // 100ms release (smooth)
follower.set_mode(EnvelopeDetectionMode::PEAK);
// Process audio
for (size_t i = 0; i < buffer_size; ++i) {
float envelope = follower.process_sample(input[i]);
// Use for compression
float threshold = 0.5f;
float ratio = 4.0f;
if (envelope > threshold) {
float excess = envelope - threshold;
float reduction = excess * (1.0f - 1.0f/ratio);
float target = envelope - reduction;
float gain = target / envelope;
output[i] = input[i] * gain;
} else {
output[i] = input[i];
}
}
Example 4: Multi-Channel Envelope Processing¶
#include "adsr_envelope.h"
#include "audio_buffer.h"
// Create envelope
ADSREnvelope<float> env;
env.set_sample_rate(44100.0f);
env.note_on(1.0f);
// Process stereo buffer
AudioBuffer<float> buffer(2, 512); // Stereo, 512 samples
// Fill buffer with oscillator
for (size_t ch = 0; ch < 2; ++ch) {
for (size_t i = 0; i < 512; ++i) {
buffer.get_channel_data(ch)[i] = oscillator.generate();
}
}
// Apply envelope to both channels
env.process(buffer);
π¨ Curve Types¶
Exponential Curves (Musical, Natural)¶
Characteristics: - Fast initial change, gradual approach to target - Mimics natural acoustic envelopes - More "musical" sound - Perceived as linear by human ear (logarithmic perception)
When to use: - Most synthesizer envelopes - Amplitude envelopes (perceived linearity) - Filter envelopes (smooth transitions) - General-purpose synthesis
Formula:
Linear Curves (Mechanical, Precise)¶
Characteristics: - Constant rate of change - Mathematically linear - More "mechanical" sound - Perceived as exponential by human ear
When to use: - Special effects - Precise timing requirements - Scientific/test applications - Modulation envelopes (not amplitude)
Formula:
Comparison:
// Exponential (default, recommended)
env.set_curve(ADSRCurve::EXPONENTIAL);
env.set_attack_time(0.1f); // Reaches 99% in 100ms
// Linear (mechanical)
env.set_curve(ADSRCurve::LINEAR);
env.set_attack_time(0.1f); // Reaches 100% in 100ms
β‘ Performance Characteristics¶
ADSREnvelope¶
| Operation | Complexity | Notes |
|---|---|---|
generate() |
O(1) | 2-5 operations per sample |
process(buffer) |
O(N) | Linear with buffer size |
set_*_time() |
O(1) | Triggers coefficient update (exp/log) |
| Memory | ~40 bytes | Per-channel state |
CPU Usage (estimated): - Exponential curve: ~5 cycles/sample (1 multiply, 1 add, 1 branch) - Linear curve: ~3 cycles/sample (1 add, 1 branch)
AREnvelope¶
| Operation | Complexity | Notes |
|---|---|---|
generate() |
O(1) | Slightly faster than ADSR (fewer stages) |
| Memory | ~24 bytes | Per-channel state |
CPU Usage: ~20% less than ADSR (no sustain stage)
EnvelopeFollower¶
| Operation | Complexity | Notes |
|---|---|---|
process_sample() |
O(1) | 1-2 multiplies, 1-2 adds |
| Peak mode | O(1) | 1 abs(), 1 multiply, 1 add |
| RMS mode | O(1) | 2 multiplies, 2 adds, 1 sqrt() |
| Memory | ~16 bytes | Per-channel state |
CPU Usage: - Peak mode: ~3 cycles/sample - RMS mode: ~15 cycles/sample (sqrt is expensive)
π§ Advanced Techniques¶
1. Velocity Sensitivity¶
Control how much note velocity affects envelope output:
ADSREnvelope<float> env;
env.set_velocity_sensitivity(1.0f); // Full sensitivity (default)
env.note_on(0.5f); // Half velocity β 50% output level
env.set_velocity_sensitivity(0.0f); // No sensitivity
env.note_on(0.5f); // Half velocity β 100% output level
env.set_velocity_sensitivity(0.5f); // Partial sensitivity
env.note_on(0.5f); // Half velocity β 75% output level
2. Retrigger Behavior (Legato)¶
ADSR allows legato retriggering:
env.note_on(1.0f);
// Generate some samples...
// Retrigger without resetting current level (smooth legato)
env.note_on(1.0f); // Continues from current level
// Hard reset (percussive retriggering)
env.kill();
env.note_on(1.0f); // Starts from zero
AR provides explicit retrigger modes:
// Hard retrigger (percussive)
env.trigger(1.0f); // Always starts from zero
// Legato retrigger
env.retrigger(1.0f); // Continues from current level
3. Envelope Modulation (Envelope-of-Envelope)¶
Use one envelope to modulate another:
ADSREnvelope<float> amp_env;
ADSREnvelope<float> mod_env;
amp_env.note_on(1.0f);
mod_env.note_on(1.0f);
for (size_t i = 0; i < buffer_size; ++i) {
float amp = amp_env.generate();
float mod = mod_env.generate();
// Modulate amplitude envelope
float final_amp = amp * (0.5f + 0.5f * mod);
output[i] = oscillator.generate() * final_amp;
}
4. Dynamic Envelope Times¶
Modulate envelope times in real-time:
// Map velocity to attack time (soft notes = slow attack)
float velocity = 0.8f;
float attack_time = 0.001f + (1.0f - velocity) * 0.1f;
env.set_attack_time(attack_time);
env.note_on(velocity);
// Map filter cutoff to decay time
float cutoff_normalized = (cutoff - 200.0f) / 1800.0f;
float decay_time = 0.05f + cutoff_normalized * 0.5f;
env.set_decay_time(decay_time);
5. Compressor with RMS Detection¶
Use envelope follower in RMS mode for smoother compression:
EnvelopeFollower<float> follower;
follower.set_mode(EnvelopeDetectionMode::RMS);
follower.set_attack_time(0.005f);
follower.set_release_time(0.1f);
for (size_t i = 0; i < buffer_size; ++i) {
float envelope = follower.process_sample(input[i]);
// Soft-knee compression
float threshold = 0.5f;
float ratio = 4.0f;
float knee = 0.1f;
float gain = compute_soft_knee_compression(
envelope, threshold, ratio, knee
);
output[i] = input[i] * gain;
}
π Envelope Design Guidelines¶
Amplitude Envelopes¶
Pads/Strings: - Attack: 0.3 - 1.0s (slow, gentle) - Decay: 0.5 - 1.0s - Sustain: 0.7 - 0.9 (high) - Release: 1.0 - 3.0s (long, smooth) - Curve: Exponential
Plucks/Guitars: - Attack: 0.001 - 0.01s (instant to fast) - Decay: 0.5 - 2.0s - Sustain: 0.0 - 0.3 (low or none) - Release: 0.1 - 0.5s - Curve: Exponential
Brass: - Attack: 0.05 - 0.2s (medium) - Decay: 0.1 - 0.3s - Sustain: 0.8 - 1.0 (high) - Release: 0.2 - 0.5s - Curve: Exponential
Drums: - Attack: 0.0001 - 0.005s (instant) - Release: 0.05 - 0.5s (depending on drum type) - Curve: Exponential - Use AREnvelope or DrumEnvelope
Filter Envelopes¶
Typical ranges: - Attack: 0.01 - 0.1s (faster than amplitude) - Decay: 0.1 - 0.5s - Sustain: 0.2 - 0.5 (lower than amplitude) - Release: 0.1 - 0.3s (shorter than amplitude) - Curve: Exponential
Purpose: Shape filter cutoff for timbral evolution
Dynamics Processing¶
Compressor: - Attack: 0.001 - 0.01s (fast to catch transients) - Release: 0.05 - 0.3s (musical release) - Mode: PEAK (accurate) or RMS (smooth)
Limiter: - Attack: 0.0001 - 0.001s (ultra-fast) - Release: 0.05 - 0.1s (fast recovery) - Mode: PEAK (prevent clipping)
Gate: - Attack: 0.001 - 0.005s (fast) - Release: 0.05 - 0.2s (avoid chatter) - Mode: RMS (smooth gating)
π§ͺ Testing¶
All envelope atoms include comprehensive test suites:
Build and Run Tests¶
Test Coverage¶
ADSREnvelope: - β Constructor and initialization (3 tests) - β Parameter setters (10 tests) - β Stage transitions - exponential (7 tests) - β Stage transitions - linear (2 tests) - β Velocity sensitivity (3 tests) - β Retrigger behavior (2 tests) - β Kill function (2 tests) - β Multi-channel processing (3 tests) - β Edge cases (8 tests) - β Reset function (2 tests) - β Percussive envelope wrapper (3 tests) - β Double precision (1 test)
Total: 46 test cases
AREnvelope (planned): - Constructor and initialization - Stage transitions - trigger() vs retrigger() - Wrapper classes (DrumEnvelope, PluckEnvelope)
EnvelopeFollower (planned): - Peak vs RMS detection modes - Attack/release smoothing - Multi-channel processing - Wrapper classes (CompressorSidechain, PeakMeter)
π API Reference¶
ADSREnvelope¶
// Construction
ADSREnvelope();
// Configuration
void set_sample_rate(T sample_rate);
void set_attack_time(T time_seconds);
void set_decay_time(T time_seconds);
void set_sustain_level(T level); // [0.0, 1.0]
void set_release_time(T time_seconds);
void set_curve(ADSRCurve curve); // LINEAR or EXPONENTIAL
void set_velocity_sensitivity(T sensitivity); // [0.0, 1.0]
// Triggering
void note_on(T velocity = 1.0); // Start attack
void note_off(); // Start release
void kill(); // Immediate silence
// Generation
T generate(); // Generate single sample
void process(AudioBuffer<T>& buffer); // Process buffer
// Status
bool is_active() const;
ADSRStage get_stage() const;
// Getters
T get_attack_time() const;
T get_decay_time() const;
T get_sustain_level() const;
T get_release_time() const;
ADSRCurve get_curve() const;
// Reset
void reset();
AREnvelope¶
// Construction
AREnvelope();
// Configuration
void set_sample_rate(T sample_rate);
void set_attack_time(T time_seconds);
void set_release_time(T time_seconds);
void set_curve(ARCurve curve);
void set_velocity_sensitivity(T sensitivity);
// Triggering
void trigger(T velocity = 1.0); // Hard retrigger (percussive)
void retrigger(T velocity = 1.0); // Legato retrigger
void kill();
// Generation
T generate();
void process(AudioBuffer<T>& buffer);
// Status
bool is_active() const;
ARStage get_stage() const;
// Getters
T get_attack_time() const;
T get_release_time() const;
ARCurve get_curve() const;
// Reset
void reset();
EnvelopeFollower¶
// Construction
EnvelopeFollower();
// Configuration
void set_sample_rate(T sample_rate);
void set_attack_time(T time_seconds);
void set_release_time(T time_seconds);
void set_mode(EnvelopeDetectionMode mode); // PEAK or RMS
// Processing
T process_sample(T input);
void process(AudioBuffer<T>& buffer);
// Status
T get_envelope_level() const;
// Getters
T get_attack_time() const;
T get_release_time() const;
EnvelopeDetectionMode get_mode() const;
// Reset
void reset();
π Integration with Other Atoms¶
With Oscillators¶
#include "adsr_envelope.h"
#include "sine_oscillator.h"
ADSREnvelope<float> amp_env;
SineOscillator<float> osc;
amp_env.note_on(1.0f);
osc.set_frequency(440.0f);
for (size_t i = 0; i < buffer_size; ++i) {
float env = amp_env.generate();
float osc_out = osc.generate();
output[i] = osc_out * env;
}
With Filters¶
#include "adsr_envelope.h"
#include "svf_filter.h"
ADSREnvelope<float> filter_env;
SVFFilter<float> filter;
filter_env.note_on(1.0f);
for (size_t i = 0; i < buffer_size; ++i) {
float env = filter_env.generate();
// Modulate filter cutoff (200 Hz - 5000 Hz)
float cutoff = 200.0f + env * 4800.0f;
filter.set_cutoff(cutoff);
output[i] = filter.process_sample(input[i]);
}
Dynamics Chain¶
#include "envelope_follower.h"
#include "onepole_filter.h"
EnvelopeFollower<float> sidechain;
OnePoleFilter<float> smoother;
sidechain.set_attack_time(0.005f);
sidechain.set_release_time(0.1f);
smoother.set_cutoff(10.0f); // 10 Hz smoothing
for (size_t i = 0; i < buffer_size; ++i) {
float envelope = sidechain.process_sample(input[i]);
float smoothed = smoother.process_sample(envelope);
float gain_reduction = compute_compressor_gain(smoothed);
output[i] = input[i] * gain_reduction;
}
π¦ Files¶
05_07_03_envelope/
βββ include/
β βββ adsr_envelope.h (~465 LOC) - Classic ADSR envelope
β βββ ar_envelope.h (~400 LOC) - Simple AR envelope
β βββ envelope_follower.h (~350 LOC) - Audio amplitude tracker
βββ tests/
β βββ test_adsr_envelope.cpp (~900 LOC) - 46 comprehensive tests
β βββ test_ar_envelope.cpp (planned)
β βββ test_envelope_follower.cpp (planned)
βββ CMakeLists.txt - Build configuration
βββ README.md - This file
βββ STATUS.md - Development progress tracking
π― Status¶
| Component | Implementation | Tests | Documentation |
|---|---|---|---|
| ADSREnvelope | β Complete | β 46 tests | β Complete |
| AREnvelope | β Complete | π Planned | β Complete |
| EnvelopeFollower | β Complete | π Planned | β Complete |
Overall Progress: 70% (Headers complete, tests partial)
π References¶
- "Synthesizer Basics" - Brent Hurtig (ADSR fundamentals)
- "The Computer Music Tutorial" - Curtis Roads (envelope theory)
- "Designing Software Synthesizer Plug-Ins in C++" - Will Pirkle (implementation)
- "Digital Audio Signal Processing" - Udo ZΓΆlzer (envelope follower)
- "The Art of VA Filter Design" - Vadim Zavalishin (TPT envelopes)
Last Updated: 2025-10-10 Maintainer: AudioLab Core Team