Game 1 Unit 9 of 16

Sound Design

Make the SID sing properly. Track-specific waveforms, filter sweeps, and audio feedback transform beeps into music.

56% of SID Symphony

The SID as Instrument

Until now, all three voices played the same waveform. Press X, C, or V — same sound, different pitch. That’s functional, but it’s not musical.

Real instruments have character. A piano sounds different from a guitar playing the same note. The SID chip can do this too. Each voice can have its own waveform, its own envelope, its own personality.

This unit transforms our rhythm game from “beeps when you press keys” to “sounds like you’re playing an instrument.”

Waveforms: The Shape of Sound

The SID offers four waveforms, each with distinct character:

WaveformBitCharacter
Triangle$10Pure, soft, flute-like
Sawtooth$20Bright, buzzy, string-like
Pulse$40Hollow to full, organ-like
Noise$80Harsh, percussive, no pitch

The control register ($D404 for voice 1) combines the waveform bit with the gate bit ($01):

; Waveform control values
WAVE_TRIANGLE = $11             ; Triangle + gate
WAVE_SAWTOOTH = $21             ; Sawtooth + gate
WAVE_PULSE    = $41             ; Pulse + gate
WAVE_NOISE    = $81             ; Noise + gate

We assign each track its own waveform:

play_voice:
            cmp #TRACK_1
            bne pv_not_1
            lda #WAVE_PULSE         ; Voice 1: Pulse wave
            sta SID_V1_CTRL
            rts
pv_not_1:
            cmp #TRACK_2
            bne pv_not_2
            lda #WAVE_SAWTOOTH      ; Voice 2: Sawtooth wave
            sta SID_V2_CTRL
            rts
pv_not_2:
            lda #WAVE_TRIANGLE      ; Voice 3: Triangle wave
            sta SID_V3_CTRL
            rts

Now each track sounds different. The top track (X) has a warm pulse wave, the middle (C) has a bright sawtooth, and the bottom (V) has a soft triangle. Play all three together and you hear a chord with depth.

ADSR: Shaping the Note

ADSR stands for Attack, Decay, Sustain, Release. These four values shape how a note evolves over time:

  • Attack: How quickly the sound reaches full volume (0=instant, F=slow)
  • Decay: How quickly it drops to sustain level
  • Sustain: Volume while key is held (0-F)
  • Release: How quickly it fades after key release
; Voice 1 - Pulse wave (punchy, responsive)
lda #$09                ; Attack=0, Decay=9
sta SID_V1_AD
lda #$52                ; Sustain=5, Release=2
sta SID_V1_SR

; Voice 2 - Sawtooth (bright, snappy)
lda #$0a                ; Attack=0, Decay=10
sta SID_V2_AD
lda #$41                ; Sustain=4, Release=1
sta SID_V2_SR

; Voice 3 - Triangle (soft, flowing)
lda #$18                ; Attack=1, Decay=8
sta SID_V3_AD
lda #$84                ; Sustain=8, Release=4
sta SID_V3_SR

The pulse wave gets instant attack for a percussive feel. The triangle gets a slightly slower attack for a gentler sound. Experiment with these values — small changes dramatically affect the feel.

The Filter: Adding Brightness

The SID has a multi-mode filter that all three voices can route through. For our hit feedback, we use a filter sweep — starting bright and fading darker.

; SID filter registers
SID_FC_LO   = SID + 21          ; Filter cutoff low
SID_FC_HI   = SID + 22          ; Filter cutoff high
SID_RES_FILT = SID + 23         ; Resonance + routing
SID_MODE_VOL = SID + 24         ; Filter mode + volume

; Set up filter in init_sid
lda #$00
sta SID_FC_LO
lda #$40                ; Cutoff ~512 (medium)
sta SID_FC_HI
lda #$87                ; Resonance $8, route all voices
sta SID_RES_FILT
lda #$1f                ; Low-pass filter + volume 15
sta SID_MODE_VOL

The cutoff frequency ($D415-$D416) controls which frequencies pass through. Higher cutoff = brighter sound. Lower cutoff = darker, muffled sound.

start_filter_sweep:
            lda #$70                ; Start cutoff high (bright)
            sta filter_cutoff
            sta SID_FC_HI
            lda #20                 ; Sweep duration
            sta filter_sweep_active
            rts

update_filter_sweep:
            lda filter_sweep_active
            beq ufs_done

            dec filter_sweep_active

            lda filter_cutoff
            cmp #$40                ; Don't go below baseline
            bcc ufs_done
            sec
            sbc #$02                ; Decrease cutoff
            sta filter_cutoff
            sta SID_FC_HI

ufs_done:
            rts

Every hit triggers start_filter_sweep, which sets the cutoff high. Each frame, update_filter_sweep gradually lowers it. The result: hits sound bright and present, then settle back to normal. It’s subtle, but it makes hits feel impactful.

The Bum Note: Audio Feedback for Misses

Visual feedback (red flash) tells you something went wrong. Audio feedback makes you feel it. When you miss a note, we briefly play a dissonant, ugly sound.

; Dissonant pitch (C# clashes with C major chord)
BUM_PITCH_LO = $0c
BUM_PITCH_HI = $09

play_bum_note:
            ; Save voice 1 pitch
            lda SID_V1_FREQ_LO
            sta saved_pitch_lo
            lda SID_V1_FREQ_HI
            sta saved_pitch_hi

            ; Set dissonant pitch
            lda #BUM_PITCH_LO
            sta SID_V1_FREQ_LO
            lda #BUM_PITCH_HI
            sta SID_V1_FREQ_HI

            ; Quick harsh envelope
            lda #$00                ; Instant attack/decay
            sta SID_V1_AD
            lda #$08                ; No sustain, quick release
            sta SID_V1_SR

            ; Noise waveform
            lda #WAVE_NOISE
            sta SID_V1_CTRL

            lda #8                  ; 8 frames
            sta bum_note_timer
            rts

We temporarily hijack voice 1 to play noise at a dissonant pitch. After 8 frames, update_bum_note restores the original pitch and envelope. The result is a quick “wrong” sound that doesn’t disrupt the music for long.

Calling the Updates

Both the filter sweep and bum note need per-frame updates in the game loop:

            ; Update flash effects for all tracks
            jsr update_flash

            ; Update sound effects (Unit 9)
            jsr update_filter_sweep
            jsr update_bum_note

            ; Update displays
            jsr update_display

And the bum note is triggered on every miss:

update_crowd_miss:
            jsr play_bum_note       ; Play dissonant sound

            lda crowd_meter
            sec
            sbc #$02
            ; ... rest of miss handling

The Transformation

Run the game now. The difference is immediate:

  • Each track has character — pulse, sawtooth, triangle
  • Hits feel bright and responsive (filter sweep)
  • Misses feel wrong (bum note)
  • The chord sounds full and musical

Same gameplay, but it sounds like a game now instead of a tech demo.

What’s Next

The game sounds good, but we’re treating all hits the same. In Unit 10, we add timing windows — different grades for Perfect, Good, and Late hits. Precision starts to matter.