Real-time

TKEO

3-sample
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

TKEO
level 0.821280 samples
Hilbert refTKEO
Smoothing4 samp (0.1 ms)

Score card

Causality
real-time
Signal model
single-carrier
Reads
peak ≈ 1.0×
Latency
≈1 sample
Cost
trivial
Domain
time
Reads (measured on a steady sine)1.00×

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

Temporal
9%
Robust
79%
Spectral
11%
Boundary
53%

How it works

Near-instant amplitude from just three samples. The Teager–Kaiser energy operator ψ[n] = x[n]² − x[n−1]·x[n+1] estimates the signal's energy A²ω² almost without delay; take the root and rescale by the carrier frequency to recover amplitude.

It is astonishingly fast and cheap and reacts in a couple of samples — but it is noisy, assumes a single narrowband tone, and needs to know (or estimate) the carrier to get the scale right. Light smoothing tames the noise.

Key terms

Teager–Kaiser energy operator (TKEO)
The three-sample estimate ψ[n] = x[n]² − x[n−1]·x[n+1]. It reads the signal's energy A²ω² from one sample and its two neighbours, with almost no delay — the whole method rests on this one line.
Instantaneous energy
What the operator actually tracks: a quantity that rises with both amplitude and frequency, not amplitude alone. Taking the square root of it recovers an amplitude-related value, which is why the build follows the energy step with a root.
Carrier frequency scaling
Because the output carries A²ω², the frequency term is baked in. To rescale back to a true amplitude A you must know or estimate the carrier frequency ω and divide it out.

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 2Teager energy

    Run the three-sample operator x[n]² − x[n−1]·x[n+1] (clamped at zero). It reads the instantaneous energy of the carrier — large where the wave is tall, near zero where it is small — but it is jagged, pulsing twice per cycle.

  3. Step 3Smooth and rescale

    Average the energy with a short centered mean, take its square root, and divide by sin(2π/CAR) to turn energy back into amplitude. The jitter melts into a fast, low-lag envelope.

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>

/* Centered (zero-lag) moving mean of width w samples. */
static void centered_mean(const double *x, double *out, int n, int w) {
    if (w < 1) w = 1;
    int half = w / 2;
    for (int i = 0; i < n; i++) {
        int lo = i - half < 0 ? 0 : i - half;
        int hi = i + half + 1 > n ? n : i + half + 1;
        double s = 0.0;
        for (int j = lo; j < hi; j++) s += x[j];
        out[i] = s / (hi - lo);
    }
}

/* TKEO envelope: psi[n] = x[n]^2 - x[n-1]*x[n+1] (clamped at 0, ends copied),
   smoothed, then sqrt and rescaled by the carrier so energy reads as amplitude. */
void tkeo_envelope(const double *x, double *env, int n,
                   int smooth_samples, double carrier_samples) {
    double *psi = malloc(n * sizeof(double));
    for (int i = 1; i < n - 1; i++) {
        double v = x[i] * x[i] - x[i - 1] * x[i + 1];
        psi[i] = v > 0.0 ? v : 0.0;
    }
    psi[0] = psi[1];
    psi[n - 1] = psi[n - 2];
    double *sm = malloc(n * sizeof(double));
    centered_mean(psi, sm, n, smooth_samples);
    double s = sin(2.0 * M_PI / carrier_samples);
    for (int i = 0; i < n; i++) env[i] = sqrt(sm[i]) / s;
    free(psi);
    free(sm);
}