The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| Create an audio context | const context = new AudioContext({ latencyHint: 'interactive' }) | View examples |
| Resume after user activation | await context.resume() | View examples |
| Release the context | await context.close() | View examples |
| Connect a media element | const source = context.createMediaElementSource(audioElement) | View examples |
| Play an AudioBuffer | const source = new AudioBufferSourceNode(context, { buffer }); source.start() | View examples |
| Decode fetched audio | const buffer = await context.decodeAudioData(await response.arrayBuffer()) | View examples |
| Control level | const gain = new GainNode(context, { gain: 0.5 }) | View examples |
| Connect graph nodes | source.connect(gain).connect(context.destination) | View examples |
| Remove graph routing | source.disconnect() | View examples |
| Schedule a gain ramp | gain.gain.linearRampToValueAtTime(1, context.currentTime + 0.2) | View examples |
| Replace future automation | gain.gain.cancelAndHoldAtTime(context.currentTime) | View examples |
| Read waveform samples | analyser.getFloatTimeDomainData(samples) | View examples |
| Load an audio worklet module | await context.audioWorklet.addModule('/audio/meter-processor.js') | View examples |
| Message a processor | node.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
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.
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 }); 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.
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;
} 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.
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();
} 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.
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); 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.
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); 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.
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(); } Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



