Ostrich is a macOS media player app for playback of Game Boy Sound System files. It presents a retro-inspired interface and visualizations of the Game Boy’s pulse audio channels. Under the covers, it is a Nintendo Game Boy emulator written in Swift.
This app is a product of my passions for system design and retro game music. Ostrich is more featured than I dreamed it would ever be when starting, and I’m happy to share it with fellow enthusiasts.
This post is a high-level overview of its implementation. If you’d like to skip to the gallery, click here. If you’re interested in learning how to build and use Ostrich yourself, please check out the readme in the GitHub repository.
Game Boy Sound System is a file format designed to capture the portions of machine code in Game Boy games that are responsible for producing the game’s music and sound effects. It bundles that machine code with some metadata that enables Game Boy CPU emulators to play the audio back with some minimal overhead logic. Basically, GBS files are audio synthesis instructions, and GBS players execute these instructions.
Thus Ostrich is comprised of, from the ground up,
- a Game Boy CPU + memory emulator
- a Sharp LR35902 computation emulator
- a Sharp LR35902 audio unit emulator with AudioKit output
- RAM, ROM, and data bus emulators
- a GBS file parser and media player model
- a UI based on AppKit / Cocoa
The Game Boy’s CPU, the Zilog Z80-like Sharp LR35902, is interesting in that it bundles computational abilities and audio synthesis abilities. Its audio synthesizer is controlled by writing to a small region of the Game Boy’s memory. For the sake of discussion, I’ll call the Z80-like computational bit the CPU and the rest the audio unit.
The CPU is modeled as a Swift class whose core registers are wrappers of eight-bit integers. The logical registers and flags are wrappers of these core registers, as appropriate. 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.
Audio Unit Emulator
The audio unit is modeled as a 48-byte RAM that interprets writes as a normal memory would but also passes the relevant data to independent representations of the audio channels. These audio channels have their own representations of logical concepts like frequency bits, duty bits, etc., and expose a clock function that manipulates these representations and controls an emulator host audio synthesis engine (AudioKit).
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 functionality for inserting and removing cartridges and exposes the LR35902 to anyone who wishes to clock it and make it invoke calls of given addresses.
The media player is modeled as an owner of a Game Boy that generates and inserts a cartridge into it when the user loads a GBS file. 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, runs a call to 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, with some modifications for usability. It uses AppKit (Cocoa) to render playback data, including audio channel waveforms, and presents controls adapted from pictures of the Game Boy’s hardware. The interface leverages AppKit’s Auto Layout and stack views to fit most desired sizes and orientations.
My favorite thing about the UI is watching the pulse waveforms bounce around, especially during intricate tunes like those from Double Dragon.
Auto Layout is pretty amazing. Here’s a shot of the UI fully compacted - 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.
Thank you for reading!