The Problem: No Browser-Based BRSTM Tool Existed
Every existing BRSTM converter was a desktop app — BrawlBox for Windows, Looping Audio Converter (requiring Java), or custom 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 third-party executables, you were simply out of luck.
The core technical challenge comes down to library availability:
- Encoding: BRSTM encoding relies on C libraries like DspTool, which were written exclusively for native desktop environments.
-
Decoding: Decoding proprietary game audio formats like
ADX,HCA, andFSBrequires vgmstream, a massive C codebase representing decades of reverse-engineered audio format support.
Architecture: Three WebAssembly Modules in One Browser Tab
To bring this native toolkit to the browser, BGM Box orchestrates three distinct WebAssembly modules compiled from C via Emscripten.
The workflow splits processing away from the main UI thread into background Web Workers, maintaining a butter-smooth 60 FPS user interface during complex encoding tasks:
+-------------------------------------------------------------------+
| MAIN THREAD (UI) |
| - Drag & Drop Handling - Audio Preview (Web Audio API) |
| - Progress Bar Rendering - SEO & Route State |
+-------------------------------------------------------------------+
|
postMessage()
v
+-------------------------------------------------------------------+
| WEB WORKERS (Background) |
| |
| 1. VgmstreamWorker 2. EncoderWorker 3. Export Worker |
| [vgmstream.wasm] [sound.wasm] [lame / vorbis] |
| Decode to PCM Encode to BRSTM Export MP3/OGG |
+-------------------------------------------------------------------+
1. vgmstream.wasm — The Universal Decoder (4.1 MB)
vgmstream is the industry-standard game audio decoding library, supporting over 100+ formats (Nintendo's BRSTM/BCSTM/BFSTM, CRI's ADX/HCA, Microsoft's XMA, FMOD's FSB, and Sony's ATRAC3).
-
Emscripten Virtual Filesystem: Because
vgmstreamexpects a POSIX filesystem, input files are written directly into memory viaFS.writeFile(). -
Worker Execution: The CLI interface is invoked via
callMain(['-o', '/out.wav', '/in.bcstm']), after which the decoded stream is read back usingFS.readFile()within the worker.
2. sound.wasm — The BRSTM Encoder (176 KB)
The encoding process relies on DspTool (developed by Alex Barney), an MIT-licensed C library designed for generating BRSTM, BCSTM, and BFSTM files.
- Takes raw PCM audio data and outputs a fully compliant Nintendo audio file complete with loop point metadata.
-
Memory Management: PCM data is pushed into the WASM heap using Emscripten’s
_mallocand converted to match Nintendo DSP ADPCM standards.
3. lame.min.js + vorbis-encoder-bundle — The Export Encoders
For reverse conversions (BRSTM → MP3, BCSTM → OGG, FSB → MP3), browser-native encoders are required:
-
MP3: Handled efficiently via
lamejs. -
OGG Vorbis:
libvorbis.jscompiled with Emscripten, bundled with Browserify to ensure compatibility withimportScripts()inside Web Workers.
Technical Deep-Dive: The Interleaved vs. Planar PCM Bug
During development, multi-channel audio output resulted in harsh, garbled noise. Tracking this down required comparing hex dumps of PCM buffers against expected byte layouts.
Interleaved PCM (vgmstream output / Web Audio API):
[ L0, R0, L1, R1, L2, R2, ... ]
Planar PCM (DspTool expectation):
[ L0, L1, L2, ... ] [ R0, R1, R2, ... ]
The bug stemmed from an indexing calculation error during the deinterleaving pass:
// ❌ Incorrect Deinterleaving Formula
const sample = pcm[channelIndex * sampleLength + i];
// ✅ Correct Deinterleaving Formula
const sample = pcm[i * totalChannels + channelIndex];
By ensuring proper data transformation before allocation to the WASM heap, multi-channel audio was rendered cleanly without UI thread stutter.
Web Worker Memory Optimization
Loading a 4.1 MB WASM module multiple times can quickly lead to heavy memory consumption. To keep performance optimal:
- Persistent Worker Allocation: Web Workers are instantiated once and kept alive throughout the session.
-
Transferable Objects: Large
ArrayBufferpayloads exchanged viapostMessage()use transferables (postMessage(data, [data.buffer])), transferring ownership zero-copy rather than cloning large audio arrays. - WASM Module Caching: Compiled WebAssembly modules are cached in IndexedDB to bypass recompilation overhead on subsequent page visits.
Try It Out
BGM Box is 100% free and open about its technical implementation. Every conversion process executes entirely inside your client browser — no server-side uploading or processing required.
👉 bgmbox.com — Convert, Play, Loop. All in one box.
Top comments (0)