This is an old revision of the document!
Table of Contents
Appendix 5 Sound System
This is Appendix 5 of the SD-8516 Programmer's Reference Guide.
Introduction
The SD-8516 is paired with the SD-450 sound subsystem; named for featuring 4 independent voices with 5 waveforms available, each with a programmable ADSR envelope. Let's dive in with an overview of the architecture and a quick look at INT 11h, the sound services library. If you are just interested in how to play sounds and music in your own games, you can skip ahead to Sound and Music.
Voice Architecture
Each voice occupies 16 bytes of memory in Bank 1:
| Offset | Register | Description |
|---|---|---|
| +$00 | FREQ_LO | Frequency low byte |
| +$01 | FREQ_MID | Frequency mid byte |
| +$02 | FREQ_HI | Frequency high byte |
| +$03 | GATE | Waveform/gate control |
| +$04 | VOLUME | Volume (0-255) |
| +$05 | ATTACK | Attack time |
| +$06 | DECAY | Decay time |
| +$07 | SUSTAIN | Sustain level |
| +$08 | RELEASE | Release time |
| +$09 | DATA1 | Pulse width / noise type |
| +$09-$0F | DATA2-7 | Reserved - Future expansion |
Voice base addresses:
- Voice 0:
$1EF80 - Voice 1:
$1EF90 - Voice 2:
$1EFA0 - Voice 3:
$1EFB0
Waveforms
Gate register values:
- 0: Silent (gate off)
- 1: Square wave
- 2: Triangle wave
- 3: Sawtooth wave
- 4: Sine wave
- 5: Pulse wave (variable width via DATA1)
- 6: White/pink/brown noise (type via DATA1)
ADSR Envelope
The Attack-Decay-Sustain-Release envelope shapes each note:
- Attack: Time to reach peak volume (0-255 × 10ms)
- Decay: Time to decay to sustain level (0-255 × 10ms)
- Sustain: Held volume level (0.0-1.0 of peak)
- Release: Time to fade to silence after gate off (0-255 × 10ms)
Example:
; Play middle C on voice 0
LDA $112B ; C4 frequency (262 Hz / 0.0596)
STA [$1ED00] ; $01ED00 = FREQ_LO/MID
LDAL #0
STAL [$1ED02] ; $01ED02 = FREQ_HI
LDAL $4D ; Initialize volume to ~30%
STAL [$1ED03] ; $1ED03 = VOLUME
LDAL $01 ; Square wave
STAL [$1ED02] ; set GATE to square wave (i.e. turn on)
You can also use the sound services library (I
Sound System Memory Map
Also see Appendix 3 Memory Map.
Sound System Memory Map
The sound system memory map occupies ($01EF80–$01EFBF) – 64 bytes total
| Address Range | Size | Symbol / Register | Description |
|---|---|---|---|
| $01EF80–$01EF8F | 16 bytes | SOUND0_BASE | Voice 0 |
| $01EF80 | 1 byte | SOUND0_FREQ_LO | Frequency low byte (bits 7–0) |
| $01EF81 | 1 byte | SOUND0_FREQ_HI | Frequency high byte (bits 15–8) |
| $01EF82 | 1 byte | SOUND0_GATE | Gate / Waveform select (gate bit + waveform type: noise, pulse, saw, triangle, etc.) |
| $01EF83 | 1 byte | SOUND0_VOLUME | Master volume for voice 0 (usually 0–15, may include global volume in some implementations) |
| $01EF84 | 1 byte | SOUND0_ATTACK | Attack rate (0–15) |
| $01EF85 | 1 byte | SOUND0_DECAY | Decay rate (0–15) |
| $01EF86 | 1 byte | SOUND0_SUSTAIN | Sustain level (0–15) |
| $01EF87 | 1 byte | SOUND0_RELEASE | Release rate (0–15) |
| $01EF88 | 1 byte | SOUND0_DATA1 | Voice-specific control / extra parameter 1 (e.g. pulse width low, filter routing, etc.) |
| $01EF89 | 1 byte | SOUND0_DATA2 | Voice-specific control / extra parameter 2 (e.g. pulse width high, ring/mod flags, etc.) |
| $01EF8A–$01EF8F | 6 bytes | — | Reserved / unused / future expansion for Voice 0 |
| Address Range | Size | Symbol / Register | Description |
|---|---|---|---|
| $01EF90–$01EF9F | 16 bytes | SOUND1_BASE | Voice 1 |
| $01EF90 | 1 byte | SOUND1_FREQ_LO | Frequency low byte |
| $01EF91 | 1 byte | SOUND1_FREQ_HI | Frequency high byte |
| $01EF92 | 1 byte | SOUND1_GATE | Gate / Waveform select |
| $01EF93 | 1 byte | SOUND1_VOLUME | Volume for voice 1 |
| $01EF94 | 1 byte | SOUND1_ATTACK | Attack rate |
| $01EF95 | 1 byte | SOUND1_DECAY | Decay rate |
| $01EF96 | 1 byte | SOUND1_SUSTAIN | Sustain level |
| $01EF97 | 1 byte | SOUND1_RELEASE | Release rate |
| $01EF98 | 1 byte | SOUND1_DATA1 | Extra control 1 |
| $01EF99 | 1 byte | SOUND1_DATA2 | Extra control 2 |
| $01EF9A–$01EF9F | 6 bytes | — | Reserved / unused / future expansion for Voice 1 |
| Address Range | Size | Symbol / Register | Description |
|---|---|---|---|
| $01EFA0–$01EFAF | 16 bytes | SOUND2_BASE | Voice 2 |
| $01EFA0 | 1 byte | SOUND2_FREQ_LO | Frequency low byte |
| $01EFA1 | 1 byte | SOUND2_FREQ_HI | Frequency high byte |
| $01EFA2 | 1 byte | SOUND2_GATE | Gate / Waveform select |
| $01EFA3 | 1 byte | SOUND2_VOLUME | Volume for voice 2 |
| $01EFA4 | 1 byte | SOUND2_ATTACK | Attack rate |
| $01EFA5 | 1 byte | SOUND2_DECAY | Decay rate |
| $01EFA6 | 1 byte | SOUND2_SUSTAIN | Sustain level |
| $01EFA7 | 1 byte | SOUND2_RELEASE | Release rate |
| $01EFA8 | 1 byte | SOUND2_DATA1 | Extra control 1 |
| $01EFA9 | 1 byte | SOUND2_DATA2 | Extra control 2 |
| $01EFAA–$01EFAF | 6 bytes | — | Reserved / unused / future expansion for Voice 2 |
| Address Range | Size | Symbol / Register | Description |
|---|---|---|---|
| $01EFB0–$01EFBF | 16 bytes | SOUND3_BASE | Voice 3 |
| $01EFB0 | 1 byte | SOUND3_FREQ_LO | Frequency low byte |
| $01EFB1 | 1 byte | SOUND3_FREQ_HI | Frequency high byte |
| $01EFB2 | 1 byte | SOUND3_GATE | Gate / Waveform select |
| $01EFB3 | 1 byte | SOUND3_VOLUME | Volume for voice 3 |
| $01EFB4 | 1 byte | SOUND3_ATTACK | Attack rate |
| $01EFB5 | 1 byte | SOUND3_DECAY | Decay rate |
| $01EFB6 | 1 byte | SOUND3_SUSTAIN | Sustain level |
| $01EFB7 | 1 byte | SOUND3_RELEASE | Release rate |
| $01EFB8 | 1 byte | SOUND3_DATA1 | Extra control 1 |
| $01EFB9 | 1 byte | SOUND3_DATA2 | Extra control 2 |
| $01EFBA–$01EFBF | 6 bytes | — | Reserved / unused / future expansion for Voice 3 |
INT 11h Sound Services
; ============================================================================
; INT 11h - SOUND & MUSIC SERVICES
; SD-450 Sound Interface Device + Music Player
; ============================================================================
;
; REGISTER CONVENTION:
; AH = command
; AL = voice (1-4 for music commands, 0-3 for hardware channel)
; B = data (BL=low, BH=high; or BL=single byte)
; C = additional data
; D, E, F = extended data (for AH=30h "new instrument")
;
; FUNCTION MAP:
; --- Music Player Control ---
; AH=00h: Poll music player (check tick timer, advance if ready)
; AH=01h: Execute next note group from song data
; AH=02h: Set clock for next note group
; AH=03h: Execute a single music command
; AH=04h: Reset music player (stop + rewind to start)
; AH=05h: Start / continue music player
; AH=06h: Pause / unpause (AL=00 pause, AL=01+ unpause)
; AH=07h: Load new song (pointer, tempo, calc tick length)
; AH=08h: Insert note data by index
; AH=09h: Delete note data by index
; AH=0Ah: Ask JavaScript to LOAD a music file
; AH=0Bh: Ask JavaScript to SAVE a music file
;
; --- Direct Sound Commands (also used by music file data) ---
; AH=10h: Turn off voice
; AH=11h: Turn on as square (+3 bytes freq: BL, BH, CL)
; AH=12h: Turn on as triangle (+3 bytes freq)
; AH=13h: Turn on as sawtooth (+3 bytes freq)
; AH=14h: Turn on as sine (+3 bytes freq)
; AH=15h: Turn on as PWM (+3 bytes freq)
; AH=16h: Turn on as noise (+3 bytes freq)
; AH=17h: Turn on as waveform byte + note index
; (BL=note index 1-88, BH=waveform 01-06)
; AH=21h: Set GATE (BL = gate byte)
; AH=22h: Set FREQ (BL=lo, BH=mid, CL=hi)
; AH=23h: Set ATTACK (BL = attack byte)
; AH=24h: Set SUSTAIN (BL = sustain byte)
; AH=25h: Set RELEASE (BL = release byte)
; AH=26h: Set DATA1 (BL = data1 byte)
; AH=27h: Set DATA2 (BL = data2 byte)
; AH=28h: Set VOLUME (BL = volume byte)
; AH=29h: Set DECAY (BL = decay byte)
; AH=30h: Set new voice data at-once (10 bytes: BCDEF)
;
; --- Legacy / Utility ---
; AH=40h: Initialize sound system
; AH=41h: Play note (blocking, with duration)
; AH=42h: Stop channel
; AH=43h: Stop all channels
; AH=44h: Set master volume
; AH=45h: Get channel status
; AH=46h: Sound effect (bell, beep, buzz, etc.)
; AH=47h: Play startup sound (GECF)
; AH=48h: Wait milliseconds (C = ms)
; AH=49h: Note index to frequency lookup (BH=index, returns B:CL)
;
; ============================================================================
Sound and Music
It is possible to play sound sound and music in the background (or while-you-wait) on the SD-8516. The boot chime “GECF” is proof of this.
There are two main ways you can play sound and music. One is by directly controlling the music registers. As listed above, you can write data to these registers and the “SD-450 Sound Chip” will play the sounds. This can be a good way to make sound effects. Alternately you can use the Music Player system in INT 11h.
Although INT 11h is used to make music, you can create short scripts to play sound effects. Each channel operates independently so you can have up to four simultaneous sounds playing at the same time. Making a sound effect relies entirely on your own creativity. Here's an example of the sound we use for the system beep; it's a good example of a directly programmed sound:
.equ SOUND_CH0_BASE $01EF80
.equ SOUND_CH0_GATE $01EF83
.equ SOUND_CH0_VOLUME $01EF84
.equ SOUND_CH0_DATA1 $01EF89
play_beep:
; Play a beep (~880hz A4)
; Set channel 0 frequency to A4
LDELM $001CD7 ; A4 freq (3 bytes: $D7, $1C, $00)
STELM [@SOUND_CH0_BASE] ; write freq_lo/mid/hi
; Set pulse width 50%
LDAL $40
STAL [@SOUND_CH0_DATA1]
; Set volume
LDAL $4D
STAL [@SOUND_CH0_VOLUME]
; Gate on with pulse waveform
LDAL @WAVE_PWM
STAL [@SOUND_CH0_GATE]
; Wait 100ms
LDC #100
LDAH $48 ; wait_ms -- wait for C ms (blocking)
INT 0x11
; Gate off
LDAL $00
STAL [@SOUND_CH0_GATE]
; Release delay
LDC #20
LDAH $48 ; wait_ms -- wait for C ms (blocking)
INT 0x11
As you can see – nothing to it!
Music Player System
First you will define a series of commands such as:
BOOT_CHIME_DATA:
; Tick 1: setup + G/C
.byte $01, $00 ; tick index 1
.byte $F1, $83, $00 ; set tempo = 131ms
.byte $F2, $03 ; min_note = 3 (eighth)
.byte $01, $28, $20 ; v1 duty 12.5%
.byte $01, $17, $2F, $05 ; v1 PWM + G4
.byte $02, $17, $1C, $02 ; v2 tri + C3
.byte $00
; Tick 2: E/A
.byte $02, $00
.byte $01, $28, $80 ; v1 duty 50%
.byte $01, $17, $2C, $05 ; v1 PWM + E4
.byte $02, $17, $14, $02 ; v2 tri + E2
.byte $00
; Tick 3: C/F
.byte $03, $00
.byte $01, $28, $40 ; v1 duty 25%
.byte $01, $17, $28, $05 ; v1 PWM + C4
.byte $02, $17, $10, $02 ; v2 tri + C2
.byte $00
; Tick 4: F/G
.byte $04, $00
.byte $01, $28, $20 ; v1 duty 12.5%
.byte $01, $17, $2D, $05 ; v1 PWM + F4
.byte $02, $17, $14, $02 ; v2 tri + E2
.byte $00
; Tick 5: silence
.byte $05, $00
.byte $01, $10 ; v1 off
.byte $02, $10 ; v2 off
.byte $00
; End
.byte $00, $00
With this data, you can play it like this:
; Initialize sound system
LDAH $40
INT 0x11
; Point ELM at our song data and load it (AH=07h)
LDELM @MUSIC_DATA
LDAH $07
INT 0x11
; Start playback: set state and prime the clock
LDAH $05
INT 0x11
; Blocking playback loop: poll until song finishes
player_loop:
YIELD
LDAH $00 ; Poll Music Player
INT 0x11 ; This checks if there is a note, and plays it.
LDAH $0F ; Check player state
INT 0x11
CMP AL, 1 ; 1 = still playing (0 = song is finished)
JZ @player_loop
; Done
LDAH $43 ; Stop player (turn off all channels)
INT 0x11 ; This causes the music player to stop.
Now, if you have a game loop, you can just put this code in your main loop:
LDAH $00 ; Poll Music Player INT 0x11 ; This checks if there is a note, and plays it.
It will quickly check if there is a note and play it. In this way you can have “background music”.
