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