Antialiasing
Overview
A trivially generated waveform with a sharp corner, such as the ±1 jump of a saw or square, contains energy well above the Nyquist frequency. Sampling that ideal shape folds the excess back into the audible band as aliasing. poly_blep and poly_blamp remove it at the source: each is a small polynomial residual that is added to the plain waveform only in the neighborhood of a discontinuity, cancelling the components that would otherwise alias.
The two functions target different orders of discontinuity:
-
poly_blep(polynomial band-limited step) corrects a step, a jump in the value itself. This is the ±1 wrap of a Saw Wave Oscillator, Square Wave Oscillator, or Pulse Wave Oscillator. -
poly_blamp(polynomial band-limited ramp) corrects a corner, a break in the slope where the value stays continuous. This is the peak and trough of a Triangle Wave Oscillator.
Both take a phase p (where along the cycle we are) and a phase dt (how far the phase advances per sample, which equals the oscillator frequency divided by the sample rate). The residual is nonzero only when p lies within dt of a discontinuity, so away from an edge the functions return zero and the plain waveform passes through untouched.
The clearest way to see them is in the time domain: generate the plain sample, add the correction, and the sharp edge becomes a smooth, rounded transition. The figure below does this at a deliberately high frequency (dt = 0.25, a quarter cycle per sample) so the correction spans a visible part of the cycle; at normal audio frequencies it is confined to a sample or two around each edge. poly_blep rounds the saw’s ±1 wrap (a step); poly_blamp rounds the triangle’s peaks (a corner).
Aliasing itself is a frequency-domain artifact, so the payoff shows up in the spectrum. A sawtooth has a harmonic at every multiple of its fundamental; the harmonics above Nyquist cannot be represented and instead fold back down to lower frequencies as aliases. The fold point is Nyquist, fs/2, so it moves with the sample rate: 22.05 kHz at 44.1 kSPS, 24 kHz at 48 kSPS. In the log-frequency spectra below, every partial that is not on a true-harmonic marker (green) is a fold. basic_saw is riddled with them, right down below the fundamental; the band-limited saw pushes them tens of dB down.
The band-limited oscillators (Saw Wave Oscillator, Square Wave Oscillator, Pulse Wave Oscillator, Triangle Wave Oscillator) apply these corrections internally, so most code never calls poly_blep or poly_blamp directly. They are exposed for building custom oscillators, or any generator that emits a piecewise-polynomial shape with known discontinuities.
The scale argument to poly_blamp matches the slope change at the corner. A triangle wave, whose slope flips between +4 and −4 over a unit cycle, uses scale = 4.
|
These corrections assume dt is small relative to a cycle (the usual audio case, many samples per period). As the oscillator frequency approaches Nyquist, dt approaches 0.5 and a single-step correction can no longer fully band-limit the waveform.
|
Declaration
constexpr float poly_blep(phase p, phase dt);
constexpr double poly_blamp(phase p, phase dt, float scale);
Expressions
Function Call
| Expression | Semantics | Return Type |
|---|---|---|
|
Step-discontinuity residual. Nonzero only when |
|
|
Corner-discontinuity residual for a slope change of
magnitude |
|
Both functions are stateless, constexpr, and free of side effects.
Example
A band-limited sawtooth. The plain ramp jumps from +1 to −1 once per cycle; subtracting a single poly_blep at that wrap removes the alias-inducing step:
constexpr float x = 2.0f / q::phase::one_cyc;
float r = (p.rep * x) - 1.0f; // plain saw, ±1
r -= q::poly_blep(p, dt); // band-limit the wrap
A band-limited triangle corrects two corners instead, one at each peak, with scale = 4 for the ±4 slope of a unit-amplitude triangle:
r += q::poly_blamp(p + edge1, dt, 4); // correct the falling corner
r -= q::poly_blamp(p + edge2, dt, 4); // correct the rising corner
See Saw Wave Oscillator and Triangle Wave Oscillator for the full oscillators these fragments come from.