February 14, 2026Music
Procedural Sound Design with Web Audio API
Each module in KM.DEV has a unique sonic signature — all synthesized in real-time with oscillators and filters.
audiowebaudiosynthesis
The Idea
Every module should sound different. Security gets an alert tone. Game gets an 8-bit coin pickup. Music gets a major triad. All synthesized — no audio files needed for UI sounds.
Web Audio Pipeline
OscillatorNode → GainNode → AudioContext.destination
Each sound is a short-lived oscillator with an exponential gain ramp to zero. The envelope (attack/decay) gives each sound its character.
Module Sound Signatures
| Module | Type | Character |
|---|---|---|
| Security | Sawtooth sweep down | Alert, urgent |
| Dev Tools | Square pulse | Digital, mechanical |
| Game | Square + high pitch | 8-bit coin pickup |
| Manga | Sine + vibrato | Soft, warm |
| Robotics | Sawtooth chord | Metallic, industrial |
| Music | Sine triad (C-E-G) | Harmonic, pure |
| Web | Triangle sweep up | Clean, ascending |
Implementation
function playTone(freq: number, type: OscillatorType, duration: number) {
const ctx = getAudioContext()
const osc = ctx.createOscillator()
const gain = ctx.createGain()
osc.type = type
osc.frequency.value = freq
gain.gain.setValueAtTime(0.15, ctx.currentTime)
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration)
osc.connect(gain)
gain.connect(ctx.destination)
osc.start()
osc.stop(ctx.currentTime + duration)
}
Browser Gotchas
The AudioContext starts in a suspended state. You must call ctx.resume() after a user gesture (click, tap). Without this, nothing plays on mobile Safari.
Also: create oscillators fresh each time. Reusing stopped oscillators throws InvalidStateError.