0
mirror of https://github.com/torvalds/GuitarPedal.git synced 2026-08-17 04:59:59 +00:00
Files
Linus Torvalds 6c6e215451 Build a biquad from the half angle, not from cos(w0)
Every cookbook constructor in biquad.h places its poles and zeros with
-2*cos(w0), so what it needs out of the sine table is an angle.  Cos is
the worst possible way to ask for one at the bottom of the range.

fastsincos() interpolates 256 entries a quarter linearly.  Near zero
phase the sine is straight and the chord is nearly exact - the error at
30Hz measures 1.4e-08 - while the cosine is at its maximum, where the
chord sits furthest under the arc, and the error is 3.8e-06.  That would
be a rounding error if cos were a number.  It is an angle, and
d(cos)/dw = -sin(w0) vanishes exactly where the error is worst, so what
comes back is off by err/sin(w0):

	 asked	    built	 error
	    20	  30.68 Hz	  +53%
	    30	  37.49 Hz	  +25%
	    60	  63.58 Hz	   +6%
	   100	 101.27 Hz	 +1.3%
	  4000	4000.06 Hz	 0.001%

At the Q of about 1 everything currently uses, a band is more than an
octave wide and none of that is audible.  It is fatal for anything
narrow: a Q of 20 at 60Hz is a 3Hz notch and the error is 3.6Hz, so it
attenuates 60Hz by 0.7dB instead of 37.

Take the half angle instead, whose sine is the well-behaved one, and
come back with the double-angle identities.  Both fall out of the one
table lookup that was happening anyway, so it costs two multiplies and
no accuracy anywhere else - 4kHz moves from +0.001% to -0.0004%.
single-pole.h already asks for the same half angle for its own reasons.

test-biquad.py is the sweep, and it is not a check that 60Hz got better.
It asks all nine constructors, every semitone from 20Hz to 20kHz, at six
Q values and four gains, where the filter actually ended up - from the
response, by the definition of each filter's own characteristic
frequency, never by rearranging the coefficients.  13068 filters; 6464
of them were outside the bound before this and none are after.

The bound is a model rather than a percentage, because the residual is
now float32 itself: at 20Hz, 1-cos(w0) is 3.4e-06, so a single ulp is
1.7% of the whole quantity carrying the angle.  Nothing built this way
can do better, and that floor falls to 0.05% by 60Hz - a flat tolerance
loose enough for 20Hz would sleep through a hundredfold error at 1kHz.

The app has drawn the correct curve all along, in float64, so this also
closes a disagreement between the graph and the pedal that was 53% wide
at the bottom of the EQ's range.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2026-08-10 10:05:06 -07:00

7 lines
69 B
Plaintext

test-fft
__pycache__
test-midi-cin
bench/bench
bench/coeff
bench/gen