Web Audio APIwithout browser

Your web-audio code, cross-platform. No native bindings, no compilation. Process audio in batch, unit-test it in CI, stream from servers, render at the edge, script it in CLI.

npm install web-audio-api

import { AudioContext } from 'web-audio-api'

const ctx = new AudioContext()
await ctx.resume()

const osc = ctx.createOscillator()
osc.connect(ctx.destination)
osc.start()

Examples

Every example is one runnable file: play it here, run it in Node, drop it into CI.

Utilities

Test signals

Illusions

Synthesis

Generative

API

Questions

Is it fast enough for realtime?

For simple graphs, yes: every measured scenario renders hundreds of times faster than realtime. The native Rust binding stays 1.5–3× quicker; AudioWorklet execution is currently synchronous, so production low-latency work remains the boundary.

Rendering 1 s of audio, Apple silicon laptop, Node 25: node benchmark/compare.js
Scenarioweb-audio-apinode-web-audio-api (Rust)
OscillatorNode0.4 ms (2500× realtime)0.3 ms (3333×)
Oscillator through BiquadFilter1.6 ms (625×)0.5 ms (2000×)
DynamicsCompressor1.2 ms (833×)0.6 ms (1667×)
ConvolverNode, 128-tap IR6.5 ms (154×)2.1 ms (476×)
Chain: osc, filter, gain0.9 ms (1111×)0.4 ms (2500×)
8-voice polyphony4.1 ms (244×)1.5 ms (667×)
How does audio I/O work?

@audio/speaker provides the default output device. Microphone examples use the optional @audio/mic adapter, then feed PCM into a MediaStreamTrack. You can also set sinkId to any Node writable stream.

Which formats can it decode?

decodeAudioData() uses @audio/decode. Its codec and container coverage is broader and more predictable than the native decodeAudioData() set in a typical browser, which varies by browser and operating system. Codecs are detected and loaded on demand for MP3, WAV, Ogg Vorbis, Opus, FLAC, AAC, ALAC, AIFF, CAF, QOA, WebM, WMA, AC-3, DTS, tracker modules, DSD, and more. Supported tracks can also be extracted from MP4, MOV, MKV, WebM, and AVI without FFmpeg or native bindings. Codec packages carry their own licences.

How big is the install?

The core is 208 KB unpacked. Codecs load on demand through @audio/decode, TypeScript types ship in the package, and the project has been maintained since 2013.

Does Tone.js work?

Yes. Install the globals before dynamically importing Tone.js so its browser checks see the Web Audio constructors.

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')
Can I test audio in CI?

Yes. 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))
Can it run without speakers?

Yes. OfflineAudioContext renders without opening @audio/speaker. A realtime context can instead send PCM to a writable stream through sinkId.

import { OfflineAudioContext } from 'web-audio-api'

const ctx = new OfflineAudioContext(2, 44100, 44100)
// build the same graph here
const audio = await ctx.startRendering()
Does it support AudioWorklets?

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')
What differs from a browser?

The I/O boundary. Node has no built-in speakers, microphones, or media elements: realtime output goes through @audio/speaker or any writable stream via sinkId, microphones come from @audio/mic, and files decode through @audio/decode. AudioWorklets run synchronously instead of on an isolated realtime thread. Everything else follows the same specification: the graph API, the 128-frame render quantum, automation, and node behavior.

How does it compare to alternatives?
Snapshot: 29 August 2026. Sizes are npm unpacked package sizes, not full dependency trees.
Aspectweb-audio-apinode-web-audio-apistandardized-audio-contextweb-audio-engine
EnginePure JavaScriptRust via Node-APIBrowser ponyfill over native Web AudioPure JavaScript
Coverage4,317 / 4,317 WPT; offline, worklets, media streamsWPT harness; minimal MediaStream supportAlmost-complete browser subset; no ScriptProcessorOlder partial API; streaming and offline contexts
EnvironmentNode 18+, Deno, BunNode 22+, supported native binariesSupported browsersNode and browser build
RealtimeSimple measured graphs run faster than realtimeNative engine with low-latency backendsUses the browser’s native enginePCM streaming context
Decode support@audio/decode, 20+ format familiesSymphonia common formatsBrowser codec supportWAV by default; custom decoders
Package size208 KB core, codecs on demand42.2 MB package3.0 MB packageRegistry metadata unavailable
Best fitPortable graphs, CI, scripts, serversNative performance and low latencyConsistent behavior across browsersLegacy PCM streaming and rendering

Example

ready