Skip to main content
The envelope module provides a flexible ADSR (Attack, Decay, Sustain, Release) envelope generator with support for velocity scaling, retriggering, and custom callbacks.

ADSREnvelope

A comprehensive ADSR envelope generator with advanced features including anti-click retriggering and configurable AD mode.

Template Parameters

T
typename
Sample type (typically double or float)

Constructor

ADSREnvelope(const char* name = "", 
             std::function<void()> resetFunc = nullptr,
             bool sustainEnabled = true)
name
const char*
default:"empty string"
Name for debugging (only used when DEBUG_ENV=1 is defined)
resetFunc
std::function<void()>
default:"nullptr"
Callback function to execute when envelope is retriggered (useful for resetting oscillator phase)
sustainEnabled
bool
default:"true"
If true, envelope is ADSR. If false, envelope is AD (suitable for drums)
Don’t call SetResetFunc() on the audio thread - std::function can allocate memory.

Envelope Stages

EStage

Enumeration of envelope stages:
kIdle
-1
Envelope is inactive
kAttack
0
Attack phase - rising from 0 to peak
kDecay
1
Decay phase - falling from peak to sustain level
kSustain
2
Sustain phase - held at sustain level
kRelease
3
Release phase - falling from current level to zero
kReleasedToRetrigger
-2
Fast fade to zero before retriggering (3ms)
kReleasedToEndEarly
-3
Fast fade to zero for voice stealing (20ms)

Methods

SetStageTime

void SetStageTime(int stage, T timeMS)
Set the duration of an envelope stage.
stage
int
Stage to set: kAttack, kDecay, or kRelease
timeMS
T
Duration in milliseconds (clamped to 0.022675 - 60000 ms)
Attack uses linear ramping, while decay and release use exponential curves for more natural-sounding envelopes.

Process

inline T Process(T sustainLevel = 0.)
Generate the next envelope sample.
sustainLevel
T
default:"0.0"
Sustain level for this sample (0.0 to 1.0). Passed as parameter to allow external smoothing.
Returns: Envelope output value scaled by velocity level

Start

inline void Start(T level, T timeScalar = 1.)
Trigger the envelope from the beginning.
level
T
Overall envelope depth, typically linked to MIDI velocity (0.0 to 1.0)
timeScalar
T
default:"1.0"
Factor to scale envelope rates (for key tracking). Values > 1.0 make the envelope faster.

Release

inline void Release()
Release the envelope (move to release stage).

Retrigger

inline void Retrigger(T newStartLevel, T timeScalar = 1.)
Retrigger the envelope while it’s still active. Performs a fast fade-out (3ms) then restarts.
newStartLevel
T
Velocity level for the new note
timeScalar
T
default:"1.0"
Time scaling factor for the new note
This method prevents clicks when voices are stolen or notes are retriggered before release completes.

Kill

inline void Kill(bool hard)
Force the envelope to end.
hard
bool
If true, instantly reset (may cause clicks). If false, fade out over 20ms.

SetSampleRate

void SetSampleRate(T sr)
Set the sample rate. Updates internal ramp coefficients.
sr
T
Sample rate in samples per second
You must also update attack, decay, and release times after changing sample rate.

GetBusy

bool GetBusy() const
Returns: true if envelope is not idle

GetReleased

bool GetReleased() const
Returns: true if envelope has been released

GetPrevOutput

T GetPrevOutput() const
Returns: Last output value (after velocity scaling)

SetResetFunc

void SetResetFunc(std::function<void()> func)
Set the callback function called when envelope is retriggered.
func
std::function<void()>
Callback function or nullptr to disable
Don’t call this on the audio thread - std::function assignment can allocate.

SetEndReleaseFunc

void SetEndReleaseFunc(std::function<void()> func)
Set the callback function called when envelope completes release.
func
std::function<void()>
Callback function or nullptr to disable

Constants

