Sound Design
Make the SID sing properly. Track-specific waveforms, filter sweeps, and audio feedback transform beeps into music.
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:
| Waveform | Bit | Character |
|---|---|---|
| Triangle | $10 | Pure, soft, flute-like |
| Sawtooth | $20 | Bright, buzzy, string-like |
| Pulse | $40 | Hollow to full, organ-like |
| Noise | $80 | Harsh, 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.