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).

loading core…

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

ItemWhat 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 resonatory[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.
__mulsi3The 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-reorderableThe 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.