Reference · Web Audio · JavaScript
📅 March 2026⏱ 12 min read⚙️ Browser API

Web Audio API Cheatsheet

The Web Audio API models audio as a directed graph: sources → processing nodes → destination. Every operation (gain, filter, reverb, synthesis, FFT analysis) is a node. This cheatsheet covers the essential nodes, parameter automation, custom DSP via AudioWorklet, and real-time spectrum analysis.

AudioContext Graph Architecture

Every sound pipeline starts with creating a context and connecting nodes like a signal chain:

// Always create on user gesture (click/keypress) — browsers require it
const ctx = new AudioContext();

// Source → [effect nodes] → destination (speakers)
source.connect(gainNode);
gainNode.connect(filterNode);
filterNode.connect(ctx.destination);
Suspended context: Browsers suspend AudioContext until a user gesture. Check ctx.state === 'suspended' and call ctx.resume() inside a click handler. A common pattern is to create the context on first click into the canvas.
Node Type Class Role
Source OscillatorNode, AudioBufferSourceNode, MediaStreamSourceNode Generates audio signal
Effect GainNode, BiquadFilterNode, ConvolverNode, DynamicsCompressorNode Processes signal
Analysis AnalyserNode Tap signal without altering it
Splitter/Merger ChannelSplitterNode, ChannelMergerNode Multi-channel routing
Destination AudioDestinationNode Final output (speakers/headphones)

OscillatorNode — Synthesis

const osc = ctx.createOscillator();
osc.type      = 'sine';   // sine | square | sawtooth | triangle | custom
osc.frequency.setValueAtTime(440, ctx.currentTime); // 440 Hz = A4

const gain = ctx.createGain();
gain.gain.value = 0.3;  // -1..1 safe range; >1 clips

osc.connect(gain);
gain.connect(ctx.destination);
osc.start();
osc.stop(ctx.currentTime + 2.0);  // plays for 2 seconds

// Custom waveform via PeriodicWave:
const real = new Float32Array([0, 1, 0.5, 0.25]); // cosine terms (DC, f, 2f, 3f)
const imag = new Float32Array([0, 0, 0,   0]);
const wave = ctx.createPeriodicWave(real, imag);
osc.type = 'custom';
osc.setPeriodicWave(wave);

GainNode & BiquadFilterNode

// Gain: simple volume control
const gain = ctx.createGain();
gain.gain.value = 0.5;   // linear amplitude; decibels: 10^(dB/20)

// BiquadFilter: EQ / tone control
const filter = ctx.createBiquadFilter();
filter.type            = 'lowpass';   // lowpass|highpass|bandpass|notch|peaking|lowshelf|highshelf|allpass
filter.frequency.value = 1000;        // cutoff freq (Hz)
filter.Q.value         = 1.0;         // resonance; >10 → ringing; Q=0.707 = Butterworth
filter.gain.value      = 6;           // only for peaking/shelf types (dB)

// Telephone effect (band-limited to 300–3400 Hz)
const lowCut = ctx.createBiquadFilter();
lowCut.type = 'highpass';
lowCut.frequency.value = 300;
const highCut = ctx.createBiquadFilter();
highCut.type = 'lowpass';
highCut.frequency.value = 3400;
source.connect(lowCut).connect(highCut).connect(ctx.destination);

ConvolverNode — Impulse Response Reverb

Convolution reverb multiplies every sample with a recorded room impulse response (IR) to add realistic acoustics. IR files (.wav) are measured by firing a starter pistol or sine sweep in the target space.

async function loadReverb(url) {
  const resp   = await fetch(url);
  const arrBuf = await resp.arrayBuffer();
  const audioBuf = await ctx.decodeAudioData(arrBuf);
  const conv = ctx.createConvolver();
  conv.buffer = audioBuf;
  conv.normalize = true;  // auto-scale IR to prevent clipping
  return conv;
}

