IIR Resonant-Filter Scope
Signals & Audio
Four 2-pole IIR
resonators, plucked in turn by impulses, ringing out on a live oscilloscope — running on a
Super Nintendo. Each output sample is fed back from the two previous outputs
(y[n] = a₁·y[n-1] + a₂·y[n-2] + x[n]), a strictly serial dependency chain the
compiler cannot reorder or vectorize the way it can a feed-forward FFT. Runs
automatically; no joypad needed. Written in C and compiled with the
llvm-mos-based 65816 toolchain
(+mos-a16, 16-bit accumulator mode).
Self-running demo — the resonators are plucked and ring on their own.
Click the screen to focus, then watch. Tab away and it pauses.
What it is
A resonator is a filter with feedback: hit it once (an impulse — a "pluck") and it rings, producing a decaying sinusoid, like a struck string or bell. This demo runs four of them tuned to a chord, arpeggiated, and draws the summed output as a scrolling oscilloscope trace. The core is three lines:
// per sample, per resonator — a1 has a small runtime vibrato: int32_t y = (a1 * y1[k] + a2 * y2[k]) >> 12; // ← the feedback (32-bit multiply) if (k == pluck) y += IMPULSE; // "pluck" = impulse excitation y2[k] = y1[k]; y1[k] = y; // shift the two-sample state // y[n] depends on y[n-1] AND y[n-2] — the loop can't be reordered.
Because y[n] depends on y[n-1] and y[n-2], the loop is
a serial chain — the exact opposite of the parallelizable butterflies in a Fourier transform.
It's the kind of code where a compiler that reorders too aggressively would silently corrupt
the result; here it stays bit-exact.
Compiler stress-test
| Item | What it exercises |
|---|---|
| y[n] ← y[n-1], y[n-2] | Each output sample is fed back from the two previous outputs. Unlike a feed-forward FFT (whose stages can be reordered/parallelized), this dependency chain is strictly serial — every sample waits on the last. |
| 2-pole resonator | y[n] = 2·r·cos(ω)·y[n-1] − r²·y[n-2] + x[n]. An impulse (the "pluck") makes it ring: a decaying sinusoid at frequency ω, decaying at rate r. Four of them tuned to a chord. |
| __mulsi3 | The a1·y[n-1] feedback term is a genuine 32-bit multiply. A small runtime vibrato on the coefficient keeps it from being folded to shift-adds (and makes the chord chorus-wobble). |
| non-reorderable | The whole point: the compiler must respect the serial data dependency. A miscompile that reordered the state updates would diverge the differential CRC — which it does not. |
Written in C with the llvm-mos 65816 toolchain and verified against bsnes-jg
(and MAME where the SPC700 IPL is present). Hit Verify fidelity to reproduce the build
gate's WRAM assert (gate CRC 0x49BD — a rotate-XOR fold of 400 output samples)
live in this tab. No far pointers — the same source passes every way: host == default ==
+mos-a16 == +mos-xy16 == bsnes-jg.