Skip to main content
← Back to Blog

December 2024 • 10 min read

Web Audio API: The Browser's Hidden Studio

Everything you need to know about building real audio applications in the browser. No plugins, no installations—just JavaScript.

Share

Most developers don't know this, but your browser has a professional-grade audio engine built in. It can synthesize sounds, process audio in real-time, analyze frequencies, and do things that used to require dedicated hardware or expensive software.

It's called the Web Audio API, and it's been shipping in every major browser for years. I've used it to build synthesizers, metronomes, ear training apps, and more. Here's what I've learned.

The Basics: Nodes and Graphs

Web Audio works like a modular synthesizer. You create nodes—oscillators, filters, gain controls, analyzers—and connect them together into a signal chain. Audio flows from source nodes through processing nodes to the destination (your speakers).

const ctx = new AudioContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();

osc.connect(gain);
gain.connect(ctx.destination);

osc.start();

That's a complete synthesizer in seven lines. An oscillator generating a tone, routed through a gain node for volume control, connected to your speakers.

The AudioContext

Everything starts with an AudioContext. It's your audio environment—the clock, the sample rate, the destination. You typically create one per application and reuse it.

One gotcha: browsers require a user gesture (click, tap) before audio can play. You can't autoplay sounds on page load. This is a security feature to prevent annoying websites, but it means you need to initialize your AudioContext in response to user interaction.

button.addEventListener('click', async () => {
  const ctx = new AudioContext();
  if (ctx.state === 'suspended') {
    await ctx.resume();
  }
  // Now you can make sound
});

Oscillators: Your Sound Source

The OscillatorNode generates periodic waveforms. You get four types built in: sine, square, sawtooth, and triangle. Each has a distinct character.

  • Sine: Pure tone, no harmonics. Clean but boring.
  • Square: Hollow, woody. Think NES or Gameboy.
  • Sawtooth: Bright and buzzy. Classic synth lead sound.
  • Triangle: Softer than square, slightly hollow.

Oscillators are single-use. Once you stop one, it's done—you have to create a new one for the next note. This feels wasteful but it's actually efficient; the browser is optimized for this pattern.

Filters: Shaping the Sound

The BiquadFilterNode is where things get interesting. It can be a lowpass filter (cuts highs), highpass (cuts lows), bandpass (cuts both), or several other types.

Two parameters matter most: frequency (the cutoff point) and Q (resonance). Crank up the Q and the filter starts to ring at the cutoff frequency. That's the classic analog synth sound.

const filter = ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 1000; // Hz
filter.Q.value = 10; // Resonance

Gain: Volume Control

The GainNode controls amplitude. A value of 1 is unity gain (no change), 0 is silence, 2 is twice as loud.

You'll use gain nodes constantly—for master volume, for envelope shapes, for mixing multiple sources. They're cheap to create and essential for any real audio work.

Scheduling: The Audio Clock

This is where Web Audio really shines. The AudioContext.currentTime property gives you a high-resolution clock that's sample-accurate. You can schedule events to happen at precise times in the future.

const now = ctx.currentTime;
gain.gain.setValueAtTime(0, now);
gain.gain.linearRampToValueAtTime(1, now + 0.01);
gain.gain.exponentialRampToValueAtTime(0.01, now + 0.5);

That's an envelope—instant attack, half-second decay. The audio engine handles the interpolation at sample rate. No JavaScript timer jitter, no callbacks, just smooth automation.

Analysis: Seeing Sound

The AnalyserNode lets you extract data from the audio stream. You can get time-domain data (the waveform) or frequency-domain data (a spectrum).

const analyser = ctx.createAnalyser();
analyser.fftSize = 256;
const dataArray = new Uint8Array(analyser.frequencyBinCount);

function draw() {
  analyser.getByteTimeDomainData(dataArray);
  // Draw to canvas...
  requestAnimationFrame(draw);
}

I use this for real-time waveform displays in my synths. Connect your audio through an analyser, read the data on each animation frame, draw it to a canvas. Simple.

Practical Tips

Use refs for real-time control. If you're using React, don't read audio parameters from state in your audio callbacks. State updates are async; refs are immediate. Your knobs will feel laggy otherwise.

Cancel scheduled values before setting new ones. If you've scheduled an automation curve and want to override it with a new value, call cancelScheduledValues() first. Otherwise the old automation will fight your new value.

Use setTimeout for sequencing, but schedule ahead. JavaScript timers aren't accurate enough for musical timing, but they're fine for triggering audio events that you schedule using the audio clock. Let the audio engine do the precise timing.

Don't be afraid to create nodes. Creating and connecting nodes is cheap. Don't try to reuse oscillators—just make new ones for each note.

What You Can Build

I've built all of these with Web Audio API:

  • • A TB-303 style acid synthesizer with step sequencer
  • • A professional metronome with tap tempo
  • • An ear training game for perfect pitch
  • • Drum synthesis (kicks, snares, hi-hats—no samples)
  • • Real-time waveform visualization

The API is capable of much more: convolution reverb, dynamics processing, 3D spatialization, custom audio worklets for DSP. The browser is a legitimate audio platform now.

Check out my projects to see these techniques in action. Everything runs in your browser, no installation required.