Decode any audio format to raw samples.
JS / WASM with no ffmpeg or native bindings; works in Node.js and browsers.
Small API, minimal size, near-native performance, lazy-loading, chunked decoding.
import decode from '@audio/decode';
const { channelData, sampleRate } = await decode(anyAudioBuffer);| Format | Package | Size | Engine |
|---|---|---|---|
| MP3 | @audio/decode-mp3 | 92 KB | WASM |
| WAV | @audio/decode-wav | 11 KB | JS |
| OGG Vorbis | @audio/decode-vorbis | 166 KB | WASM |
| FLAC | @audio/decode-flac | 135 KB | WASM |
| Opus | @audio/decode-opus | 166 KB | WASM |
| M4A / AAC / ALAC | @audio/decode-aac | 368 KB | WASM + JS |
| QOA | @audio/decode-qoa | 8 KB | JS |
| AIFF | @audio/decode-aiff | 20 KB | JS |
| CAF | @audio/decode-caf | 9 KB | JS |
| WebM | @audio/decode-webm | 250 KB | WASM |
| AMR | @audio/decode-amr | 241 KB | WASM |
| WMA | @audio/decode-wma | 91 KB | WASM |
Auto-detects format. Input can be ArrayBuffer, Uint8Array, Buffer, or anything that materializes to bytes, including a Blob/File or fetch Response.
import decode from '@audio/decode'
let { channelData, sampleRate } = await decode(buf)
let fromFile = await decode(fileInput.files[0]) // File
let fromUrl = await decode(await fetch(url)) // Responselet dec = await decode.mp3()
let a = await dec(chunk1) // { channelData, sampleRate }
let b = await dec(chunk2)
await dec() // closeimport decode from '@audio/decode'
for await (let { channelData, sampleRate } of decode.mp3(response.body)) {
// process chunks
}Works with ReadableStream, fetch body, Node stream, or any async iterable.
Formats: mp3, flac, opus, oga, m4a, wav, qoa, aac, aiff, caf, webm, amr, wma.
Works from a CDN without a bundler. Codecs load on demand via dynamic import, only for formats you decode:
<script type="module">
import decode from 'https://esm.sh/@audio/decode'
let { channelData, sampleRate } = await decode(buf)
</script>For self-hosting, use an import map to point @audio/decode and each needed @audio/decode-* package to local files. Codec-internal files load by relative path.
@audio/decode-flac, @audio/decode-vorbis, @audio/decode-mp3, and @audio/decode-wav decode inside an AudioWorklet without Blob, TextDecoder, Worker, or fetch.
The umbrella remains async for detection, lazy imports, and Blob/Response inputs.
Import a codec package directly for synchronous calls.
wav, qoa, aiff, and caf are synchronous:
import decode from '@audio/decode-wav'
let pcm = decode(wavBytes)WASM codecs initialize asynchronously, then decode synchronously:
import { decoder } from '@audio/decode-flac'
let dec = await decoder()
let pcm = dec.decode(bytes)
let tail = dec.flush()
dec.free()Read tags, pictures, markers and regions without decoding samples. Available for
wav, mp3, flac, oga (Ogg Vorbis), opus, and m4a.
import { wav, mp3, flac, oga, opus, m4a } from '@audio/decode/meta'
let { meta, sampleRate, markers, regions } = mp3(bytes)
// meta: { title, artist, album, year, bpm, key, comment, pictures, raw, ... }
// markers: [{ sample, label }]
// regions: [{ sample, length, label }]Each codec sub-package also exposes its parser directly:
import { parseMeta } from '@audio/decode-wav/meta'
let info = parseMeta(wavBytes)Each @audio/decode-* package is a self-contained ESM module that can run in a worker:
// decode-worker.js
import decode from '@audio/decode-mp3'
self.onmessage = async ({ data }) => {
let pcm = await decode(data)
self.postMessage(pcm, pcm.channelData.map(ch => ch.buffer))
}
// main.js
let worker = new Worker('./decode-worker.js', { type: 'module' })
worker.postMessage(mp3buf, [mp3buf])
worker.onmessage = ({ data }) => { /* { channelData, sampleRate } */ }- encode – encode PCM into any audio format.
- audio-type – detect audio format from buffer.
The umbrella and most codec packages are MIT. Three codecs use another license: @audio/decode-aac GPL-2.0, @audio/decode-wma GPL-2.0-or-later, and @audio/decode-amr Apache-2.0. Install only the codecs whose licenses fit your project. The umbrella loads them on demand.
