Notes Appear
Notes scroll across the tracks in time with a beat. The rhythm game emerges.
In Unit 1, you could trigger sounds by pressing keys. Now those keys have a purpose.
This unit adds the core rhythm game mechanic: notes scroll from right to left across the tracks. When a note reaches the hit zone, press the matching key. It’s simple, but it transforms three keyboard triggers into an actual game.
Run It
Assemble and run:
acme -f cbm -o symphony.prg symphony.asm

Notes appear on the right and scroll left toward the yellow hit zone. Press Z, X, or C when a note reaches the zone. The track flashes and the SID plays. A simple test pattern loops endlessly so you can practice your timing.
How Notes Work
Notes are stored in parallel arrays - one for track number, one for column position:
; Note storage uses parallel arrays
; Each note has a track (1-3) and column position
MAX_NOTES = 8 ; Maximum notes on screen at once
note_track:
!fill MAX_NOTES, 0 ; Track number (0=inactive, 1-3=active)
note_col:
!fill MAX_NOTES, 0 ; Screen column position (1-37)
; To find an empty slot, scan for note_track[x] == 0
; To process all notes, loop through and skip where note_track[x] == 0
A note with track = 0 is inactive (empty slot). Active notes have track 1, 2, or 3. Each frame, we scan all slots, skip inactive ones, and update active ones.
Beat Timing
The C64 runs at 50 frames per second (PAL). At 120 BPM, that’s 2 beats per second, or 25 frames per beat:
; Beat timing with frame counting
; PAL C64 runs at 50Hz (50 frames per second)
; At 120 BPM, we get 2 beats per second = 25 frames per beat
FRAMES_PER_BEAT = 25 ; 50Hz / 2 beats per second
frame_count = $02 ; Current frame within beat (0-24)
beat_count = $03 ; Current beat in song (0-255)
main_loop:
; Wait for raster sync (once per frame)
lda #$FF
wait_raster:
cmp $D012
bne wait_raster
; Increment frame counter
inc frame_count
lda frame_count
cmp #FRAMES_PER_BEAT
bcc no_new_beat
; New beat!
lda #0
sta frame_count
jsr check_spawn_note ; Spawn notes for this beat
inc beat_count
no_new_beat:
; ... rest of game loop
Every 25 frames, we trigger a new beat. The frame_count variable tracks where we are within the current beat. When it hits 25, we reset it and increment beat_count.
Song Data
The song is stored as pairs of (beat, track):
; Song data format: pairs of (beat, track)
; Beat = which beat to spawn (0, 1, 2, ...)
; Track = which track (1=top/Z, 2=middle/X, 3=bottom/C)
; $FF = end of song (loops back to start)
song_data:
; Bar 1 (beats 0-7)
!byte 0, 1 ; Beat 0, track 1 (Z)
!byte 2, 2 ; Beat 2, track 2 (X)
!byte 4, 3 ; Beat 4, track 3 (C)
!byte 6, 1 ; Beat 6, track 1 (Z)
; Bar 2 (beats 8-15)
!byte 8, 2 ; Beat 8, track 2
!byte 10, 3 ; Beat 10, track 3
!byte 12, 1 ; Beat 12, track 1
!byte 14, 2 ; Beat 14, track 2
; Bar 3 (beats 16-23)
!byte 16, 3
!byte 18, 1
!byte 20, 2
!byte 22, 3
; Bar 4 (beats 24-31) - faster pattern
!byte 24, 1
!byte 25, 2 ; Consecutive beats!
!byte 26, 3
!byte 28, 1
!byte 29, 2
!byte 30, 3
!byte $FF ; End marker - song loops
Beat 0 means “spawn immediately when the song starts”. Beat 2 means “spawn on the third beat”. The pattern repeats when we hit the $FF marker.
Bar 4 demonstrates faster patterns - notes on consecutive beats (24, 25, 26) require quicker reactions.
Moving Notes
Every frame, active notes move left by one column:
; Move all active notes left by one column each frame
; Notes that scroll past column 1 are deactivated
update_notes:
ldx #0
update_loop:
lda note_track,x
beq update_next ; Skip inactive notes (track = 0)
jsr erase_note ; Remove from old position
dec note_col,x ; Move left one column
lda note_col,x
cmp #1
bcc update_deactivate ; Scrolled off left edge?
jsr draw_note ; Draw at new position
jmp update_next
update_deactivate:
lda #0
sta note_track,x ; Mark as inactive
update_next:
inx
cpx #MAX_NOTES
bne update_loop
rts
The sequence is:
- Erase the note from its current position (restore the track line)
- Decrement the column position
- If still on screen, draw at the new position
- If off the left edge, mark as inactive
This erase-move-draw pattern is the foundation of all character-based animation on the C64.
Try This: Change the Pattern
Edit the song data to create your own rhythm:
song_data:
; Your pattern here
!byte 0, 1 ; Beat 0, track 1
!byte 1, 2 ; Beat 1, track 2
!byte 2, 3 ; Beat 2, track 3
!byte 3, 1 ; Beat 3, track 1
; ... continue the pattern
!byte $FF ; End marker
Try making notes appear on every beat, or create gaps for breathing room. Notice how different patterns feel to play.
Try This: Change the Tempo
Modify FRAMES_PER_BEAT to change the speed:
FRAMES_PER_BEAT = 50 ; 60 BPM - slow and easy
FRAMES_PER_BEAT = 25 ; 120 BPM - default
FRAMES_PER_BEAT = 17 ; 180 BPM - fast!
FRAMES_PER_BEAT = 12 ; 250 BPM - very challenging
Lower values mean faster beats. At 12 frames per beat, the notes fly across the screen.
The Complete Code
; ============================================================================
; SID SYMPHONY - Unit 2: Notes Appear
; ============================================================================
; Notes scroll from right to left across three tracks. When they reach the
; hit zone, press the matching key to play the sound. A simple test pattern
; loops continuously so you can practice timing.
;
; Controls: Z = Track 1 (high), X = Track 2 (mid), C = Track 3 (low)
; ============================================================================
; ----------------------------------------------------------------------------
; Memory Addresses
; ----------------------------------------------------------------------------
SCREEN = $0400 ; Screen memory base
COLRAM = $D800 ; Colour RAM base
BORDER = $D020 ; Border colour
BGCOL = $D021 ; Background colour
; SID registers
SID = $D400 ; SID base address
SID_V1_FREQ_LO = $D400 ; Voice 1 frequency low
SID_V1_FREQ_HI = $D401 ; Voice 1 frequency high
SID_V1_PWLO = $D402 ; Voice 1 pulse width low
SID_V1_PWHI = $D403 ; Voice 1 pulse width high
SID_V1_CTRL = $D404 ; Voice 1 control register
SID_V1_AD = $D405 ; Voice 1 attack/decay
SID_V1_SR = $D406 ; Voice 1 sustain/release
SID_V2_FREQ_LO = $D407 ; Voice 2 frequency low
SID_V2_FREQ_HI = $D408 ; Voice 2 frequency high
SID_V2_PWLO = $D409 ; Voice 2 pulse width low
SID_V2_PWHI = $D40A ; Voice 2 pulse width high
SID_V2_CTRL = $D40B ; Voice 2 control register
SID_V2_AD = $D40C ; Voice 2 attack/decay
SID_V2_SR = $D40D ; Voice 2 sustain/release
SID_V3_FREQ_LO = $D40E ; Voice 3 frequency low
SID_V3_FREQ_HI = $D40F ; Voice 3 frequency high
SID_V3_PWLO = $D410 ; Voice 3 pulse width low
SID_V3_PWHI = $D411 ; Voice 3 pulse width high
SID_V3_CTRL = $D412 ; Voice 3 control register
SID_V3_AD = $D413 ; Voice 3 attack/decay
SID_V3_SR = $D414 ; Voice 3 sustain/release
SID_FLTLO = $D415 ; Filter cutoff low
SID_FLTHI = $D416 ; Filter cutoff high
SID_FLTCTRL = $D417 ; Filter control
SID_VOLUME = $D418 ; Volume and filter mode
; CIA keyboard
CIA1_PRA = $DC00 ; CIA1 Port A (keyboard column)
CIA1_PRB = $DC01 ; CIA1 Port B (keyboard row)
; Colours
BLACK = 0
WHITE = 1
RED = 2
CYAN = 3
PURPLE = 4
GREEN = 5
BLUE = 6
YELLOW = 7
ORANGE = 8
BROWN = 9
LIGHT_RED = 10
DARK_GREY = 11
GREY = 12
LIGHT_GREEN = 13
LIGHT_BLUE = 14
LIGHT_GREY = 15
; Track positions (row on screen)
TRACK1_ROW = 8 ; High voice track
TRACK2_ROW = 12 ; Mid voice track
TRACK3_ROW = 16 ; Low voice track
; Hit zone column
HIT_ZONE_COL = 3 ; Where notes need to be hit
; Note settings
NOTE_CHAR = $57 ; Character for note (filled dot/ball)
TRACK_CHAR = $2D ; Minus character for track line
MAX_NOTES = 8 ; Maximum notes on screen at once
NOTE_SPAWN_COL = 37 ; Where notes appear (right side)
; Timing
FRAMES_PER_BEAT = 25 ; ~120 BPM at 50Hz (PAL)
; Zero page pointers
ZP_PTR = $FB ; General purpose pointer
ZP_PTR_HI = $FC
; ----------------------------------------------------------------------------
; Variables (in low memory)
; ----------------------------------------------------------------------------
frame_count = $02 ; Frame counter (0-255)
beat_count = $03 ; Current beat in song (0-255)
song_pos = $04 ; Position in song data (word)
song_pos_hi = $05
temp_track = $06 ; Temporary storage for track number
; ----------------------------------------------------------------------------
; BASIC Stub - SYS 2064
; ----------------------------------------------------------------------------
* = $0801
!byte $0C, $08 ; Pointer to next line
!byte $0A, $00 ; Line number 10
!byte $9E ; SYS token
!text "2064" ; Address
!byte $00 ; End of line
!byte $00, $00 ; End of program
; ----------------------------------------------------------------------------
; Main Program
; ----------------------------------------------------------------------------
* = $0810
start:
jsr init_screen ; Set up the display
jsr init_sid ; Configure SID chip
jsr init_notes ; Clear note arrays
; Initialize song position
lda #<song_data
sta song_pos
lda #>song_data
sta song_pos_hi
; Initialize counters
lda #0
sta frame_count
sta beat_count
main_loop:
; Wait for raster (smooth timing)
lda #$FF
wait_raster:
cmp $D012
bne wait_raster
; Update frame counter
inc frame_count
lda frame_count
cmp #FRAMES_PER_BEAT
bcc no_new_beat
; New beat!
lda #0
sta frame_count
jsr check_spawn_note
inc beat_count
no_new_beat:
; Move all notes left
jsr update_notes
; Reset track colours to default
jsr reset_track_colours
; Check keyboard and play sounds
jsr check_keys
jmp main_loop
; ----------------------------------------------------------------------------
; Initialize Notes
; ----------------------------------------------------------------------------
init_notes:
ldx #MAX_NOTES-1
lda #0
init_notes_loop:
sta note_track,x
sta note_col,x
dex
bpl init_notes_loop
rts
; ----------------------------------------------------------------------------
; Check Spawn Note
; ----------------------------------------------------------------------------
; Song data is in beat order. Process all entries matching current beat,
; then return. Entries are (beat, track) pairs ending with $FF.
check_spawn_note:
ldy #0
spawn_check_loop:
; Read beat number for current entry
lda (song_pos),y
cmp #$FF
beq spawn_restart_song
; If entry beat > current beat, we're done for this beat
cmp beat_count
beq spawn_match ; Equal - spawn this note
bcs spawn_done ; Greater - done for now (entries are ordered)
; Entry beat < current beat - shouldn't happen, but skip it
jmp spawn_advance
spawn_match:
; Get track number (next byte)
iny
lda (song_pos),y
jsr spawn_note
dey ; Reset Y for next iteration
spawn_advance:
; Move song_pos forward by 2 bytes
lda song_pos
clc
adc #2
sta song_pos
lda song_pos_hi
adc #0
sta song_pos_hi
jmp spawn_check_loop
spawn_done:
rts
spawn_restart_song:
lda #<song_data
sta song_pos
lda #>song_data
sta song_pos_hi
lda #0
sta beat_count
rts
; ----------------------------------------------------------------------------
; Spawn Note
; ----------------------------------------------------------------------------
; Input: A = track number (1-3)
spawn_note:
sta temp_track
ldx #0
spawn_find_slot:
lda note_track,x
beq spawn_found_slot
inx
cpx #MAX_NOTES
bne spawn_find_slot
rts ; No empty slot
spawn_found_slot:
lda temp_track
sta note_track,x
lda #NOTE_SPAWN_COL
sta note_col,x
jsr draw_note
rts
; ----------------------------------------------------------------------------
; Update Notes
; ----------------------------------------------------------------------------
update_notes:
ldx #0
update_loop:
lda note_track,x
beq update_next
jsr erase_note
dec note_col,x
lda note_col,x
cmp #1
bcc update_deactivate
jsr draw_note
jmp update_next
update_deactivate:
lda #0
sta note_track,x
update_next:
inx
cpx #MAX_NOTES
bne update_loop
rts
; ----------------------------------------------------------------------------
; Draw Note
; ----------------------------------------------------------------------------
; Input: X = note index
draw_note:
lda note_track,x
cmp #1
beq draw_note_t1
cmp #2
beq draw_note_t2
cmp #3
beq draw_note_t3
rts
draw_note_t1:
lda note_col,x
clc
adc #<(SCREEN + TRACK1_ROW * 40)
sta ZP_PTR
lda #>(SCREEN + TRACK1_ROW * 40)
adc #0
sta ZP_PTR_HI
ldy #0
lda #NOTE_CHAR
sta (ZP_PTR),y
lda note_col,x
clc
adc #<(COLRAM + TRACK1_ROW * 40)
sta ZP_PTR
lda #>(COLRAM + TRACK1_ROW * 40)
adc #0
sta ZP_PTR_HI
lda #LIGHT_RED
sta (ZP_PTR),y
rts
draw_note_t2:
lda note_col,x
clc
adc #<(SCREEN + TRACK2_ROW * 40)
sta ZP_PTR
lda #>(SCREEN + TRACK2_ROW * 40)
adc #0
sta ZP_PTR_HI
ldy #0
lda #NOTE_CHAR
sta (ZP_PTR),y
lda note_col,x
clc
adc #<(COLRAM + TRACK2_ROW * 40)
sta ZP_PTR
lda #>(COLRAM + TRACK2_ROW * 40)
adc #0
sta ZP_PTR_HI
lda #LIGHT_GREEN
sta (ZP_PTR),y
rts
draw_note_t3:
lda note_col,x
clc
adc #<(SCREEN + TRACK3_ROW * 40)
sta ZP_PTR
lda #>(SCREEN + TRACK3_ROW * 40)
adc #0
sta ZP_PTR_HI
ldy #0
lda #NOTE_CHAR
sta (ZP_PTR),y
lda note_col,x
clc
adc #<(COLRAM + TRACK3_ROW * 40)
sta ZP_PTR
lda #>(COLRAM + TRACK3_ROW * 40)
adc #0
sta ZP_PTR_HI
lda #LIGHT_BLUE
sta (ZP_PTR),y
rts
; ----------------------------------------------------------------------------
; Erase Note
; ----------------------------------------------------------------------------
; Input: X = note index
erase_note:
lda note_track,x
cmp #1
beq erase_note_t1
cmp #2
beq erase_note_t2
cmp #3
beq erase_note_t3
rts
erase_note_t1:
lda note_col,x
clc
adc #<(SCREEN + TRACK1_ROW * 40)
sta ZP_PTR
lda #>(SCREEN + TRACK1_ROW * 40)
adc #0
sta ZP_PTR_HI
ldy #0
lda #TRACK_CHAR
sta (ZP_PTR),y
lda note_col,x
clc
adc #<(COLRAM + TRACK1_ROW * 40)
sta ZP_PTR
lda #>(COLRAM + TRACK1_ROW * 40)
adc #0
sta ZP_PTR_HI
lda #GREY
sta (ZP_PTR),y
rts
erase_note_t2:
lda note_col,x
clc
adc #<(SCREEN + TRACK2_ROW * 40)
sta ZP_PTR
lda #>(SCREEN + TRACK2_ROW * 40)
adc #0
sta ZP_PTR_HI
ldy #0
lda #TRACK_CHAR
sta (ZP_PTR),y
lda note_col,x
clc
adc #<(COLRAM + TRACK2_ROW * 40)
sta ZP_PTR
lda #>(COLRAM + TRACK2_ROW * 40)
adc #0
sta ZP_PTR_HI
lda #GREY
sta (ZP_PTR),y
rts
erase_note_t3:
lda note_col,x
clc
adc #<(SCREEN + TRACK3_ROW * 40)
sta ZP_PTR
lda #>(SCREEN + TRACK3_ROW * 40)
adc #0
sta ZP_PTR_HI
ldy #0
lda #TRACK_CHAR
sta (ZP_PTR),y
lda note_col,x
clc
adc #<(COLRAM + TRACK3_ROW * 40)
sta ZP_PTR
lda #>(COLRAM + TRACK3_ROW * 40)
adc #0
sta ZP_PTR_HI
lda #GREY
sta (ZP_PTR),y
rts
; ----------------------------------------------------------------------------
; Initialize Screen
; ----------------------------------------------------------------------------
init_screen:
lda #BLACK
sta BORDER
sta BGCOL
ldx #0
lda #$20
clr_screen:
sta SCREEN,x
sta SCREEN+$100,x
sta SCREEN+$200,x
sta SCREEN+$2E8,x
inx
bne clr_screen
ldx #0
lda #GREY
clr_colour:
sta COLRAM,x
sta COLRAM+$100,x
sta COLRAM+$200,x
sta COLRAM+$2E8,x
inx
bne clr_colour
jsr draw_tracks
jsr draw_hit_zones
jsr draw_labels
rts
; ----------------------------------------------------------------------------
; Draw Tracks
; ----------------------------------------------------------------------------
draw_tracks:
ldx #0
lda #TRACK_CHAR
draw_t1:
sta SCREEN + (TRACK1_ROW * 40),x
inx
cpx #38
bne draw_t1
ldx #0
draw_t2:
sta SCREEN + (TRACK2_ROW * 40),x
inx
cpx #38
bne draw_t2
ldx #0
draw_t3:
sta SCREEN + (TRACK3_ROW * 40),x
inx
cpx #38
bne draw_t3
rts
; ----------------------------------------------------------------------------
; Draw Hit Zones
; ----------------------------------------------------------------------------
draw_hit_zones:
lda #$7D ; Pipe character
sta SCREEN + (TRACK1_ROW * 40) + HIT_ZONE_COL
sta SCREEN + ((TRACK1_ROW-1) * 40) + HIT_ZONE_COL
sta SCREEN + ((TRACK1_ROW+1) * 40) + HIT_ZONE_COL
sta SCREEN + (TRACK2_ROW * 40) + HIT_ZONE_COL
sta SCREEN + ((TRACK2_ROW-1) * 40) + HIT_ZONE_COL
sta SCREEN + ((TRACK2_ROW+1) * 40) + HIT_ZONE_COL
sta SCREEN + (TRACK3_ROW * 40) + HIT_ZONE_COL
sta SCREEN + ((TRACK3_ROW-1) * 40) + HIT_ZONE_COL
sta SCREEN + ((TRACK3_ROW+1) * 40) + HIT_ZONE_COL
lda #YELLOW
sta COLRAM + (TRACK1_ROW * 40) + HIT_ZONE_COL
sta COLRAM + ((TRACK1_ROW-1) * 40) + HIT_ZONE_COL
sta COLRAM + ((TRACK1_ROW+1) * 40) + HIT_ZONE_COL
sta COLRAM + (TRACK2_ROW * 40) + HIT_ZONE_COL
sta COLRAM + ((TRACK2_ROW-1) * 40) + HIT_ZONE_COL
sta COLRAM + ((TRACK2_ROW+1) * 40) + HIT_ZONE_COL
sta COLRAM + (TRACK3_ROW * 40) + HIT_ZONE_COL
sta COLRAM + ((TRACK3_ROW-1) * 40) + HIT_ZONE_COL
sta COLRAM + ((TRACK3_ROW+1) * 40) + HIT_ZONE_COL
rts
; ----------------------------------------------------------------------------
; Draw Labels
; ----------------------------------------------------------------------------
draw_labels:
ldx #0
draw_title:
lda title_text,x
beq draw_title_done
sta SCREEN + 13,x
lda #WHITE
sta COLRAM + 13,x
inx
bne draw_title
draw_title_done:
lda #$1A ; Z
sta SCREEN + (TRACK1_ROW * 40)
lda #LIGHT_RED
sta COLRAM + (TRACK1_ROW * 40)
lda #$18 ; X
sta SCREEN + (TRACK2_ROW * 40)
lda #LIGHT_GREEN
sta COLRAM + (TRACK2_ROW * 40)
lda #$03 ; C
sta SCREEN + (TRACK3_ROW * 40)
lda #LIGHT_BLUE
sta COLRAM + (TRACK3_ROW * 40)
ldx #0
draw_instr:
lda instr_text,x
beq draw_instr_done
sta SCREEN + (23 * 40) + 3,x
lda #GREY
sta COLRAM + (23 * 40) + 3,x
inx
bne draw_instr
draw_instr_done:
rts
title_text:
!scr "sid symphony"
!byte 0
instr_text:
!scr "hit notes when they reach |"
!byte 0
; ----------------------------------------------------------------------------
; Initialize SID
; ----------------------------------------------------------------------------
init_sid:
ldx #$18
lda #0
clear_sid:
sta SID,x
dex
bpl clear_sid
lda #$0F
sta SID_VOLUME
; Voice 1 - High pitch, sawtooth
lda #$00
sta SID_V1_FREQ_LO
lda #$1C
sta SID_V1_FREQ_HI
lda #$09
sta SID_V1_AD
lda #$00
sta SID_V1_SR
; Voice 2 - Mid pitch, pulse
lda #$00
sta SID_V2_FREQ_LO
lda #$0E
sta SID_V2_FREQ_HI
lda #$08
sta SID_V2_PWHI
lda #$09
sta SID_V2_AD
lda #$00
sta SID_V2_SR
; Voice 3 - Low pitch, triangle
lda #$00
sta SID_V3_FREQ_LO
lda #$07
sta SID_V3_FREQ_HI
lda #$09
sta SID_V3_AD
lda #$00
sta SID_V3_SR
rts
; ----------------------------------------------------------------------------
; Reset Track Colours
; ----------------------------------------------------------------------------
reset_track_colours:
ldx #0
lda #GREY
reset_t1:
sta COLRAM + (TRACK1_ROW * 40),x
inx
cpx #38
bne reset_t1
ldx #0
reset_t2:
sta COLRAM + (TRACK2_ROW * 40),x
inx
cpx #38
bne reset_t2
ldx #0
reset_t3:
sta COLRAM + (TRACK3_ROW * 40),x
inx
cpx #38
bne reset_t3
; Restore key labels
lda #LIGHT_RED
sta COLRAM + (TRACK1_ROW * 40)
lda #LIGHT_GREEN
sta COLRAM + (TRACK2_ROW * 40)
lda #LIGHT_BLUE
sta COLRAM + (TRACK3_ROW * 40)
; Restore hit zone colours
lda #YELLOW
sta COLRAM + (TRACK1_ROW * 40) + HIT_ZONE_COL
sta COLRAM + (TRACK2_ROW * 40) + HIT_ZONE_COL
sta COLRAM + (TRACK3_ROW * 40) + HIT_ZONE_COL
; Redraw note colours
jsr redraw_all_notes
rts
; ----------------------------------------------------------------------------
; Redraw All Notes
; ----------------------------------------------------------------------------
redraw_all_notes:
ldx #0
redraw_loop:
lda note_track,x
beq redraw_next
jsr draw_note
redraw_next:
inx
cpx #MAX_NOTES
bne redraw_loop
rts
; ----------------------------------------------------------------------------
; Check Keys
; ----------------------------------------------------------------------------
check_keys:
lda #$FD
sta CIA1_PRA
lda CIA1_PRB
and #$10
bne check_x_key
jsr play_voice1
jsr flash_track1
check_x_key:
lda #$FB
sta CIA1_PRA
lda CIA1_PRB
and #$80
bne check_c_key
jsr play_voice2
jsr flash_track2
check_c_key:
lda #$FB
sta CIA1_PRA
lda CIA1_PRB
and #$10
bne check_keys_done
jsr play_voice3
jsr flash_track3
check_keys_done:
lda #$FF
sta CIA1_PRA
rts
; ----------------------------------------------------------------------------
; Play Voices
; ----------------------------------------------------------------------------
play_voice1:
lda #$21
sta SID_V1_CTRL
rts
play_voice2:
lda #$41
sta SID_V2_CTRL
rts
play_voice3:
lda #$11
sta SID_V3_CTRL
rts
; ----------------------------------------------------------------------------
; Flash Tracks
; ----------------------------------------------------------------------------
flash_track1:
ldx #0
lda #RED
flash_t1_loop:
sta COLRAM + (TRACK1_ROW * 40),x
inx
cpx #38
bne flash_t1_loop
lda #WHITE
sta COLRAM + (TRACK1_ROW * 40)
rts
flash_track2:
ldx #0
lda #GREEN
flash_t2_loop:
sta COLRAM + (TRACK2_ROW * 40),x
inx
cpx #38
bne flash_t2_loop
lda #WHITE
sta COLRAM + (TRACK2_ROW * 40)
rts
flash_track3:
ldx #0
lda #BLUE
flash_t3_loop:
sta COLRAM + (TRACK3_ROW * 40),x
inx
cpx #38
bne flash_t3_loop
lda #WHITE
sta COLRAM + (TRACK3_ROW * 40)
rts
; ----------------------------------------------------------------------------
; Song Data
; ----------------------------------------------------------------------------
; Format: beat, track (1-3)
; $FF marks end of song
song_data:
; Simple test pattern - 4 bars at 120 BPM
; Bar 1
!byte 0, 1
!byte 2, 2
!byte 4, 3
!byte 6, 1
; Bar 2
!byte 8, 2
!byte 10, 3
!byte 12, 1
!byte 14, 2
; Bar 3
!byte 16, 3
!byte 18, 1
!byte 20, 2
!byte 22, 3
; Bar 4 - faster pattern
!byte 24, 1
!byte 25, 2
!byte 26, 3
!byte 28, 1
!byte 29, 2
!byte 30, 3
!byte $FF ; End marker
; ----------------------------------------------------------------------------
; Note Arrays
; ----------------------------------------------------------------------------
note_track:
!fill MAX_NOTES, 0
note_col:
!fill MAX_NOTES, 0
What You’ve Learnt
- Parallel arrays - Multiple arrays with matching indices store related data (track and column for each note)
- Frame counting - Count frames to trigger events at specific intervals
- Song data format - Simple (beat, track) pairs define when notes appear
- Erase-move-draw - The fundamental animation pattern: remove old, update position, draw new
- Screen coordinates - Calculating addresses with
SCREEN + (row * 40) + column
What’s Next
In Unit 3, you’ll make the game your own. Change the SID voices, experiment with waveforms and ADSR, pick different colours. You’ll learn the internals by modifying them.
What Changed
| 1 | 1 | ; ============================================================================ | |
| 2 | - | ; SID SYMPHONY - Unit 1: Hello SID | |
| 2 | + | ; SID SYMPHONY - Unit 2: Notes Appear | |
| 3 | 3 | ; ============================================================================ | |
| 4 | - | ; Your first contact with the SID chip. Three tracks, three keys, three voices. | |
| 5 | - | ; Press Z/X/C to trigger sounds and see the tracks flash. | |
| 4 | + | ; Notes scroll from right to left across three tracks. When they reach the | |
| 5 | + | ; hit zone, press the matching key to play the sound. A simple test pattern | |
| 6 | + | ; loops continuously so you can practice timing. | |
| 6 | 7 | ; | |
| 7 | 8 | ; Controls: Z = Track 1 (high), X = Track 2 (mid), C = Track 3 (low) | |
| 8 | 9 | ; ============================================================================ | |
| ... | |||
| 76 | 77 | | |
| 77 | 78 | ; Hit zone column | |
| 78 | 79 | HIT_ZONE_COL = 3 ; Where notes need to be hit | |
| 80 | + | | |
| 81 | + | ; Note settings | |
| 82 | + | NOTE_CHAR = $57 ; Character for note (filled dot/ball) | |
| 83 | + | TRACK_CHAR = $2D ; Minus character for track line | |
| 84 | + | MAX_NOTES = 8 ; Maximum notes on screen at once | |
| 85 | + | NOTE_SPAWN_COL = 37 ; Where notes appear (right side) | |
| 86 | + | | |
| 87 | + | ; Timing | |
| 88 | + | FRAMES_PER_BEAT = 25 ; ~120 BPM at 50Hz (PAL) | |
| 89 | + | | |
| 90 | + | ; Zero page pointers | |
| 91 | + | ZP_PTR = $FB ; General purpose pointer | |
| 92 | + | ZP_PTR_HI = $FC | |
| 93 | + | | |
| 94 | + | ; ---------------------------------------------------------------------------- | |
| 95 | + | ; Variables (in low memory) | |
| 96 | + | ; ---------------------------------------------------------------------------- | |
| 97 | + | | |
| 98 | + | frame_count = $02 ; Frame counter (0-255) | |
| 99 | + | beat_count = $03 ; Current beat in song (0-255) | |
| 100 | + | song_pos = $04 ; Position in song data (word) | |
| 101 | + | song_pos_hi = $05 | |
| 102 | + | temp_track = $06 ; Temporary storage for track number | |
| 79 | 103 | | |
| 80 | 104 | ; ---------------------------------------------------------------------------- | |
| 81 | 105 | ; BASIC Stub - SYS 2064 | |
| ... | |||
| 99 | 123 | start: | |
| 100 | 124 | jsr init_screen ; Set up the display | |
| 101 | 125 | jsr init_sid ; Configure SID chip | |
| 126 | + | jsr init_notes ; Clear note arrays | |
| 127 | + | | |
| 128 | + | ; Initialize song position | |
| 129 | + | lda #<song_data | |
| 130 | + | sta song_pos | |
| 131 | + | lda #>song_data | |
| 132 | + | sta song_pos_hi | |
| 133 | + | | |
| 134 | + | ; Initialize counters | |
| 135 | + | lda #0 | |
| 136 | + | sta frame_count | |
| 137 | + | sta beat_count | |
| 102 | 138 | | |
| 103 | 139 | main_loop: | |
| 104 | 140 | ; Wait for raster (smooth timing) | |
| 105 | 141 | lda #$FF | |
| 106 | - | - cmp $D012 | |
| 107 | - | bne - | |
| 142 | + | wait_raster: | |
| 143 | + | cmp $D012 | |
| 144 | + | bne wait_raster | |
| 145 | + | | |
| 146 | + | ; Update frame counter | |
| 147 | + | inc frame_count | |
| 148 | + | lda frame_count | |
| 149 | + | cmp #FRAMES_PER_BEAT | |
| 150 | + | bcc no_new_beat | |
| 151 | + | | |
| 152 | + | ; New beat! | |
| 153 | + | lda #0 | |
| 154 | + | sta frame_count | |
| 155 | + | jsr check_spawn_note | |
| 156 | + | inc beat_count | |
| 157 | + | | |
| 158 | + | no_new_beat: | |
| 159 | + | ; Move all notes left | |
| 160 | + | jsr update_notes | |
| 108 | 161 | | |
| 109 | 162 | ; Reset track colours to default | |
| 110 | 163 | jsr reset_track_colours | |
| ... | |||
| 113 | 166 | jsr check_keys | |
| 114 | 167 | | |
| 115 | 168 | jmp main_loop | |
| 169 | + | | |
| 170 | + | ; ---------------------------------------------------------------------------- | |
| 171 | + | ; Initialize Notes | |
| 172 | + | ; ---------------------------------------------------------------------------- | |
| 173 | + | | |
| 174 | + | init_notes: | |
| 175 | + | ldx #MAX_NOTES-1 | |
| 176 | + | lda #0 | |
| 177 | + | init_notes_loop: | |
| 178 | + | sta note_track,x | |
| 179 | + | sta note_col,x | |
| 180 | + | dex | |
| 181 | + | bpl init_notes_loop | |
| 182 | + | rts | |
| 183 | + | | |
| 184 | + | ; ---------------------------------------------------------------------------- | |
| 185 | + | ; Check Spawn Note | |
| 186 | + | ; ---------------------------------------------------------------------------- | |
| 187 | + | ; Song data is in beat order. Process all entries matching current beat, | |
| 188 | + | ; then return. Entries are (beat, track) pairs ending with $FF. | |
| 189 | + | | |
| 190 | + | check_spawn_note: | |
| 191 | + | ldy #0 | |
| 192 | + | | |
| 193 | + | spawn_check_loop: | |
| 194 | + | ; Read beat number for current entry | |
| 195 | + | lda (song_pos),y | |
| 196 | + | cmp #$FF | |
| 197 | + | beq spawn_restart_song | |
| 198 | + | | |
| 199 | + | ; If entry beat > current beat, we're done for this beat | |
| 200 | + | cmp beat_count | |
| 201 | + | beq spawn_match ; Equal - spawn this note | |
| 202 | + | bcs spawn_done ; Greater - done for now (entries are ordered) | |
| 203 | + | | |
| 204 | + | ; Entry beat < current beat - shouldn't happen, but skip it | |
| 205 | + | jmp spawn_advance | |
| 206 | + | | |
| 207 | + | spawn_match: | |
| 208 | + | ; Get track number (next byte) | |
| 209 | + | iny | |
| 210 | + | lda (song_pos),y | |
| 211 | + | jsr spawn_note | |
| 212 | + | dey ; Reset Y for next iteration | |
| 213 | + | | |
| 214 | + | spawn_advance: | |
| 215 | + | ; Move song_pos forward by 2 bytes | |
| 216 | + | lda song_pos | |
| 217 | + | clc | |
| 218 | + | adc #2 | |
| 219 | + | sta song_pos | |
| 220 | + | lda song_pos_hi | |
| 221 | + | adc #0 | |
| 222 | + | sta song_pos_hi | |
| 223 | + | jmp spawn_check_loop | |
| 224 | + | | |
| 225 | + | spawn_done: | |
| 226 | + | rts | |
| 227 | + | | |
| 228 | + | spawn_restart_song: | |
| 229 | + | lda #<song_data | |
| 230 | + | sta song_pos | |
| 231 | + | lda #>song_data | |
| 232 | + | sta song_pos_hi | |
| 233 | + | lda #0 | |
| 234 | + | sta beat_count | |
| 235 | + | rts | |
| 236 | + | | |
| 237 | + | ; ---------------------------------------------------------------------------- | |
| 238 | + | ; Spawn Note | |
| 239 | + | ; ---------------------------------------------------------------------------- | |
| 240 | + | ; Input: A = track number (1-3) | |
| 241 | + | | |
| 242 | + | spawn_note: | |
| 243 | + | sta temp_track | |
| 244 | + | | |
| 245 | + | ldx #0 | |
| 246 | + | spawn_find_slot: | |
| 247 | + | lda note_track,x | |
| 248 | + | beq spawn_found_slot | |
| 249 | + | inx | |
| 250 | + | cpx #MAX_NOTES | |
| 251 | + | bne spawn_find_slot | |
| 252 | + | rts ; No empty slot | |
| 253 | + | | |
| 254 | + | spawn_found_slot: | |
| 255 | + | lda temp_track | |
| 256 | + | sta note_track,x | |
| 257 | + | lda #NOTE_SPAWN_COL | |
| 258 | + | sta note_col,x | |
| 259 | + | jsr draw_note | |
| 260 | + | rts | |
| 261 | + | | |
| 262 | + | ; ---------------------------------------------------------------------------- | |
| 263 | + | ; Update Notes | |
| 264 | + | ; ---------------------------------------------------------------------------- | |
| 265 | + | | |
| 266 | + | update_notes: | |
| 267 | + | ldx #0 | |
| 268 | + | | |
| 269 | + | update_loop: | |
| 270 | + | lda note_track,x | |
| 271 | + | beq update_next | |
| 272 | + | | |
| 273 | + | jsr erase_note | |
| 274 | + | | |
| 275 | + | dec note_col,x | |
| 276 | + | lda note_col,x | |
| 277 | + | cmp #1 | |
| 278 | + | bcc update_deactivate | |
| 279 | + | | |
| 280 | + | jsr draw_note | |
| 281 | + | jmp update_next | |
| 282 | + | | |
| 283 | + | update_deactivate: | |
| 284 | + | lda #0 | |
| 285 | + | sta note_track,x | |
| 286 | + | | |
| 287 | + | update_next: | |
| 288 | + | inx | |
| 289 | + | cpx #MAX_NOTES | |
| 290 | + | bne update_loop | |
| 291 | + | rts | |
| 292 | + | | |
| 293 | + | ; ---------------------------------------------------------------------------- | |
| 294 | + | ; Draw Note | |
| 295 | + | ; ---------------------------------------------------------------------------- | |
| 296 | + | ; Input: X = note index | |
| 297 | + | | |
| 298 | + | draw_note: | |
| 299 | + | lda note_track,x | |
| 300 | + | cmp #1 | |
| 301 | + | beq draw_note_t1 | |
| 302 | + | cmp #2 | |
| 303 | + | beq draw_note_t2 | |
| 304 | + | cmp #3 | |
| 305 | + | beq draw_note_t3 | |
| 306 | + | rts | |
| 307 | + | | |
| 308 | + | draw_note_t1: | |
| 309 | + | lda note_col,x | |
| 310 | + | clc | |
| 311 | + | adc #<(SCREEN + TRACK1_ROW * 40) | |
| 312 | + | sta ZP_PTR | |
| 313 | + | lda #>(SCREEN + TRACK1_ROW * 40) | |
| 314 | + | adc #0 | |
| 315 | + | sta ZP_PTR_HI | |
| 316 | + | | |
| 317 | + | ldy #0 | |
| 318 | + | lda #NOTE_CHAR | |
| 319 | + | sta (ZP_PTR),y | |
| 320 | + | | |
| 321 | + | lda note_col,x | |
| 322 | + | clc | |
| 323 | + | adc #<(COLRAM + TRACK1_ROW * 40) | |
| 324 | + | sta ZP_PTR | |
| 325 | + | lda #>(COLRAM + TRACK1_ROW * 40) | |
| 326 | + | adc #0 | |
| 327 | + | sta ZP_PTR_HI | |
| 328 | + | lda #LIGHT_RED | |
| 329 | + | sta (ZP_PTR),y | |
| 330 | + | rts | |
| 331 | + | | |
| 332 | + | draw_note_t2: | |
| 333 | + | lda note_col,x | |
| 334 | + | clc | |
| 335 | + | adc #<(SCREEN + TRACK2_ROW * 40) | |
| 336 | + | sta ZP_PTR | |
| 337 | + | lda #>(SCREEN + TRACK2_ROW * 40) | |
| 338 | + | adc #0 | |
| 339 | + | sta ZP_PTR_HI | |
| 340 | + | | |
| 341 | + | ldy #0 | |
| 342 | + | lda #NOTE_CHAR | |
| 343 | + | sta (ZP_PTR),y | |
| 344 | + | | |
| 345 | + | lda note_col,x | |
| 346 | + | clc | |
| 347 | + | adc #<(COLRAM + TRACK2_ROW * 40) | |
| 348 | + | sta ZP_PTR | |
| 349 | + | lda #>(COLRAM + TRACK2_ROW * 40) | |
| 350 | + | adc #0 | |
| 351 | + | sta ZP_PTR_HI | |
| 352 | + | lda #LIGHT_GREEN | |
| 353 | + | sta (ZP_PTR),y | |
| 354 | + | rts | |
| 355 | + | | |
| 356 | + | draw_note_t3: | |
| 357 | + | lda note_col,x | |
| 358 | + | clc | |
| 359 | + | adc #<(SCREEN + TRACK3_ROW * 40) | |
| 360 | + | sta ZP_PTR | |
| 361 | + | lda #>(SCREEN + TRACK3_ROW * 40) | |
| 362 | + | adc #0 | |
| 363 | + | sta ZP_PTR_HI | |
| 364 | + | | |
| 365 | + | ldy #0 | |
| 366 | + | lda #NOTE_CHAR | |
| 367 | + | sta (ZP_PTR),y | |
| 368 | + | | |
| 369 | + | lda note_col,x | |
| 370 | + | clc | |
| 371 | + | adc #<(COLRAM + TRACK3_ROW * 40) | |
| 372 | + | sta ZP_PTR | |
| 373 | + | lda #>(COLRAM + TRACK3_ROW * 40) | |
| 374 | + | adc #0 | |
| 375 | + | sta ZP_PTR_HI | |
| 376 | + | lda #LIGHT_BLUE | |
| 377 | + | sta (ZP_PTR),y | |
| 378 | + | rts | |
| 379 | + | | |
| 380 | + | ; ---------------------------------------------------------------------------- | |
| 381 | + | ; Erase Note | |
| 382 | + | ; ---------------------------------------------------------------------------- | |
| 383 | + | ; Input: X = note index | |
| 384 | + | | |
| 385 | + | erase_note: | |
| 386 | + | lda note_track,x | |
| 387 | + | cmp #1 | |
| 388 | + | beq erase_note_t1 | |
| 389 | + | cmp #2 | |
| 390 | + | beq erase_note_t2 | |
| 391 | + | cmp #3 | |
| 392 | + | beq erase_note_t3 | |
| 393 | + | rts | |
| 394 | + | | |
| 395 | + | erase_note_t1: | |
| 396 | + | lda note_col,x | |
| 397 | + | clc | |
| 398 | + | adc #<(SCREEN + TRACK1_ROW * 40) | |
| 399 | + | sta ZP_PTR | |
| 400 | + | lda #>(SCREEN + TRACK1_ROW * 40) | |
| 401 | + | adc #0 | |
| 402 | + | sta ZP_PTR_HI | |
| 403 | + | | |
| 404 | + | ldy #0 | |
| 405 | + | lda #TRACK_CHAR | |
| 406 | + | sta (ZP_PTR),y | |
| 407 | + | | |
| 408 | + | lda note_col,x | |
| 409 | + | clc | |
| 410 | + | adc #<(COLRAM + TRACK1_ROW * 40) | |
| 411 | + | sta ZP_PTR | |
| 412 | + | lda #>(COLRAM + TRACK1_ROW * 40) | |
| 413 | + | adc #0 | |
| 414 | + | sta ZP_PTR_HI | |
| 415 | + | lda #GREY | |
| 416 | + | sta (ZP_PTR),y | |
| 417 | + | rts | |
| 418 | + | | |
| 419 | + | erase_note_t2: | |
| 420 | + | lda note_col,x | |
| 421 | + | clc | |
| 422 | + | adc #<(SCREEN + TRACK2_ROW * 40) | |
| 423 | + | sta ZP_PTR | |
| 424 | + | lda #>(SCREEN + TRACK2_ROW * 40) | |
| 425 | + | adc #0 | |
| 426 | + | sta ZP_PTR_HI | |
| 427 | + | | |
| 428 | + | ldy #0 | |
| 429 | + | lda #TRACK_CHAR | |
| 430 | + | sta (ZP_PTR),y | |
| 431 | + | | |
| 432 | + | lda note_col,x | |
| 433 | + | clc | |
| 434 | + | adc #<(COLRAM + TRACK2_ROW * 40) | |
| 435 | + | sta ZP_PTR | |
| 436 | + | lda #>(COLRAM + TRACK2_ROW * 40) | |
| 437 | + | adc #0 | |
| 438 | + | sta ZP_PTR_HI | |
| 439 | + | lda #GREY | |
| 440 | + | sta (ZP_PTR),y | |
| 441 | + | rts | |
| 442 | + | | |
| 443 | + | erase_note_t3: | |
| 444 | + | lda note_col,x | |
| 445 | + | clc | |
| 446 | + | adc #<(SCREEN + TRACK3_ROW * 40) | |
| 447 | + | sta ZP_PTR | |
| 448 | + | lda #>(SCREEN + TRACK3_ROW * 40) | |
| 449 | + | adc #0 | |
| 450 | + | sta ZP_PTR_HI | |
| 451 | + | | |
| 452 | + | ldy #0 | |
| 453 | + | lda #TRACK_CHAR | |
| 454 | + | sta (ZP_PTR),y | |
| 455 | + | | |
| 456 | + | lda note_col,x | |
| 457 | + | clc | |
| 458 | + | adc #<(COLRAM + TRACK3_ROW * 40) | |
| 459 | + | sta ZP_PTR | |
| 460 | + | lda #>(COLRAM + TRACK3_ROW * 40) | |
| 461 | + | adc #0 | |
| 462 | + | sta ZP_PTR_HI | |
| 463 | + | lda #GREY | |
| 464 | + | sta (ZP_PTR),y | |
| 465 | + | rts | |
| 116 | 466 | | |
| 117 | 467 | ; ---------------------------------------------------------------------------- | |
| 118 | 468 | ; Initialize Screen | |
| 119 | 469 | ; ---------------------------------------------------------------------------- | |
| 120 | - | ; Clears screen and draws the three tracks with hit zones | |
| 121 | 470 | | |
| 122 | 471 | init_screen: | |
| 123 | - | ; Set border and background | |
| 124 | 472 | lda #BLACK | |
| 125 | 473 | sta BORDER | |
| 126 | 474 | sta BGCOL | |
| 127 | 475 | | |
| 128 | - | ; Clear screen with spaces | |
| 129 | 476 | ldx #0 | |
| 130 | - | lda #$20 ; Space character | |
| 131 | - | - sta SCREEN,x | |
| 477 | + | lda #$20 | |
| 478 | + | clr_screen: | |
| 479 | + | sta SCREEN,x | |
| 132 | 480 | sta SCREEN+$100,x | |
| 133 | 481 | sta SCREEN+$200,x | |
| 134 | 482 | sta SCREEN+$2E8,x | |
| 135 | 483 | inx | |
| 136 | - | bne - | |
| 484 | + | bne clr_screen | |
| 137 | 485 | | |
| 138 | - | ; Set all colours to grey | |
| 139 | 486 | ldx #0 | |
| 140 | 487 | lda #GREY | |
| 141 | - | - sta COLRAM,x | |
| 488 | + | clr_colour: | |
| 489 | + | sta COLRAM,x | |
| 142 | 490 | sta COLRAM+$100,x | |
| 143 | 491 | sta COLRAM+$200,x | |
| 144 | 492 | sta COLRAM+$2E8,x | |
| 145 | 493 | inx | |
| 146 | - | bne - | |
| 494 | + | bne clr_colour | |
| 147 | 495 | | |
| 148 | - | ; Draw track lines | |
| 149 | 496 | jsr draw_tracks | |
| 150 | - | | |
| 151 | - | ; Draw hit zones | |
| 152 | 497 | jsr draw_hit_zones | |
| 153 | - | | |
| 154 | - | ; Draw labels | |
| 155 | 498 | jsr draw_labels | |
| 156 | 499 | | |
| 157 | 500 | rts | |
| ... | |||
| 159 | 502 | ; ---------------------------------------------------------------------------- | |
| 160 | 503 | ; Draw Tracks | |
| 161 | 504 | ; ---------------------------------------------------------------------------- | |
| 162 | - | ; Draws horizontal lines for each track using minus characters | |
| 163 | 505 | | |
| 164 | 506 | draw_tracks: | |
| 165 | - | ; Track 1 (row 8) | |
| 166 | 507 | ldx #0 | |
| 167 | - | lda #$2D ; Minus character for track line | |
| 168 | - | - sta SCREEN + (TRACK1_ROW * 40),x | |
| 508 | + | lda #TRACK_CHAR | |
| 509 | + | draw_t1: | |
| 510 | + | sta SCREEN + (TRACK1_ROW * 40),x | |
| 169 | 511 | inx | |
| 170 | 512 | cpx #38 | |
| 171 | - | bne - | |
| 513 | + | bne draw_t1 | |
| 172 | 514 | | |
| 173 | - | ; Track 2 (row 12) | |
| 174 | 515 | ldx #0 | |
| 175 | - | - sta SCREEN + (TRACK2_ROW * 40),x | |
| 516 | + | draw_t2: | |
| 517 | + | sta SCREEN + (TRACK2_ROW * 40),x | |
| 176 | 518 | inx | |
| 177 | 519 | cpx #38 | |
| 178 | - | bne - | |
| 520 | + | bne draw_t2 | |
| 179 | 521 | | |
| 180 | - | ; Track 3 (row 16) | |
| 181 | 522 | ldx #0 | |
| 182 | - | - sta SCREEN + (TRACK3_ROW * 40),x | |
| 523 | + | draw_t3: | |
| 524 | + | sta SCREEN + (TRACK3_ROW * 40),x | |
| 183 | 525 | inx | |
| 184 | 526 | cpx #38 | |
| 185 | - | bne - | |
| 527 | + | bne draw_t3 | |
| 186 | 528 | | |
| 187 | 529 | rts | |
| 188 | 530 | | |
| 189 | 531 | ; ---------------------------------------------------------------------------- | |
| 190 | 532 | ; Draw Hit Zones | |
| 191 | 533 | ; ---------------------------------------------------------------------------- | |
| 192 | - | ; Draws vertical bars at the hit zone position | |
| 193 | 534 | | |
| 194 | 535 | draw_hit_zones: | |
| 195 | - | ; Draw vertical line at hit zone column | |
| 196 | - | ; Using pipe character for hit zone marker | |
| 197 | 536 | lda #$7D ; Pipe character | |
| 198 | 537 | | |
| 199 | - | ; Hit zone spans from track 1 to track 3 | |
| 200 | 538 | sta SCREEN + (TRACK1_ROW * 40) + HIT_ZONE_COL | |
| 201 | 539 | sta SCREEN + ((TRACK1_ROW-1) * 40) + HIT_ZONE_COL | |
| 202 | 540 | sta SCREEN + ((TRACK1_ROW+1) * 40) + HIT_ZONE_COL | |
| ... | |||
| 209 | 547 | sta SCREEN + ((TRACK3_ROW-1) * 40) + HIT_ZONE_COL | |
| 210 | 548 | sta SCREEN + ((TRACK3_ROW+1) * 40) + HIT_ZONE_COL | |
| 211 | 549 | | |
| 212 | - | ; Colour the hit zones yellow | |
| 213 | 550 | lda #YELLOW | |
| 214 | 551 | sta COLRAM + (TRACK1_ROW * 40) + HIT_ZONE_COL | |
| 215 | 552 | sta COLRAM + ((TRACK1_ROW-1) * 40) + HIT_ZONE_COL | |
| ... | |||
| 228 | 565 | ; ---------------------------------------------------------------------------- | |
| 229 | 566 | ; Draw Labels | |
| 230 | 567 | ; ---------------------------------------------------------------------------- | |
| 231 | - | ; Draws track labels and instructions | |
| 232 | 568 | | |
| 233 | 569 | draw_labels: | |
| 234 | - | ; Title "SID SYMPHONY" at top | |
| 235 | 570 | ldx #0 | |
| 236 | - | - lda title_text,x | |
| 237 | - | beq + | |
| 571 | + | draw_title: | |
| 572 | + | lda title_text,x | |
| 573 | + | beq draw_title_done | |
| 238 | 574 | sta SCREEN + 13,x | |
| 239 | 575 | lda #WHITE | |
| 240 | 576 | sta COLRAM + 13,x | |
| 241 | 577 | inx | |
| 242 | - | bne - | |
| 243 | - | + | |
| 244 | - | ; Track labels | |
| 245 | - | ; "Z" for track 1 | |
| 578 | + | bne draw_title | |
| 579 | + | draw_title_done: | |
| 580 | + | | |
| 246 | 581 | lda #$1A ; Z | |
| 247 | 582 | sta SCREEN + (TRACK1_ROW * 40) | |
| 248 | 583 | lda #LIGHT_RED | |
| 249 | 584 | sta COLRAM + (TRACK1_ROW * 40) | |
| 250 | 585 | | |
| 251 | - | ; "X" for track 2 | |
| 252 | 586 | lda #$18 ; X | |
| 253 | 587 | sta SCREEN + (TRACK2_ROW * 40) | |
| 254 | 588 | lda #LIGHT_GREEN | |
| 255 | 589 | sta COLRAM + (TRACK2_ROW * 40) | |
| 256 | 590 | | |
| 257 | - | ; "C" for track 3 | |
| 258 | 591 | lda #$03 ; C | |
| 259 | 592 | sta SCREEN + (TRACK3_ROW * 40) | |
| 260 | 593 | lda #LIGHT_BLUE | |
| 261 | 594 | sta COLRAM + (TRACK3_ROW * 40) | |
| 262 | 595 | | |
| 263 | - | ; Instructions at bottom | |
| 264 | 596 | ldx #0 | |
| 265 | - | - lda instr_text,x | |
| 266 | - | beq + | |
| 267 | - | sta SCREEN + (23 * 40) + 5,x | |
| 597 | + | draw_instr: | |
| 598 | + | lda instr_text,x | |
| 599 | + | beq draw_instr_done | |
| 600 | + | sta SCREEN + (23 * 40) + 3,x | |
| 268 | 601 | lda #GREY | |
| 269 | - | sta COLRAM + (23 * 40) + 5,x | |
| 602 | + | sta COLRAM + (23 * 40) + 3,x | |
| 270 | 603 | inx | |
| 271 | - | bne - | |
| 272 | - | + | |
| 604 | + | bne draw_instr | |
| 605 | + | draw_instr_done: | |
| 606 | + | | |
| 273 | 607 | rts | |
| 274 | 608 | | |
| 275 | 609 | title_text: | |
| ... | |||
| 277 | 611 | !byte 0 | |
| 278 | 612 | | |
| 279 | 613 | instr_text: | |
| 280 | - | !scr "press z, x, c to play" | |
| 614 | + | !scr "hit notes when they reach |" | |
| 281 | 615 | !byte 0 | |
| 282 | 616 | | |
| 283 | 617 | ; ---------------------------------------------------------------------------- | |
| 284 | 618 | ; Initialize SID | |
| 285 | 619 | ; ---------------------------------------------------------------------------- | |
| 286 | - | ; Sets up SID with three distinct voices ready to play | |
| 287 | 620 | | |
| 288 | 621 | init_sid: | |
| 289 | - | ; Clear all SID registers first | |
| 290 | 622 | ldx #$18 | |
| 291 | 623 | lda #0 | |
| 292 | - | - sta SID,x | |
| 624 | + | clear_sid: | |
| 625 | + | sta SID,x | |
| 293 | 626 | dex | |
| 294 | - | bpl - | |
| 627 | + | bpl clear_sid | |
| 295 | 628 | | |
| 296 | - | ; Set volume to maximum | |
| 297 | 629 | lda #$0F | |
| 298 | 630 | sta SID_VOLUME | |
| 299 | 631 | | |
| 300 | - | ; Voice 1 - High pitch, sawtooth wave | |
| 632 | + | ; Voice 1 - High pitch, sawtooth | |
| 301 | 633 | lda #$00 | |
| 302 | 634 | sta SID_V1_FREQ_LO | |
| 303 | - | lda #$1C ; High frequency (~523 Hz, C5) | |
| 635 | + | lda #$1C | |
| 304 | 636 | sta SID_V1_FREQ_HI | |
| 305 | - | lda #$09 ; Attack=0, Decay=9 | |
| 637 | + | lda #$09 | |
| 306 | 638 | sta SID_V1_AD | |
| 307 | - | lda #$00 ; Sustain=0, Release=0 | |
| 639 | + | lda #$00 | |
| 308 | 640 | sta SID_V1_SR | |
| 309 | 641 | | |
| 310 | - | ; Voice 2 - Mid pitch, pulse wave | |
| 642 | + | ; Voice 2 - Mid pitch, pulse | |
| 311 | 643 | lda #$00 | |
| 312 | 644 | sta SID_V2_FREQ_LO | |
| 313 | - | lda #$0E ; Mid frequency (~262 Hz, C4) | |
| 645 | + | lda #$0E | |
| 314 | 646 | sta SID_V2_FREQ_HI | |
| 315 | - | lda #$08 ; 50% pulse width | |
| 647 | + | lda #$08 | |
| 316 | 648 | sta SID_V2_PWHI | |
| 317 | - | lda #$09 ; Attack=0, Decay=9 | |
| 649 | + | lda #$09 | |
| 318 | 650 | sta SID_V2_AD | |
| 319 | - | lda #$00 ; Sustain=0, Release=0 | |
| 651 | + | lda #$00 | |
| 320 | 652 | sta SID_V2_SR | |
| 321 | 653 | | |
| 322 | - | ; Voice 3 - Low pitch, triangle wave | |
| 654 | + | ; Voice 3 - Low pitch, triangle | |
| 323 | 655 | lda #$00 | |
| 324 | 656 | sta SID_V3_FREQ_LO | |
| 325 | - | lda #$07 ; Low frequency (~131 Hz, C3) | |
| 657 | + | lda #$07 | |
| 326 | 658 | sta SID_V3_FREQ_HI | |
| 327 | - | lda #$09 ; Attack=0, Decay=9 | |
| 659 | + | lda #$09 | |
| 328 | 660 | sta SID_V3_AD | |
| 329 | - | lda #$00 ; Sustain=0, Release=0 | |
| 661 | + | lda #$00 | |
| 330 | 662 | sta SID_V3_SR | |
| 331 | 663 | | |
| 332 | 664 | rts | |
| ... | |||
| 334 | 666 | ; ---------------------------------------------------------------------------- | |
| 335 | 667 | ; Reset Track Colours | |
| 336 | 668 | ; ---------------------------------------------------------------------------- | |
| 337 | - | ; Returns tracks to their default colours | |
| 338 | 669 | | |
| 339 | 670 | reset_track_colours: | |
| 340 | - | ; Track 1 - default grey | |
| 341 | 671 | ldx #0 | |
| 342 | 672 | lda #GREY | |
| 343 | - | - sta COLRAM + (TRACK1_ROW * 40),x | |
| 673 | + | reset_t1: | |
| 674 | + | sta COLRAM + (TRACK1_ROW * 40),x | |
| 344 | 675 | inx | |
| 345 | 676 | cpx #38 | |
| 346 | - | bne - | |
| 677 | + | bne reset_t1 | |
| 347 | 678 | | |
| 348 | - | ; Track 2 - default grey | |
| 349 | 679 | ldx #0 | |
| 350 | - | - sta COLRAM + (TRACK2_ROW * 40),x | |
| 680 | + | reset_t2: | |
| 681 | + | sta COLRAM + (TRACK2_ROW * 40),x | |
| 351 | 682 | inx | |
| 352 | 683 | cpx #38 | |
| 353 | - | bne - | |
| 684 | + | bne reset_t2 | |
| 354 | 685 | | |
| 355 | - | ; Track 3 - default grey | |
| 356 | 686 | ldx #0 | |
| 357 | - | - sta COLRAM + (TRACK3_ROW * 40),x | |
| 687 | + | reset_t3: | |
| 688 | + | sta COLRAM + (TRACK3_ROW * 40),x | |
| 358 | 689 | inx | |
| 359 | 690 | cpx #38 | |
| 360 | - | bne - | |
| 691 | + | bne reset_t3 | |
| 361 | 692 | | |
| 362 | 693 | ; Restore key labels | |
| 363 | 694 | lda #LIGHT_RED | |
| ... | |||
| 372 | 703 | sta COLRAM + (TRACK1_ROW * 40) + HIT_ZONE_COL | |
| 373 | 704 | sta COLRAM + (TRACK2_ROW * 40) + HIT_ZONE_COL | |
| 374 | 705 | sta COLRAM + (TRACK3_ROW * 40) + HIT_ZONE_COL | |
| 706 | + | | |
| 707 | + | ; Redraw note colours | |
| 708 | + | jsr redraw_all_notes | |
| 709 | + | | |
| 710 | + | rts | |
| 711 | + | | |
| 712 | + | ; ---------------------------------------------------------------------------- | |
| 713 | + | ; Redraw All Notes | |
| 714 | + | ; ---------------------------------------------------------------------------- | |
| 375 | 715 | | |
| 716 | + | redraw_all_notes: | |
| 717 | + | ldx #0 | |
| 718 | + | redraw_loop: | |
| 719 | + | lda note_track,x | |
| 720 | + | beq redraw_next | |
| 721 | + | jsr draw_note | |
| 722 | + | redraw_next: | |
| 723 | + | inx | |
| 724 | + | cpx #MAX_NOTES | |
| 725 | + | bne redraw_loop | |
| 376 | 726 | rts | |
| 377 | 727 | | |
| 378 | 728 | ; ---------------------------------------------------------------------------- | |
| 379 | 729 | ; Check Keys | |
| 380 | 730 | ; ---------------------------------------------------------------------------- | |
| 381 | - | ; Reads keyboard and triggers sounds for Z, X, C keys | |
| 382 | 731 | | |
| 383 | 732 | check_keys: | |
| 384 | - | ; Check Z key (row 1, column 2) | |
| 385 | - | ; Z is at keyboard matrix position: row=$FD (bit 1 low), col=$10 (bit 4) | |
| 386 | - | lda #$FD ; Select row 1 (bit 1 = 0) | |
| 733 | + | lda #$FD | |
| 387 | 734 | sta CIA1_PRA | |
| 388 | 735 | lda CIA1_PRB | |
| 389 | - | and #$10 ; Check column 4 (Z key) | |
| 390 | - | bne + ; Branch if not pressed | |
| 736 | + | and #$10 | |
| 737 | + | bne check_x_key | |
| 391 | 738 | jsr play_voice1 | |
| 392 | 739 | jsr flash_track1 | |
| 393 | - | + | |
| 394 | - | ; Check X key (row 2, column 7) | |
| 395 | - | ; X is at keyboard matrix position: row=$FB (bit 2 low), col=$80 (bit 7) | |
| 396 | - | lda #$FB ; Select row 2 (bit 2 = 0) | |
| 740 | + | | |
| 741 | + | check_x_key: | |
| 742 | + | lda #$FB | |
| 397 | 743 | sta CIA1_PRA | |
| 398 | 744 | lda CIA1_PRB | |
| 399 | - | and #$80 ; Check column 7 (X key) | |
| 400 | - | bne + ; Branch if not pressed | |
| 745 | + | and #$80 | |
| 746 | + | bne check_c_key | |
| 401 | 747 | jsr play_voice2 | |
| 402 | 748 | jsr flash_track2 | |
| 403 | - | + | |
| 404 | - | ; Check C key (row 2, column 4) | |
| 405 | - | ; C is at keyboard matrix position: row=$FB (bit 2 low), col=$10 (bit 4) | |
| 406 | - | lda #$FB ; Select row 2 (bit 2 = 0) | |
| 749 | + | | |
| 750 | + | check_c_key: | |
| 751 | + | lda #$FB | |
| 407 | 752 | sta CIA1_PRA | |
| 408 | 753 | lda CIA1_PRB | |
| 409 | - | and #$10 ; Check column 4 (C key) | |
| 410 | - | bne + ; Branch if not pressed | |
| 754 | + | and #$10 | |
| 755 | + | bne check_keys_done | |
| 411 | 756 | jsr play_voice3 | |
| 412 | 757 | jsr flash_track3 | |
| 413 | - | + | |
| 414 | - | ; Reset keyboard scanning | |
| 758 | + | | |
| 759 | + | check_keys_done: | |
| 415 | 760 | lda #$FF | |
| 416 | 761 | sta CIA1_PRA | |
| 417 | - | | |
| 418 | 762 | rts | |
| 419 | 763 | | |
| 420 | 764 | ; ---------------------------------------------------------------------------- | |
| 421 | - | ; Play Voice 1 | |
| 765 | + | ; Play Voices | |
| 422 | 766 | ; ---------------------------------------------------------------------------- | |
| 423 | - | ; Triggers voice 1 (high, sawtooth) | |
| 424 | 767 | | |
| 425 | 768 | play_voice1: | |
| 426 | - | lda #$21 ; Gate on + sawtooth waveform | |
| 769 | + | lda #$21 | |
| 427 | 770 | sta SID_V1_CTRL | |
| 428 | 771 | rts | |
| 429 | - | | |
| 430 | - | ; ---------------------------------------------------------------------------- | |
| 431 | - | ; Play Voice 2 | |
| 432 | - | ; ---------------------------------------------------------------------------- | |
| 433 | - | ; Triggers voice 2 (mid, pulse) | |
| 434 | 772 | | |
| 435 | 773 | play_voice2: | |
| 436 | - | lda #$41 ; Gate on + pulse waveform | |
| 774 | + | lda #$41 | |
| 437 | 775 | sta SID_V2_CTRL | |
| 438 | 776 | rts | |
| 439 | - | | |
| 440 | - | ; ---------------------------------------------------------------------------- | |
| 441 | - | ; Play Voice 3 | |
| 442 | - | ; ---------------------------------------------------------------------------- | |
| 443 | - | ; Triggers voice 3 (low, triangle) | |
| 444 | 777 | | |
| 445 | 778 | play_voice3: | |
| 446 | - | lda #$11 ; Gate on + triangle waveform | |
| 779 | + | lda #$11 | |
| 447 | 780 | sta SID_V3_CTRL | |
| 448 | 781 | rts | |
| 449 | 782 | | |
| 450 | 783 | ; ---------------------------------------------------------------------------- | |
| 451 | - | ; Flash Track 1 | |
| 784 | + | ; Flash Tracks | |
| 452 | 785 | ; ---------------------------------------------------------------------------- | |
| 453 | - | ; Highlights track 1 in red | |
| 454 | 786 | | |
| 455 | 787 | flash_track1: | |
| 456 | 788 | ldx #0 | |
| 457 | 789 | lda #RED | |
| 458 | - | - sta COLRAM + (TRACK1_ROW * 40),x | |
| 790 | + | flash_t1_loop: | |
| 791 | + | sta COLRAM + (TRACK1_ROW * 40),x | |
| 459 | 792 | inx | |
| 460 | 793 | cpx #38 | |
| 461 | - | bne - | |
| 462 | - | ; Keep label visible | |
| 794 | + | bne flash_t1_loop | |
| 463 | 795 | lda #WHITE | |
| 464 | 796 | sta COLRAM + (TRACK1_ROW * 40) | |
| 465 | 797 | rts | |
| 466 | - | | |
| 467 | - | ; ---------------------------------------------------------------------------- | |
| 468 | - | ; Flash Track 2 | |
| 469 | - | ; ---------------------------------------------------------------------------- | |
| 470 | - | ; Highlights track 2 in green | |
| 471 | 798 | | |
| 472 | 799 | flash_track2: | |
| 473 | 800 | ldx #0 | |
| 474 | 801 | lda #GREEN | |
| 475 | - | - sta COLRAM + (TRACK2_ROW * 40),x | |
| 802 | + | flash_t2_loop: | |
| 803 | + | sta COLRAM + (TRACK2_ROW * 40),x | |
| 476 | 804 | inx | |
| 477 | 805 | cpx #38 | |
| 478 | - | bne - | |
| 479 | - | ; Keep label visible | |
| 806 | + | bne flash_t2_loop | |
| 480 | 807 | lda #WHITE | |
| 481 | 808 | sta COLRAM + (TRACK2_ROW * 40) | |
| 482 | 809 | rts | |
| 483 | - | | |
| 484 | - | ; ---------------------------------------------------------------------------- | |
| 485 | - | ; Flash Track 3 | |
| 486 | - | ; ---------------------------------------------------------------------------- | |
| 487 | - | ; Highlights track 3 in blue | |
| 488 | 810 | | |
| 489 | 811 | flash_track3: | |
| 490 | 812 | ldx #0 | |
| 491 | 813 | lda #BLUE | |
| 492 | - | - sta COLRAM + (TRACK3_ROW * 40),x | |
| 814 | + | flash_t3_loop: | |
| 815 | + | sta COLRAM + (TRACK3_ROW * 40),x | |
| 493 | 816 | inx | |
| 494 | 817 | cpx #38 | |
| 495 | - | bne - | |
| 496 | - | ; Keep label visible | |
| 818 | + | bne flash_t3_loop | |
| 497 | 819 | lda #WHITE | |
| 498 | 820 | sta COLRAM + (TRACK3_ROW * 40) | |
| 499 | 821 | rts | |
| 822 | + | | |
| 823 | + | ; ---------------------------------------------------------------------------- | |
| 824 | + | ; Song Data | |
| 825 | + | ; ---------------------------------------------------------------------------- | |
| 826 | + | ; Format: beat, track (1-3) | |
| 827 | + | ; $FF marks end of song | |
| 828 | + | | |
| 829 | + | song_data: | |
| 830 | + | ; Simple test pattern - 4 bars at 120 BPM | |
| 831 | + | ; Bar 1 | |
| 832 | + | !byte 0, 1 | |
| 833 | + | !byte 2, 2 | |
| 834 | + | !byte 4, 3 | |
| 835 | + | !byte 6, 1 | |
| 836 | + | | |
| 837 | + | ; Bar 2 | |
| 838 | + | !byte 8, 2 | |
| 839 | + | !byte 10, 3 | |
| 840 | + | !byte 12, 1 | |
| 841 | + | !byte 14, 2 | |
| 842 | + | | |
| 843 | + | ; Bar 3 | |
| 844 | + | !byte 16, 3 | |
| 845 | + | !byte 18, 1 | |
| 846 | + | !byte 20, 2 | |
| 847 | + | !byte 22, 3 | |
| 848 | + | | |
| 849 | + | ; Bar 4 - faster pattern | |
| 850 | + | !byte 24, 1 | |
| 851 | + | !byte 25, 2 | |
| 852 | + | !byte 26, 3 | |
| 853 | + | !byte 28, 1 | |
| 854 | + | !byte 29, 2 | |
| 855 | + | !byte 30, 3 | |
| 856 | + | | |
| 857 | + | !byte $FF ; End marker | |
| 858 | + | | |
| 859 | + | ; ---------------------------------------------------------------------------- | |
| 860 | + | ; Note Arrays | |
| 861 | + | ; ---------------------------------------------------------------------------- | |
| 862 | + | | |
| 863 | + | note_track: | |
| 864 | + | !fill MAX_NOTES, 0 | |
| 865 | + | | |
| 866 | + | note_col: | |
| 867 | + | !fill MAX_NOTES, 0 | |
| 500 | 868 | |