C64 Workshop Part 4
Collision Detection and Sound
The C64 has hardware collision detection built right into the VIC-II chip, and the legendary SID sound chip can create amazing music and sound effects. Let’s learn how to detect when sprites hit things and make some noise!
You can use your sprite and background from Parts 2 & 3 if applicable!
Objective
Create a program that when a sprite collides with something, a sound effect is played.
Requirements
- Detect when your sprite collides with background or sprite graphics
- Play a sound effect using the SID chip when a collision occurs
Bonus
- Create multi-coloured sprites (3 colours + transparency)
- Use different SID waveforms (triangle, sawtooth, pulse, noise)
- Track score in some way, output it however you like
Hardware Collision Detection
The VIC-II chip automatically tracks sprite collisions so you don’t need to write complex overlap checking code! Two collision registers tell you exactly which sprites hit something:
Sprite-to-Background Collision: Address 53278 ($D01E)
- Each bit represents a sprite (bit 0 = sprite 0, bit 1 = sprite 1, etc.)
- When a sprite’s pixels overlap background graphics, its bit becomes 1
- Important: Reading this register clears it, so save the value first
Sprite-to-Sprite Collision: Address 53279 ($D01F)
- When two sprites overlap, both of their bits become 1
- Also clears when read
The SID Sound Chip
The SID (Sound Interface Device) has three independent voices that can play simultaneously. Each voice has its own frequency, waveform, and envelope controls. Think of them like three instruments in a band—you can play a melody on voice 1, a bass line on voice 2, and sound effects on voice 3, all at the same time.
Each voice can produce four waveforms: triangle (smooth, flute-like), sawtooth (bright, brassy), pulse (hollow, clarinet-like), and noise (percussion, explosions).
To make a sound, you need three steps:
- Set the volume (register 54296) - controls all voices
- Set the frequency/pitch (registers vary by voice)
- Turn on the waveform and gate (register varies by voice)
SID Base Address: 54272 ($D400)
Voice Register Layout:
- Voice 1: Registers 54272-54278
- Voice 2: Registers 54279-54285
- Voice 3: Registers 54286-54292
Each voice has the same register pattern, offset by 7 bytes.
Key Registers (Voice 1):
- 54272: Frequency low byte (0-255) - fine tuning
- 54273: Frequency high byte (0-255) - coarse tuning
- 54276: Waveform control (bit 0 = gate on/off, bits 4-7 = waveform type)
- 54277: Attack/Decay envelope
- 54278: Sustain/Release envelope
- 54296: Master volume (0-15) - affects all three voices
Think of frequency like a 16-bit number split across two bytes. Higher numbers = higher pitch.
Resources / Reference
Reading Collision Registers
Always save the collision value to a variable first, because reading the register clears it:
10 C=PEEK(53278): REM SAVE COLLISION VALUE
20 IF C AND 1 THEN PRINT "SPRITE 0 HIT!"
30 IF C AND 2 THEN PRINT "SPRITE 1 HIT!"
40 IF C AND 4 THEN PRINT "SPRITE 2 HIT!"
The AND operator tests individual bits:
AND 1= bit 0 (sprite 0)AND 2= bit 1 (sprite 1)AND 4= bit 2 (sprite 2)AND 8= bit 3 (sprite 3), etc.
Why save first? If you do IF PEEK(53278) AND 1 twice, the second read will always be 0 because the first read cleared the register!
Playing a Simple Sound
Basic sound effect:
10 S=54272: REM SID BASE
20 POKE S+24,15: REM MAX VOLUME
30 POKE S+0,50: POKE S+1,100: REM FREQUENCY
40 POKE S+4,17: REM TRIANGLE WAVE, GATE ON
50 FOR D=1 TO 50: NEXT D
60 POKE S+4,16: REM GATE OFF
How it works:
- Line 20: Sets volume to maximum (15)
- Line 30: Sets frequency (low byte 50, high byte 100)
- Line 40: Bit 4 (value 16) = triangle wave, bit 0 (value 1) = gate on. 16+1=17
- Line 60: Gate off (bit 0 = 0) to stop sound
SID Waveforms
Register 54276 controls the waveform type and gate. The gate (bit 0) starts/stops the sound:
| Waveform | Bit | Value | Binary | Gate On | Binary |
|---|---|---|---|---|---|
| Triangle | 4 | 16 | 00010000 | 17 | 00010001 |
| Sawtooth | 5 | 32 | 00100000 | 33 | 00100001 |
| Pulse | 6 | 64 | 01000000 | 65 | 01000001 |
| Noise | 7 | 128 | 10000000 | 129 | 10000001 |
The “Gate On” value is waveform value + 1 (sets bit 0).
10 POKE 54276,33: REM SAWTOOTH (32) + GATE (1)
20 FOR D=1 TO 100: NEXT D
30 POKE 54276,32: REM GATE OFF (BIT 0 = 0)
Why turn gate off? The ADSR envelope only triggers when gate goes from 0→1. If you leave it on and try to play another note, it won’t retrigger.
Musical Note Frequencies
The frequency is stored as a 16-bit number split across two registers. The high byte goes in one register, the low byte in another. The formula is: Frequency = (High × 256) + Low
Middle octave note values:
| Note | Low | High | Note | Low | High | Note | Low | High |
|---|---|---|---|---|---|---|---|---|
| C | 25 | 68 | E | 102 | 85 | G# | 222 | 107 |
| C# | 143 | 72 | F | 110 | 90 | A | 26 | 114 |
| D | 162 | 76 | F# | 142 | 95 | A# | 117 | 120 |
| D# | 205 | 80 | G | 179 | 101 | B | 230 | 127 |
Example - Play middle C:
10 POKE 54272,25: POKE 54273,68
20 POKE 54276,17: REM TRIANGLE, GATE ON
Changing octaves: The table shows middle octave values. To shift octaves, calculate the full 16-bit frequency, multiply or divide by 2, then split it back into low/high bytes. For example, middle C = (68×256)+25 = 17,433. One octave higher: 17,433×2 = 34,866. Split: high = 34,866÷256 = 136, low = 34,866 MOD 256 = 50. This only works for transposing the SAME note up or down octaves.
ADSR Envelope
Shape your sounds with Attack, Decay, Sustain, Release:
- 54277: Attack/Decay (high nibble = attack, low nibble = decay)
- 54278: Sustain/Release (high nibble = sustain, low nibble = release)
10 POKE 54277,9: REM ATTACK=0, DECAY=9
20 POKE 54278,0: REM SUSTAIN=0, RELEASE=0
Higher attack = slower fade-in. Higher decay = slower fade-out.
Multi-Coloured Sprites
Regular sprites use 1 bit per pixel (on/off = 24 pixels wide). Multi-coloured sprites use 2 bits per pixel (4 colour choices = 12 pixels wide).
Pixel bit patterns:
00= Transparent01= Multi-colour 0 (register 53285) - shared by all sprites10= Sprite individual colour (register 53287-53294) - unique per sprite11= Multi-colour 1 (register 53286) - shared by all sprites
Enable multi-colour for sprite 0:
10 POKE 53276,1: REM BIT 0 ON
20 POKE 53285,6: REM MULTI-COLOUR 0 = BLUE
30 POKE 53286,3: REM MULTI-COLOUR 1 = CYAN
40 POKE 53287,2: REM SPRITE COLOUR = RED
How sprite data changes:
Normal sprite byte: 10101010 = 8 pixels (alternating on/off)
Multi-colour byte: 10101010 = 4 pixels read as 10, 10, 10, 10 = four red pixels (using sprite individual colour)
Another example: 11111111 = 11, 11, 11, 11 = four cyan pixels (multi-colour 1)
Mixed: 00011011 = 00, 01, 10, 11 = transparent, blue, red, cyan (one of each!)
Multi-Colour Sprite Editor
Click pixels to cycle through colours (colour 1/2/3/transparent). Click colour swatches to pick from C64 palette:
Collision and Multi-Colour Registers
| Register | Address | Purpose |
|---|---|---|
| Sprite-Background Collision | 53278 | Bit N = sprite N hit background |
| Sprite-Sprite Collision | 53279 | Bit N = sprite N hit another sprite |
| Multi-Colour Enable | 53276 | Bit N = sprite N uses multi-colour |
| Multi-Colour 0 | 53285 | Shared colour for 01 bits |
| Multi-Colour 1 | 53286 | Shared colour for 11 bits |
SID Sound Chip Registers
| Register | Address | Purpose |
|---|---|---|
| Frequency Low (Voice 1) | 54272 | Low byte of pitch |
| Frequency High (Voice 1) | 54273 | High byte of pitch |
| Waveform/Gate (Voice 1) | 54276 | Bit 0=gate, bits 4-7=waveform |
| Attack/Decay (Voice 1) | 54277 | High nibble=attack, low=decay |
| Sustain/Release (Voice 1) | 54278 | High nibble=sustain, low=release |
| Master Volume | 54296 | Volume level (0-15) |
Note: Voice 2 registers start at 54279, Voice 3 at 54286.
Example: Collision Detection with Sound
This minimal example shows a sprite moving automatically that detects collision and plays a sound:
10 REM SETUP SPRITE
20 POKE 2040,128: POKE 53269,1: REM POINTER AND ENABLE
30 POKE 53287,1: REM WHITE
40 GOSUB 1000: REM LOAD SPRITE DATA
50 REM DRAW OBSTACLE
60 FOR I=1024 TO 1063: POKE I,160: NEXT
70 REM SETUP SID
80 POKE 54296,15: REM MAX VOLUME
90 X=100: Y=100: DX=1: DY=1
100 REM MAIN LOOP
110 X=X+DX: Y=Y+DY
120 IF X<24 OR X>320 THEN DX=-DX
130 IF Y<50 OR Y>229 THEN DY=-DY
140 POKE 53248,X: POKE 53249,Y
150 C=PEEK(53278): REM SAVE COLLISION
160 IF C AND 1 THEN GOSUB 300
170 GOTO 110
300 REM COLLISION!
310 POKE 53287,2: REM RED
320 POKE 54272,50: POKE 54273,100
330 POKE 54276,129: REM NOISE, GATE ON
340 FOR D=1 TO 30: NEXT D
350 POKE 54276,128: REM GATE OFF
360 POKE 53287,1: REM WHITE
370 RETURN
1000 REM SPRITE DATA AT 8192
1010 FOR I=0 TO 62: READ A: POKE 8192+I,A: NEXT
1020 RETURN
1030 DATA 0,126,0,1,255,128,3,255,192
1040 DATA 7,231,224,15,255,240,31,255,248
1050 DATA 31,255,248,15,255,240,7,231,224
1060 DATA 3,255,192,1,255,128,0,126,0
1070 DATA 0,0,0,0,0,0,0,0,0
1080 DATA 0,0,0,0,0,0,0,0,0
1090 DATA 0,0,0,0,0,0,0,0,0
Key lines:
- Line 150: Saves collision value before testing
- Line 160: Tests if sprite 0 (bit 0) collided
- Lines 320-350: Plays noise sound effect
- Line 310/360: Visual feedback (sprite flashes red)
Tips for Success
- Always save collision register values before testing bits—reading clears them
- Turn gate off (bit 0 = 0) before starting a new note or it won’t retrigger
- Start with simple triangle or sawtooth waves before experimenting with pulse/noise
- Multi-colour sprites are half the horizontal resolution (12×21 instead of 24×21)
- Multi-colour 0 and 1 are shared by all multi-colour sprites—plan your palette
- Test collision detection separately before adding sound to simplify debugging
- Higher ADSR values = slower envelope changes (experiment to find what sounds good)
Previous: Part 3 - Sprites and Joystick Control | Back to Home