Sound & Music
Game Boy audio architecture, hUGE music driver, sound effect playback, channel muting, and direct hardware register control for plugin development. The sound system is implemented in C.
Game Boy Audio Architecture
The Game Boy has four dedicated sound channels, each with distinct synthesis capabilities. All channels share a 4-bit volume range (0–15) and a length counter. On the Game Boy Color, each channel can be individually panned left/right for stereo output.
| Channel | Type | Capabilities |
|---|---|---|
| CH1 | Pulse | Frequency sweep, 4 duty cycles, volume envelope |
| CH2 | Pulse | 4 duty cycles, volume envelope (no sweep) |
| CH3 | Wave | Custom 32-sample 4-bit waveform |
| CH4 | Noise | LFSR-based noise, adjustable frequency |
Music and sound effects share these four channels. When a sound effect plays on a channel, it temporarily overrides the music on that channel. Plan your music arrangements and SFX channel usage to avoid unwanted cutoffs.
hUGE Music Driver
GB Studio uses the hUGE tracker driver for music playback. It supports the .uge file format and is installed as a VBlank routine, guaranteeing consistent tempo regardless of main loop performance.
- Driver runs in the VBlank ISR — precise timing on every frame
- Supports all 4 channels with effects (arpeggio, portamento, vibrato, etc.)
- Music data is banked — loaded via
far_ptr_t(bank + address)
Music Playback (C API)
// Start music playback
music_play(bank, music_ptr);
// Stop music
music_stop();
GBVM Music Commands
| Command | Description |
|---|---|
VM_MUSIC_PLAY | Play a music track (bank, pointer) |
VM_MUSIC_STOP | Stop current music |
VM_MUSIC_MUTE | Mute specific channels (bitmask) |
VM_MUSIC_ROUTINE | Set music event routine (callback on pattern events) |
Channel Mute Masks
Mute individual channels to free them for sound effects or direct hardware control. The mask is a 4-bit value where each bit corresponds to a channel:
| Bit | Channel | Hex Value |
|---|---|---|
| 0 | CH1 (Pulse 1) | 0x01 |
| 1 | CH2 (Pulse 2) | 0x02 |
| 2 | CH3 (Wave) | 0x04 |
| 3 | CH4 (Noise) | 0x08 |
// Mute CH4 so noise SFX won't conflict with music
VM_MUSIC_MUTE 0x08
// Mute CH1 and CH2 for a dual-pulse SFX
VM_MUSIC_MUTE 0x03
// Unmute all channels
VM_MUSIC_MUTE 0x00
Sound Effects System
Sound effects are played via a VGM-style player that runs alongside the music driver. An SFX temporarily takes over one or more channels; when the effect finishes, the channel returns to the music driver automatically.
SFX Playback
// GBVM:
VM_SFX_PLAY bank, pointer, mask, priority
// mask: which channels the SFX uses (same bitmask as mute)
// priority: higher value = won't be interrupted by lower priority SFX
- Channel mask: Specifies which channels the SFX will use (same format as mute masks)
- Priority: Higher-priority SFX will interrupt lower-priority ones, but not vice versa
- SFX data is banked and referenced via
far_ptr_t
Built-in Sound Types
GB Studio includes predefined sound effect types accessible from the event editor:
- Beep tones (short pulse blips)
- Tone sweeps (ascending/descending frequency)
- Noise bursts (percussion, explosions)
- Custom SFX via
.vgmfiles or direct register writes
SFX playback is handled by sfx_play_isr, a Z80 assembly ISR. This ensures sound effects play at consistent speed, independent of main loop timing.
Direct Channel Control
For plugins that need precise control over audio hardware, you can write directly to the Game Boy’s sound registers. This is necessary for custom tones, engine sound effects, and any audio behavior not covered by the built-in SFX system.
Pulse Channel Registers (CH1 / CH2)
| Register | Address | Description |
|---|---|---|
NR10 | 0xFF10 | CH1 sweep (period, direction, shift) |
NR11 | 0xFF11 | CH1 duty cycle + length |
NR12 | 0xFF12 | CH1 volume envelope |
NR13 | 0xFF13 | CH1 frequency low byte |
NR14 | 0xFF14 | CH1 frequency high + trigger |
NR21–24 | 0xFF16–19 | CH2 (same layout, no sweep register) |
Noise Channel Registers (CH4)
| Register | Address | Description |
|---|---|---|
NR41 | 0xFF20 | Length |
NR42 | 0xFF21 | Volume envelope |
NR43 | 0xFF22 | Polynomial counter (frequency) |
NR44 | 0xFF23 | Trigger + length enable |
Volume Envelope Format
Registers NR12, NR22, and NR42 share the same envelope format:
NRx2 format: VVVV DNNN
V = initial volume (0-15)
D = direction (0 = decrease, 1 = increase)
N = envelope period (0 = disabled, 1-7 = speed)
| Value | Effect |
|---|---|
0xF0 | Volume 15, no envelope (constant full volume) |
0xF1 | Volume 15, fade out slowly |
0xF7 | Volume 15, fade out quickly |
0x11 | Volume 1, fade in slowly |
0x00 | Silence (volume 0, no envelope) |
Frequency Calculation
The pulse and wave channels use an 11-bit frequency register. To play a specific note frequency:
frequency_register = 2048 - (131072 / desired_hz)
| Note | Hz | Register Value |
|---|---|---|
| C4 (Middle C) | 262 | 1548 |
| A4 | 440 | 1750 |
| C5 | 523 | 1797 |
| A5 | 880 | 1899 |
Common Patterns
Playing SFX Without Interrupting Music
Mute the target channel in the music driver before playing a sound effect on it:
// Mute CH4 in music, then use CH4 for noise SFX
VM_MUSIC_MUTE 0x08
VM_SFX_PLAY bank, sfx_ptr, 0x08, 5
Custom Tone from a Plugin
Play a 440 Hz tone (A4) on CH2 using direct register writes:
// Play a 440Hz tone on CH2
NR21_REG = 0x80; // 50% duty cycle
NR22_REG = 0xF0; // Volume 15, no envelope
UINT16 freq = 2048 - (131072 / 440); // = 1750
NR23_REG = freq & 0xFF; // Low byte
NR24_REG = 0x80 | ((freq >> 8) & 0x07); // Trigger + high bits
If music is playing, mute the target channel with VM_MUSIC_MUTE before writing to its registers. Otherwise the music driver will immediately overwrite your values on the next VBlank.
Stopping a Tone
Set the volume envelope to zero to silence a channel:
// Silence CH2
NR22_REG = 0x00; // Volume 0, no envelope
NR24_REG = 0x80; // Trigger to apply the new envelope