Split out of #23, which its title currently carries. They are not the same defect and #23's fix will not touch this one.
What is measured
audiofilters.Phaser at its defaults (6 all-pass stages), 48 kHz stereo, 0.09 s of 1 kHz at −6 dBFS then digital silence, pulled to 600 blocks: the output settles after 20 blocks to a constant −4 LSB and stays there. Re-run to 3000 blocks (32 s of audio) with the settle window excluded, the set of distinct sample values over blocks 60..3000 is exactly [-4]; block 100 and block 2999 are identical. This is a held DC constant, not a decaying tail.
Rate and channel count matter and must be stated with any re-measurement: the same probe reads exact zero at 22.05 kHz mono. A run at another rate will look like the defect is gone.
Full table, method and harness note: the effects-program Phase 0 comment on #23 (#23 (comment)).
Why it is a different defect
audioif_phaser.c never calls audioif_biquad. grep -rln audioif_biquad src/ matches only:
src/synthio/Biquad.c
src/synthio/Biquad.h
src/shared/audioif_biquad.c
src/shared/audioif_biquad.h
src/shared/audioif_trig.h
src/cpython/_audioif.c
#23's cause is the AUDIOIF_BIQUAD_STATE_SHIFT 12 recursion in audioif_biquad.c:159-182 — state carrying 12 bits below the sample LSB, round-to-nearest at every step, no dither and no leak term. The Phaser's hold has a different origin: its own int16_t allpass_words[] / feedback_words[] (audioif_phaser.c:28-38), which are in plain sample units with no fractional bits at all. Fixing the biquad's state format will not move the Phaser's −4 LSB by one bit.
What is not proposed
audioif_phaser.c is a CircuitPython-ported kernel. It is never modified — the standing rule, and Brad's hold of 2026-09-03 on #23. This issue exists to record the defect against the right kernel, so that:
Split out of #23, which its title currently carries. They are not the same defect and #23's fix will not touch this one.
What is measured
audiofilters.Phaserat its defaults (6 all-pass stages), 48 kHz stereo, 0.09 s of 1 kHz at −6 dBFS then digital silence, pulled to 600 blocks: the output settles after 20 blocks to a constant −4 LSB and stays there. Re-run to 3000 blocks (32 s of audio) with the settle window excluded, the set of distinct sample values over blocks 60..3000 is exactly[-4]; block 100 and block 2999 are identical. This is a held DC constant, not a decaying tail.Rate and channel count matter and must be stated with any re-measurement: the same probe reads exact zero at 22.05 kHz mono. A run at another rate will look like the defect is gone.
Full table, method and harness note: the effects-program Phase 0 comment on #23 (#23 (comment)).
Why it is a different defect
audioif_phaser.cnever callsaudioif_biquad.grep -rln audioif_biquad src/matches only:#23's cause is the
AUDIOIF_BIQUAD_STATE_SHIFT 12recursion inaudioif_biquad.c:159-182— state carrying 12 bits below the sample LSB, round-to-nearest at every step, no dither and no leak term. The Phaser's hold has a different origin: its ownint16_t allpass_words[]/feedback_words[](audioif_phaser.c:28-38), which are in plain sample units with no fractional bits at all. Fixing the biquad's state format will not move the Phaser's −4 LSB by one bit.What is not proposed
audioif_phaser.cis a CircuitPython-ported kernel. It is never modified — the standing rule, and Brad's hold of 2026-09-03 on #23. This issue exists to record the defect against the right kernel, so that:Phaserclass's Tier 1 invariant ("a decaying tail reaches exact zero — no held DC") has a filed cause to point at rather than a rediscovery every time someone measures it.