Web Audio APIwithout the browser
A JavaScript DSP engine for Node.js.
Render existing Web Audio graphs offline.
- Render sound on the server
- Test audio in CI without speakers
- Run Tone.js in Node
- Script audio from the terminal
npm install web-audio-api
Not W3C certification.
import { AudioContext } from 'web-audio-api'
const ctx = new AudioContext()
await ctx.resume()
await ctx.resume()
const pluck = ctx.createBufferSource()
pluck.buffer = ctx.createBuffer(1, 1, ctx.sampleRate)
pluck.buffer.getChannelData(0)[0] = 1
const string = ctx.createDelay()
string.delayTime.value = 1 / 220
const damping = ctx.createBiquadFilter()
damping.frequency.value = 4000
damping.Q.value = -3 // dB: no resonance, the loop only decays
const feedback = ctx.createGain()
feedback.gain.value = 0.995
pluck.connect(string)
string.connect(damping).connect(feedback).connect(string)
string.connect(ctx.destination)
pluck.start()
setTimeout(() => ctx.close(), 6000)
Examples
All 46 examplesFAQ
Will my existing code work?
Web Audio graph code can usually be reused. Tone.js integration is tested through the polyfill. Code that depends on HTML media elements or other browser APIs needs adaptation. Load the globals before importing Tone.js:
import 'web-audio-api/polyfill'
const Tone = await import('tone')
const ctx = new AudioContext()
await ctx.resume()
Tone.setContext(ctx)
new Tone.Synth().toDestination()
.triggerAttackRelease('C4', '8n')Or start Node with the polyfill already loaded, node --import web-audio-api/polyfill app.js, and a static import * as Tone from 'tone' works.
Also checked through the polyfill: tunajs effects, smplr with its soundfonts fetched over the network, and Pizzicato once its context is resumed. soundfont-player and Howler reach for XMLHttpRequest, navigator, and HTML5 Audio, which Node does not have.
How does it differ from browser Web Audio?
100% of the Web Platform Tests in this project’s Node runner pass: 4,322 of 4,322. This is not W3C certification or a guarantee of complete browser equivalence. See the CI results and test scope and harness adaptations.
Verified for v1.6.0 on .
The graph API follows Web Audio, including automation and 128-frame rendering. Device access, media elements, and worklet execution differ:
| Aspect | Browser | Node |
|---|---|---|
| Output | destination plays through the device | @audio/speaker by default, or any writable stream via sinkId |
| Microphone | getUserMedia() | @audio/mic feeding a MediaStreamTrack; the polyfill installs getUserMedia() |
| Files | fetch(), then the browser’s codecs | fs, then @audio/decode |
| Media elements | MediaElementAudioSourceNode plays a tag | A silent stub; decode to a buffer instead |
| AudioWorklet | Its own realtime thread | Synchronous, on the same thread |
| Lifecycle | resume() inside a user gesture | resume() at will; close() when the process is done |
Keep graph construction in one function that takes a context; the browser adapter supplies the user gesture and the native context, the Node adapter imports the package and closes the context when the process is done.
How do I render and test in CI?
Render in memory, then assert samples, peaks, RMS, spectra, duration, or channel count. No audio device is opened.
import assert from 'node:assert/strict'
import { OfflineAudioContext } from 'web-audio-api'
const ctx = new OfflineAudioContext(1, 44100, 44100)
const osc = ctx.createOscillator()
osc.connect(ctx.destination)
osc.start()
const audio = await ctx.startRendering()
assert(audio.getChannelData(0).some(Boolean))@audio/assert provides checks for frequency, peak level, clipping, and DC offset. They throw ordinary errors and work with any test runner.
Where does it run?
Node has the broadest test coverage. Deno and Bun run offline rendering checks in CI. Other targets have narrower verification, listed below; audio devices and codecs are separate from the DSP engine.
| Runtime | Offline render | Speaker | Microphone | Decoding |
|---|---|---|---|---|
| Yes | Yes | Yes | Yes | |
| Yes | Yes | Untested | Yes | |
| Yes | Yes | Untested | Yes | |
| Yes | Through the browser’s Web Audio | Untested | Yes | |
| Yes | No device | No | Untested | |
| Yes | No device | No | Untested | |
| Yes, with a shim | No device | No | Untested | |
| Yes, with a shim | No device | No | Untested | |
| Compiles and runs | Development builds; conformance and devices unverified | |||
| Compiles and runs | Compiler-revision dependent; conformance and devices unverified | |||
Node runs the full suite and 4,322 WPT in CI. Deno, Bun, and LLRT render offline in CI; Deno and Bun devices and decoding were checked on macOS, the browser row by loading esm.sh in Chromium, workerd and the bare engine shells with the engine bundled and its adapters stubbed. The shim a bare shell needs is EventTarget, Event, queueMicrotask, and timers. Not yet tried: Hermes, GraalJS. See compiler results and reproductions for JZ and Porffor.
The engine also runs as ESM in a browser tab or Web Worker. Workers have no native Web Audio API; use the package’s OfflineAudioContext for rendering there.
import { OfflineAudioContext } from 'https://esm.sh/web-audio-api'Is it fast enough for realtime?
Realtime playback is supported. The simple graphs below render faster than realtime, but offline throughput does not measure playback latency. AudioWorklets run on the main thread, without realtime thread isolation; benchmark your workload if low latency is essential.
| Scenario | web-audio-api (JS) v1.5.5 median / p95 | node-web-audio-api (Rust) v1.0.8 median / p95 |
|---|---|---|
| silence (baseline) | 0.06 / 0.09 ms | 0.08 / 0.14 ms |
| OscillatorNode | 0.23 / 0.27 ms | 0.27 / 0.33 ms |
| Osc → Gain | 0.34 / 0.50 ms | 0.28 / 0.36 ms |
| Osc → BiquadFilter | 0.39 / 0.45 ms | 0.35 / 0.45 ms |
| Osc → StereoPanner | 0.34 / 0.38 ms | 0.23 / 0.33 ms |
| DynamicsCompressor | 0.85 / 0.99 ms | 0.50 / 0.63 ms |
| ConvolverNode (128-tap IR) | 2.10 / 2.22 ms | 1.80 / 1.99 ms |
| Osc → Filter → Gain (chain) | 0.47 / 0.60 ms | 0.38 / 0.45 ms |
| 8-voice polyphony | 2.26 / 2.34 ms | 1.18 / 1.95 ms |
Do AudioWorklets work?
Yes. Modules can be registered by URL, data URL, Blob URL, or an inline callback in Node. Worklets currently execute synchronously rather than on an isolated realtime thread.
import { AudioContext, AudioWorkletNode } from 'web-audio-api'
const ctx = new AudioContext()
await ctx.resume()
await ctx.audioWorklet.addModule(scope => {
class PassThrough extends scope.AudioWorkletProcessor {
process(inputs, outputs) {
outputs[0][0].set(inputs[0][0] || [])
return true
}
}
scope.registerProcessor('pass-through', PassThrough)
})
const node = new AudioWorkletNode(ctx, 'pass-through')How do I use speakers and microphones?
@audio/speaker provides speaker output through a native device backend. The DSP engine itself needs no native bindings. Microphone examples use the optional @audio/mic adapter, then feed PCM into a MediaStreamTrack. You can also set sinkId to any Node writable stream. Speaker output works on Node, Deno, Bun, and through the browser's own Web Audio; the microphone adapter is verified on Node.
Which audio formats can I decode?
decodeAudioData() uses @audio/decode: the format is detected and its codec loaded on demand, without FFmpeg or native bindings, independently of the browser’s built-in codecs.
- Compressed
- MP3, AAC, Ogg Vorbis, Opus, WMA, AMR, Musepack, QOA
- Lossless
- FLAC, ALAC, WavPack, APE, TTA
- Uncompressed
- WAV, AIFF, CAF, DSF and DFF (DSD)
- Surround
- AC-3, E-AC-3, DTS
- Video containers
- MP4, M4A, MOV, M4V, 3GP, WebM, MKV, AVI: the audio track is extracted
- Tracker modules
- MOD, XM, S3M, IT
Codec packages have separate licences, including GPL and LGPL. Check the decoder licence table before redistributing them. Decoding loads the complete audio into memory.
The npm archive (167 KB gzip) contains the engine, TypeScript definitions, and examples. Dependencies are additional, including @audio/decode and its codecs. Codecs are installed together but loaded only when needed; disk usage varies by platform and dependency version.
When should I use another engine?
Choose this package for portable JavaScript DSP, server rendering, and tests. Evaluate IRCAM’s node-web-audio-api for native execution and low-latency device backends; it ships prebuilt binaries for supported platforms. Keep a browser when your application needs DOM or HTML media behavior.
| Aspect | web-audio-api | node-web-audio-api | standardized-audio-context | web-audio-engine |
|---|---|---|---|---|
| Engine | Pure JavaScript | Rust via Node-API | Browser ponyfill over native Web Audio | Pure JavaScript |
| Coverage | 4,322 WPT passing in the Node runner | WPT harness; minimal MediaStream support | Almost-complete browser subset; no ScriptProcessor | Older partial API; streaming and offline contexts |
| Environment | Node 18+, Deno, Bun | Node 22+, supported native binaries | Supported browsers | Node and browser build |
| Realtime | Simple measured graphs run faster than realtime | Native engine with low-latency backends | Uses the browser’s native engine | PCM streaming context |
| Decode support | @audio/decode, 20+ format families | Symphonia common formats | Browser codec support | WAV by default; custom decoders |
| Best fit | Portable graphs, CI, scripts, servers | Native performance and low latency | Consistent behavior across browsers | Legacy PCM streaming and rendering |
Comparison reviewed 29 August 2026; package names link to their documentation. The WPT count refers to this project’s Node runner, not a shared comparison run across engines.