You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: lib/audioeffects/README.md
+29-28Lines changed: 29 additions & 28 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -125,7 +125,7 @@ marginal share of one block on each board at construction defaults.
125
125
|`DynamicEQ`| audioif (`audiobiquad`, `audiodynamics`, `audioroute`) | 8 | 0 samples | 14.5 % P4 / 26.0 % S3 of a block at patch 0 against the palette-derived 17 % / 29 % (listed remainder; `Splitter` taps=3 is a named gap). T2/T5 miss above ~5 ms attack; T4/T6 out-of-band miss below Q 2 | none - the exactly complementary split: one bell that does nothing until the sound *in that band* crosses a threshold, and a measured **wire** when it is idle |
126
126
|`LowPass`| audioif (`audiobiquad`) | 5 | 0 samples | 5.6 % P4 / 9.6 % S3 of a block, **over** its 1.5 % / 5 % budget, and no `" - lean"` patch is possible: every section runs whatever its `mix` is, so no macro position is cheaper than any other | none - the two-pole analog prototype `H(s) = 1/(s² + 2Rs + 1)`: one knob slides the curve, one decides how loud the corner stands, −3 dB at Resonance 0.707 and +24 dB at 16. **Exact except at the bottom of the Frequency knob**: at f₀ 20 Hz with Q 16 at 24 dB/oct the corner stands 0.44 dB low and the curve stops being one shape below 25 Hz (float32 coefficients near z = 1), and "rings for Q periods" is a 12 dB/oct statement below about 0.17·F_s - at 24 dB/oct the ring runs 1.36× the Resonance number at Q 2 up to 1.71× at Q 16. The roll-off, −12.03 dB/oct on the warped axis with unity DC, holds everywhere |
127
127
|`HighPass`| audioif (`audiobiquad`) | 5 | 0 samples | 5.7 % P4 / 9.6 % S3 of a block, **over** its 1.5 % / 5 % budget, and no patch on this surface is cheaper - patch 0 is already one live section and two wires | none - RBJ's two-pole low-cut, with an exact transmission zero at DC: a held offset decays to zero rather than to the 71 LSB the ported node parks on at a 10 Hz corner. Two bounds, measured: the two numbers on the panel are exact only above about 40 Hz at 48 kHz - at a 10 Hz corner the peak is 0.6 dB shy of `Resonance` (Q 8, 24 dB/oct) and the curve is 0.21 dB off its own shape, both the node's `float` coefficients rather than the cut - and at 24 dB/oct the corner **rings 1.85x longer** than the knob says, which is the price of reading the same gain at both slopes. The cut, the 12/24 dB/oct skirt and the exact zero at DC hold everywhere |
128
-
|`BandPass`| audioif (`audiobiquad`) | 4 | 0 samples | 3.8 % P4 / 6.2 % S3 of a block, inside its 4 % / 13 % budget, and the same at every patch | none - the two-pole resonant band-pass in RBJ's constant 0 dB peak-gain form, so `Width` moves the skirts without moving the peak. **Above 100 Hz.** Below it, at Q >= 4, the peak loses up to 0.50 dB and the loss changes sign with the level: the kernel's float32 recursion, not the form (audioif#64). The +-6 dB/oct and -3 dB figures are Q 0.707 statements below about 2 kHz - above that the bilinear warp moves them, and this class tracks the warped prototype to 0.009 dB |
128
+
|`BandPass`| audioif (`audiobiquad`) | 4 | 0 samples | 3.8 % P4 / 6.2 % S3 of a block, inside its 4 % / 13 % budget, and the same at every patch | none - the two-pole resonant band-pass in RBJ's constant 0 dB peak-gain form, so `Width` moves the skirts without moving the peak. The peak holds within 0.05 dB in 55 of 56 measured cells, both knob stops included; the one miss is -0.09 dB at f0 31.5 Hz with Q 32 (audioif#64 fixed the rest). The +-6 dB/oct and -3 dB figures are Q 0.707 statements below about 2 kHz - above that the bilinear warp moves them, and this class tracks the warped prototype to 0.009 dB |
129
129
|`Notch`| audioif (`audiobiquad`) | 5 | 0 samples | 5.6 % P4 / 9.6 % S3 of a block, **over** its 1.5 % / 5 % budget; a `" - lean"` patch is still owed and none was invented | none (the Twin-T was weighed and dropped on scope) - a band-stop whose `Width` is a bandwidth and not a depth, with a Harmonics toggle for mains hum. A `float` coefficient set cannot put the zeros exactly on the unit circle, so at 60 Hz it is a hum *reducer*, not an eliminator |
130
130
|`LadderFilter`| audioif (`audioladder`) | 7 | 0 samples | 16.2 % P4 / 26.7 % S3 of a block at patch 4 against the palette-derived 17 % / 28 %; lean patch 6 is 8.6 % / 14.7 %. T5 and T1's stopband slope stay disconfirmed | the Moog transistor ladder - four one-pole stages round one global feedback loop with an odd saturator **inside** it, so the passband sinks as `Resonance` rises. That droop is the circuit |
131
131
|`CombFilter`| audioif (`audioecho`, `audiobiquad`) | 6 | 0 samples | 7.4 % P4 / 12.2 % S3 of a block at patch 0 against the palette-derived 8 % / 13 %. Above Feedback 0.5 the parked ring's period is the nearest whole number of samples to F_s/Frequency, not the fractional delay the comb was asked for: +17.4 cents at 1760 Hz / Feedback 0.8 (27 samples at 48 kHz) and at most a half-sample — about 70 cents — near 4 kHz. The first-repeat tap still lands within 0.01 cents. Below Feedback 0.5, and at half-sample tunings, the tail reaches exact zero. T2/T5 miss at fractional tunings | none - the naked textbook feedback comb `y(n) = x(n) + g·y(n−M)`: a delay short enough to be a pitch, fed back, so noise grows resonances on that note's harmonic series |
@@ -266,33 +266,34 @@ macros move their children's controls.
266
266
267
267
## A note on how low a filter can go
268
268
269
-
Anywhere in the band, is the short answer - but it is worth knowing that
270
-
this was not always true, because the failure was silent and you may still
271
-
meet it on a stock CircuitPython board (below).
272
-
273
-
Every biquad in the engine used to keep its coefficients as Q15 integers,
274
-
which is the right trade on a microcontroller and costs low frequencies.
275
-
Below about 300 Hz they quantized into something that was no longer the
276
-
filter you asked for: a `LowPass` at 100 Hz returned **silence**, a
277
-
`HighPass` at 30 Hz returned **+21 dB of noise**, and a low shelf at 80 Hz
278
-
lifted the whole band by 13.4 dB instead of its 1.5. A second, unrelated
279
-
shortcut in the same file - one polynomial fitted to sine and cosine over
280
-
[0, π/2], which is only 12 kHz at 48 kHz - broke the *top* of the band too,
281
-
badly enough that a `HighPass` at 22 kHz passed its entire stopband.
282
-
283
-
Both are fixed. Coefficients now get as many fractional bits as each
284
-
individual filter has room for, the recursion accumulates in 64 bits and
285
-
keeps its feedback below the sample grid, and the trigonometry is a proper
286
-
series. Measured against the closed-form response, every mode lands within
287
-
**0.03 dB from 50 Hz to 22 kHz**. Ten octave bands all read +6.01 dB or
288
-
better on a +6 dB request - the claim `GraphicEQ` used to carry, and read on
289
-
`ParametricEQ` since both were rebuilt onto `audiobiquad`. The pre-rebuild
290
-
`MultibandCompressor`'s three bands recombined flat to 0.23 dB from 30 Hz to
291
-
8 kHz on those Q15 biquads; the rebuilt class is on `audiobiquad`'s float
292
-
sections instead, for the tail rather than the shape, and sums to 0.12 dB
293
-
from 30 Hz to 20 kHz. [audioif's `docs/upstream-diff.md`](https://github.com/PyDevices/audioif/blob/main/docs/upstream-diff.md),
294
-
"The biquads were Q15, so they could not go low", has the arithmetic, the
295
-
before-and-after table, and what it cost in instructions on an M0.
269
+
Anywhere in the band on `audiobiquad`, which is what the rebuilt filters and
270
+
EQs here are built on. On `synthio.Biquad` - and so `audiofilters.Filter` and
271
+
a `Note.filter` chain - the low end is CircuitPython's, on every target, and
272
+
it is worth knowing why.
273
+
274
+
CircuitPython keeps a biquad's coefficients as Q15 integers, which is the
275
+
right trade on a microcontroller and costs low frequencies. Below about
276
+
300 Hz they quantize into something that is no longer the filter you asked
277
+
for: a `LowPass` at 100 Hz returned **silence**, a `HighPass` at 30 Hz
278
+
returned **+21 dB of noise**, and a low shelf at 80 Hz lifted the whole band
279
+
by 13.4 dB instead of its 1.5. A low-pass fed a DC burst can also park at a
280
+
fixed point for ever - half of full scale from a 40 Hz low-pass.
281
+
282
+
audioif widened that arithmetic once for every biquad. Since audioif#77 the
283
+
split is by ownership: `synthio.Biquad` is a node CircuitPython also has, so
284
+
it runs CircuitPython's arithmetic everywhere (at the current floor that
285
+
80 Hz shelf reads -7.65 dB), and the widened kernel belongs to
286
+
`audiobiquad`, which is audioif's own. There, coefficients get as many
287
+
fractional bits as each filter has room for, the recursion accumulates in
288
+
64 bits and keeps its feedback below the sample grid, and the trigonometry
289
+
is a proper series. Measured against the closed-form response, every mode
290
+
lands within **0.03 dB from 50 Hz to 22 kHz**. Ten octave bands all read
291
+
+6.01 dB or better on a +6 dB request, read on `ParametricEQ`. The rebuilt
292
+
`MultibandCompressor` sums to 0.12 dB from 30 Hz to 20 kHz on
293
+
`audiobiquad`'s float sections. `Saturation` builds its shelves there too,
294
+
and falls back to `synthio.Biquad` only where `audiobiquad` is missing.
0 commit comments