Using the Web Audio API in JavaScript: Full Guide

Learn how to generate, process, and play sound programmatically with the Web Audio API. Create oscillators, connect nodes, and control volume and frequency.

6 min read

The Web Audio API lets you generate, process, and play sound entirely in JavaScript without any audio files. You build an audio graph by connecting nodes together, like patching cables on a physical synthesizer. The API is powerful enough to build music apps, game sound effects, and audio visualizers.

Here is the simplest sound you can make, a 440 Hz beep that plays for one second:

javascriptjavascript
const audioContext = new AudioContext();
const oscillator = audioContext.createOscillator();
oscillator.type = "sine";
oscillator.frequency.value = 440;
 
oscillator.connect(audioContext.destination);
oscillator.start();
oscillator.stop(audioContext.currentTime + 1);

The oscillator generates a sine wave at 440 Hz (the note A). The connect method sends its output to the audio context's destination, which is your speakers. Calling start begins playback, and stop schedules it to end after one second.

The audio graph model

Every sound in the Web Audio API flows through a graph of connected nodes. You create source nodes that produce audio, processor nodes that modify it, and a destination node that outputs to the speakers.

Web Audio API graph: oscillator to speakers

The oscillator produces a raw waveform. The gain node adjusts the volume. The destination represents your speakers or headphones. You can insert any number of processing nodes between the source and destination, including filters, delays, compressors, and analyzers.

Creating and controlling an oscillator

The OscillatorNode generates a periodic waveform. The type property sets the waveform shape, and the frequency property sets the pitch in Hertz.

javascriptjavascript
const audioContext = new AudioContext();
const oscillator = audioContext.createOscillator();
 
oscillator.type = "square";
oscillator.frequency.value = 220;

The available waveform types are sine (smooth, pure tone), square (hollow, video game-like), sawtooth (buzzy, bright), and triangle (softer than square). Each sounds different because of its harmonic content.

To play the oscillator, connect it to the destination and call start. The stop method takes a time in seconds from the audio context's currentTime.

javascriptjavascript
oscillator.connect(audioContext.destination);
oscillator.start();
oscillator.stop(audioContext.currentTime + 2);

You can change the frequency while the oscillator is playing by setting frequency.value at any time. This creates pitch bends and gliding effects without restarting the sound.

Controlling volume with a gain node

A GainNode adjusts the amplitude of the audio passing through it. The gain property works like a volume knob, where 1 is the original volume, 0 is silence, and values above 1 amplify.

javascriptjavascript
const gainNode = audioContext.createGain();
gainNode.gain.value = 0.5;
 
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);

By inserting the gain node between the oscillator and the destination, you control the output volume without changing the oscillator itself. The gain value can be animated over time for fade-in and fade-out effects.

Handling the autoplay policy

Modern browsers require a user gesture before playing audio. The AudioContext starts in a suspended state, and calling start on an oscillator has no effect until the context is resumed.

javascriptjavascript
const audioContext = new AudioContext();
 
document.querySelector("#playButton").addEventListener("click", () => {
  audioContext.resume().then(() => {
    const oscillator = audioContext.createOscillator();
    oscillator.connect(audioContext.destination);
    oscillator.start();
  });
});

The resume method returns a promise that resolves when the context is allowed to play. Always call resume inside a click or keypress handler. Creating the AudioContext early and resuming it on interaction is the standard pattern.

Putting it together: a simple synth

Combining an oscillator, gain node, and user interaction produces a basic playable synthesizer. The frequency changes with each click.

javascriptjavascript
const audioContext = new AudioContext();
const gainNode = audioContext.createGain();
gainNode.gain.value = 0.3;
gainNode.connect(audioContext.destination);
 
function playNote(frequency) {
  const oscillator = audioContext.createOscillator();
  oscillator.type = "sine";
  oscillator.frequency.value = frequency;
  oscillator.connect(gainNode);
  oscillator.start();
  oscillator.stop(audioContext.currentTime + 0.5);
}

Each note creates a new oscillator, plays it for half a second, and lets it stop. Oscillators are single-use. After calling stop, you cannot restart them. Create a new one for each note.

Common mistakes

  • Forgetting to resume the AudioContext. Sound will not play until the context is running, which requires a user gesture.
  • Calling start without first calling connect. An unconnected oscillator produces audio that goes nowhere.
  • Trying to restart a stopped oscillator. Oscillators are one-shot. Create a new one for each sound.
  • Setting gain values too high. Values above 1 amplify the signal, which can cause distortion. Keep gain at or below 1 for clean output.
  • Creating too many AudioContexts. One context per page is enough. Create it once and reuse it.

Quick reference

OperationCode
Create contextconst ctx = new AudioContext()
Create oscillatorctx.createOscillator()
Create gain nodectx.createGain()
Connect nodessource.connect(destination)
Play soundoscillator.start()
Stop soundoscillator.stop(ctx.currentTime + seconds)
Resume contextawait ctx.resume() (requires user gesture)
Set volumegainNode.gain.value = 0.5
Rune AI

Rune AI

Key Insights

  • Create an AudioContext as the environment for all audio processing.
  • OscillatorNode generates synthetic waveforms at a given frequency.
  • Connect nodes together using the connect method to build an audio graph.
  • GainNode controls volume at any point in the graph.
  • The AudioContext starts suspended. Resume it from a user gesture before playing sound.
RunePowered by Rune AI

Frequently Asked Questions

Why do I need a user gesture to start the AudioContext?

Browsers block autoplaying audio to prevent annoying sounds on page load. The AudioContext starts in a suspended state and must be resumed from a user gesture like a click or keypress using audioContext.resume().

Can I play an MP3 file with the Web Audio API?

Yes. Use fetch to load the file as an ArrayBuffer, then call audioContext.decodeAudioData to convert it into an AudioBuffer. Connect the buffer to an AudioBufferSourceNode and start playback.

What is the difference between an oscillator and an audio buffer?

An oscillator generates a synthetic waveform like a sine, square, or sawtooth wave mathematically. An audio buffer holds decoded audio data from a file like an MP3 or WAV. Both can be played through the same audio graph.

Does the Web Audio API work on mobile?

Yes, it is supported on iOS Safari and Android Chrome. However, iOS requires a user gesture to resume the AudioContext, and audio may stop when the screen locks unless you handle the page visibility change event.

Conclusion

The Web Audio API models sound as a graph of connected nodes. An AudioContext is the canvas. Oscillators and buffer sources generate sound. Gain nodes control volume. Destination nodes send sound to the speakers. Connect nodes together in a chain and call start to play. This simple model scales from a beep to a full synthesizer.