Ostrich is a macOS app for playback of Game Boy Sound System files.
Its retro interface visualizes the Game Boy’s pulse audio channels. Under the covers, it’s a Nintendo Game Boy emulator written in Swift.
This app came way further than I expected it to. I owe a lot of that to Austin Zheng and his Swift tutelage. Love of retro game music and interest in embedded devices also helped.
A Game Boy Sound System file captures the bits of machine code in a Game Boy game that produce its music and sound effects. GBS files are audio synthesis instructions, and GBS players execute these instructions.
Ostrich is comprised of
- a Sharp LR35902 emulator
- a CPU emulator
- an APU (audio processing unit) emulator with AudioKit output
- RAM, ROM, and data bus emulators
- a GBS parser and media player model
- a UI built on AppKit / Cocoa
The Game Boy’s SoC, the Sharp LR35902, bundles computational and audio synthesis abilities. Its CPU is a lot like the Zilog Z80. Its audio unit is controlled by writing to a small region of memory.
The CPU is a class with an eight-bit integer for each core register. The logical registers and flags are wrappers of these core registers. Instructions are modeled as generic functions that accept data types of particular width and read/writeability, so that a single function may capture most or all variants of a given instruction family. Like ADD or LD.
Instruction parsing is.. a really big switch statement.
The APU is modeled as a 48-byte RAM. It interprets writes as a normal memory would, but also passes the relevant data to independent representations of audio channels. Each audio channel has its own representations of frequency bits, duty bits, etc., and exposes a clock function that manipulates these representations and controls its AudioKit output.
Game Boy Emulator
The Game Boy itself is modeled as an owner of an LR35902, some RAM, a data bus connecting everything, and, optionally, a game cartridge. It exposes means of inserting and removing cartridges, and exposes its LR35902 to anyone who wishes to clock it or have it call specific addresses.
The media player is an owner of a Game Boy. When the user loads a GBS file, the player generates and inserts a cartridge into it. When the user plays a song, the player resets the Game Boy’s memory, initializes some of its registers like the stack pointer and program counter, calls a given address, and begins clocking it. The media player is a model, in the MVC sense, and is commanded by the controller.
The user interface is modeled after the original Dot Matrix Game Boy. It uses AppKit (Cocoa) to render playback data, including audio channel waveforms, and presents controls adapted from photos of the Game Boy’s hardware. The interface leverages AppKit’s Auto Layout and stack views to fit most desired sizes and orientations.
Watching the pulse waveforms bounce around is so satisfying. Especially during intricate tunes like Double Dragon’s.
Auto Layout is amazing. Here’s a shot of the UI fully shrunk - the text goes into a Winamp-esque scroll mode when it doesn’t fit in the display.
It’s come a long from its statically sized start.
Thanks for reading!