From terminal oscilloscopes to WebAssembly in 150KB
This is Part 2 of a 4-part series on building a complete YM2149 chiptune ecosystem in Rust. In Part 1, we covered the chip itself and the cycle-accurate emulator core. Now let's look at the tools built on top of it.
The Terminal Player
Before building anything graphical, I wanted a simple way to test playback. The CLI player started as a debugging tool and grew into something I actually enjoy using.
The CLI player supports YM, SNDH, AY and AKS songs, with real-time oscilloscope, spectrum analyzer, and a searchable playlist browser.
The CLI player with spectrum bins and oscillators for visuals
The interface shows:
- Metadata: Song title, author, format, duration
- Oscilloscope: Per-channel waveform display (those aren't just pretty — they help debug envelope timing issues)
- Spectrum analyzer: Frequency content of each channel
- Playback controls: Keyboard shortcuts for mute, pause, volume, navigation
The playlist browser pulls from a local music directory and displays format tags — [SNDH], [AY], [YM1-6/YMT1/2], [AKS] — so you can see at a glance what you're loading. Type to search, arrow keys to navigate, Enter to play.
Playing "Wings of Death 6" by Jochen Hippel — a YM5 format file with digi-drum effects.
# Install the CLI globally
cargo install ym2149-replayer-cli
# Browse a directory
ym2149-replayer ./chiptunes/
# Play a single file
ym2149-replayer ./examples/ym/ND-Toxygene.ym
The status line at the bottom shows per-channel state: tone (T) or noise (N), amplitude, frequency, and note name. When a channel plays a digi-drum sample, it shows [DRUM] instead of a frequency. These details matter when you're trying to figure out why a particular track sounds different from the original hardware.
Arkos Tracker supports multiple YM2149 chips in one song — something that makes it a unique soundchip editor. The CLI handles multi-PSG files transparently.
Browser Playback in 150KB
WebAssembly compilation was almost anticlimactic. Rust's wasm-pack tool handled most of the work. The tricky part was the audio API.
Web Audio requires you to run code in an AudioWorklet — a separate thread with strict timing requirements. The WASM module exposes a simple interface:
const player = new Ym2149Player();
player.load(arrayBuffer); // YM, SNDH, AY, or AKS
player.play();
// In the AudioWorklet
process(inputs, outputs) {
const samples = player.generate_samples(128);
outputs[0][0].set(samples);
return true;
}
The compiled WASM weighs about 150KB — smaller than most MP3 files, yet it contains complete emulation of the YM2149, Z80 CPU (for AY files), and Motorola 68000 (for SNDH files).
The browser demo supports loading your own song files (AY, SNDH, YM, AKS) besides providing some examples for each format. The UI shows metadata, playback progress, and per-channel mute toggles.
Performance Characteristics
Numbers matter. Here's what to expect:
| Metric | Value |
|---|---|
| CPU per active player | ~2-5% of one core @ 44.1kHz |
| Memory per player | ~1-2MB (mostly frame data) |
| Latency (native) | ~20ms with default buffer |
| Latency (WASM) | ~40-80ms depending on browser |
| WASM binary size | ~150KB (gzipped: ~60KB) |
The hot path — clock() and get_sample() — runs 44,100 times per second per player. It's optimized for this: no allocations, no branches in the inner loop, cache-friendly data layout. On modern hardware, you could run dozens of simultaneous players before audio becomes a bottleneck.
For comparison, decoding a compressed audio format like Vorbis or MP3 typically uses similar CPU but requires megabytes of audio data to be loaded into memory. The YM2149 approach trades decode complexity for generation complexity, and the generation is simpler.
What's Next
In Part 3, we'll integrate with the Bevy game engine — playlists, crossfading, sound effects, and a demoscene-style visualization example.
Try It
- Website: ym2149-rs.org
- Browser demo: ym2149-rs.org/demo
-
CLI install:
cargo install ym2149-replayer-cli - GitHub: github.com/slippyex/ym2149-rs



Top comments (0)