Emulating Nintendo Gameboy's APU

COM-418 Final project / Sylvain Kuchen / June 2022

Motivation

During the first year of undergraduate CS studies at EPFL, all students follow a a class called CS-108 Introduction à la programmation orientée objet. As the name suggests, it is an introductory class to object oriented programming, with a focus on practice. The main component of this 9-credit course is a programming project. It's a different one every year. At the time I took the course, the project was emulating a Gameboy. It was not a simple project, as debugging an emulator is rather tricky and bugs can be subtle. In the end, my group's implementation was working quite well but there was a missing piece: we did not support sound.

After a quick look at the available documentation, I confirmed that this would not be straight forward. I am a procrastinator at heart, so while this kept nagging me, I was waiting for a good reason to start the endeavour.

With the announcement of COM-418 Computers & Music, the perfect opportunity appeared at my very last semester at EPFL :)

Goals

I want for the emulation to reproduce sound as close as possible to what the hardware the produces. However, the Audio Processing Unit (APU) has many many quirks and its documentation is sparse. Since it would be great to also be able play the game, this has to extend my existing emulator.

People from the emulator development community have written tests for Gameboy emulators. The tests verify correctness by running on the emulated CPU and checking hardware for expected behaviour. My objective was to pass a certain test suite, called Blargg's tests.

Overview

The Gameboy's APU has four sound channels:

  1. Pulse A - This channel produces 4 different kinds of square waves at different frequencies. It also has a Sweep module which lets the programmer easily make sound increasing/decreasing in frequency
  2. Pulse B - Same as Pulse A, except it has no sweep module
  3. Wave - Plays samples from a RAM bank, called the Wave Rame, at different frequencies
  4. Noise - Produces "white" noise using a random number generator

The Gameboy components are attached to an 8-bit, 16-bit addressed main bus. The CPU can use the APU by writing to its registers at 0xFF10 - 0xFF26. Each channel has 5 registers. There are 3 more registers to control the mixer. The wave RAM can store 32 4-bit samples.

Each channel has a modulation unit (Pulse, Wave, LFSR). These units produce 4-bit samples, and are clocked at different rates by the Frame Sequencer. The duration of the sound they play can be limited by a Length Counter. Their volume can be adjusted by Volume Envelope. This kind of unit can be configured to fade sound in or out

There is a DAC for each channel. They can be individually toggle on/off to enable/disable a channel. The voltages they output are added together in the mixer. The mixer then output to stereo speakers. It can control the volume of each speaker.

APU overview

Results

I have extended the original emulator (called Gameboj) and added code for the APU. Check the git history to see how it has progressed (commit before sound support, last commit)

The 4 channels are implemented and work! The sound produced matches closely the original version. There are some problems left to solve: the Sweep unit is not working correctly and there is a frequency issue with the Noise channel. But overall, is is fairly enjoyable to listen to (but I mean, it is still 4-bit music)

I have recorded two iconic games from the Gameboy era, once on my emulator and once on Sameboy, which considered a reference emulator. See below:

Super Mario Land 2

Gameboj
Sameboy

Not so bad! There are some problems with the sweep, which we can hear when Mario enters a pipe or collects coins.

The Legend of Zelda

Gameboj
Sameboy

Also close, but still some problems with the Noise channel. The pitch of lightning is too high and the sound when the title appears is less crisp.

At the time of writing, Blargg tests are sadly not all passing. Registers and length counters tests are passing, but sweep tests are not.

Try it!

The code is available on Github. There are instructions on how to compile and run it. This requires you to install the right version of Java and JavaFX. If you can't be bothered, you can use this custom JRE with the right version of java and all modules bundled (this will only work on a x64 Linux installation though). After you have downloaded and extracted it, simply run the run.sh script in the folder

Conclusion

I am glad that I finally was able to work on this project. I appreciate a lot the freedom that was given in COM-418, and hope my idea and work lived up to expectations. Even though there are no more deadlines, I want to continue working on it this summer and fix the last problems!