ADPCM Waverider
Algorithms & Numeric
An IMA-ADPCM
audio decoder — the compression used by countless game samples. Each 4-bit code nudges a
predictor that is clamped with saturating add/sub (G_SADDSAT/
G_SSUBSAT), and the step size walks an 89-entry table. It's a true
serial feedback loop: every sample depends on the last. Drawn as a scrolling
oscilloscope. Compiler stress-test #89.
Self-running — a decoded ADPCM waveform scrolls across the scope.
Click the screen to focus, then watch. Tab away and it pauses.
What it is
A pseudo-random 4-bit ADPCM code stream is decoded into a 16-bit waveform and plotted across the scope. Because the predictor accumulates, the trace wanders and then rails against the saturation limits at the top and bottom — exactly where the saturating add/sub keeps it from wrapping into a glitch.
// IMA-ADPCM: predictor updated with SATURATING add/sub, inside a serial feedback loop.
int16_t decode(uint8_t n) {
int16_t step = STEP[idx];
int32_t diff = step>>3;
if (n&4) diff+=step; if (n&2) diff+=step>>1; if (n&1) diff+=step>>2;
pred = (n&8) ? __builtin_elementwise_sub_sat(pred, diff) // G_SSUBSAT
: __builtin_elementwise_add_sat(pred, diff); // G_SADDSAT
idx += IDX[n]; clamp(idx, 0, 88); // step-size LUT walk
return pred; // next output depends on THIS pred (feedback)
} The feedback recurrence is the hard part for a compiler: the saturating clamp sits on the critical path of a loop that can't be reordered, so the backend must get both the saturating opcode and the dependency chain right. The host oracle checks the decoded waveform is bit-identical.
Compiler stress-test
| Item | What it exercises |
|---|---|
| saturating feedback | The predictor is clamped every sample with __builtin_elementwise_add_sat/_sub_sat (G_SADDSAT/G_SSUBSAT) so a loud passage rails at ±full-scale instead of wrapping to the opposite sign — a decode wrap would be an audible click. The clamp is inside a serial recurrence, so a miscompiled saturate would corrupt the entire rest of the waveform. |
| serial recurrence | Each output sample is pred += ±diff of the previous sample — a true feedback loop that cannot be vectorized or reordered. The step size also walks an 89-entry table indexed by a running state updated every nibble. |
| host vs target | The host oracle (gcc) has no elementwise saturating builtins and uses a bit-identical manual clamp; the 65816 target (clang) uses the real intrinsics. The differential gate confirms both produce the same waveform CRC. |
| distinct from #48 / #67 | #48 IIR is a wrapping fixed-point feedback filter (no saturation). #67 huffman has no feedback at all. This is the saturating-arithmetic-in-a-feedback-loop member of the battery. |
Written in C with the llvm-mos 65816 toolchain and verified against
bsnes-jg and MAME. Hit Verify fidelity to reproduce the build gate's WRAM assert
(ADPCM CRC 0xCA56 — a fold of the decoded predictor stream) live in this tab.
No far pointers — host == default == +mos-a16 == +mos-xy16,
-verify clean.