EARLY_RELEASE_TIME
T
default:"20.0"
Duration in milliseconds for soft kill fade-out
RETRIGGER_RELEASE_TIME
T
default:"3.0"
Duration in milliseconds for retrigger fade-out
MIN_ENV_TIME_MS
T
default:"0.022675736961451"
Minimum stage time (1 sample at 44.1kHz)
MAX_ENV_TIME_MS
T
default:"60000.0"
Maximum stage time (60 seconds)
ENV_VALUE_LOW
T
default:"0.000001"
Threshold value for considering envelope at zero (-120dB)
ENV_VALUE_HIGH
T
default:"0.999"
Threshold value for considering envelope at peak

Usage Examples

Basic ADSR Envelope

// Create envelope for a synthesizer voice
ADSREnvelope<double> env("VoiceEnv", nullptr, true);
env.SetSampleRate(44100.0);

// Set envelope times
env.SetStageTime(ADSREnvelope<double>::kAttack, 10.0);   // 10ms attack
env.SetStageTime(ADSREnvelope<double>::kDecay, 100.0);   // 100ms decay
env.SetStageTime(ADSREnvelope<double>::kRelease, 500.0); // 500ms release

// Trigger on note-on with velocity
void OnNoteOn(int velocity) {
  double level = velocity / 127.0;
  env.Start(level);
}

// Release on note-off
void OnNoteOff() {
  env.Release();
}

// Process audio
void ProcessBlock(double** output, int nFrames) {
  double sustainLevel = mSustainParam->Value();
  
  for (int i = 0; i < nFrames; i++) {
    double envValue = env.Process(sustainLevel);
    output[0][i] = oscillator.Process() * envValue;
  }
}

AD Envelope for Drums

// Create AD envelope (no sustain)
ADSREnvelope<double> drumEnv("DrumEnv", nullptr, false);
drumEnv.SetSampleRate(44100.0);
drumEnv.SetStageTime(ADSREnvelope<double>::kAttack, 1.0);  // 1ms attack
drumEnv.SetStageTime(ADSREnvelope<double>::kDecay, 200.0); // 200ms decay

// Trigger drum hit
drumEnv.Start(1.0);

// Process (no need to call Release - decays automatically)
for (int i = 0; i < blockSize; i++) {
  output[i] = drumSample[i] * drumEnv.Process();
}

Envelope with Retrigger and Callbacks

class Voice {
  SinOscillator<double> osc;
  ADSREnvelope<double> env;
  
public:
  Voice() : env("Voice", [this]() { 
    // Reset oscillator phase when retriggered
    osc.Reset(); 
  }, true) {
    env.SetSampleRate(44100.0);
  }
  
  void Trigger(double freq, double velocity, bool isRetrigger) {
    osc.SetFreqCPS(freq);
    
    if (isRetrigger && env.GetBusy()) {
      env.Retrigger(velocity);
    } else {
      env.Start(velocity);
    }
  }
  
  double Process(double sustainLevel) {
    return osc.Process() * env.Process(sustainLevel);
  }
};

Key Tracking (Velocity Scaling)

// Make higher notes have faster envelopes
void TriggerNote(int midiNote, int velocity) {
  double level = velocity / 127.0;
  
  // Scale envelope speed based on note (middle C = 1.0)
  double keyTrack = pow(2.0, (midiNote - 60) / 12.0);
  
  env.Start(level, keyTrack);
}

Implementation Details

Curve Types

Attack

Linear ramp from 0 to 1 for predictable rise time

Decay

Exponential curve using expm1() for natural decay

Sustain

Constant level held until release

Release

Exponential curve matching decay algorithm

Anti-Click Features

  1. Retrigger Mode: Fast 3ms fade to zero before restarting
  2. Early Release: 20ms fade when voice is stolen
  3. Thresholding: Uses -120dB threshold to detect zero

Sustain Level Parameter

The sustain level is passed to Process() rather than set as a member variable:
  • Allows external smoothing of sustain parameter
  • Prevents discontinuities when sustain is changed during playback
  • Gives more control to the calling code

Code Location

Source: IPlug/Extras/ADSREnvelope.h:1

See Also