Portable Web Audio API for Node.js. No native dependencies. 100% WPT conformance.
npm install web-audio-api
Use
import { AudioContext } from 'web-audio-api' const ctx = new AudioContext() await ctx.resume() const osc = ctx.createOscillator() osc.frequency.value = 440 osc.connect(ctx.destination) osc.start() // → A440 through your speakers
Built-in speaker output via audio-speaker — no extra setup.
Offline rendering
import { OfflineAudioContext } from 'web-audio-api' const ctx = new OfflineAudioContext(2, 44100, 44100) // 1 second, stereo const osc = ctx.createOscillator() osc.frequency.value = 440 osc.connect(ctx.destination) osc.start() const buffer = await ctx.startRendering() // buffer.getChannelData(0) → Float32Array of 44100 samples
Examples
node examples/<name>.js — all parametric. Positional args or key=value with prefix matching (f=440, freq=440 both work). Note names (A4, C#3, Eb5), k for kHz (20k), s/m/h for duration (10m).
| Example | |
|---|---|
| Test Signals | |
| tone.js | Reference pitch — sine A4 2s |
| sweep.js | Hear the audible range — 20..20k exp 3s |
| noise.js | White, pink, brown, blue, violet — pink 2s |
| impulse.js | Dirac click — 5 0.5s |
| dtmf.js | Dial a phone number — 5551234 |
| stereo-test.js | Left, right, center — 1k 1s |
| metronome.js | Programmable click — 120..240 10m X-x- |
| Illusions | |
| shepard.js | Pitch that rises forever — up 15s |
| risset-rhythm.js | Beat that accelerates forever — up 120 20s |
| binaural-beats.js | Third tone from two (headphones!) — 200 10 10s |
| missing-fundamental.js | Your brain fills in the note — 100 3s |
| beating.js | Two close frequencies dance — 440 3 5s |
| Synthesis | |
| subtractive-synth.js | Sawtooth → filter sweep → ADSR |
| additive.js | Waveforms from harmonics — square 220 16 3s |
| fm-synthesis.js | DX7 frequency modulation — 440 2 5 3s |
| karplus-strong.js | A string plucked from noise — A4 4s |
| Generative | |
| sequencer.js | Step sequencer — precise timing |
| serial.js | Twelve-tone rows (Webern) — 72 30s |
| gamelan.js | Balinese kotekan — two parts, one melody — 120 20s |
| drone.js | Tanpura shimmer — C3 30s |
| jazz.js | Modal jazz — new every time |
| API | |
| speaker.js | Hello world |
| lfo.js | Tremolo via LFO |
| spatial.js | Sound moving through space |
| worklet.js | Custom AudioWorkletProcessor |
| linked-params.js | One source controlling many gains |
| fft.js | Frequency spectrum |
| render-to-buffer.js | Offline render → buffer |
| process-file.js | Audio file → EQ + compress → render |
| pipe-stdout.js | PCM to stdout — pipe to aplay, sox, etc. |
Node extensions
Beyond the spec, for Node.js. Not portable to browsers.
addModule(fn)— register a processor via callback instead of URL, no file neededsinkId: stream— pipe PCM to any writable:new AudioContext({ sinkId: process.stdout })thennode synth.js | aplay -f cdnumberOfChannels,bitDepth— control output format in the constructor
FAQ
- How do I close an AudioContext?
-
Or with explicit resource management:
using ctx = new AudioContext() - Why does it start suspended?
-
Per W3C spec — browsers require user gesture before audio plays. Call
await ctx.resume()to start.OfflineAudioContextdoesn't need it. - Does it work with Tone.js?
-
import { AudioContext } from 'web-audio-api' import * as Tone from 'tone' Tone.setContext(new AudioContext())
- How do I decode audio files?
-
const buffer = await ctx.decodeAudioData(readFileSync('track.mp3'))
WAV, MP3, FLAC, OGG, AAC via audio-decode.
- How do I use it as a polyfill?
-
import 'web-audio-api/polyfill' // AudioContext, GainNode, etc. are now global
- Can I unit-test audio code?
-
OfflineAudioContextrenders without speakers — pair with any test runner. See render-to-buffer.js. - How fast is it?
-
All scenarios render faster than real-time. Pure JS matches Rust napi on simple graphs; heavier DSP (convolution, compression) is 2–4× slower — WASM kernels planned.
npm run bench:allto measure.
Architecture
Pull-based audio graph. AudioDestinationNode pulls upstream via _tick(), 128-sample render quanta per spec. AudioWorklet runs synchronously (no thread isolation). DSP kernels separated from graph plumbing for future WASM swap.
EventTarget ← Emitter ← DspObject ← AudioNode ← concrete nodes
← AudioParam
EventTarget ← Emitter ← AudioPort ← AudioInput / AudioOutput
Alternatives
- node-web-audio-api — Rust napi bindings. Faster heavy DSP, but Node-only with native compilation and partial spec.
- standardized-audio-context — Browser-only. Normalizes cross-browser quirks.
- web-audio-api-rs — Pure Rust / WASM.
- web-audio-engine — Archived. Partial spec coverage.
License
MIT