Skip to content

Fix synthio_biquad_filter_reset() clearing only half the filter state - #11278

Merged
tannewt merged 1 commit into
adafruit:mainfrom
bdbarnett:fix-biquad-reset
Aug 31, 2026
Merged

Fix synthio_biquad_filter_reset() clearing only half the filter state#11278
tannewt merged 1 commit into
adafruit:mainfrom
bdbarnett:fix-biquad-reset

Conversation

@bdbarnett

Copy link
Copy Markdown

Fixes #11268.

shared-module/synthio/Biquad.h defines the filter state as four
int32_t:

typedef struct {
    int32_t x[2], y[2];
} biquad_filter_state;

but shared-module/synthio/Biquad.c only clears the first two:

void synthio_biquad_filter_reset(biquad_filter_state *st) {
    memset(&st->x, 0, 4 * sizeof(int16_t));
}

The struct is 16 bytes; 4 * sizeof(int16_t) is 8. x[0] and x[1] get
cleared, but y[0] and y[1] -- the feedback history -- do not.
biquad_filter_sample() reads all four members as int32_t, so the
int16_t sizing looks like a leftover from an earlier, narrower state
layout.

A biquad's y history is what the recursion runs on, so what survives a
"reset" is not a cosmetic detail: feeding a reset filter pure silence
produces a decaying tail of whatever it played before, and for a low-pass
with poles near the unit circle that tail starts close to full scale.
Both callers -- audiofilters_filter_reset_buffer() (called when an
AudioOut starts playback) and synthio.Note's filter (re)initialisation
-- want the full reset.

Repro

An 800 Hz low-pass fed a loud 200 Hz tone for four blocks, then
reset_buffer(), then silence. Peak of the first "silent" block, unix
coverage build of main:

peak out (int16)
before 28082 (-1.3 dBFS)
after 0

Includes a regression test
(tests/circuitpython/audiofilter_filter_reset_buffer.py) with the same
scenario; it fails on current main and passes with the fix.

biquad_filter_state is four int32_t (x[2], y[2]), but the reset only
memset the first 8 bytes -- x[0] and x[1] -- leaving y[0] and y[1],
the feedback history, untouched. A "reset" filter keeps ringing on
whatever it last saw, so a low-pass fed silence after loud audio
plays a decaying tail of the previous signal instead of silence.

Fixes adafruit#11268

@tannewt tannewt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@tannewt
tannewt merged commit 65f4965 into adafruit:main Aug 31, 2026
562 of 564 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

synthio_biquad_filter_reset() only clears half the filter state

2 participants