One biquad section, designed to MATCH THE ANALOGUE PROTOTYPE rather than to be its bilinear
image. THE single transcription of the design in the codebase: both the coefficients the
engine pushes (rbjCoeffs) and the magnitude the surface draws
(biquadMagnitudeDb) come from here, so the drawn curve can never disagree with what
the engine runs.
WHY THIS IS NOT THE RBJ COOKBOOK ANY MORE (operator ruling, 2026-09-14). The cookbook
designs by the bilinear transform, which compresses the whole analogue frequency axis into
[0, Nyquist). Pre-warping puts f0 back where it was asked for and leaves everything
above it squeezed: at the 96 kHz the graph runs, a +12 dB bell at 16 kHz Q 2 was 1.28 dB
low at 20 kHz and a +12 dB high shelf at 12 kHz 0.50 dB high — measured, not argued
(an internal spec §5). Raising the graph rate shrinks
that error but costs 1.98x the CPU on every stage; an EQ is LINEAR, so oversampling it buys
nothing at all. The ruling: match the prototype BY COEFFICIENT DESIGN, at the rate the desk
runs.
THE DESIGNS, per kind — each one measured against its analogue prototype by
packages/pipewire-native/tools/eq-matched-probe.c, which prints the before/after table:
peaking, lowShelf, highShelf — matched-Z pole/zero placement (Vicanek, Matched Second
Order Digital Filters, 2016). Every pole and zero of the prototype is finite, so exp(sT)
places all four exactly and one scalar sets the reference gain (unity at DC, except the low
shelf, whose DC gain is the shelf gain). A peaking section is a zero pair at f0 damped
A/(2Q) over a pole pair at f0 damped 1/(2AQ); a shelf is a pair at f0*sqrt(A) over a
pair at f0/sqrt(A) (swapped for the low shelf), both damped 1/(2Q).
lowpass — matched-Z poles, but the prototype's two zeros are at s = infinity and the
map has nowhere to put them, so the numerator is instead FITTED to the prototype's
magnitude at DC, at f0 and at Nyquist (Vicanek's construction for the same reason).
notch — matched-Z poles under the cookbook's own numerator, which already IS the
matched-Z zero pair ({1, -2cos(w0), 1} puts both zeros exactly on the unit circle at
f0), so only the denominator changes and the null at f0 stays exact.
highpass — UNCHANGED, still the cookbook's. Measured first: over the 20-1000 Hz its
pass filter serves, the bilinear error is 0.005 dB at 96 kHz — there is nothing to fix, and
the fitted alternative is infeasible over much of the space and worse where it is not.
A 0 dB peaking or shelf section is still EXACTLY identity — zeros and poles are designed by
the same call with the same arguments when A = 1, so they cancel term for term and the
scalar is exactly 1. eqBandIsIdentity rests on that.
gainDb is used only by the peaking/shelf kinds (the pass kinds ignore it). The
centre/cutoff is clamped just under Nyquist so a band dragged to the top of the audio band
can't produce a degenerate section.
One biquad section, designed to MATCH THE ANALOGUE PROTOTYPE rather than to be its bilinear image. THE single transcription of the design in the codebase: both the coefficients the engine pushes (rbjCoeffs) and the magnitude the surface draws (biquadMagnitudeDb) come from here, so the drawn curve can never disagree with what the engine runs.
WHY THIS IS NOT THE RBJ COOKBOOK ANY MORE (operator ruling, 2026-09-14). The cookbook designs by the bilinear transform, which compresses the whole analogue frequency axis into
[0, Nyquist). Pre-warping putsf0back where it was asked for and leaves everything above it squeezed: at the 96 kHz the graph runs, a +12 dB bell at 16 kHz Q 2 was 1.28 dB low at 20 kHz and a +12 dB high shelf at 12 kHz 0.50 dB high — measured, not argued (an internal spec§5). Raising the graph rate shrinks that error but costs 1.98x the CPU on every stage; an EQ is LINEAR, so oversampling it buys nothing at all. The ruling: match the prototype BY COEFFICIENT DESIGN, at the rate the desk runs.THE DESIGNS, per kind — each one measured against its analogue prototype by
packages/pipewire-native/tools/eq-matched-probe.c, which prints the before/after table:exp(sT)places all four exactly and one scalar sets the reference gain (unity at DC, except the low shelf, whose DC gain is the shelf gain). A peaking section is a zero pair atf0dampedA/(2Q)over a pole pair atf0damped1/(2AQ); a shelf is a pair atf0*sqrt(A)over a pair atf0/sqrt(A)(swapped for the low shelf), both damped1/(2Q).s = infinityand the map has nowhere to put them, so the numerator is instead FITTED to the prototype's magnitude at DC, atf0and at Nyquist (Vicanek's construction for the same reason).{1, -2cos(w0), 1}puts both zeros exactly on the unit circle atf0), so only the denominator changes and the null atf0stays exact.A 0 dB peaking or shelf section is still EXACTLY identity — zeros and poles are designed by the same call with the same arguments when
A = 1, so they cancel term for term and the scalar is exactly 1. eqBandIsIdentity rests on that.gainDbis used only by the peaking/shelf kinds (the pass kinds ignore it). The centre/cutoff is clamped just under Nyquist so a band dragged to the top of the audio band can't produce a degenerate section.