A graph of audio nodes
The Web Audio API does its work inside an audio context. You link nodes into a chain: a source makes the samples, other nodes change them, and the destination sends the result to the speakers or headphones. MDN calls this an audio routing graph. It's been available across browsers since April 2021.
A pure tone takes three pieces:
- Oscillator: a source that makes a repeating wave. Its type is sine, square, sawtooth or triangle, and its frequency is in hertz, 440 by default.
- Gain node: multiplies the samples by a number, which sets the volume.
- Destination: the context's output, which plays through the speakers.
In code, the chain is oscillator.connect(gain).connect(ctx.destination), followed by oscillator.start().
Browsers don't autoplay
A page can't start sound on its own. A context created outside a user gesture starts in the suspended state, and it has to be resumed after the visitor clicks or taps, with resume(). MDN's advice is to create or resume the context from inside a user gesture.
That's why tone generators wait for a Play button, and why a page that loads silent isn't broken.
Sample rate and the top frequency
A context has a sample rate, the number of samples it makes each second. By default it's the preferred rate of the output device. MDN says 44,100 Hz is the most common, and typical values run from 8000 to 96,000 Hz.
A digital signal can only hold frequencies below half the sample rate, which is called the Nyquist frequency. At 44,100 Hz that's 22,050 Hz, and at 96,000 Hz it's 48,000 Hz. Even the common rate covers the whole range of hearing. Scheduling is precise too, with high timing accuracy and low latency, which is why drum machines and sequencers work in a browser.
How the Frequency Generator uses it
The Frequency Generator makes each tone with an oscillator, sends it through its own gain node, and mixes up to four of them into a master gain node before the destination. It all runs in your browser, with no audio files and no server.
Each oscillator's gain is its volume setting, a small loudness correction for its waveform, and a share based on how many oscillators are active. That last part keeps the sum from clipping, so the graph needs no compressor.
The context is created when you press Play, which satisfies the autoplay rule. The generator also suspends it while the tab is hidden and resumes it when you come back, to save processing power.