// Wet/dry mix with GainNodes:
const dry = ctx.createGain(); dry.gain.value = 0.7;
const wet = ctx.createGain(); wet.gain.value = 0.3;
source.connect(dry).connect(ctx.destination);
source.connect(conv).connect(wet).connect(ctx.destination);
No fetch needed: You can synthesize a reverb IR using an exponentially decaying white noise buffer. Use a 2–3 second buffer of noise multiplied by exp(-4 * t / duration) — gives a natural diffuse hall sound good enough for games.

AnalyserNode — Real-Time FFT

const analyser = ctx.createAnalyser();
analyser.fftSize           = 2048;   // must be power of 2; 32–32768
analyser.smoothingTimeConstant = 0.8; // 0=no smoothing, 1=max (sluggish)
analyser.minDecibels       = -90;
analyser.maxDecibels       = -10;

source.connect(analyser);
analyser.connect(ctx.destination); // analyser passes audio through unmodified

const bufLen  = analyser.frequencyBinCount;  // = fftSize / 2
const freqBuf = new Uint8Array(bufLen);      // 0–255 per bin (magnitude)
const timeBuf = new Uint8Array(bufLen);      // waveform (time domain)

function drawSpectrum(canvas) {
  analyser.getByteFrequencyData(freqBuf);   // fill freq data
  const ctx2 = canvas.getContext('2d');
  ctx2.clearRect(0, 0, canvas.width, canvas.height);
  const barW = canvas.width / bufLen;
  freqBuf.forEach((val, i) => {
    const pct = val / 255;
    ctx2.fillStyle = `hsl(${200 + pct * 160}, 80%, 50%)`;
    ctx2.fillRect(i * barW, canvas.height * (1 - pct), barW - 1, canvas.height * pct);
  });
  requestAnimationFrame(() => drawSpectrum(canvas));
}

Frequency of bin i: f = i × sampleRate / fftSize. Default sample rate is 44100 Hz, so with fftSize=2048: bin 0 = 0 Hz, bin 1 = 21.5 Hz, bin 1024 = 22050 Hz (Nyquist).

AudioWorkletProcessor — Custom DSP

ScriptProcessorNode is deprecated. AudioWorkletProcessor runs in the dedicated audio thread (no garbage collection pauses) and processes audio in 128-sample blocks:

// worklet-processor.js (separate file, served as script)
class WhiteNoiseProcessor extends AudioWorkletProcessor {
  process(inputs, outputs, parameters) {
    const output = outputs[0];
    for (const channel of output) {
      for (let i = 0; i < channel.length; i++) {
        channel[i] = (Math.random() * 2 - 1) * 0.3;
      }
    }
    return true; // return false to deactivate node
  }
}
registerProcessor('white-noise', WhiteNoiseProcessor);

// In main script:
await ctx.audioWorklet.addModule('worklet-processor.js');
const noise = new AudioWorkletNode(ctx, 'white-noise');
noise.connect(ctx.destination);
Cross-origin restriction: addModule() requires the worklet file to be same-origin or have CORS headers. During local development, serve files with a local HTTP server — do not use file:// protocol.

AudioParam Automation

All AudioParams support sample-accurate scheduling — far more precise than setTimeout:

const g = ctx.createGain();
const t = ctx.currentTime;

g.gain.setValueAtTime(0,  t);          // instant jump
g.gain.linearRampToValueAtTime(1, t + 0.01); // attack
g.gain.exponentialRampToValueAtTime(0.7, t + 0.3); // decay (must be >0)
g.gain.setTargetAtTime(0.5, t + 0.3, 0.1); // sustain (time constant)
g.gain.cancelScheduledValues(t + 2.0);
g.gain.linearRampToValueAtTime(0, t + 2.5); // release

// LFO modulating pitch:
const lfo = ctx.createOscillator();
lfo.frequency.value = 5;  // 5 Hz vibrato
const lfoGain = ctx.createGain();
lfoGain.gain.value = 15; // ±15 Hz pitch deviation
lfo.connect(lfoGain).connect(osc.frequency);

Common Gotchas