How to Build a Zero-Dependency Audio Synth in the Browser Using Web Audio API
The Web Audio API is a criminally underused tool for generating dynamic, real-time sound. This post walks through creating a mini synth from scratch — no libraries, no UI frameworks, just pure browser-native sound generation using JavaScript. Ideal for game devs, creative coders, or anyone who wants to learn procedural audio at a low level. Why Use Web Audio API Directly? No external libraries or builds needed Precise control over oscillators, filters, and timing Useful in games, interactive apps, or generative art Step 1: Create the Audio Context and Oscillator The AudioContext is your entry point to the Web Audio graph: const ctx = new (window.AudioContext || window.webkitAudioContext)(); const osc = ctx.createOscillator(); osc.type = 'sawtooth'; // Try sine, square, triangle, etc. osc.frequency.value = 440; // A4 osc.start(); Step 2: Add Envelope Control (ADSR) We'll use a GainNode to fade the volume in and out for each note: const gain = ctx.createGain(); gain.gain.setValueAtTime(0, ctx.currentTime); gain.gain.linearRampToValueAtTime(1, ctx.currentTime + 0.1); // attack gain.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.6); // decay + release osc.connect(gain).connect(ctx.destination); Step 3: Wire It to a Key Press Basic synth: play a tone on key press, release it after a fixed time: document.addEventListener('keydown', (e) => { const note = e.key.toUpperCase(); const freqMap = { A: 440, W: 466, S: 494, D: 523 }; // add more if (!freqMap[note]) return; const o = ctx.createOscillator(); const g = ctx.createGain(); o.type = 'square'; o.frequency.value = freqMap[note]; g.gain.setValueAtTime(0, ctx.currentTime); g.gain.linearRampToValueAtTime(0.9, ctx.currentTime + 0.05); g.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.4); o.connect(g).connect(ctx.destination); o.start(); o.stop(ctx.currentTime + 0.5); }); Step 4: Add Filter for Character A low-pass filter can shape the tone: const filter = ctx.createBiquadFilter(); filter.type = 'lowpass'; filter.frequency.value = 800; osc.connect(filter).connect(gain).connect(ctx.destination); Pros and Cons ✅ Pros Completely free, native to every modern browser Fine-grained control of sound generation Great for games, audio toys, or learning synthesis ⚠️ Cons No polyphony unless manually managed Inconsistent latency across devices Touch event restrictions on mobile (needs user interaction)
The Web Audio API is a criminally underused tool for generating dynamic, real-time sound. This post walks through creating a mini synth from scratch — no libraries, no UI frameworks, just pure browser-native sound generation using JavaScript. Ideal for game devs, creative coders, or anyone who wants to learn procedural audio at a low level.
Why Use Web Audio API Directly?
- No external libraries or builds needed
- Precise control over oscillators, filters, and timing
- Useful in games, interactive apps, or generative art
Step 1: Create the Audio Context and Oscillator
The AudioContext
is your entry point to the Web Audio graph:
const ctx = new (window.AudioContext || window.webkitAudioContext)();
const osc = ctx.createOscillator();
osc.type = 'sawtooth'; // Try sine, square, triangle, etc.
osc.frequency.value = 440; // A4
osc.start();
Step 2: Add Envelope Control (ADSR)
We'll use a GainNode to fade the volume in and out for each note:
const gain = ctx.createGain();
gain.gain.setValueAtTime(0, ctx.currentTime);
gain.gain.linearRampToValueAtTime(1, ctx.currentTime + 0.1); // attack
gain.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.6); // decay + release
osc.connect(gain).connect(ctx.destination);
Step 3: Wire It to a Key Press
Basic synth: play a tone on key press, release it after a fixed time:
document.addEventListener('keydown', (e) => {
const note = e.key.toUpperCase();
const freqMap = { A: 440, W: 466, S: 494, D: 523 }; // add more
if (!freqMap[note]) return;
const o = ctx.createOscillator();
const g = ctx.createGain();
o.type = 'square';
o.frequency.value = freqMap[note];
g.gain.setValueAtTime(0, ctx.currentTime);
g.gain.linearRampToValueAtTime(0.9, ctx.currentTime + 0.05);
g.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.4);
o.connect(g).connect(ctx.destination);
o.start();
o.stop(ctx.currentTime + 0.5);
});
Step 4: Add Filter for Character
A low-pass filter can shape the tone:
const filter = ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 800;
osc.connect(filter).connect(gain).connect(ctx.destination);
Pros and Cons
✅ Pros
- Completely free, native to every modern browser
- Fine-grained control of sound generation
- Great for games, audio toys, or learning synthesis
⚠️ Cons
- No polyphony unless manually managed
- Inconsistent latency across devices
- Touch event restrictions on mobile (needs user interaction)