Song Data
Design the data format for songs. Create the first complete musical pattern.
A rhythm game needs music. This unit defines the song data format and creates the first complete song.
Until now, notes used placeholder data and fixed frequencies. Now each note carries its actual pitch. Hit a note and hear the melody. The game becomes musical.
Run It
Assemble and run:
acme -f cbm -o symphony.prg symphony.asm

Play through the song and listen. Each hit plays the correct note from the melody. Miss and you hear the harsh noise - a gap in the music. The game and music become one.
The Song Format
Each note entry is now three bytes:
; ============================================================================
; SONG DATA FORMAT
; ============================================================================
; Each note entry is 3 bytes:
; byte 0: beat number (0-63, when note spawns)
; byte 1: track number (1=Z high, 2=X mid, 3=C low)
; byte 2: note frequency high byte
;
; End of song marker: $FF
;
; Note frequency table (high byte only):
; C3=$11 D3=$13 E3=$16 F3=$17 G3=$1A A3=$1D B3=$21
; C4=$23 D4=$27 E4=$2C F4=$2F G4=$35 A4=$3B B4=$42
; C5=$47 D5=$4F E5=$58 F5=$5E G5=$6A A5=$77 B5=$85
; ============================================================================
song_data:
; Bar 1 - Opening phrase
!byte 0, 1, $47 ; Beat 0, Track 1 (Z), C5
!byte 2, 2, $2C ; Beat 2, Track 2 (X), E4
!byte 4, 3, $11 ; Beat 4, Track 3 (C), C3
; Bar 2 - Descending
!byte 8, 1, $3B ; A4
!byte 10, 2, $27 ; D4
!byte 12, 3, $13 ; D3
; ... more bars ...
!byte $FF ; End of song marker
The format is:
- Beat - When the note spawns (0 to song length minus 1)
- Track - Which track (1=Z, 2=X, 3=C)
- Frequency - High byte of SID frequency
The $FF byte marks the end of song data.
SID Frequency Reference
The SID uses 16-bit frequency values, but for simplicity we only store the high byte. Here are useful note values:
| Note | C | D | E | F | G | A | B |
|---|---|---|---|---|---|---|---|
| Octave 3 | $11 | $13 | $16 | $17 | $1A | $1D | $21 |
| Octave 4 | $23 | $27 | $2C | $2F | $35 | $3B | $42 |
| Octave 5 | $47 | $4F | $58 | $5E | $6A | $77 | $85 |
Track 1 (Z) plays high notes with sawtooth. Track 2 (X) plays mid notes with pulse. Track 3 (C) plays low notes with triangle. Three voices, three timbres, three octave ranges.
Reading 3-Byte Entries
The spawn routine now reads three bytes per entry:
; ----------------------------------------------------------------------------
; Check Spawn Note - Now reads 3-byte song entries
; ----------------------------------------------------------------------------
check_spawn_note:
ldy #0
spawn_check_loop:
lda (song_pos),y
cmp #$FF
beq spawn_restart_song
cmp beat_count
beq spawn_match
bcs spawn_done
jmp spawn_advance
spawn_match:
iny
lda (song_pos),y ; Track number
sta temp_track
iny
lda (song_pos),y ; Note frequency
pha
lda temp_track
jsr spawn_note_with_freq
pla ; Clean up stack
dey
dey
spawn_advance:
lda song_pos
clc
adc #3 ; 3 bytes per entry now
sta song_pos
lda song_pos_hi
adc #0
sta song_pos_hi
jmp spawn_check_loop
spawn_done:
rts
The key change is adc #3 instead of adc #2. Each entry advances by three bytes. The frequency is stored in the note array alongside track and column.
Playing the Correct Note
When a note is hit, we play its stored frequency:
; ----------------------------------------------------------------------------
; Play Voice With Note Frequency
; ----------------------------------------------------------------------------
; When a note is hit, we play its stored frequency, not a fixed pitch.
; This makes each hit sound like part of the melody.
play_voice1_note:
lda #0
sta SID_V1_FREQ_LO
lda hit_note_freq ; Use the note's actual pitch
sta SID_V1_FREQ_HI
lda #VOICE1_WAVE
ora #$01 ; Gate on
sta SID_V1_CTRL
rts
play_voice2_note:
lda #0
sta SID_V2_FREQ_LO
lda hit_note_freq
sta SID_V2_FREQ_HI
lda #VOICE2_WAVE
ora #$01
sta SID_V2_CTRL
rts
play_voice3_note:
lda #0
sta SID_V3_FREQ_LO
lda hit_note_freq
sta SID_V3_FREQ_HI
lda #VOICE3_WAVE
ora #$01
sta SID_V3_CTRL
rts
The hit_note_freq variable stores the frequency from the hit note. Instead of fixed pitches, each track voice plays whatever note was hit. Hit the right notes and you hear the melody.
Musical Composition
The song data creates a simple 8-bar phrase:
song_data:
; Bar 1 - Opening phrase
!byte 0, 1, $47 ; C5 - high
!byte 2, 2, $2C ; E4 - mid
!byte 4, 3, $11 ; C3 - low
; Bar 2 - Descending
!byte 8, 1, $3B ; A4
!byte 10, 2, $27 ; D4
!byte 12, 3, $13 ; D3
; ...
The pattern:
- Bars 1-2: Establish the tonality (C major)
- Bars 3-4: Build tension with faster notes
- Bars 5-6: Introduce variation
- Bars 7-8: Climax and resolve
When the song loops, players hear the progression repeat. Musical structure makes the game feel purposeful.
Memory Usage
Each song entry is 3 bytes. Our 30-note song uses 90 bytes plus the $FF terminator. A 64-beat song with dense notes might use 100-150 bytes. Plenty of room for longer songs.
The Complete Code
; ============================================================================
; SID SYMPHONY - Unit 9: Song Data
; ============================================================================
; Design the song data format. Create the first complete musical pattern.
; Notes now carry pitch information - each hit plays its actual note.
;
; Controls: Z = Track 1 (high), X = Track 2 (mid), C = Track 3 (low)
; ============================================================================
; ============================================================================
; CUSTOMISATION SECTION
; ============================================================================
; SID Voice Settings (default for when no note playing)
VOICE1_WAVE = $21 ; Sawtooth
VOICE2_WAVE = $41 ; Pulse
VOICE3_WAVE = $11 ; Triangle
; Default frequencies (used if no note data)
VOICE1_FREQ = $1C ; High pitch
VOICE2_FREQ = $0E ; Mid pitch
VOICE3_FREQ = $07 ; Low pitch
VOICE_AD = $09 ; Attack=0, Decay=9
VOICE_SR = $00 ; Sustain=0, Release=0
PULSE_WIDTH = $08 ; 50% duty cycle
; Miss sound settings
MISS_FREQ = $08 ; Low rumble
MISS_WAVE = $81 ; Noise waveform
MISS_AD = $00 ; Instant attack, no decay
MISS_SR = $90 ; High sustain, fast release
; Visual Settings
BORDER_COL = 0 ; Black border
BG_COL = 0 ; Black background
TRACK1_NOTE_COL = 10 ; Light red
TRACK2_NOTE_COL = 13 ; Light green
TRACK3_NOTE_COL = 14 ; Light blue
TRACK_LINE_COL = 11 ; Dark grey
HIT_ZONE_COL = 7 ; Yellow
HIT_COL = 1 ; White - flash on successful hit
PERFECT_COL = 1 ; White - perfect hit border flash
GOOD_COL = 7 ; Yellow - good hit border flash
MISS_COL = 2 ; Red - miss border flash
HEALTH_COL = 5 ; Green for health bar
; ============================================================================
; SCORING SETTINGS
; ============================================================================
PERFECT_SCORE = 100
GOOD_SCORE = 50
; ============================================================================
; HEALTH SETTINGS
; ============================================================================
HEALTH_MAX = 64
HEALTH_START = 32
HEALTH_PERFECT = 4
HEALTH_GOOD = 2
HEALTH_MISS = 8
; ============================================================================
; HIT DETECTION SETTINGS
; ============================================================================
HIT_ZONE_MIN = 2
HIT_ZONE_MAX = 5
HIT_ZONE_CENTRE = 3
; ============================================================================
; SONG SETTINGS
; ============================================================================
SONG_LENGTH = 64 ; Beats in the song
; ============================================================================
; MEMORY MAP
; ============================================================================
SCREEN = $0400
COLRAM = $D800
BORDER = $D020
BGCOL = $D021
CHARPTR = $D018
CHARSET = $3000
; SID registers
SID = $D400
SID_V1_FREQ_LO = $D400
SID_V1_FREQ_HI = $D401
SID_V1_PWHI = $D403
SID_V1_CTRL = $D404
SID_V1_AD = $D405
SID_V1_SR = $D406
SID_V2_FREQ_LO = $D407
SID_V2_FREQ_HI = $D408
SID_V2_PWHI = $D40A
SID_V2_CTRL = $D40B
SID_V2_AD = $D40C
SID_V2_SR = $D40D
SID_V3_FREQ_LO = $D40E
SID_V3_FREQ_HI = $D40F
SID_V3_PWHI = $D411
SID_V3_CTRL = $D412
SID_V3_AD = $D413
SID_V3_SR = $D414
SID_VOLUME = $D418
; CIA keyboard
CIA1_PRA = $DC00
CIA1_PRB = $DC01
; Track positions
TRACK1_ROW = 8
TRACK2_ROW = 12
TRACK3_ROW = 16
; Health bar position
HEALTH_ROW = 23
; Hit zone
HIT_ZONE_COLUMN = 3
; Custom character codes
CHAR_NOTE = 128
CHAR_TRACK = 129
CHAR_HITZONE = 130
CHAR_SPACE = 32
CHAR_BAR_FULL = 131
CHAR_BAR_EMPTY = 132
; Note settings
MAX_NOTES = 8
NOTE_SPAWN_COL = 37
; Timing
FRAMES_PER_BEAT = 25
; Zero page
ZP_PTR = $FB
ZP_PTR_HI = $FC
; Variables
frame_count = $02
beat_count = $03
song_pos = $04
song_pos_hi = $05
temp_track = $06
key_pressed = $07
hit_quality = $08
border_flash = $09
miss_track = $0A
game_over = $0B
hit_note_freq = $0C ; Frequency of note being hit
; ----------------------------------------------------------------------------
; BASIC Stub
; ----------------------------------------------------------------------------
* = $0801
!byte $0C, $08
!byte $0A, $00
!byte $9E
!text "2064"
!byte $00
!byte $00, $00
; ----------------------------------------------------------------------------
; Main Program
; ----------------------------------------------------------------------------
* = $0810
start:
jsr copy_charset
jsr init_screen
jsr init_sid
jsr init_notes
jsr init_score
jsr init_health
lda #<song_data
sta song_pos
lda #>song_data
sta song_pos_hi
lda #0
sta frame_count
sta beat_count
sta border_flash
sta game_over
main_loop:
lda #$FF
wait_raster:
cmp $D012
bne wait_raster
lda game_over
bne main_loop
inc frame_count
lda frame_count
cmp #FRAMES_PER_BEAT
bcc no_new_beat
lda #0
sta frame_count
jsr check_spawn_note
inc beat_count
no_new_beat:
jsr update_notes
jsr reset_track_colours
jsr update_border_flash
jsr check_keys
jmp main_loop
; ----------------------------------------------------------------------------
; Copy Character Set from ROM to RAM
; ----------------------------------------------------------------------------
copy_charset:
sei
lda $01
pha
and #$FB
sta $01
ldx #0
copy_loop:
lda $D000,x
sta CHARSET,x
lda $D100,x
sta CHARSET+$100,x
lda $D200,x
sta CHARSET+$200,x
lda $D300,x
sta CHARSET+$300,x
lda $D400,x
sta CHARSET+$400,x
lda $D500,x
sta CHARSET+$500,x
lda $D600,x
sta CHARSET+$600,x
lda $D700,x
sta CHARSET+$700,x
inx
bne copy_loop
pla
sta $01
cli
jsr define_custom_chars
lda #$1C
sta CHARPTR
rts
; ----------------------------------------------------------------------------
; Define Custom Characters
; ----------------------------------------------------------------------------
define_custom_chars:
lda #%00000110
sta CHARSET + (CHAR_NOTE * 8) + 0
lda #%00011110
sta CHARSET + (CHAR_NOTE * 8) + 1
lda #%01111110
sta CHARSET + (CHAR_NOTE * 8) + 2
lda #%11111110
sta CHARSET + (CHAR_NOTE * 8) + 3
lda #%11111110
sta CHARSET + (CHAR_NOTE * 8) + 4
lda #%01111110
sta CHARSET + (CHAR_NOTE * 8) + 5
lda #%00011110
sta CHARSET + (CHAR_NOTE * 8) + 6
lda #%00000110
sta CHARSET + (CHAR_NOTE * 8) + 7
lda #%00000000
sta CHARSET + (CHAR_TRACK * 8) + 0
lda #%00000000
sta CHARSET + (CHAR_TRACK * 8) + 1
lda #%00000000
sta CHARSET + (CHAR_TRACK * 8) + 2
lda #%11111111
sta CHARSET + (CHAR_TRACK * 8) + 3
lda #%11111111
sta CHARSET + (CHAR_TRACK * 8) + 4
lda #%00000000
sta CHARSET + (CHAR_TRACK * 8) + 5
lda #%00000000
sta CHARSET + (CHAR_TRACK * 8) + 6
lda #%00000000
sta CHARSET + (CHAR_TRACK * 8) + 7
lda #%01100110
sta CHARSET + (CHAR_HITZONE * 8) + 0
sta CHARSET + (CHAR_HITZONE * 8) + 1
sta CHARSET + (CHAR_HITZONE * 8) + 2
sta CHARSET + (CHAR_HITZONE * 8) + 3
sta CHARSET + (CHAR_HITZONE * 8) + 4
sta CHARSET + (CHAR_HITZONE * 8) + 5
sta CHARSET + (CHAR_HITZONE * 8) + 6
sta CHARSET + (CHAR_HITZONE * 8) + 7
lda #%11111111
sta CHARSET + (CHAR_BAR_FULL * 8) + 0
sta CHARSET + (CHAR_BAR_FULL * 8) + 1
sta CHARSET + (CHAR_BAR_FULL * 8) + 2
sta CHARSET + (CHAR_BAR_FULL * 8) + 3
sta CHARSET + (CHAR_BAR_FULL * 8) + 4
sta CHARSET + (CHAR_BAR_FULL * 8) + 5
sta CHARSET + (CHAR_BAR_FULL * 8) + 6
sta CHARSET + (CHAR_BAR_FULL * 8) + 7
lda #%11111111
sta CHARSET + (CHAR_BAR_EMPTY * 8) + 0
lda #%10000001
sta CHARSET + (CHAR_BAR_EMPTY * 8) + 1
sta CHARSET + (CHAR_BAR_EMPTY * 8) + 2
sta CHARSET + (CHAR_BAR_EMPTY * 8) + 3
sta CHARSET + (CHAR_BAR_EMPTY * 8) + 4
sta CHARSET + (CHAR_BAR_EMPTY * 8) + 5
sta CHARSET + (CHAR_BAR_EMPTY * 8) + 6
lda #%11111111
sta CHARSET + (CHAR_BAR_EMPTY * 8) + 7
rts
; ----------------------------------------------------------------------------
; Initialize Health
; ----------------------------------------------------------------------------
init_health:
lda #HEALTH_START
sta health
jsr display_health
rts
; ----------------------------------------------------------------------------
; Initialize Score
; ----------------------------------------------------------------------------
init_score:
lda #0
sta score_lo
sta score_hi
sta miss_count
jsr display_score
jsr display_misses
rts
; ----------------------------------------------------------------------------
; Initialize Notes
; ----------------------------------------------------------------------------
init_notes:
ldx #MAX_NOTES-1
lda #0
init_notes_loop:
sta note_track,x
sta note_col,x
sta note_freq,x ; Also clear note frequencies
dex
bpl init_notes_loop
rts
; ----------------------------------------------------------------------------
; Check Spawn Note - Now reads 3-byte song entries
; ----------------------------------------------------------------------------
check_spawn_note:
ldy #0
spawn_check_loop:
lda (song_pos),y
cmp #$FF
beq spawn_restart_song
cmp beat_count
beq spawn_match
bcs spawn_done
jmp spawn_advance
spawn_match:
iny
lda (song_pos),y ; Track number
sta temp_track
iny
lda (song_pos),y ; Note frequency
pha
lda temp_track
jsr spawn_note_with_freq
pla ; Clean up stack
dey
dey
spawn_advance:
lda song_pos
clc
adc #3 ; 3 bytes per entry now
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 With Frequency
; Input: A = track, stack has frequency
; ----------------------------------------------------------------------------
spawn_note_with_freq:
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
spawn_found_slot:
lda temp_track
sta note_track,x
lda #NOTE_SPAWN_COL
sta note_col,x
; Get frequency from stack (caller pushed it)
tsx
lda $0103,x ; Get frequency from stack
sta note_freq,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_miss
jsr draw_note
jmp update_next
update_miss:
lda note_track,x
sta miss_track
lda #0
sta note_track,x
jsr handle_miss
update_next:
inx
cpx #MAX_NOTES
bne update_loop
rts
; ----------------------------------------------------------------------------
; Handle Miss
; ----------------------------------------------------------------------------
handle_miss:
inc miss_count
jsr play_miss_sound
lda #MISS_COL
sta BORDER
lda #8
sta border_flash
jsr display_misses
jsr decrease_health
rts
; ----------------------------------------------------------------------------
; Decrease Health
; ----------------------------------------------------------------------------
decrease_health:
lda health
sec
sbc #HEALTH_MISS
bcc health_zero
sta health
jsr display_health
jsr check_game_over
rts
health_zero:
lda #0
sta health
jsr display_health
jsr check_game_over
rts
; ----------------------------------------------------------------------------
; Increase Health
; ----------------------------------------------------------------------------
increase_health:
clc
adc health
cmp #HEALTH_MAX
bcc health_ok
lda #HEALTH_MAX
health_ok:
sta health
jsr display_health
rts
; ----------------------------------------------------------------------------
; Check Game Over
; ----------------------------------------------------------------------------
check_game_over:
lda health
bne not_game_over
lda #1
sta game_over
jsr show_game_over
not_game_over:
rts
; ----------------------------------------------------------------------------
; Show Game Over
; ----------------------------------------------------------------------------
show_game_over:
ldx #0
game_over_loop:
lda game_over_text,x
beq game_over_done
sta SCREEN + (12 * 40) + 15,x
lda #2
sta COLRAM + (12 * 40) + 15,x
inx
jmp game_over_loop
game_over_done:
lda #2
sta BORDER
rts
game_over_text:
!scr "game over"
!byte 0
; ----------------------------------------------------------------------------
; Display Health
; ----------------------------------------------------------------------------
display_health:
lda health
lsr
lsr
lsr
sta temp_health
ldx #0
lda temp_health
beq draw_empty_bars
draw_full_bars:
lda #CHAR_BAR_FULL
sta SCREEN + (HEALTH_ROW * 40) + 16,x
lda #HEALTH_COL
sta COLRAM + (HEALTH_ROW * 40) + 16,x
inx
cpx temp_health
bne draw_full_bars
draw_empty_bars:
cpx #8
beq health_done
lda #CHAR_BAR_EMPTY
sta SCREEN + (HEALTH_ROW * 40) + 16,x
lda #11
sta COLRAM + (HEALTH_ROW * 40) + 16,x
inx
jmp draw_empty_bars
health_done:
rts
temp_health: !byte 0
; ----------------------------------------------------------------------------
; Play Miss Sound
; ----------------------------------------------------------------------------
play_miss_sound:
lda #0
sta SID_V3_FREQ_LO
lda #MISS_FREQ
sta SID_V3_FREQ_HI
lda #MISS_AD
sta SID_V3_AD
lda #MISS_SR
sta SID_V3_SR
lda #MISS_WAVE
ora #$01
sta SID_V3_CTRL
rts
; ----------------------------------------------------------------------------
; Draw Note
; ----------------------------------------------------------------------------
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 #CHAR_NOTE
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 #TRACK1_NOTE_COL
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 #CHAR_NOTE
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 #TRACK2_NOTE_COL
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 #CHAR_NOTE
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 #TRACK3_NOTE_COL
sta (ZP_PTR),y
rts
; ----------------------------------------------------------------------------
; Erase Note
; ----------------------------------------------------------------------------
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 #CHAR_TRACK
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 #TRACK_LINE_COL
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 #CHAR_TRACK
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 #TRACK_LINE_COL
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 #CHAR_TRACK
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 #TRACK_LINE_COL
sta (ZP_PTR),y
rts
; ----------------------------------------------------------------------------
; Initialize Screen
; ----------------------------------------------------------------------------
init_screen:
lda #BORDER_COL
sta BORDER
lda #BG_COL
sta BGCOL
ldx #0
lda #CHAR_SPACE
clr_screen:
sta SCREEN,x
sta SCREEN+$100,x
sta SCREEN+$200,x
sta SCREEN+$2E8,x
inx
bne clr_screen
ldx #0
lda #TRACK_LINE_COL
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:
lda #CHAR_TRACK
ldx #0
draw_t1:
sta SCREEN + (TRACK1_ROW * 40),x
inx
cpx #38
bne draw_t1
lda #CHAR_TRACK
ldx #0
draw_t2:
sta SCREEN + (TRACK2_ROW * 40),x
inx
cpx #38
bne draw_t2
lda #CHAR_TRACK
ldx #0
draw_t3:
sta SCREEN + (TRACK3_ROW * 40),x
inx
cpx #38
bne draw_t3
rts
; ----------------------------------------------------------------------------
; Draw Hit Zones
; ----------------------------------------------------------------------------
draw_hit_zones:
lda #CHAR_HITZONE
sta SCREEN + ((TRACK1_ROW-2) * 40) + HIT_ZONE_COLUMN
sta SCREEN + ((TRACK1_ROW-1) * 40) + HIT_ZONE_COLUMN
sta SCREEN + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
sta SCREEN + ((TRACK1_ROW+1) * 40) + HIT_ZONE_COLUMN
sta SCREEN + ((TRACK2_ROW-1) * 40) + HIT_ZONE_COLUMN
sta SCREEN + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
sta SCREEN + ((TRACK2_ROW+1) * 40) + HIT_ZONE_COLUMN
sta SCREEN + ((TRACK3_ROW-1) * 40) + HIT_ZONE_COLUMN
sta SCREEN + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
sta SCREEN + ((TRACK3_ROW+1) * 40) + HIT_ZONE_COLUMN
sta SCREEN + ((TRACK3_ROW+2) * 40) + HIT_ZONE_COLUMN
lda #HIT_ZONE_COL
sta COLRAM + ((TRACK1_ROW-2) * 40) + HIT_ZONE_COLUMN
sta COLRAM + ((TRACK1_ROW-1) * 40) + HIT_ZONE_COLUMN
sta COLRAM + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
sta COLRAM + ((TRACK1_ROW+1) * 40) + HIT_ZONE_COLUMN
sta COLRAM + ((TRACK2_ROW-1) * 40) + HIT_ZONE_COLUMN
sta COLRAM + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
sta COLRAM + ((TRACK2_ROW+1) * 40) + HIT_ZONE_COLUMN
sta COLRAM + ((TRACK3_ROW-1) * 40) + HIT_ZONE_COLUMN
sta COLRAM + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
sta COLRAM + ((TRACK3_ROW+1) * 40) + HIT_ZONE_COLUMN
sta COLRAM + ((TRACK3_ROW+2) * 40) + HIT_ZONE_COLUMN
rts
; ----------------------------------------------------------------------------
; Draw Labels
; ----------------------------------------------------------------------------
draw_labels:
ldx #0
draw_score_label:
lda score_label,x
beq draw_score_label_done
sta SCREEN + 1,x
lda #1
sta COLRAM + 1,x
inx
bne draw_score_label
draw_score_label_done:
ldx #0
draw_miss_label:
lda miss_label,x
beq draw_miss_label_done
sta SCREEN + 15,x
lda #2
sta COLRAM + 15,x
inx
bne draw_miss_label
draw_miss_label_done:
ldx #0
draw_title:
lda title_text,x
beq draw_title_done
sta SCREEN + 27,x
lda #1
sta COLRAM + 27,x
inx
bne draw_title
draw_title_done:
ldx #0
draw_health_label:
lda health_label,x
beq draw_health_label_done
sta SCREEN + (HEALTH_ROW * 40) + 8,x
lda #5
sta COLRAM + (HEALTH_ROW * 40) + 8,x
inx
bne draw_health_label
draw_health_label_done:
lda #$1A
sta SCREEN + (TRACK1_ROW * 40)
lda #TRACK1_NOTE_COL
sta COLRAM + (TRACK1_ROW * 40)
lda #$18
sta SCREEN + (TRACK2_ROW * 40)
lda #TRACK2_NOTE_COL
sta COLRAM + (TRACK2_ROW * 40)
lda #$03
sta SCREEN + (TRACK3_ROW * 40)
lda #TRACK3_NOTE_COL
sta COLRAM + (TRACK3_ROW * 40)
rts
score_label:
!scr "score:"
!byte 0
miss_label:
!scr "miss:"
!byte 0
title_text:
!scr "sid symphony"
!byte 0
health_label:
!scr "health:"
!byte 0
; ----------------------------------------------------------------------------
; Initialize SID
; ----------------------------------------------------------------------------
init_sid:
ldx #$18
lda #0
clear_sid:
sta SID,x
dex
bpl clear_sid
lda #$0F
sta SID_VOLUME
lda #$00
sta SID_V1_FREQ_LO
lda #VOICE1_FREQ
sta SID_V1_FREQ_HI
lda #PULSE_WIDTH
sta SID_V1_PWHI
lda #VOICE_AD
sta SID_V1_AD
lda #VOICE_SR
sta SID_V1_SR
lda #$00
sta SID_V2_FREQ_LO
lda #VOICE2_FREQ
sta SID_V2_FREQ_HI
lda #PULSE_WIDTH
sta SID_V2_PWHI
lda #VOICE_AD
sta SID_V2_AD
lda #VOICE_SR
sta SID_V2_SR
lda #$00
sta SID_V3_FREQ_LO
lda #VOICE3_FREQ
sta SID_V3_FREQ_HI
lda #PULSE_WIDTH
sta SID_V3_PWHI
lda #VOICE_AD
sta SID_V3_AD
lda #VOICE_SR
sta SID_V3_SR
rts
; ----------------------------------------------------------------------------
; Reset Track Colours
; ----------------------------------------------------------------------------
reset_track_colours:
ldx #0
lda #TRACK_LINE_COL
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
lda #TRACK1_NOTE_COL
sta COLRAM + (TRACK1_ROW * 40)
lda #TRACK2_NOTE_COL
sta COLRAM + (TRACK2_ROW * 40)
lda #TRACK3_NOTE_COL
sta COLRAM + (TRACK3_ROW * 40)
lda #HIT_ZONE_COL
sta COLRAM + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
sta COLRAM + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
sta COLRAM + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
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
; ----------------------------------------------------------------------------
; Update Border Flash
; ----------------------------------------------------------------------------
update_border_flash:
lda border_flash
beq flash_done
dec border_flash
bne flash_done
lda #BORDER_COL
sta BORDER
flash_done:
rts
; ----------------------------------------------------------------------------
; Check Keys
; ----------------------------------------------------------------------------
check_keys:
lda #$FD
sta CIA1_PRA
lda CIA1_PRB
and #$10
bne check_x_key
lda #1
sta key_pressed
jsr check_hit
bcc check_x_key
jsr play_voice1_note
jsr flash_track1_hit
jsr award_points
check_x_key:
lda #$FB
sta CIA1_PRA
lda CIA1_PRB
and #$80
bne check_c_key
lda #2
sta key_pressed
jsr check_hit
bcc check_c_key
jsr play_voice2_note
jsr flash_track2_hit
jsr award_points
check_c_key:
lda #$FB
sta CIA1_PRA
lda CIA1_PRB
and #$10
bne check_keys_done
lda #3
sta key_pressed
jsr check_hit
bcc check_keys_done
jsr play_voice3_note
jsr flash_track3_hit
jsr award_points
check_keys_done:
lda #$FF
sta CIA1_PRA
rts
; ----------------------------------------------------------------------------
; Check Hit - Now stores note frequency for playback
; ----------------------------------------------------------------------------
check_hit:
ldx #0
check_hit_loop:
lda note_track,x
beq check_hit_next
cmp key_pressed
bne check_hit_next
lda note_col,x
cmp #HIT_ZONE_MIN
bcc check_hit_next
cmp #HIT_ZONE_MAX+1
bcs check_hit_next
; Store note frequency for sound playback
lda note_freq,x
sta hit_note_freq
cmp #HIT_ZONE_CENTRE
bcc hit_good
cmp #HIT_ZONE_CENTRE+2
bcs hit_good
lda #2
sta hit_quality
jmp hit_found
hit_good:
lda #1
sta hit_quality
hit_found:
jsr erase_note
lda #0
sta note_track,x
sec
rts
check_hit_next:
inx
cpx #MAX_NOTES
bne check_hit_loop
lda #0
sta hit_quality
clc
rts
; ----------------------------------------------------------------------------
; Award Points
; ----------------------------------------------------------------------------
award_points:
lda hit_quality
cmp #2
beq award_perfect
lda score_lo
clc
adc #GOOD_SCORE
sta score_lo
lda score_hi
adc #0
sta score_hi
lda #GOOD_COL
sta BORDER
lda #4
sta border_flash
lda #HEALTH_GOOD
jsr increase_health
jmp award_done
award_perfect:
lda score_lo
clc
adc #PERFECT_SCORE
sta score_lo
lda score_hi
adc #0
sta score_hi
lda #PERFECT_COL
sta BORDER
lda #6
sta border_flash
lda #HEALTH_PERFECT
jsr increase_health
award_done:
jsr display_score
rts
; ----------------------------------------------------------------------------
; Display Score
; ----------------------------------------------------------------------------
display_score:
lda score_lo
sta work_lo
lda score_hi
sta work_hi
ldx #0
div_10000:
lda work_lo
sec
sbc #<10000
tay
lda work_hi
sbc #>10000
bcc done_10000
sta work_hi
sty work_lo
inx
jmp div_10000
done_10000:
txa
ora #$30
sta SCREEN + 8
ldx #0
div_1000:
lda work_lo
sec
sbc #<1000
tay
lda work_hi
sbc #>1000
bcc done_1000
sta work_hi
sty work_lo
inx
jmp div_1000
done_1000:
txa
ora #$30
sta SCREEN + 9
ldx #0
div_100:
lda work_lo
sec
sbc #100
bcc done_100
sta work_lo
inx
jmp div_100
done_100:
txa
ora #$30
sta SCREEN + 10
ldx #0
div_10:
lda work_lo
sec
sbc #10
bcc done_10
sta work_lo
inx
jmp div_10
done_10:
txa
ora #$30
sta SCREEN + 11
lda work_lo
ora #$30
sta SCREEN + 12
lda #7
sta COLRAM + 8
sta COLRAM + 9
sta COLRAM + 10
sta COLRAM + 11
sta COLRAM + 12
rts
; ----------------------------------------------------------------------------
; Display Misses
; ----------------------------------------------------------------------------
display_misses:
lda miss_count
ldx #0
miss_div_10:
cmp #10
bcc miss_done_10
sec
sbc #10
inx
jmp miss_div_10
miss_done_10:
pha
txa
ora #$30
sta SCREEN + 21
pla
ora #$30
sta SCREEN + 22
lda #2
sta COLRAM + 21
sta COLRAM + 22
rts
work_lo: !byte 0
work_hi: !byte 0
; ----------------------------------------------------------------------------
; Play Voices With Note Frequency
; ----------------------------------------------------------------------------
play_voice1_note:
lda #0
sta SID_V1_FREQ_LO
lda hit_note_freq
sta SID_V1_FREQ_HI
lda #VOICE1_WAVE
ora #$01
sta SID_V1_CTRL
rts
play_voice2_note:
lda #0
sta SID_V2_FREQ_LO
lda hit_note_freq
sta SID_V2_FREQ_HI
lda #VOICE2_WAVE
ora #$01
sta SID_V2_CTRL
rts
play_voice3_note:
lda #0
sta SID_V3_FREQ_LO
lda hit_note_freq
sta SID_V3_FREQ_HI
lda #VOICE3_WAVE
ora #$01
sta SID_V3_CTRL
rts
; ----------------------------------------------------------------------------
; Flash Tracks on Hit
; ----------------------------------------------------------------------------
flash_track1_hit:
ldx #0
lda #HIT_COL
flash_t1h_loop:
sta COLRAM + (TRACK1_ROW * 40),x
inx
cpx #38
bne flash_t1h_loop
lda #1
sta COLRAM + (TRACK1_ROW * 40)
rts
flash_track2_hit:
ldx #0
lda #HIT_COL
flash_t2h_loop:
sta COLRAM + (TRACK2_ROW * 40),x
inx
cpx #38
bne flash_t2h_loop
lda #1
sta COLRAM + (TRACK2_ROW * 40)
rts
flash_track3_hit:
ldx #0
lda #HIT_COL
flash_t3h_loop:
sta COLRAM + (TRACK3_ROW * 40),x
inx
cpx #38
bne flash_t3h_loop
lda #1
sta COLRAM + (TRACK3_ROW * 40)
rts
; ============================================================================
; SONG DATA
; ============================================================================
; Format: beat, track, note_freq_hi
;
; Note frequency table (high byte only for simplicity):
; C3=$11 D3=$13 E3=$16 F3=$17 G3=$1A A3=$1D B3=$21
; C4=$23 D4=$27 E4=$2C F4=$2F G4=$35 A4=$3B B4=$42
; C5=$47 D5=$4F E5=$58 F5=$5E G5=$6A A5=$77 B5=$85
;
; Track 1 (Z) = high notes, sawtooth
; Track 2 (X) = mid notes, pulse
; Track 3 (C) = low notes, triangle
; ============================================================================
song_data:
; Bar 1 - Opening phrase
!byte 0, 1, $47 ; C5 - high
!byte 2, 2, $2C ; E4 - mid
!byte 4, 3, $11 ; C3 - low
; Bar 2 - Descending
!byte 8, 1, $3B ; A4
!byte 10, 2, $27 ; D4
!byte 12, 3, $13 ; D3
; Bar 3 - Building
!byte 16, 1, $35 ; G4
!byte 17, 2, $2C ; E4
!byte 18, 1, $3B ; A4
!byte 20, 3, $16 ; E3
; Bar 4 - Resolution
!byte 24, 1, $47 ; C5
!byte 26, 2, $35 ; G4
!byte 28, 3, $11 ; C3
; Bar 5 - New phrase
!byte 32, 2, $2F ; F4
!byte 34, 1, $4F ; D5
!byte 36, 3, $17 ; F3
; Bar 6 - Variation
!byte 40, 1, $58 ; E5
!byte 42, 2, $2C ; E4
!byte 44, 3, $11 ; C3
!byte 46, 2, $27 ; D4
; Bar 7 - Climax approach
!byte 48, 1, $6A ; G5
!byte 49, 2, $35 ; G4
!byte 50, 1, $58 ; E5
!byte 52, 3, $1A ; G3
!byte 54, 2, $2F ; F4
; Bar 8 - Resolution/Loop point
!byte 56, 1, $47 ; C5
!byte 58, 2, $2C ; E4
!byte 60, 3, $11 ; C3
!byte 62, 1, $35 ; G4
!byte $FF ; End of song
; ----------------------------------------------------------------------------
; Note Arrays
; ----------------------------------------------------------------------------
note_track:
!fill MAX_NOTES, 0
note_col:
!fill MAX_NOTES, 0
note_freq:
!fill MAX_NOTES, 0 ; Note frequency for each active note
; ----------------------------------------------------------------------------
; Game Variables
; ----------------------------------------------------------------------------
score_lo: !byte 0
score_hi: !byte 0
miss_count: !byte 0
health: !byte 0
Try This: Compose Your Own
Create a different melody:
song_data:
; Simple ascending scale
!byte 0, 3, $11 ; C3
!byte 4, 3, $13 ; D3
!byte 8, 3, $16 ; E3
!byte 12, 3, $17 ; F3
!byte 16, 2, $1A ; G3 (switch to mid track)
!byte 20, 2, $1D ; A3
!byte 24, 1, $21 ; B3 (switch to high track)
!byte 28, 1, $23 ; C4
!byte $FF
Experiment with different patterns. What makes a satisfying rhythm?
Try This: Denser Patterns
Add more notes for a harder challenge:
; Rapid alternation
!byte 0, 1, $47
!byte 1, 2, $2C
!byte 2, 3, $11
!byte 3, 1, $47
!byte 4, 2, $2C
!byte 5, 3, $11
; ...
Notes on every beat create intense gameplay.
What You’ve Learnt
- Data-driven design - Song structure defined in data, not code
- SID frequencies - Note values for the sound chip
- Three-byte entries - Expandable format for future features
- Musical structure - Bars, phrases, and progression
What’s Next
In Unit 10, we’ll add song progress tracking. The game will show how far through the song you are and handle the song ending properly - either victory or game over.
What Changed
| 1 | 1 | ; ============================================================================ | |
| 2 | - | ; SID SYMPHONY - Unit 8: Health System | |
| 2 | + | ; SID SYMPHONY - Unit 9: Song Data | |
| 3 | 3 | ; ============================================================================ | |
| 4 | - | ; A health meter that grows on hits and shrinks on misses. The game can be | |
| 5 | - | ; lost. Stakes make success meaningful. | |
| 4 | + | ; Design the song data format. Create the first complete musical pattern. | |
| 5 | + | ; Notes now carry pitch information - each hit plays its actual note. | |
| 6 | 6 | ; | |
| 7 | 7 | ; Controls: Z = Track 1 (high), X = Track 2 (mid), C = Track 3 (low) | |
| 8 | 8 | ; ============================================================================ | |
| ... | |||
| 11 | 11 | ; CUSTOMISATION SECTION | |
| 12 | 12 | ; ============================================================================ | |
| 13 | 13 | | |
| 14 | - | ; SID Voice Settings | |
| 14 | + | ; SID Voice Settings (default for when no note playing) | |
| 15 | 15 | VOICE1_WAVE = $21 ; Sawtooth | |
| 16 | 16 | VOICE2_WAVE = $41 ; Pulse | |
| 17 | 17 | VOICE3_WAVE = $11 ; Triangle | |
| 18 | 18 | | |
| 19 | + | ; Default frequencies (used if no note data) | |
| 19 | 20 | VOICE1_FREQ = $1C ; High pitch | |
| 20 | 21 | VOICE2_FREQ = $0E ; Mid pitch | |
| 21 | 22 | VOICE3_FREQ = $07 ; Low pitch | |
| ... | |||
| 40 | 41 | | |
| 41 | 42 | TRACK_LINE_COL = 11 ; Dark grey | |
| 42 | 43 | HIT_ZONE_COL = 7 ; Yellow | |
| 43 | - | | |
| 44 | - | FLASH1_COL = 2 ; Red | |
| 45 | - | FLASH2_COL = 5 ; Green | |
| 46 | - | FLASH3_COL = 6 ; Blue | |
| 47 | 44 | | |
| 48 | 45 | HIT_COL = 1 ; White - flash on successful hit | |
| 49 | 46 | PERFECT_COL = 1 ; White - perfect hit border flash | |
| ... | |||
| 63 | 60 | ; HEALTH SETTINGS | |
| 64 | 61 | ; ============================================================================ | |
| 65 | 62 | | |
| 66 | - | HEALTH_MAX = 64 ; Maximum health | |
| 67 | - | HEALTH_START = 32 ; Starting health (half) | |
| 68 | - | HEALTH_PERFECT = 4 ; Health gained on perfect hit | |
| 69 | - | HEALTH_GOOD = 2 ; Health gained on good hit | |
| 70 | - | HEALTH_MISS = 8 ; Health lost on miss | |
| 63 | + | HEALTH_MAX = 64 | |
| 64 | + | HEALTH_START = 32 | |
| 65 | + | HEALTH_PERFECT = 4 | |
| 66 | + | HEALTH_GOOD = 2 | |
| 67 | + | HEALTH_MISS = 8 | |
| 71 | 68 | | |
| 72 | 69 | ; ============================================================================ | |
| 73 | 70 | ; HIT DETECTION SETTINGS | |
| ... | |||
| 76 | 73 | HIT_ZONE_MIN = 2 | |
| 77 | 74 | HIT_ZONE_MAX = 5 | |
| 78 | 75 | HIT_ZONE_CENTRE = 3 | |
| 76 | + | | |
| 77 | + | ; ============================================================================ | |
| 78 | + | ; SONG SETTINGS | |
| 79 | + | ; ============================================================================ | |
| 80 | + | | |
| 81 | + | SONG_LENGTH = 64 ; Beats in the song | |
| 79 | 82 | | |
| 80 | 83 | ; ============================================================================ | |
| 81 | 84 | ; MEMORY MAP | |
| ... | |||
| 158 | 161 | hit_quality = $08 | |
| 159 | 162 | border_flash = $09 | |
| 160 | 163 | miss_track = $0A | |
| 161 | - | game_over = $0B ; Non-zero when game ended | |
| 164 | + | game_over = $0B | |
| 165 | + | hit_note_freq = $0C ; Frequency of note being hit | |
| 162 | 166 | | |
| 163 | 167 | ; ---------------------------------------------------------------------------- | |
| 164 | 168 | ; BASIC Stub | |
| ... | |||
| 204 | 208 | cmp $D012 | |
| 205 | 209 | bne wait_raster | |
| 206 | 210 | | |
| 207 | - | ; Check if game is over | |
| 208 | 211 | lda game_over | |
| 209 | - | bne main_loop ; Freeze if game over | |
| 212 | + | bne main_loop | |
| 210 | 213 | | |
| 211 | 214 | inc frame_count | |
| 212 | 215 | lda frame_count | |
| ... | |||
| 276 | 279 | ; ---------------------------------------------------------------------------- | |
| 277 | 280 | | |
| 278 | 281 | define_custom_chars: | |
| 279 | - | ; Note character - diamond shape | |
| 280 | 282 | lda #%00000110 | |
| 281 | 283 | sta CHARSET + (CHAR_NOTE * 8) + 0 | |
| 282 | 284 | lda #%00011110 | |
| ... | |||
| 294 | 296 | lda #%00000110 | |
| 295 | 297 | sta CHARSET + (CHAR_NOTE * 8) + 7 | |
| 296 | 298 | | |
| 297 | - | ; Track character - horizontal line | |
| 298 | 299 | lda #%00000000 | |
| 299 | 300 | sta CHARSET + (CHAR_TRACK * 8) + 0 | |
| 300 | 301 | lda #%00000000 | |
| ... | |||
| 312 | 313 | lda #%00000000 | |
| 313 | 314 | sta CHARSET + (CHAR_TRACK * 8) + 7 | |
| 314 | 315 | | |
| 315 | - | ; Hit zone character - vertical bars | |
| 316 | 316 | lda #%01100110 | |
| 317 | 317 | sta CHARSET + (CHAR_HITZONE * 8) + 0 | |
| 318 | 318 | sta CHARSET + (CHAR_HITZONE * 8) + 1 | |
| ... | |||
| 323 | 323 | sta CHARSET + (CHAR_HITZONE * 8) + 6 | |
| 324 | 324 | sta CHARSET + (CHAR_HITZONE * 8) + 7 | |
| 325 | 325 | | |
| 326 | - | ; Health bar full - solid block | |
| 327 | 326 | lda #%11111111 | |
| 328 | 327 | sta CHARSET + (CHAR_BAR_FULL * 8) + 0 | |
| 329 | 328 | sta CHARSET + (CHAR_BAR_FULL * 8) + 1 | |
| ... | |||
| 334 | 333 | sta CHARSET + (CHAR_BAR_FULL * 8) + 6 | |
| 335 | 334 | sta CHARSET + (CHAR_BAR_FULL * 8) + 7 | |
| 336 | 335 | | |
| 337 | - | ; Health bar empty - outline | |
| 338 | 336 | lda #%11111111 | |
| 339 | 337 | sta CHARSET + (CHAR_BAR_EMPTY * 8) + 0 | |
| 340 | 338 | lda #%10000001 | |
| ... | |||
| 382 | 380 | init_notes_loop: | |
| 383 | 381 | sta note_track,x | |
| 384 | 382 | sta note_col,x | |
| 383 | + | sta note_freq,x ; Also clear note frequencies | |
| 385 | 384 | dex | |
| 386 | 385 | bpl init_notes_loop | |
| 387 | 386 | rts | |
| 388 | 387 | | |
| 389 | 388 | ; ---------------------------------------------------------------------------- | |
| 390 | - | ; Check Spawn Note | |
| 389 | + | ; Check Spawn Note - Now reads 3-byte song entries | |
| 391 | 390 | ; ---------------------------------------------------------------------------- | |
| 392 | 391 | | |
| 393 | 392 | check_spawn_note: | |
| ... | |||
| 406 | 405 | | |
| 407 | 406 | spawn_match: | |
| 408 | 407 | iny | |
| 409 | - | lda (song_pos),y | |
| 410 | - | jsr spawn_note | |
| 408 | + | lda (song_pos),y ; Track number | |
| 409 | + | sta temp_track | |
| 410 | + | iny | |
| 411 | + | lda (song_pos),y ; Note frequency | |
| 412 | + | pha | |
| 413 | + | lda temp_track | |
| 414 | + | jsr spawn_note_with_freq | |
| 415 | + | pla ; Clean up stack | |
| 416 | + | dey | |
| 411 | 417 | dey | |
| 412 | 418 | | |
| 413 | 419 | spawn_advance: | |
| 414 | 420 | lda song_pos | |
| 415 | 421 | clc | |
| 416 | - | adc #2 | |
| 422 | + | adc #3 ; 3 bytes per entry now | |
| 417 | 423 | sta song_pos | |
| 418 | 424 | lda song_pos_hi | |
| 419 | 425 | adc #0 | |
| ... | |||
| 433 | 439 | rts | |
| 434 | 440 | | |
| 435 | 441 | ; ---------------------------------------------------------------------------- | |
| 436 | - | ; Spawn Note | |
| 442 | + | ; Spawn Note With Frequency | |
| 443 | + | ; Input: A = track, stack has frequency | |
| 437 | 444 | ; ---------------------------------------------------------------------------- | |
| 438 | 445 | | |
| 439 | - | spawn_note: | |
| 446 | + | spawn_note_with_freq: | |
| 440 | 447 | sta temp_track | |
| 441 | 448 | | |
| 442 | 449 | ldx #0 | |
| ... | |||
| 453 | 460 | sta note_track,x | |
| 454 | 461 | lda #NOTE_SPAWN_COL | |
| 455 | 462 | sta note_col,x | |
| 463 | + | | |
| 464 | + | ; Get frequency from stack (caller pushed it) | |
| 465 | + | tsx | |
| 466 | + | lda $0103,x ; Get frequency from stack | |
| 467 | + | sta note_freq,x | |
| 468 | + | | |
| 456 | 469 | jsr draw_note | |
| 457 | 470 | rts | |
| 458 | 471 | | |
| ... | |||
| 505 | 518 | sta border_flash | |
| 506 | 519 | | |
| 507 | 520 | jsr display_misses | |
| 508 | - | | |
| 509 | - | ; Decrease health | |
| 510 | 521 | jsr decrease_health | |
| 511 | 522 | | |
| 512 | 523 | rts | |
| 513 | 524 | | |
| 514 | 525 | ; ---------------------------------------------------------------------------- | |
| 515 | - | ; Decrease Health - Subtract HEALTH_MISS, clamp at 0 | |
| 526 | + | ; Decrease Health | |
| 516 | 527 | ; ---------------------------------------------------------------------------- | |
| 517 | 528 | | |
| 518 | 529 | decrease_health: | |
| 519 | 530 | lda health | |
| 520 | 531 | sec | |
| 521 | 532 | sbc #HEALTH_MISS | |
| 522 | - | bcc health_zero ; Underflow - set to 0 | |
| 533 | + | bcc health_zero | |
| 523 | 534 | sta health | |
| 524 | 535 | jsr display_health | |
| 525 | 536 | jsr check_game_over | |
| ... | |||
| 533 | 544 | rts | |
| 534 | 545 | | |
| 535 | 546 | ; ---------------------------------------------------------------------------- | |
| 536 | - | ; Increase Health - Add amount, clamp at HEALTH_MAX | |
| 537 | - | ; Input: A = amount to add | |
| 547 | + | ; Increase Health | |
| 538 | 548 | ; ---------------------------------------------------------------------------- | |
| 539 | 549 | | |
| 540 | 550 | increase_health: | |
| ... | |||
| 542 | 552 | adc health | |
| 543 | 553 | cmp #HEALTH_MAX | |
| 544 | 554 | bcc health_ok | |
| 545 | - | lda #HEALTH_MAX ; Clamp to max | |
| 555 | + | lda #HEALTH_MAX | |
| 546 | 556 | health_ok: | |
| 547 | 557 | sta health | |
| 548 | 558 | jsr display_health | |
| ... | |||
| 556 | 566 | lda health | |
| 557 | 567 | bne not_game_over | |
| 558 | 568 | | |
| 559 | - | ; Game over! | |
| 560 | 569 | lda #1 | |
| 561 | 570 | sta game_over | |
| 562 | - | | |
| 563 | - | ; Display game over message | |
| 564 | 571 | jsr show_game_over | |
| 565 | 572 | | |
| 566 | 573 | not_game_over: | |
| ... | |||
| 571 | 578 | ; ---------------------------------------------------------------------------- | |
| 572 | 579 | | |
| 573 | 580 | show_game_over: | |
| 574 | - | ; Display "GAME OVER" in centre of screen | |
| 575 | 581 | ldx #0 | |
| 576 | 582 | game_over_loop: | |
| 577 | 583 | lda game_over_text,x | |
| 578 | 584 | beq game_over_done | |
| 579 | 585 | sta SCREEN + (12 * 40) + 15,x | |
| 580 | - | lda #2 ; Red | |
| 586 | + | lda #2 | |
| 581 | 587 | sta COLRAM + (12 * 40) + 15,x | |
| 582 | 588 | inx | |
| 583 | 589 | jmp game_over_loop | |
| 584 | 590 | game_over_done: | |
| 585 | - | | |
| 586 | - | ; Flash border red | |
| 587 | 591 | lda #2 | |
| 588 | 592 | sta BORDER | |
| 589 | - | | |
| 590 | 593 | rts | |
| 591 | 594 | | |
| 592 | 595 | game_over_text: | |
| ... | |||
| 594 | 597 | !byte 0 | |
| 595 | 598 | | |
| 596 | 599 | ; ---------------------------------------------------------------------------- | |
| 597 | - | ; Display Health - 8 character bar | |
| 600 | + | ; Display Health | |
| 598 | 601 | ; ---------------------------------------------------------------------------- | |
| 599 | 602 | | |
| 600 | 603 | display_health: | |
| 601 | - | ; Health ranges 0-64, displayed as 8 characters | |
| 602 | - | ; Each character represents 8 health points | |
| 603 | - | | |
| 604 | 604 | lda health | |
| 605 | - | lsr ; Divide by 8 | |
| 606 | 605 | lsr | |
| 607 | 606 | lsr | |
| 608 | - | sta temp_health ; Full bars to draw | |
| 607 | + | lsr | |
| 608 | + | sta temp_health | |
| 609 | 609 | | |
| 610 | - | ; Draw full bars | |
| 611 | 610 | ldx #0 | |
| 612 | 611 | lda temp_health | |
| 613 | 612 | beq draw_empty_bars | |
| ... | |||
| 621 | 620 | cpx temp_health | |
| 622 | 621 | bne draw_full_bars | |
| 623 | 622 | | |
| 624 | - | ; Draw empty bars for remainder | |
| 625 | 623 | draw_empty_bars: | |
| 626 | 624 | cpx #8 | |
| 627 | 625 | beq health_done | |
| 628 | 626 | lda #CHAR_BAR_EMPTY | |
| 629 | 627 | sta SCREEN + (HEALTH_ROW * 40) + 16,x | |
| 630 | - | lda #11 ; Dark grey | |
| 628 | + | lda #11 | |
| 631 | 629 | sta COLRAM + (HEALTH_ROW * 40) + 16,x | |
| 632 | 630 | inx | |
| 633 | 631 | jmp draw_empty_bars | |
| ... | |||
| 937 | 935 | ; ---------------------------------------------------------------------------- | |
| 938 | 936 | | |
| 939 | 937 | draw_labels: | |
| 940 | - | ; Draw "SCORE:" label | |
| 941 | 938 | ldx #0 | |
| 942 | 939 | draw_score_label: | |
| 943 | 940 | lda score_label,x | |
| ... | |||
| 949 | 946 | bne draw_score_label | |
| 950 | 947 | draw_score_label_done: | |
| 951 | 948 | | |
| 952 | - | ; Draw "MISS:" label | |
| 953 | 949 | ldx #0 | |
| 954 | 950 | draw_miss_label: | |
| 955 | 951 | lda miss_label,x | |
| ... | |||
| 961 | 957 | bne draw_miss_label | |
| 962 | 958 | draw_miss_label_done: | |
| 963 | 959 | | |
| 964 | - | ; Draw title | |
| 965 | 960 | ldx #0 | |
| 966 | 961 | draw_title: | |
| 967 | 962 | lda title_text,x | |
| ... | |||
| 973 | 968 | bne draw_title | |
| 974 | 969 | draw_title_done: | |
| 975 | 970 | | |
| 976 | - | ; Draw "HEALTH:" label | |
| 977 | 971 | ldx #0 | |
| 978 | 972 | draw_health_label: | |
| 979 | 973 | lda health_label,x | |
| 980 | 974 | beq draw_health_label_done | |
| 981 | 975 | sta SCREEN + (HEALTH_ROW * 40) + 8,x | |
| 982 | - | lda #5 ; Green | |
| 976 | + | lda #5 | |
| 983 | 977 | sta COLRAM + (HEALTH_ROW * 40) + 8,x | |
| 984 | 978 | inx | |
| 985 | 979 | bne draw_health_label | |
| 986 | 980 | draw_health_label_done: | |
| 987 | 981 | | |
| 988 | - | ; Track labels | |
| 989 | - | lda #$1A ; Z | |
| 982 | + | lda #$1A | |
| 990 | 983 | sta SCREEN + (TRACK1_ROW * 40) | |
| 991 | 984 | lda #TRACK1_NOTE_COL | |
| 992 | 985 | sta COLRAM + (TRACK1_ROW * 40) | |
| 993 | 986 | | |
| 994 | - | lda #$18 ; X | |
| 987 | + | lda #$18 | |
| 995 | 988 | sta SCREEN + (TRACK2_ROW * 40) | |
| 996 | 989 | lda #TRACK2_NOTE_COL | |
| 997 | 990 | sta COLRAM + (TRACK2_ROW * 40) | |
| 998 | 991 | | |
| 999 | - | lda #$03 ; C | |
| 992 | + | lda #$03 | |
| 1000 | 993 | sta SCREEN + (TRACK3_ROW * 40) | |
| 1001 | 994 | lda #TRACK3_NOTE_COL | |
| 1002 | 995 | sta COLRAM + (TRACK3_ROW * 40) | |
| ... | |||
| 1157 | 1150 | sta key_pressed | |
| 1158 | 1151 | jsr check_hit | |
| 1159 | 1152 | bcc check_x_key | |
| 1160 | - | jsr play_voice1 | |
| 1153 | + | jsr play_voice1_note | |
| 1161 | 1154 | jsr flash_track1_hit | |
| 1162 | 1155 | jsr award_points | |
| 1163 | 1156 | | |
| ... | |||
| 1172 | 1165 | sta key_pressed | |
| 1173 | 1166 | jsr check_hit | |
| 1174 | 1167 | bcc check_c_key | |
| 1175 | - | jsr play_voice2 | |
| 1168 | + | jsr play_voice2_note | |
| 1176 | 1169 | jsr flash_track2_hit | |
| 1177 | 1170 | jsr award_points | |
| 1178 | 1171 | | |
| ... | |||
| 1187 | 1180 | sta key_pressed | |
| 1188 | 1181 | jsr check_hit | |
| 1189 | 1182 | bcc check_keys_done | |
| 1190 | - | jsr play_voice3 | |
| 1183 | + | jsr play_voice3_note | |
| 1191 | 1184 | jsr flash_track3_hit | |
| 1192 | 1185 | jsr award_points | |
| 1193 | 1186 | | |
| ... | |||
| 1197 | 1190 | rts | |
| 1198 | 1191 | | |
| 1199 | 1192 | ; ---------------------------------------------------------------------------- | |
| 1200 | - | ; Check Hit | |
| 1193 | + | ; Check Hit - Now stores note frequency for playback | |
| 1201 | 1194 | ; ---------------------------------------------------------------------------- | |
| 1202 | 1195 | | |
| 1203 | 1196 | check_hit: | |
| ... | |||
| 1215 | 1208 | bcc check_hit_next | |
| 1216 | 1209 | cmp #HIT_ZONE_MAX+1 | |
| 1217 | 1210 | bcs check_hit_next | |
| 1211 | + | | |
| 1212 | + | ; Store note frequency for sound playback | |
| 1213 | + | lda note_freq,x | |
| 1214 | + | sta hit_note_freq | |
| 1218 | 1215 | | |
| 1219 | 1216 | cmp #HIT_ZONE_CENTRE | |
| 1220 | 1217 | bcc hit_good | |
| ... | |||
| 1268 | 1265 | lda #4 | |
| 1269 | 1266 | sta border_flash | |
| 1270 | 1267 | | |
| 1271 | - | ; Increase health for good hit | |
| 1272 | 1268 | lda #HEALTH_GOOD | |
| 1273 | 1269 | jsr increase_health | |
| 1274 | 1270 | | |
| ... | |||
| 1288 | 1284 | lda #6 | |
| 1289 | 1285 | sta border_flash | |
| 1290 | 1286 | | |
| 1291 | - | ; Increase health for perfect hit | |
| 1292 | 1287 | lda #HEALTH_PERFECT | |
| 1293 | 1288 | jsr increase_health | |
| 1294 | 1289 | | |
| ... | |||
| 1417 | 1412 | work_hi: !byte 0 | |
| 1418 | 1413 | | |
| 1419 | 1414 | ; ---------------------------------------------------------------------------- | |
| 1420 | - | ; Play Voices | |
| 1415 | + | ; Play Voices With Note Frequency | |
| 1421 | 1416 | ; ---------------------------------------------------------------------------- | |
| 1422 | 1417 | | |
| 1423 | - | play_voice1: | |
| 1418 | + | play_voice1_note: | |
| 1419 | + | lda #0 | |
| 1420 | + | sta SID_V1_FREQ_LO | |
| 1421 | + | lda hit_note_freq | |
| 1422 | + | sta SID_V1_FREQ_HI | |
| 1424 | 1423 | lda #VOICE1_WAVE | |
| 1425 | 1424 | ora #$01 | |
| 1426 | 1425 | sta SID_V1_CTRL | |
| 1427 | 1426 | rts | |
| 1428 | 1427 | | |
| 1429 | - | play_voice2: | |
| 1428 | + | play_voice2_note: | |
| 1429 | + | lda #0 | |
| 1430 | + | sta SID_V2_FREQ_LO | |
| 1431 | + | lda hit_note_freq | |
| 1432 | + | sta SID_V2_FREQ_HI | |
| 1430 | 1433 | lda #VOICE2_WAVE | |
| 1431 | 1434 | ora #$01 | |
| 1432 | 1435 | sta SID_V2_CTRL | |
| 1433 | 1436 | rts | |
| 1434 | 1437 | | |
| 1435 | - | play_voice3: | |
| 1438 | + | play_voice3_note: | |
| 1439 | + | lda #0 | |
| 1440 | + | sta SID_V3_FREQ_LO | |
| 1441 | + | lda hit_note_freq | |
| 1442 | + | sta SID_V3_FREQ_HI | |
| 1436 | 1443 | lda #VOICE3_WAVE | |
| 1437 | 1444 | ora #$01 | |
| 1438 | 1445 | sta SID_V3_CTRL | |
| ... | |||
| 1478 | 1485 | sta COLRAM + (TRACK3_ROW * 40) | |
| 1479 | 1486 | rts | |
| 1480 | 1487 | | |
| 1481 | - | ; ---------------------------------------------------------------------------- | |
| 1482 | - | ; Song Data | |
| 1483 | - | ; ---------------------------------------------------------------------------- | |
| 1488 | + | ; ============================================================================ | |
| 1489 | + | ; SONG DATA | |
| 1490 | + | ; ============================================================================ | |
| 1491 | + | ; Format: beat, track, note_freq_hi | |
| 1492 | + | ; | |
| 1493 | + | ; Note frequency table (high byte only for simplicity): | |
| 1494 | + | ; C3=$11 D3=$13 E3=$16 F3=$17 G3=$1A A3=$1D B3=$21 | |
| 1495 | + | ; C4=$23 D4=$27 E4=$2C F4=$2F G4=$35 A4=$3B B4=$42 | |
| 1496 | + | ; C5=$47 D5=$4F E5=$58 F5=$5E G5=$6A A5=$77 B5=$85 | |
| 1497 | + | ; | |
| 1498 | + | ; Track 1 (Z) = high notes, sawtooth | |
| 1499 | + | ; Track 2 (X) = mid notes, pulse | |
| 1500 | + | ; Track 3 (C) = low notes, triangle | |
| 1501 | + | ; ============================================================================ | |
| 1484 | 1502 | | |
| 1485 | 1503 | song_data: | |
| 1486 | - | !byte 0, 1 | |
| 1487 | - | !byte 2, 2 | |
| 1488 | - | !byte 4, 3 | |
| 1489 | - | !byte 6, 1 | |
| 1504 | + | ; Bar 1 - Opening phrase | |
| 1505 | + | !byte 0, 1, $47 ; C5 - high | |
| 1506 | + | !byte 2, 2, $2C ; E4 - mid | |
| 1507 | + | !byte 4, 3, $11 ; C3 - low | |
| 1490 | 1508 | | |
| 1491 | - | !byte 8, 2 | |
| 1492 | - | !byte 10, 3 | |
| 1493 | - | !byte 12, 1 | |
| 1494 | - | !byte 14, 2 | |
| 1509 | + | ; Bar 2 - Descending | |
| 1510 | + | !byte 8, 1, $3B ; A4 | |
| 1511 | + | !byte 10, 2, $27 ; D4 | |
| 1512 | + | !byte 12, 3, $13 ; D3 | |
| 1495 | 1513 | | |
| 1496 | - | !byte 16, 3 | |
| 1497 | - | !byte 18, 1 | |
| 1498 | - | !byte 20, 2 | |
| 1499 | - | !byte 22, 3 | |
| 1514 | + | ; Bar 3 - Building | |
| 1515 | + | !byte 16, 1, $35 ; G4 | |
| 1516 | + | !byte 17, 2, $2C ; E4 | |
| 1517 | + | !byte 18, 1, $3B ; A4 | |
| 1518 | + | !byte 20, 3, $16 ; E3 | |
| 1500 | 1519 | | |
| 1501 | - | !byte 24, 1 | |
| 1502 | - | !byte 25, 2 | |
| 1503 | - | !byte 26, 3 | |
| 1504 | - | !byte 28, 1 | |
| 1505 | - | !byte 29, 2 | |
| 1506 | - | !byte 30, 3 | |
| 1520 | + | ; Bar 4 - Resolution | |
| 1521 | + | !byte 24, 1, $47 ; C5 | |
| 1522 | + | !byte 26, 2, $35 ; G4 | |
| 1523 | + | !byte 28, 3, $11 ; C3 | |
| 1507 | 1524 | | |
| 1508 | - | !byte $FF | |
| 1525 | + | ; Bar 5 - New phrase | |
| 1526 | + | !byte 32, 2, $2F ; F4 | |
| 1527 | + | !byte 34, 1, $4F ; D5 | |
| 1528 | + | !byte 36, 3, $17 ; F3 | |
| 1529 | + | | |
| 1530 | + | ; Bar 6 - Variation | |
| 1531 | + | !byte 40, 1, $58 ; E5 | |
| 1532 | + | !byte 42, 2, $2C ; E4 | |
| 1533 | + | !byte 44, 3, $11 ; C3 | |
| 1534 | + | !byte 46, 2, $27 ; D4 | |
| 1535 | + | | |
| 1536 | + | ; Bar 7 - Climax approach | |
| 1537 | + | !byte 48, 1, $6A ; G5 | |
| 1538 | + | !byte 49, 2, $35 ; G4 | |
| 1539 | + | !byte 50, 1, $58 ; E5 | |
| 1540 | + | !byte 52, 3, $1A ; G3 | |
| 1541 | + | !byte 54, 2, $2F ; F4 | |
| 1542 | + | | |
| 1543 | + | ; Bar 8 - Resolution/Loop point | |
| 1544 | + | !byte 56, 1, $47 ; C5 | |
| 1545 | + | !byte 58, 2, $2C ; E4 | |
| 1546 | + | !byte 60, 3, $11 ; C3 | |
| 1547 | + | !byte 62, 1, $35 ; G4 | |
| 1548 | + | | |
| 1549 | + | !byte $FF ; End of song | |
| 1509 | 1550 | | |
| 1510 | 1551 | ; ---------------------------------------------------------------------------- | |
| 1511 | 1552 | ; Note Arrays | |
| ... | |||
| 1516 | 1557 | | |
| 1517 | 1558 | note_col: | |
| 1518 | 1559 | !fill MAX_NOTES, 0 | |
| 1560 | + | | |
| 1561 | + | note_freq: | |
| 1562 | + | !fill MAX_NOTES, 0 ; Note frequency for each active note | |
| 1519 | 1563 | | |
| 1520 | 1564 | ; ---------------------------------------------------------------------------- | |
| 1521 | 1565 | ; Game Variables |