Behind every classic game soundtrack is a small, odd audio format: PlayStation VAG, GameCube DSP, Dreamcast AICA, CRI ADX. They are all variations on one idea — predict each sample from the last two and store only a 4-bit correction — and the tools for them are mostly native programs. We wanted to read and write them from a web page, with one file and nothing to load. So we wrote one and we are releasing it: gamedac.js, MIT licensed, on GitHub.
Is there already one?
We searched npm and GitHub in October 2026. In JavaScript there are decoders: small packages for PlayStation ADPCM and Nintendo DSP-ADPCM (@tootallnate/ps-adpcm and dsp-adpcm, published in June 2026 and without a README), a BRSTM file decoder (brstm) and an HCA decoder (hca.js). We found no JavaScript encoder for any of these formats, and nothing at all for ADX, CD-XA, Dreamcast AICA or OKI. Elsewhere, vgmstream (C) decodes, VGAudio (C#) and psxavenc (C) encode. “We found none” describes our search, not a guarantee.
Using it
<script src="gamedac.js"></script>
<script>
const dsp = gamedac.encode({ format: "dsp", channels: [left, right], sampleRate: 32000 }); // a stereo .dsp file
const song = gamedac.decode(bytesOfAnyGameFile); // format recognised from the bytes
const wav = gamedac.toWav(song); // plain 16-bit WAV
</script>
Nine format ids cover VAG (mono and interleaved stereo), raw VB, CD-XA, GameCube/Wii DSP, ADX, Dreamcast AICA (raw and in a WAV), and OKI (raw and in a WAV). Every one has an encoder and a decoder. The demo page in the download encodes your own audio into any of them and opens game files to play or save as WAV.
How it works
Audio is cut into small blocks. Each block says which predictor and scale to use, and every sample after that is a 4-bit correction. The encoders pick those choices by closed-loop search: for every block they try each predictor and scale, run the decoder's own arithmetic on the trial, and keep whichever gives the smallest error, starting from the state the previous block really produced. The GameCube encoder also analyses the whole clip first to choose the eight predictor pairs its format allows.
What checking against vgmstream found
Our encoders and decoders agreed with each other from the start, which proves very little: a mistake made on both sides cancels. So we encoded test files, decoded the same bytes with gamedac and with vgmstream (compiled to WebAssembly), and compared the samples. The first run disagreed on most formats. Six problems, all fixed:
- PlayStation rounding and clamping. We rounded the prediction and clamped the running state; vgmstream shifts without rounding and clamps only the output. Close on quiet audio, far apart on loud audio.
- CD-XA rounding. The same family of difference.
- GameCube stereo. Our stereo file wrote a channel count of 2; vgmstream decoded it as noise. The layout it reads has the field set to 1, two headers and 8-byte interleave.
- VAGi stereo. Needed audio at
0x800and a size field counting one channel. - Dreamcast AICA. The predictor leaks by 254/256 every sample; our plain Yamaha decoder did not, so every AICA file differed.
- OKI. vgmstream keeps the fraction bits in a 16-bit domain; our 12-bit version rounded them away and drifted.
What this does not prove: vgmstream is a decoder that agrees with games and tools, not hardware itself, and where it differs from the textbook formulas (PlayStation rounding) we followed it.
The numbers
Measured in Node 24 on one Windows PC with synthetic, seeded signals (chord tones and a voice-like signal); npm run measure regenerates every number and figure.
| What | Result |
|---|---|
| Code size | 76.9 KB as shipped (with licence and comments), 57.6 KB stripped of comments, 21.5 KB gzipped (14.9 KB stripped and gzipped), all formats together |
| File size | 25 to 29% of 16-bit PCM (4.0 to 4.7 bits per sample including headers) |
| Signal-to-noise, VAG / XA / DSP / ADX | 43 to 48 dB on tones, 35 to 41 dB on voice-like signal |
| Signal-to-noise, AICA / Yamaha / OKI | 35 / 31 / 21 dB on tones, 28 / 13 / 9 dB on voice-like signal |
| Decoding speed, 10 s of audio | 1 to 7 ms |
| Encoding speed, 10 s of audio | 2 ms (OKI) to 0.86 s (DSP, which analyses the whole clip) |
How we tested it — and what we have not
The test page in the download runs 94 checks in your own browser, offline (the same ones run in Node): a round trip for every format, content detection, signal-to-noise floors, stereo channels staying separate, deterministic output, interleaved and 16-bit input, split DSP files, loop points for VAG, DSP and ADX, the file layouts other tools depend on, and the errors for bad input. Fixed-clip regression vectors — hashes of the bytes and the decoded samples, recorded when they were bit-identical to vgmstream — keep the fixes from drifting. All passed.
The honest limits: no hardware, emulator or in-game test, and no comparison with the console makers' tools. The encoders are plain closed-loop searches with no noise shaping, and we have not compared their quality with other encoders. The plain Yamaha dialect has no independent check, because vgmstream reads that WAV tag as AICA. There is no ADX encryption, AHX or HCA, no container formats such as BRSTM, and DSP and XA stop at two channels. PlayStation, Nintendo, GameCube, Wii, Dreamcast and CRI ADX are trademarks or formats of their owners; this is not affiliated with them and we have made no legal review.
Download
The source is on GitHub: github.com/dialersapps/gamedac (issues and pull requests welcome). The downloads below are plain files with nothing to build, no tracking and no outside requests:
- gamedac-1.0.0.zip — everything in one file (library, ES module, demo, tests, vgmstream check, figures, README, licence)
- gamedac.js — the library (plain script tag,
require()and Workers) - gamedac.mjs — the same code as an ES module
- demo.html — encode your own audio, or open a game file and play it (everything stays in your browser)
- test.html — the in-browser test suite
- README.md — API, options, limits
- LICENSE — MIT, copyright Dialer's Apps 2026
The code was written by Dialer's Apps from public format documentation; it contains no code copied from another project, and vgmstream was used only as a black-box reference decoder to check our output.