Real-time

Peak-hold

analog-style
time-domainreal-timesingle-carrierpeak ≈ 1.0×

An envelope detector traces the loudness contour of a waveform — the slow outline riding over the fast carrier inside it. Every graph on this page is drawn by the method's real algorithm, and the sliders at the top drive all of them at once.

The whole method, live

Peak-hold
level 0.781280 samples
Hilbert refPeak-hold
Hold4 samp (0.1 ms)
Decay32 samp (0.7 ms)

Score card

Causality
real-time
Signal model
single-carrier
Reads
peak ≈ 1.0×
Latency
none up · decay down
Cost
trivial
Domain
time
Reads (measured on a steady sine)0.96×

Tracking error vs the true envelope, by challenge axis — longer bar is a tighter fit. Computed live across the oracle generators.

Temporal
14%
Robust
30%
Spectral
7%
Boundary
6%

How it works

Catches every transient instantly, then bleeds down. Attack is zero — the output jumps to each new peak the instant it arrives — optionally holds for a set number of samples, then decays exponentially.

This is the behavior of analog diode detectors and PPM meters: superb for catching brief spikes a smoother would miss, but the held line is jagged and is a poor control signal for anything that wants smooth gain.

Key terms

Instant attack
The output jumps to each new peak the moment it arrives — zero rise time. There is no smoothing on the way up, so even a single-sample spike is captured and no transient is missed.
Hold time
After a peak, the level is held flat for a set number of samples before it starts to fall. The plateau keeps a brief crest visible long enough to read.
Exponential decay
Once the hold expires the level bleeds down by a fixed fraction per sample: y *= decay. The fall is fast at first and slows as it approaches zero, tracing the familiar sag between peaks.

Building the envelope, step by step

Each step adds one idea and shows a graph with only that principle applied — drawn by the real algorithm on the page's own input, working up to the finished curve.

  1. Step 1The carrier

    Start with the raw waveform — a fast carrier whose height swells and fades. The envelope is the slow outline we want, not the fast wiggle inside it.

  2. Step 2Rectify

    Fold the negative half upward with |x|, the way a diode does in an analog detector. Every sample is now positive, so the detector can chase the amplitude instead of the signed waveform.

  3. Step 3Capture and decay

    Whenever a rectified sample rises above the current line, jump instantly to it — zero attack. Below it, the line bleeds down exponentially. The result is a sawtooth that pins each crest exactly and sags between them.

The code

Six readable forms of the exact algorithm that draws the curves above — C, JS and Python ports, an optimized C, a fixed-coefficient version, and a user-controlled one whose parameters match the sliders.

#include <math.h>

/* exp(-1/tau): the one-pole coefficient for a time constant of
   tau samples. Clamped at 1 so a tiny tau can't blow up. */
static double decay_coeff(double tau) {
    return exp(-1.0 / (tau < 1.0 ? 1.0 : tau));
}

/* Peak-hold: rectify, jump instantly to each new peak and arm the hold
   counter, count the hold down while below the peak, then decay. */
void peak_hold(const double *x, double *env, int n,
               double decay_samples, int hold_samples) {
    double dec = decay_coeff(decay_samples);
    if (hold_samples < 0) hold_samples = 0;
    double e = 0.0;
    int cnt = 0;
    for (int i = 0; i < n; i++) {
        double r = fabs(x[i]);
        if (r >= e) {           /* new peak: instant attack, arm hold */
            e = r;
            cnt = hold_samples;
        } else if (cnt > 0) {   /* holding the level */
            cnt--;
        } else {                /* released: exponential decay */
            e *= dec;
        }
        env[i] = e;
    }
}