Skip to content

SID Multi-Voice

Step a note pattern once per raster interrupt, handing each of the SID's three voices its note from a frequency table — the skeleton of every C64 music player.

sidmusicrastersound-driveraudio

Overview

One voice plays one note; setting a voice up gives it a waveform and an envelope. Music is the step up: all three voices at once, each handed a fresh note on a regular beat. On the C64 that beat comes from the raster interrupt — the same 50 Hz tick that drives the display drives the tune. Count those interrupts into rows, and on each row look up a frequency per voice from a note table and gate it. Voice 1 takes the lead, voice 2 the harmony, voice 3 the bass. That loop — a tick, a pattern, a frequency table — is every C64 music driver in miniature.

Code

; =============================================================================
; SID MULTI-VOICE - C64
; A raster-IRQ tick steps a pattern; each of the 3 voices gets its note
; IRQ wiring is exactly as in Raster Splits — music_tick is what it calls
; =============================================================================

SID_FREQ_LO = $D400        ; per voice: base + 0
SID_FREQ_HI = $D401        ;           base + 1
SID_CTRL    = $D404        ;           base + 4  (waveform + gate)
SID_AD      = $D405        ;           base + 5
SID_SR      = $D406        ;           base + 6
SID_VOLUME  = $D418

VOICE1 = 0                 ; the three voices are 7 bytes apart
VOICE2 = 7
VOICE3 = 14

TEMPO = 6                  ; raster frames per row
ROWS  = 8

music_init:
    lda #$0F
    sta SID_VOLUME             ; master volume up

    ldx #VOICE1                ; same plucked envelope on every voice
    jsr set_env
    ldx #VOICE2
    jsr set_env
    ldx #VOICE3
    jsr set_env

    lda #TEMPO
    sta mtimer
    lda #0
    sta mrow
    rts

set_env:                       ; X = voice base offset
    lda #$09
    sta SID_AD,x               ; attack 0, decay 9
    lda #$00
    sta SID_SR,x               ; sustain 0, release 0 -> a pluck
    rts

; --- Call once per raster IRQ; a new row lands every TEMPO frames ---
music_tick:
    dec mtimer
    beq .row
    rts                        ; between rows — voices hold
.row:
    lda #TEMPO
    sta mtimer

    ldy mrow                   ; reload the row index before each read
    lda pat_v1,y
    ldx #VOICE1
    jsr voice_note             ; lead

    ldy mrow
    lda pat_v2,y
    ldx #VOICE2
    jsr voice_note             ; harmony

    ldy mrow
    lda pat_v3,y
    ldx #VOICE3
    jsr voice_note             ; bass

    inc mrow
    lda mrow
    cmp #ROWS
    bne .done
    lda #0
    sta mrow                   ; loop the pattern
.done:
    rts

; A = note index (0 = rest/hold), X = voice base offset
voice_note:
    cmp #0
    beq .rest
    tay                        ; note index -> Y (freq table lookup)
    lda freq_lo,y
    sta SID_FREQ_LO,x
    lda freq_hi,y
    sta SID_FREQ_HI,x
    lda #$21                   ; sawtooth + gate on -> strike the note
    sta SID_CTRL,x
.rest:
    rts

mtimer: !byte 0
mrow:   !byte 0
; SID frequency values, PAL. Index 0 = rest.
;           C-3     C-4     E-4     G-4
freq_lo: !byte 0, $B3,    $67,    $EE,    $13
freq_hi: !byte 0, $08,    $11,    $15,    $1A
pat_v1:  !byte 2, 3, 4, 3,  2, 3, 4, 3     ; lead: a C-E-G arpeggio
pat_v2:  !byte 4, 4, 2, 2,  4, 4, 2, 2     ; harmony
pat_v3:  !byte 1, 0, 1, 0,  1, 0, 1, 0     ; bass on the beat

Trade-offs

Aspect Cost
CPU A few bytes most frames; a little more on a row change
Memory The frequency table, the patterns, two bytes of state
Limitation Three voices — and an effect must borrow one from the song

When to use: Any in-game music. Every SID tune, from title screens to demos, is this shape with more on top.

When to avoid: A single reactive bleep; that’s a note trigger, not a sequencer.

The raster IRQ is the clock

The player keeps no timer of its own — it rides the raster interrupt. Wire an IRQ to fire once a frame, call music_tick from it, and you have a rock-steady 50 Hz (PAL) heartbeat for free. mtimer counts those frames down; only when it reaches zero does a new row land, so TEMPO sets the speed — 6 frames a row is the classic tracker default. It’s why C64 music and C64 raster effects are the same craft: both are things you do on the interrupt, and a demo’s music and its display are driven by the very same tick.

Three voices, three jobs

The SID has exactly three voices, evenly spaced 7 bytes apart — $D400, $D407, $D40E — which is why the code indexes every register with a base offset in X. The classic division of labour is lead, harmony and bass, and because all three share the SID’s single filter, a driver often reserves voice 3 for bass or drums so it can cut it in and out (voice 3 can even be silenced from the display without a gate, via $D418 bit 7). Wanting a fourth voice means faking it — rapidly switching one voice between two notes, the arpeggio “chord” that gives SID music its unmistakable shimmer.

The hard restart

Re-striking a note is the SID’s famous trap. Gating a voice on (CTRL bit 0 going 0→1) starts the attack — but if the voice is already gated on from the last note, writing the same waveform does nothing and the envelope never restarts. Worse, the SID’s envelope generator has a well-known bug where a poorly-timed gate leaves the volume stuck. The cure every driver uses is the hard restart: a frame or two before each note, gate the voice off and load a fast release, so the envelope is reliably at zero and ready to attack cleanly. The skeleton above re-gates in place for clarity; a shipping player sets its notes up one tick early precisely to hard-restart them.

From skeleton to player

A real SID driver adds four things to this frame:

  • Instruments — a waveform, pulse-width and ADSR per note, rewritten each row, so a voice can pluck, sweep or drone rather than blare one flat saw.
  • The filter$D415$D418 shape the tone across voices; sweeping the cutoff each frame is half of what makes SID music sing.
  • Effects — vibrato, portamento, pulse-width modulation and arpeggio, all run on the in-between ticks.
  • An order list — a sequence of pattern numbers, so a tune reuses a handful of short patterns instead of storing every bar.

Add those and a note-trigger effects routine that borrows a voice, and you have a player like the ones behind the C64’s greatest scores — the shape a tool such as GoatTracker exports.

Patterns: SID Voice Setup, SID Note Trigger, Raster Splits

Vault: The SID Chip | Commodore 64