The essentials

Quick reference

One focused task per row. Jump to the related section for complete, working examples.

UseSyntaxExamples
Create an audio contextconst context = new AudioContext({ latencyHint: 'interactive' })View examples
Resume after user activationawait context.resume()View examples
Release the contextawait context.close()View examples
Connect a media elementconst source = context.createMediaElementSource(audioElement)View examples
Play an AudioBufferconst source = new AudioBufferSourceNode(context, { buffer }); source.start()View examples
Decode fetched audioconst buffer = await context.decodeAudioData(await response.arrayBuffer())View examples
Control levelconst gain = new GainNode(context, { gain: 0.5 })View examples
Connect graph nodessource.connect(gain).connect(context.destination)View examples
Remove graph routingsource.disconnect()View examples
Schedule a gain rampgain.gain.linearRampToValueAtTime(1, context.currentTime + 0.2)View examples
Replace future automationgain.gain.cancelAndHoldAtTime(context.currentTime)View examples
Read waveform samplesanalyser.getFloatTimeDomainData(samples)View examples
Load an audio worklet moduleawait context.audioWorklet.addModule('/audio/meter-processor.js')View examples
Message a processornode.port.postMessage({ type: 'reset' })View examples

Web Audio routes sources, processors, and destinations through an AudioContext graph. It is suited to synthesis, effects, analysis, spatialization, and precise scheduling—not ordinary media playback alone. Create or resume audible contexts from a user gesture, schedule with the audio clock, avoid clipping and main-thread DSP, and close contexts that the application no longer owns.

Step by step

Detailed examples

01

Start audio from user intent and own the context lifecycle

Browsers commonly suspend new AudioContext instances until a trusted user activation. Create one reusable context for a feature and call resume() inside the click or key handler; do not construct a context per sound. The standard context states are suspended, running, and closed, reported through statechange. suspend() pauses processing but retains resources, while close() releases them permanently. Audio output, media capture, and worklets can expose sensitive signals, so do not start them unexpectedly.

Initialize once from a play button
let context;
playButton.addEventListener('click', async () => {
  context ??= new AudioContext({ latencyHint: 'interactive' });
  if (context.state === 'suspended') await context.resume();
  await playCue(context);
});
window.addEventListener('pagehide', () => context?.close(), { once: true });
Back to quick reference ↑
02

Choose streaming or decoded sources by workload

MediaElementAudioSourceNode preserves the HTMLMediaElement streaming and controls path but each media element can be associated with only one such source node. AudioBufferSourceNode offers sample-accurate playback of decoded memory-resident audio and is single-use: create a fresh node for every start. decodeAudioData() expects complete file data and may consume substantial memory. Oscillator and constant-source nodes are also scheduled sources and should be stopped when finished.

Play one decoded sound at a precise time
async function loadBuffer(context, url) {
  const response = await fetch(url);
  if (!response.ok) throw new Error('Audio fetch failed');
  return context.decodeAudioData(await response.arrayBuffer());
}
function playBuffer(context, buffer, when = context.currentTime) {
  const source = new AudioBufferSourceNode(context, { buffer });
  source.connect(context.destination);
  source.start(when);
  return source;
}
Back to quick reference ↑
03

Build explicit graphs with safe gain staging

AudioNode connections form a directed routing graph; fan-out and fan-in are allowed, and feedback requires a delay in the cycle. Values from multiple inputs are mixed and can clip, so place GainNodes at submix and master boundaries and leave headroom. Gain values are linear amplitude, not decibels. Disconnect obsolete paths, stop scheduled sources, and drop JavaScript references; merely disconnecting a source does not end capture or playback.

Route two sources through a master bus
const master = new GainNode(context, { gain: 0.7 });
const compressor = new DynamicsCompressorNode(context);
music.connect(master);
effects.connect(master);
master.connect(compressor).connect(context.destination);
function removeMusic() {
  music.stop();
  music.disconnect();
}
Back to quick reference ↑
04

Schedule AudioParam changes on the audio clock

AudioParam automation is evaluated by the rendering system and is more stable than repeatedly assigning values from setTimeout or animation frames. Anchor a change with setValueAtTime(), then schedule ramps or targets using context.currentTime. Exponential ramps require strictly positive endpoints. Existing future events remain unless canceled; use cancelScheduledValues() or cancelAndHoldAtTime() before replacing an envelope. Never schedule NaN, infinity, or times in the past without understanding clamping behavior.

Apply a click-free envelope
function fadeTo(context, param, value, seconds) {
  const now = context.currentTime;
  param.cancelAndHoldAtTime(now);
  param.linearRampToValueAtTime(value, now + seconds);
}
fadeTo(context, master.gain, 0, 0.15);
Back to quick reference ↑
05

Reuse analysis buffers and respect signal privacy

AnalyserNode passes audio unchanged while exposing waveform and frequency snapshots. Choose fftSize as a supported power of two and allocate arrays once; allocating each animation frame creates garbage. frequencyBinCount is half fftSize. Analysis of microphone, calls, or protected user content can reveal speech and behavior, so require the same consent and retention policy as the underlying signal and avoid sending raw frames to telemetry.

Sample a waveform without per-frame allocation
const analyser = new AnalyserNode(context, { fftSize: 2048 });
source.connect(analyser).connect(context.destination);
const samples = new Float32Array(analyser.fftSize);
function draw() {
  analyser.getFloatTimeDomainData(samples);
  drawWaveform(samples);
  frameId = requestAnimationFrame(draw);
}
let frameId = requestAnimationFrame(draw);
Back to quick reference ↑
06

Move custom DSP to AudioWorklet with bounded work

AudioWorkletProcessor runs on the rendering thread and must finish each render quantum promptly. Load its module from a secure context, create an AudioWorkletNode only after registration, and exchange control data through its MessagePort or AudioParams. Do not fetch, access the DOM, block, allocate heavily, or log continuously inside process(). SharedArrayBuffer optimization additionally requires cross-origin isolation. ScriptProcessorNode is deprecated and should not be used for new DSP.

Load and connect a registered processor
await context.audioWorklet.addModule('/audio/meter-processor.js');
const meter = new AudioWorkletNode(context, 'meter', { numberOfInputs: 1, numberOfOutputs: 1 });
meter.port.addEventListener('message', event => updateMeter(event.data));
meter.port.start();
source.connect(meter).connect(context.destination);
function disposeMeter() { meter.port.close(); meter.disconnect(); }
Back to quick reference ↑

Sources and further reading

References

Authoritative documentation used to verify and expand this cheat sheet.

  1. World Wide Web ConsortiumWeb Audio API 1.1w3.org
  2. World Wide Web ConsortiumWeb Audio APIw3.org
  3. Web Audio Working GroupWeb Audio API: Editors' Draftwebaudio.github.io
  4. WHATWGHTML Standard: Autoplayhtml.spec.whatwg.org
  5. World Wide Web ConsortiumSecure Contextsw3.org

Help us improve

Found a typo or missing example?

Tell us what would make this cheat sheet clearer, more complete, or more useful.

Share feedback