2026-06-11

Zero Sound Files — Synthesizing SFX with Web Audio

I kept external assets at zero throughout the rebuild, so sound effects couldn't be an exception.
There isn't a single audio file.
Every sound is synthesized on the spot with the Web Audio API.

There are only two basic tools: an oscillator (which generates a waveform) and gain (volume).
Add a starting frequency, a duration, a waveform type, and an envelope that decays the volume toward zero over time, and you've got one short sound effect.
I reuse this single function for everything.

function blip(freq, dur, type, gain = 0.15, slide = 0) {
  const o = c.createOscillator();
  const g = c.createGain();
  o.type = type;
  o.frequency.setValueAtTime(freq, c.currentTime);
  g.gain.setValueAtTime(gain, c.currentTime);
  g.gain.exponentialRampToValueAtTime(0.0001, c.currentTime + dur);
  o.connect(g).connect(c.destination);
  o.start(); o.stop(c.currentTime + dur);
}

I split the timbres: a low sine wave for jumps, a gritty sawtooth for taking a hit.
My favorite detail is the collect sound — the pitch rises as your combo climbs.

export const playCollect = (combo: number) =>
  blip(520 + Math.min(combo, 24) * 40, 0.18, "triangle", 0.18, 120);

520 + min(combo,24)*40Hz.
Grab one and it's 520Hz; as the combo builds it steps up a notch at a time and caps out at the ceiling.
When you collect a streak in a row, the pitch trickles upward — and that rising feel was more thrilling than the combo itself.
There's no way I'd have gotten that satisfaction from replaying the same canned sound.

I also covered the browser environment check.
If window or AudioContext is missing, every function quietly falls back to a no-op.
That keeps things from blowing up under SSR or unsupported environments.

That wraps up the rebuild dev log.
The takeaway is simple — even with zero external assets, code alone was enough to make a game that feels cute and satisfying to play.

Try it yourself →