2026-02-28
Procedural audio, take one

The game has sound. Cards click when you play them, attacks land with a thud, the victory chime is a small triumph in a single second.
All of it is procedural — built at click-time from oscillators, noise bursts, and biquad filters routed through a master compressor and a tiny convolver reverb. The whole audio module is one TypeScript file, no audio assets. A SFX call looks like:
function fxAttack(c) {
const sub = osc({ type: "sine", freq: 220, glideTo: 60, ... });
const body = osc({ type: "sawtooth", freq: 320, glideTo: 110, ... });
const crack = noise({ duration: 70, filter: "highpass", ... });
return [sub, body, crack];
}Why bother with this when a single mp3 would be done in five minutes? Two reasons. First, asset weight — the whole audio system is <15 KB transferred. Second, every interaction is unique enough that pre-baked samples either sound stale or balloon to dozens of variants.
Combat ambience is a low drone (two detuned sawtooths through a low-pass) that fades in on combat enter. It's ugly on its own, but it sells the "pressure" of the fight when stacked under the SFX.
What's missing: actual music. A drone is not a score. I have an idea for a slow chord-progression pad with per-scene palette — minor on the map, warmer at campfires — but that's its own evening. Today I just wanted the game to stop being silent.
A mute toggle and a reduced-motion toggle landed in Settings today too. Accessibility is non-negotiable; volume sliders coming later.