Offline

Savitzky–Golay

poly-fit
time-domainzero-lagsingle-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

Savitzky–Golay
level 0.521280 samples
Hilbert refSavitzky–Golay
Window33 samp (0.7 ms)
Poly order

Score card

Causality
zero-lag
Signal model
single-carrier
Reads
peak ≈ 1.0×
Latency
none (zero-lag)
Cost
moderate
Domain
time
Reads (measured on a steady sine)0.63×

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

Temporal
8%
Robust
32%
Spectral
5%
Boundary
32%

How it works

Smooths the noise but keeps the peak heights. In a sliding window it fits a low-order polynomial by least squares and keeps the fitted value at the center. Because the fit is a polynomial rather than a flat average, it follows the curvature of a peak instead of flattening it, so peak amplitudes survive.

Raise the order to preserve sharper features, widen the window to smooth more. It can wiggle or undershoot near abrupt edges, the classic polynomial-fit artifact.

Key terms

Savitzky–Golay filter
A sliding-window least-squares polynomial fit: at each position it fits a polynomial to the samples inside the window and keeps the fitted value at the window center. Smoothing that bends with the signal instead of averaging it flat.
Polynomial order
The degree of the fitted polynomial. A higher order has more freedom to bend, so it can follow sharper curvature and hold a peak's height; a lower order fits a gentler, smoother line.
Window length
The number of samples in the sliding fit — the span the polynomial is fitted over. A wider window averages over more samples and smooths more; a narrower one tracks fast detail but lets more ripple through.

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|. Every sample is now positive, so the polynomial fit settles toward the amplitude instead of averaging back to zero.

  3. Step 3Sliding polynomial fit

    Slide a window across the rectified signal and, at each step, least-squares fit a low-order polynomial to the samples inside it. Keep the fitted value at the window's center. The curve can bend with the swell, so it smooths the ripple without shaving the peaks down.

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>

/* Savitzky–Golay smoothing of |x|. The kernel `coeffs` (length `win`,
   an odd number) is the precomputed least-squares filter — see the JS or
   Python tab for the normal-equations build. Here we just rectify and
   convolve, reflecting the signal at both edges so the window never reads
   past the ends. */
void savgol(const double *x, double *env, int n,
            const double *coeffs, int win) {
    int half = (win - 1) / 2;
    for (int i = 0; i < n; i++) {
        double s = 0.0;
        for (int k = 0; k < win; k++) {
            int idx = i + k - half;
            if (idx < 0)  idx = -idx;            /* reflect at the left */
            if (idx >= n) idx = 2 * n - 2 - idx; /* reflect at the right */
            s += coeffs[k] * fabs(x[idx]);
        }
        env[i] = s;
    }
}