How I Built a BRSTM Converter with WebAssembly — Technical Deep Dive
The Problem: No Browser-Based BRSTM Tool Existed
Every existing BRSTM converter was a desktop app — BrawlBox for Windows, Looping Audio Converter (a .NET Framework app written in C#), or command-line tools that needed compiling from source. If you were on a Mac, on Linux, or wanted to quickly convert a file without installing anything, you were out of luck.
The core challenge: BRSTM encoding requires C libraries (DspTool) that were never designed to run in a browser. And decoding game audio formats like ADX, HCA, and FSB requires vgmstream, a massive C codebase with decades of reverse-engineered format support.
Architecture: Three WebAssembly Modules in One Browser Tab
BGM Box uses three distinct WebAssembly modules:
1. vgmstream.wasm — The Universal Decoder (4.1 MB)
vgmstream is the industry-standard game audio decoding library. It supports 100+ formats — from Nintendo's BRSTM/BCSTM/BFSTM to CRI's ADX/HCA, Microsoft's XMA, FMOD's FSB, and Sony's ATRAC3. The vgmstream project provides a pre-built WASM binary, which I wrapped in a Web Worker so decoding never blocks the UI thread.
The hardest part was integrating with the Emscripten virtual filesystem that vgmstream's WASM build uses internally. Files are written to the virtual FS via FS.writeFile(), decoded via vgmstream's CLI interface (callMain(['-o', '/out.wav', '/in.bcstm'])), then read back from FS.readFile() — all inside a Web Worker.
2. sound.wasm — The BRSTM Encoder (176 KB)
The encoding side comes from DspTool, Alex Barney's MIT-licensed C library for creating BRSTM, BCSTM, and BFSTM files. This module takes raw PCM audio data and produces a Nintendo-compatible audio file with full loop point support.
One gotcha: DspTool expects PCM in planar format ([all L samples][all R samples]), but vgmstream outputs PCM in interleaved format ([L,R,L,R...]). The glue code in the encoder Worker handles this conversion before passing data to the WASM heap.
3. lame.min.js + vorbis-encoder-bundle — The Output Encoders
For reverse conversions (BRSTM→MP3, BCSTM→OGG, FSB→MP3), I needed encoders that run in the browser. LAME (MP3) was straightforward via lamejs. For OGG Vorbis, I used libvorbis.js compiled with Emscripten, bundled with Browserify for importScriptscompatibility in Web Workers.
Why Web Workers Matter
Every heavy operation — decoding a 5-minute BCSTM file, encoding PCM to MP3 — runs in a Web Worker. This keeps the main thread responsive for UI updates, progress bars, and audio preview. A typical conversion triggers three Workers:
- VgmstreamWorker — loads vgmstream.wasm, decodes input → PCM
- EncoderWorker — loads sound.wasm, encodes PCM → BRSTM
- Mp3EncoderWorker or OggEncoderWorker — encodes PCM → MP3/OGG
Workers communicate via postMessage with structured clone or transferable objects. The 4.1 MB vgmstream WASM binary loads once and stays cached for subsequent conversions.
The Interleaved vs. Planar PCM Bug
One bug cost me three hours of debugging: multi-channel audio playback was producing distorted, garbled sound. The root cause was a PCM layout mismatch. vgmstream outputs interleaved stereo ([L0,R0,L1,R1...]), but the AudioBuffer API expects planar channels ([L0,L1...][R0,R1...]). The deinterleaving formula needed to be pcm[i * totalChannels + channelIndex], not pcm[channelIndex * sampleLength + i]. A one-line fix, but it took scanning through hex dumps of PCM data to find it.
Try It
BGM Box is 100% free and open about its tech stack. Every conversion runs locally in your browser — no server-side processing, no file uploads.
bgmbox.com — Convert, Play, Loop. All in one box.