Decode any audio format to raw samples.
JS / WASM – no ffmpeg, no native bindings, works in both node and browser.
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 | 4 KB | JS |
| OGG Vorbis | @audio/decode-vorbis | 164 KB | WASM |
| FLAC | @audio/decode-flac | 133 KB | WASM |
| Opus | @audio/decode-opus | 178 KB | WASM |
| M4A / AAC | @audio/decode-aac | 368 KB | WASM |
| QOA | @audio/decode-qoa | 8 KB | JS |
| AIFF | @audio/decode-aiff | 20 KB | JS |
| CAF | @audio/decode-caf | 7 KB | JS |
| WebM | @audio/decode-webm | 263 KB | WASM |
| AMR | @audio/decode-amr | 241 KB | WASM |
| WMA | @audio/decode-wma | 91 KB | WASM |
Auto-detects format. Input can be ArrayBuffer, Uint8Array, or Buffer.
import decode from 'audio-decode'
let { channelData, sampleRate } = await decode(buf)let 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.
Each codec is a self-contained bundle under @audio/* — no transitive deps, no import map bloat.
For selective loading in the browser (avoids bundling all codecs):
<script type="importmap">
{
"imports": {
"audio-decode": "https://esm.sh/audio-decode",
"audio-type": "https://esm.sh/audio-type",
"@audio/decode-mp3": "https://esm.sh/@audio/decode-mp3",
"@audio/decode-wav": "https://esm.sh/@audio/decode-wav",
"@audio/decode-flac": "https://esm.sh/@audio/decode-flac",
"@audio/decode-opus": "https://esm.sh/@audio/decode-opus",
"@audio/decode-vorbis": "https://esm.sh/@audio/decode-vorbis",
"@audio/decode-aac": "https://esm.sh/@audio/decode-aac",
"@audio/decode-qoa": "https://esm.sh/@audio/decode-qoa",
"@audio/decode-aiff": "https://esm.sh/@audio/decode-aiff",
"@audio/decode-caf": "https://esm.sh/@audio/decode-caf",
"@audio/decode-webm": "https://esm.sh/@audio/decode-webm",
"@audio/decode-amr": "https://esm.sh/@audio/decode-amr",
"@audio/decode-wma": "https://esm.sh/@audio/decode-wma"
}
}
</script>
<script type="module">
import decode from 'audio-decode'
let { channelData, sampleRate } = await decode(buf)
</script>Only list the codecs you need — each @audio/decode-* package bundles all its WASM/JS deps internally.
Each @audio/decode-* package is a self-contained ESM module — import directly 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 } */ }- audio-encode – encode PCM into any audio format.
- audio-type – detect audio format from buffer.
