Skip to content
Game 1 Unit 4 of 64 1 hr learning time

Custom Graphics

Design custom characters for notes, tracks, and hit zones. The game looks professional.

6% of SID Symphony

The game works. Now make it look like a real game.

This unit replaces the placeholder ASCII characters with custom graphics. You’ll learn how the C64’s character system works by designing your own characters - notes that look like arrows, tracks that look like clean lines, hit zones that stand out.

Run It

Assemble and run:

acme -f cbm -o symphony.prg symphony.asm

Unit 4 Screenshot

Compare this to the previous units. The tracks are now clean horizontal lines. The hit zone is a distinctive yellow double bar. Notes are chevrons pointing left - arrows showing the direction of travel. The game looks professional.

Custom graphics — chevron notes, clean tracks, double-bar hit zone

The C64 Character System

The C64 displays characters from an 8×8 pixel grid. There are 256 characters, each defined by 8 bytes (one byte per row). The default characters live in ROM at $D000, but ROM is read-only.

To create custom characters:

  1. Copy the ROM characters to RAM
  2. Modify the RAM copy
  3. Point the VIC-II chip to the RAM location

Copying the Character ROM

The character ROM shares address space with I/O chips. To read it, we temporarily remap memory:

; Copy Character ROM to RAM
; The C64's character ROM is read-only. To modify characters, we copy
; the ROM to RAM and point the VIC-II to our RAM copy.

CHARSET     = $3000             ; Our custom charset location

copy_charset:
            ; Disable interrupts - ROM switching must be atomic
            sei

            ; The C64's processor port at $01 controls memory mapping
            ; Bit 2 (CHAREN) = 0 makes character ROM visible at $D000
            lda $01
            pha                 ; Save current state
            and #$FB            ; Clear bit 2
            sta $01             ; Character ROM now at $D000-$DFFF

            ; Copy all 256 characters (256 * 8 = 2048 bytes)
            ldx #0
copy_loop:
            lda $D000,x         ; Copy 256 bytes at a time
            sta CHARSET,x       ; To our RAM location
            lda $D100,x
            sta CHARSET+$100,x
            ; ... (continues for all 8 pages)
            inx
            bne copy_loop

            ; Restore processor port
            pla
            sta $01

            ; Re-enable interrupts
            cli
            rts

The processor port at $01 controls what appears at certain addresses. When we clear bit 2, the character ROM becomes visible at $D000-$DFFF. We copy all 2KB to our RAM location at $3000.

Designing Characters

Each character is 8 bytes. Each bit in a byte is one pixel:

; Character Design - 8x8 Pixel Grids
; Each character is 8 bytes, one byte per row.
; Each bit is one pixel (1=foreground, 0=background).

; Character 128: Note (chevron pointing left)
; Design:
;   ......XX  = $06
;   ....XXXX  = $1E
;   ..XXXXXX  = $7E
;   XXXXXXXX  = $FE
;   XXXXXXXX  = $FE
;   ..XXXXXX  = $7E
;   ....XXXX  = $1E
;   ......XX  = $06

CHAR_NOTE   = 128

            lda #%00000110      ; Row 0: tip of arrow
            sta CHARSET + (CHAR_NOTE * 8) + 0
            lda #%00011110      ; Row 1: wider
            sta CHARSET + (CHAR_NOTE * 8) + 1
            lda #%01111110      ; Row 2: wider still
            sta CHARSET + (CHAR_NOTE * 8) + 2
            lda #%11111110      ; Row 3: full width (minus 1 pixel)
            sta CHARSET + (CHAR_NOTE * 8) + 3
            lda #%11111110      ; Row 4: same
            sta CHARSET + (CHAR_NOTE * 8) + 4
            lda #%01111110      ; Row 5: narrowing
            sta CHARSET + (CHAR_NOTE * 8) + 5
            lda #%00011110      ; Row 6: narrower
            sta CHARSET + (CHAR_NOTE * 8) + 6
            lda #%00000110      ; Row 7: tip
            sta CHARSET + (CHAR_NOTE * 8) + 7

The note character is a chevron pointing left - an arrow showing “this note is moving toward the hit zone.” The design uses binary literals so you can see the pixel pattern directly in the code.

Try This: Design Your Own Note

; Filled circle note:
;   ..XXXX..  = %00111100 = $3C
;   .XXXXXX.  = %01111110 = $7E
;   XXXXXXXX  = %11111111 = $FF
;   XXXXXXXX  = %11111111 = $FF
;   XXXXXXXX  = %11111111 = $FF
;   XXXXXXXX  = %11111111 = $FF
;   .XXXXXX.  = %01111110 = $7E
;   ..XXXX..  = %00111100 = $3C

            lda #$3C
            sta CHARSET + (CHAR_NOTE * 8) + 0
            lda #$7E
            sta CHARSET + (CHAR_NOTE * 8) + 1
            ; ... etc

Try This: Diamond Note

; Diamond shape:
;   ...XX...  = %00011000 = $18
;   ..XXXX..  = %00111100 = $3C
;   .XXXXXX.  = %01111110 = $7E
;   XXXXXXXX  = %11111111 = $FF
;   XXXXXXXX  = %11111111 = $FF
;   .XXXXXX.  = %01111110 = $7E
;   ..XXXX..  = %00111100 = $3C
;   ...XX...  = %00011000 = $18

Pointing VIC-II to Custom Characters

The VIC-II chip needs to know where to find character data:

; VIC-II Character Memory Pointer ($D018)
;
; The VIC-II chip uses $D018 to find both screen memory and character data.
;
; Bits 4-7: Screen memory location (within VIC bank)
; Bits 1-3: Character memory location (within VIC bank)
; Bit 0:    Not used for memory selection
;
; Character memory calculation:
;   Location = (bits 1-3) * $0800
;
;   %000 = $0000  (not usable - zero page)
;   %001 = $0800
;   %010 = $1000  (default: character ROM mapped here)
;   %011 = $1800
;   %100 = $2000
;   %101 = $2800
;   %110 = $3000  <- We use this
;   %111 = $3800
;
; For charset at $3000 with screen at $0400:
;   Screen: $0400 / $0400 = 1, bits 4-7 = %0001 = $10
;   Charset: $3000 / $0800 = 6, bits 1-3 = %110 = $0C
;   Combined: $10 | $0C = $1C

CHARPTR     = $D018

            lda #$1C            ; Screen at $0400, charset at $3000
            sta CHARPTR         ; VIC now uses our custom characters

The $D018 register controls both screen memory and character memory locations. We set it to $1C to use our charset at $3000 while keeping the screen at the default $0400.

Character Codes

We use character codes 128-130 for our custom graphics:

CodeCharacterPurpose
128ChevronNote graphic
129Horizontal lineTrack line
130Vertical barsHit zone marker

These codes (128+) are in the “reverse video” range of the original character set. We overwrite them with our custom designs. The standard letters and numbers (codes 0-127) remain intact for text display.

The Complete Code

; ============================================================================
; SID SYMPHONY - Unit 4: Custom Graphics
; ============================================================================
; Design custom characters for a polished look. Notes are arrows, tracks are
; clean lines, hit zones have distinctive markers. The game looks professional.
;
; Controls: Z = Track 1 (high), X = Track 2 (mid), C = Track 3 (low)
; ============================================================================

; ============================================================================
; CUSTOMISATION SECTION
; ============================================================================

; SID Voice Settings
VOICE1_WAVE = $21               ; Sawtooth
VOICE2_WAVE = $41               ; Pulse
VOICE3_WAVE = $11               ; Triangle

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

; 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

FLASH1_COL  = 2                 ; Red
FLASH2_COL  = 5                 ; Green
FLASH3_COL  = 6                 ; Blue

; ============================================================================
; MEMORY MAP
; ============================================================================

SCREEN      = $0400             ; Screen memory
COLRAM      = $D800             ; Colour RAM
BORDER      = $D020             ; Border colour
BGCOL       = $D021             ; Background colour
CHARPTR     = $D018             ; Character memory pointer

CHARSET     = $3000             ; Custom character set location

; 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

; Hit zone
HIT_ZONE_COLUMN = 3

; Custom character codes (we'll define these)
CHAR_NOTE   = 128               ; Filled arrow/circle for notes
CHAR_TRACK  = 129               ; Thin horizontal line for tracks
CHAR_HITZONE = 130              ; Vertical bar for hit zone
CHAR_SPACE  = 32                ; Space

; 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

; ----------------------------------------------------------------------------
; 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    ; Copy ROM charset and add custom chars
            jsr init_screen
            jsr init_sid
            jsr init_notes

            lda #<song_data
            sta song_pos
            lda #>song_data
            sta song_pos_hi

            lda #0
            sta frame_count
            sta beat_count

main_loop:
            lda #$FF
wait_raster:
            cmp $D012
            bne wait_raster

            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 check_keys

            jmp main_loop

; ----------------------------------------------------------------------------
; Copy Character Set from ROM to RAM
; ----------------------------------------------------------------------------
; Copies the standard character ROM to $3000, then adds custom characters

copy_charset:
            ; Disable interrupts during ROM access
            sei

            ; Make character ROM visible at $D000
            lda $01
            pha                 ; Save processor port
            and #$FB            ; Clear bit 2 (CHAREN)
            sta $01             ; Character ROM now at $D000

            ; Copy 2KB (256 characters * 8 bytes)
            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

            ; Restore processor port
            pla
            sta $01

            ; Re-enable interrupts
            cli

            ; Define our custom characters
            jsr define_custom_chars

            ; Point VIC-II to our charset at $3000
            ; Screen at $0400 = %0001 in upper nibble
            ; Charset at $3000 = %110 in bits 1-3 = $0C
            lda #$1C
            sta CHARPTR

            rts

; ----------------------------------------------------------------------------
; Define Custom Characters
; ----------------------------------------------------------------------------
; Creates note, track, and hit zone graphics

define_custom_chars:
            ; Character 128: Note (filled chevron pointing left)
            lda #%00000110      ; Row 0
            sta CHARSET + (CHAR_NOTE * 8) + 0
            lda #%00011110      ; Row 1
            sta CHARSET + (CHAR_NOTE * 8) + 1
            lda #%01111110      ; Row 2
            sta CHARSET + (CHAR_NOTE * 8) + 2
            lda #%11111110      ; Row 3
            sta CHARSET + (CHAR_NOTE * 8) + 3
            lda #%11111110      ; Row 4
            sta CHARSET + (CHAR_NOTE * 8) + 4
            lda #%01111110      ; Row 5
            sta CHARSET + (CHAR_NOTE * 8) + 5
            lda #%00011110      ; Row 6
            sta CHARSET + (CHAR_NOTE * 8) + 6
            lda #%00000110      ; Row 7
            sta CHARSET + (CHAR_NOTE * 8) + 7

            ; Character 129: Track line (centered horizontal line)
            lda #%00000000      ; Row 0
            sta CHARSET + (CHAR_TRACK * 8) + 0
            lda #%00000000      ; Row 1
            sta CHARSET + (CHAR_TRACK * 8) + 1
            lda #%00000000      ; Row 2
            sta CHARSET + (CHAR_TRACK * 8) + 2
            lda #%11111111      ; Row 3 - the line
            sta CHARSET + (CHAR_TRACK * 8) + 3
            lda #%11111111      ; Row 4 - the line
            sta CHARSET + (CHAR_TRACK * 8) + 4
            lda #%00000000      ; Row 5
            sta CHARSET + (CHAR_TRACK * 8) + 5
            lda #%00000000      ; Row 6
            sta CHARSET + (CHAR_TRACK * 8) + 6
            lda #%00000000      ; Row 7
            sta CHARSET + (CHAR_TRACK * 8) + 7

            ; Character 130: Hit zone (vertical bars)
            lda #%01100110      ; Row 0
            sta CHARSET + (CHAR_HITZONE * 8) + 0
            lda #%01100110      ; Row 1
            sta CHARSET + (CHAR_HITZONE * 8) + 1
            lda #%01100110      ; Row 2
            sta CHARSET + (CHAR_HITZONE * 8) + 2
            lda #%01100110      ; Row 3
            sta CHARSET + (CHAR_HITZONE * 8) + 3
            lda #%01100110      ; Row 4
            sta CHARSET + (CHAR_HITZONE * 8) + 4
            lda #%01100110      ; Row 5
            sta CHARSET + (CHAR_HITZONE * 8) + 5
            lda #%01100110      ; Row 6
            sta CHARSET + (CHAR_HITZONE * 8) + 6
            lda #%01100110      ; Row 7
            sta CHARSET + (CHAR_HITZONE * 8) + 7

            rts

; ----------------------------------------------------------------------------
; 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
; ----------------------------------------------------------------------------

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
            jsr spawn_note
            dey

spawn_advance:
            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
; ----------------------------------------------------------------------------

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

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 - Uses custom note character
; ----------------------------------------------------------------------------

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      ; Custom note character
            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 - Restores track line character
; ----------------------------------------------------------------------------

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     ; Custom track character
            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

            ; Clear screen with spaces
            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

            ; Set all colours to track line colour
            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 - Uses custom track character
; ----------------------------------------------------------------------------

draw_tracks:
            ; Track 1
            lda #CHAR_TRACK
            ldx #0
draw_t1:
            sta SCREEN + (TRACK1_ROW * 40),x
            inx
            cpx #38
            bne draw_t1

            ; Track 2
            lda #CHAR_TRACK
            ldx #0
draw_t2:
            sta SCREEN + (TRACK2_ROW * 40),x
            inx
            cpx #38
            bne draw_t2

            ; Track 3
            lda #CHAR_TRACK
            ldx #0
draw_t3:
            sta SCREEN + (TRACK3_ROW * 40),x
            inx
            cpx #38
            bne draw_t3

            rts

; ----------------------------------------------------------------------------
; Draw Hit Zones - Uses custom hit zone character
; ----------------------------------------------------------------------------

draw_hit_zones:
            lda #CHAR_HITZONE   ; Custom hit zone character

            ; Draw hit zone bars spanning all three tracks
            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

            ; Colour the hit zones
            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_title:
            lda title_text,x
            beq draw_title_done
            sta SCREEN + 13,x
            lda #1              ; White
            sta COLRAM + 13,x
            inx
            bne draw_title
draw_title_done:

            ; Track labels - use standard characters for letters
            lda #$1A            ; Z (screen code)
            sta SCREEN + (TRACK1_ROW * 40)
            lda #TRACK1_NOTE_COL
            sta COLRAM + (TRACK1_ROW * 40)

            lda #$18            ; X
            sta SCREEN + (TRACK2_ROW * 40)
            lda #TRACK2_NOTE_COL
            sta COLRAM + (TRACK2_ROW * 40)

            lda #$03            ; C
            sta SCREEN + (TRACK3_ROW * 40)
            lda #TRACK3_NOTE_COL
            sta COLRAM + (TRACK3_ROW * 40)

            ldx #0
draw_instr:
            lda instr_text,x
            beq draw_instr_done
            sta SCREEN + (23 * 40) + 6,x
            lda #TRACK_LINE_COL
            sta COLRAM + (23 * 40) + 6,x
            inx
            bne draw_instr
draw_instr_done:

            rts

title_text:
            !scr "sid symphony"
            !byte 0

instr_text:
            !scr "custom graphics active"
            !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

            ; Restore key labels
            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)

            ; Restore hit zone colours
            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

; ----------------------------------------------------------------------------
; 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 #VOICE1_WAVE
            ora #$01
            sta SID_V1_CTRL
            rts

play_voice2:
            lda #VOICE2_WAVE
            ora #$01
            sta SID_V2_CTRL
            rts

play_voice3:
            lda #VOICE3_WAVE
            ora #$01
            sta SID_V3_CTRL
            rts

; ----------------------------------------------------------------------------
; Flash Tracks
; ----------------------------------------------------------------------------

flash_track1:
            ldx #0
            lda #FLASH1_COL
flash_t1_loop:
            sta COLRAM + (TRACK1_ROW * 40),x
            inx
            cpx #38
            bne flash_t1_loop
            lda #1
            sta COLRAM + (TRACK1_ROW * 40)
            rts

flash_track2:
            ldx #0
            lda #FLASH2_COL
flash_t2_loop:
            sta COLRAM + (TRACK2_ROW * 40),x
            inx
            cpx #38
            bne flash_t2_loop
            lda #1
            sta COLRAM + (TRACK2_ROW * 40)
            rts

flash_track3:
            ldx #0
            lda #FLASH3_COL
flash_t3_loop:
            sta COLRAM + (TRACK3_ROW * 40),x
            inx
            cpx #38
            bne flash_t3_loop
            lda #1
            sta COLRAM + (TRACK3_ROW * 40)
            rts

; ----------------------------------------------------------------------------
; Song Data
; ----------------------------------------------------------------------------

song_data:
            !byte 0, 1
            !byte 2, 2
            !byte 4, 3
            !byte 6, 1

            !byte 8, 2
            !byte 10, 3
            !byte 12, 1
            !byte 14, 2

            !byte 16, 3
            !byte 18, 1
            !byte 20, 2
            !byte 22, 3

            !byte 24, 1
            !byte 25, 2
            !byte 26, 3
            !byte 28, 1
            !byte 29, 2
            !byte 30, 3

            !byte $FF

; ----------------------------------------------------------------------------
; Note Arrays
; ----------------------------------------------------------------------------

note_track:
            !fill MAX_NOTES, 0

note_col:
            !fill MAX_NOTES, 0

Try This: Thicker Track Lines

; Three-pixel thick track:
            lda #%00000000      ; Row 0
            sta CHARSET + (CHAR_TRACK * 8) + 0
            lda #%00000000      ; Row 1
            sta CHARSET + (CHAR_TRACK * 8) + 1
            lda #%11111111      ; Row 2 - top of line
            sta CHARSET + (CHAR_TRACK * 8) + 2
            lda #%11111111      ; Row 3 - middle
            sta CHARSET + (CHAR_TRACK * 8) + 3
            lda #%11111111      ; Row 4 - bottom
            sta CHARSET + (CHAR_TRACK * 8) + 4
            lda #%00000000      ; Row 5
            sta CHARSET + (CHAR_TRACK * 8) + 5

Try This: Dotted Track Lines

; Dotted track (alternating pixels):
            lda #%00000000
            sta CHARSET + (CHAR_TRACK * 8) + 0
            sta CHARSET + (CHAR_TRACK * 8) + 1
            sta CHARSET + (CHAR_TRACK * 8) + 2
            lda #%10101010      ; Alternating pattern
            sta CHARSET + (CHAR_TRACK * 8) + 3
            lda #%01010101      ; Offset pattern
            sta CHARSET + (CHAR_TRACK * 8) + 4
            lda #%00000000
            sta CHARSET + (CHAR_TRACK * 8) + 5

What You’ve Learnt

  • Character ROM - Default characters at $D000, read-only
  • Memory remapping - Processor port $01 controls what’s visible at certain addresses
  • Character definitions - 8 bytes per character, 1 bit per pixel
  • VIC-II pointer - $D018 tells the VIC where to find screen and character memory
  • Binary pixel design - Use %xxxxxxxx notation to visualise character shapes

What’s Next

In Unit 5, we’ll detect when keypresses actually match notes in the hit zone. Right now, pressing keys plays sounds regardless of timing - we’ll add real hit detection.

What Changed

Unit 3 → Unit 4
+198-83
11 ; ============================================================================
2-; SID SYMPHONY - Unit 3: Making It Your Own
2+; SID SYMPHONY - Unit 4: Custom Graphics
33 ; ============================================================================
4-; Customise the game to make it yours. Change the SID voices, pick your own
5-; colours, experiment with waveforms and ADSR. Learn the internals by
6-; modifying them and hearing/seeing the results.
4+; Design custom characters for a polished look. Notes are arrows, tracks are
5+; clean lines, hit zones have distinctive markers. The game looks professional.
76 ;
87 ; Controls: Z = Track 1 (high), X = Track 2 (mid), C = Track 3 (low)
98 ; ============================================================================
109
1110 ; ============================================================================
12-; CUSTOMISATION SECTION - Change these values and reassemble!
11+; CUSTOMISATION SECTION
1312 ; ============================================================================
14-
15-; --- SID Voice Settings ---
16-; Waveforms: $11=triangle, $21=sawtooth, $41=pulse, $81=noise
17-VOICE1_WAVE = $21 ; Sawtooth - bright, buzzy
18-VOICE2_WAVE = $41 ; Pulse - hollow, reedy
19-VOICE3_WAVE = $11 ; Triangle - soft, mellow
20-
21-; Frequencies (higher = higher pitch)
22-; Common notes: $07=C3, $0E=C4, $1C=C5, $38=C6
23-VOICE1_FREQ = $1C ; High pitch (C5)
24-VOICE2_FREQ = $0E ; Mid pitch (C4)
25-VOICE3_FREQ = $07 ; Low pitch (C3)
2613
27-; ADSR - Attack/Decay/Sustain/Release
28-; Attack: 0-15 (0=2ms, 15=8s) Decay: 0-15 (0=6ms, 15=24s)
29-; Sustain: 0-15 (volume level) Release: 0-15 (0=6ms, 15=24s)
30-VOICE_AD = $09 ; Attack=0 (instant), Decay=9 (medium)
31-VOICE_SR = $00 ; Sustain=0 (none), Release=0 (instant)
14+; SID Voice Settings
15+VOICE1_WAVE = $21 ; Sawtooth
16+VOICE2_WAVE = $41 ; Pulse
17+VOICE3_WAVE = $11 ; Triangle
3218
33-; Pulse width (only affects pulse wave, $41)
34-PULSE_WIDTH = $08 ; $08 = 50% duty cycle (square wave)
19+VOICE1_FREQ = $1C ; High pitch
20+VOICE2_FREQ = $0E ; Mid pitch
21+VOICE3_FREQ = $07 ; Low pitch
3522
36-; --- Visual Settings ---
37-; Colours: 0=black, 1=white, 2=red, 3=cyan, 4=purple, 5=green
38-; 6=blue, 7=yellow, 8=orange, 9=brown, 10=light red
39-; 11=dark grey, 12=grey, 13=light green, 14=light blue, 15=light grey
23+VOICE_AD = $09 ; Attack=0, Decay=9
24+VOICE_SR = $00 ; Sustain=0, Release=0
25+PULSE_WIDTH = $08 ; 50% duty cycle
4026
41-BORDER_COL = 6 ; Blue border
27+; Visual Settings
28+BORDER_COL = 0 ; Black border
4229 BG_COL = 0 ; Black background
4330
44-TRACK1_NOTE_COL = 10 ; Light red notes on track 1
45-TRACK2_NOTE_COL = 13 ; Light green notes on track 2
46-TRACK3_NOTE_COL = 14 ; Light blue notes on track 3
31+TRACK1_NOTE_COL = 10 ; Light red
32+TRACK2_NOTE_COL = 13 ; Light green
33+TRACK3_NOTE_COL = 14 ; Light blue
4734
48-TRACK_LINE_COL = 11 ; Dark grey track lines
49-HIT_ZONE_COL = 7 ; Yellow hit zone
35+TRACK_LINE_COL = 11 ; Dark grey
36+HIT_ZONE_COL = 7 ; Yellow
5037
51-; Track flash colours (when key pressed)
52-FLASH1_COL = 2 ; Red flash for track 1
53-FLASH2_COL = 5 ; Green flash for track 2
54-FLASH3_COL = 6 ; Blue flash for track 3
38+FLASH1_COL = 2 ; Red
39+FLASH2_COL = 5 ; Green
40+FLASH3_COL = 6 ; Blue
5541
5642 ; ============================================================================
57-; END OF CUSTOMISATION - Code below uses the settings above
43+; MEMORY MAP
5844 ; ============================================================================
5945
60-; ----------------------------------------------------------------------------
61-; Memory Addresses
62-; ----------------------------------------------------------------------------
46+SCREEN = $0400 ; Screen memory
47+COLRAM = $D800 ; Colour RAM
48+BORDER = $D020 ; Border colour
49+BGCOL = $D021 ; Background colour
50+CHARPTR = $D018 ; Character memory pointer
6351
64-SCREEN = $0400
65-COLRAM = $D800
66-BORDER = $D020
67-BGCOL = $D021
52+CHARSET = $3000 ; Custom character set location
6853
6954 ; SID registers
7055 SID = $D400
7156 SID_V1_FREQ_LO = $D400
7257 SID_V1_FREQ_HI = $D401
73-SID_V1_PWLO = $D402
7458 SID_V1_PWHI = $D403
7559 SID_V1_CTRL = $D404
7660 SID_V1_AD = $D405
...
7862
7963 SID_V2_FREQ_LO = $D407
8064 SID_V2_FREQ_HI = $D408
81-SID_V2_PWLO = $D409
8265 SID_V2_PWHI = $D40A
8366 SID_V2_CTRL = $D40B
8467 SID_V2_AD = $D40C
...
8669
8770 SID_V3_FREQ_LO = $D40E
8871 SID_V3_FREQ_HI = $D40F
89-SID_V3_PWLO = $D410
9072 SID_V3_PWHI = $D411
9173 SID_V3_CTRL = $D412
9274 SID_V3_AD = $D413
...
10385 TRACK2_ROW = 12
10486 TRACK3_ROW = 16
10587
106-; Hit zone column
88+; Hit zone
10789 HIT_ZONE_COLUMN = 3
90+
91+; Custom character codes (we'll define these)
92+CHAR_NOTE = 128 ; Filled arrow/circle for notes
93+CHAR_TRACK = 129 ; Thin horizontal line for tracks
94+CHAR_HITZONE = 130 ; Vertical bar for hit zone
95+CHAR_SPACE = 32 ; Space
10896
10997 ; Note settings
110-NOTE_CHAR = $57 ; Filled dot character
111-TRACK_CHAR = $2D ; Minus character
11298 MAX_NOTES = 8
11399 NOTE_SPAWN_COL = 37
114100
...
146132 * = $0810
147133
148134 start:
135+ jsr copy_charset ; Copy ROM charset and add custom chars
149136 jsr init_screen
150137 jsr init_sid
151138 jsr init_notes
...
181168 jsr check_keys
182169
183170 jmp main_loop
171+
172+; ----------------------------------------------------------------------------
173+; Copy Character Set from ROM to RAM
174+; ----------------------------------------------------------------------------
175+; Copies the standard character ROM to $3000, then adds custom characters
176+
177+copy_charset:
178+ ; Disable interrupts during ROM access
179+ sei
180+
181+ ; Make character ROM visible at $D000
182+ lda $01
183+ pha ; Save processor port
184+ and #$FB ; Clear bit 2 (CHAREN)
185+ sta $01 ; Character ROM now at $D000
186+
187+ ; Copy 2KB (256 characters * 8 bytes)
188+ ldx #0
189+copy_loop:
190+ lda $D000,x
191+ sta CHARSET,x
192+ lda $D100,x
193+ sta CHARSET+$100,x
194+ lda $D200,x
195+ sta CHARSET+$200,x
196+ lda $D300,x
197+ sta CHARSET+$300,x
198+ lda $D400,x
199+ sta CHARSET+$400,x
200+ lda $D500,x
201+ sta CHARSET+$500,x
202+ lda $D600,x
203+ sta CHARSET+$600,x
204+ lda $D700,x
205+ sta CHARSET+$700,x
206+ inx
207+ bne copy_loop
208+
209+ ; Restore processor port
210+ pla
211+ sta $01
212+
213+ ; Re-enable interrupts
214+ cli
215+
216+ ; Define our custom characters
217+ jsr define_custom_chars
218+
219+ ; Point VIC-II to our charset at $3000
220+ ; Screen at $0400 = %0001 in upper nibble
221+ ; Charset at $3000 = %110 in bits 1-3 = $0C
222+ lda #$1C
223+ sta CHARPTR
224+
225+ rts
226+
227+; ----------------------------------------------------------------------------
228+; Define Custom Characters
229+; ----------------------------------------------------------------------------
230+; Creates note, track, and hit zone graphics
231+
232+define_custom_chars:
233+ ; Character 128: Note (filled chevron pointing left)
234+ lda #%00000110 ; Row 0
235+ sta CHARSET + (CHAR_NOTE * 8) + 0
236+ lda #%00011110 ; Row 1
237+ sta CHARSET + (CHAR_NOTE * 8) + 1
238+ lda #%01111110 ; Row 2
239+ sta CHARSET + (CHAR_NOTE * 8) + 2
240+ lda #%11111110 ; Row 3
241+ sta CHARSET + (CHAR_NOTE * 8) + 3
242+ lda #%11111110 ; Row 4
243+ sta CHARSET + (CHAR_NOTE * 8) + 4
244+ lda #%01111110 ; Row 5
245+ sta CHARSET + (CHAR_NOTE * 8) + 5
246+ lda #%00011110 ; Row 6
247+ sta CHARSET + (CHAR_NOTE * 8) + 6
248+ lda #%00000110 ; Row 7
249+ sta CHARSET + (CHAR_NOTE * 8) + 7
250+
251+ ; Character 129: Track line (centered horizontal line)
252+ lda #%00000000 ; Row 0
253+ sta CHARSET + (CHAR_TRACK * 8) + 0
254+ lda #%00000000 ; Row 1
255+ sta CHARSET + (CHAR_TRACK * 8) + 1
256+ lda #%00000000 ; Row 2
257+ sta CHARSET + (CHAR_TRACK * 8) + 2
258+ lda #%11111111 ; Row 3 - the line
259+ sta CHARSET + (CHAR_TRACK * 8) + 3
260+ lda #%11111111 ; Row 4 - the line
261+ sta CHARSET + (CHAR_TRACK * 8) + 4
262+ lda #%00000000 ; Row 5
263+ sta CHARSET + (CHAR_TRACK * 8) + 5
264+ lda #%00000000 ; Row 6
265+ sta CHARSET + (CHAR_TRACK * 8) + 6
266+ lda #%00000000 ; Row 7
267+ sta CHARSET + (CHAR_TRACK * 8) + 7
268+
269+ ; Character 130: Hit zone (vertical bars)
270+ lda #%01100110 ; Row 0
271+ sta CHARSET + (CHAR_HITZONE * 8) + 0
272+ lda #%01100110 ; Row 1
273+ sta CHARSET + (CHAR_HITZONE * 8) + 1
274+ lda #%01100110 ; Row 2
275+ sta CHARSET + (CHAR_HITZONE * 8) + 2
276+ lda #%01100110 ; Row 3
277+ sta CHARSET + (CHAR_HITZONE * 8) + 3
278+ lda #%01100110 ; Row 4
279+ sta CHARSET + (CHAR_HITZONE * 8) + 4
280+ lda #%01100110 ; Row 5
281+ sta CHARSET + (CHAR_HITZONE * 8) + 5
282+ lda #%01100110 ; Row 6
283+ sta CHARSET + (CHAR_HITZONE * 8) + 6
284+ lda #%01100110 ; Row 7
285+ sta CHARSET + (CHAR_HITZONE * 8) + 7
286+
287+ rts
184288
185289 ; ----------------------------------------------------------------------------
186290 ; Initialize Notes
...
298402 rts
299403
300404 ; ----------------------------------------------------------------------------
301-; Draw Note
405+; Draw Note - Uses custom note character
302406 ; ----------------------------------------------------------------------------
303407
304408 draw_note:
...
321425 sta ZP_PTR_HI
322426
323427 ldy #0
324- lda #NOTE_CHAR
428+ lda #CHAR_NOTE ; Custom note character
325429 sta (ZP_PTR),y
326430
327431 lda note_col,x
...
345449 sta ZP_PTR_HI
346450
347451 ldy #0
348- lda #NOTE_CHAR
452+ lda #CHAR_NOTE
349453 sta (ZP_PTR),y
350454
351455 lda note_col,x
...
369473 sta ZP_PTR_HI
370474
371475 ldy #0
372- lda #NOTE_CHAR
476+ lda #CHAR_NOTE
373477 sta (ZP_PTR),y
374478
375479 lda note_col,x
...
384488 rts
385489
386490 ; ----------------------------------------------------------------------------
387-; Erase Note
491+; Erase Note - Restores track line character
388492 ; ----------------------------------------------------------------------------
389493
390494 erase_note:
...
407511 sta ZP_PTR_HI
408512
409513 ldy #0
410- lda #TRACK_CHAR
514+ lda #CHAR_TRACK ; Custom track character
411515 sta (ZP_PTR),y
412516
413517 lda note_col,x
...
431535 sta ZP_PTR_HI
432536
433537 ldy #0
434- lda #TRACK_CHAR
538+ lda #CHAR_TRACK
435539 sta (ZP_PTR),y
436540
437541 lda note_col,x
...
455559 sta ZP_PTR_HI
456560
457561 ldy #0
458- lda #TRACK_CHAR
562+ lda #CHAR_TRACK
459563 sta (ZP_PTR),y
460564
461565 lda note_col,x
...
479583 lda #BG_COL
480584 sta BGCOL
481585
586+ ; Clear screen with spaces
482587 ldx #0
483- lda #$20
588+ lda #CHAR_SPACE
484589 clr_screen:
485590 sta SCREEN,x
486591 sta SCREEN+$100,x
...
489594 inx
490595 bne clr_screen
491596
597+ ; Set all colours to track line colour
492598 ldx #0
493599 lda #TRACK_LINE_COL
494600 clr_colour:
...
506612 rts
507613
508614 ; ----------------------------------------------------------------------------
509-; Draw Tracks
615+; Draw Tracks - Uses custom track character
510616 ; ----------------------------------------------------------------------------
511617
512618 draw_tracks:
619+ ; Track 1
620+ lda #CHAR_TRACK
513621 ldx #0
514- lda #TRACK_CHAR
515622 draw_t1:
516623 sta SCREEN + (TRACK1_ROW * 40),x
517624 inx
518625 cpx #38
519626 bne draw_t1
520627
628+ ; Track 2
629+ lda #CHAR_TRACK
521630 ldx #0
522631 draw_t2:
523632 sta SCREEN + (TRACK2_ROW * 40),x
...
525634 cpx #38
526635 bne draw_t2
527636
637+ ; Track 3
638+ lda #CHAR_TRACK
528639 ldx #0
529640 draw_t3:
530641 sta SCREEN + (TRACK3_ROW * 40),x
...
535646 rts
536647
537648 ; ----------------------------------------------------------------------------
538-; Draw Hit Zones
649+; Draw Hit Zones - Uses custom hit zone character
539650 ; ----------------------------------------------------------------------------
540651
541652 draw_hit_zones:
542- lda #$7D
653+ lda #CHAR_HITZONE ; Custom hit zone character
543654
544- sta SCREEN + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
655+ ; Draw hit zone bars spanning all three tracks
656+ sta SCREEN + ((TRACK1_ROW-2) * 40) + HIT_ZONE_COLUMN
545657 sta SCREEN + ((TRACK1_ROW-1) * 40) + HIT_ZONE_COLUMN
658+ sta SCREEN + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
546659 sta SCREEN + ((TRACK1_ROW+1) * 40) + HIT_ZONE_COLUMN
547660
548- sta SCREEN + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
549661 sta SCREEN + ((TRACK2_ROW-1) * 40) + HIT_ZONE_COLUMN
662+ sta SCREEN + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
550663 sta SCREEN + ((TRACK2_ROW+1) * 40) + HIT_ZONE_COLUMN
551664
552- sta SCREEN + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
553665 sta SCREEN + ((TRACK3_ROW-1) * 40) + HIT_ZONE_COLUMN
666+ sta SCREEN + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
554667 sta SCREEN + ((TRACK3_ROW+1) * 40) + HIT_ZONE_COLUMN
668+ sta SCREEN + ((TRACK3_ROW+2) * 40) + HIT_ZONE_COLUMN
555669
670+ ; Colour the hit zones
556671 lda #HIT_ZONE_COL
557- sta COLRAM + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
672+ sta COLRAM + ((TRACK1_ROW-2) * 40) + HIT_ZONE_COLUMN
558673 sta COLRAM + ((TRACK1_ROW-1) * 40) + HIT_ZONE_COLUMN
674+ sta COLRAM + (TRACK1_ROW * 40) + HIT_ZONE_COLUMN
559675 sta COLRAM + ((TRACK1_ROW+1) * 40) + HIT_ZONE_COLUMN
560676
561- sta COLRAM + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
562677 sta COLRAM + ((TRACK2_ROW-1) * 40) + HIT_ZONE_COLUMN
678+ sta COLRAM + (TRACK2_ROW * 40) + HIT_ZONE_COLUMN
563679 sta COLRAM + ((TRACK2_ROW+1) * 40) + HIT_ZONE_COLUMN
564680
565- sta COLRAM + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
566681 sta COLRAM + ((TRACK3_ROW-1) * 40) + HIT_ZONE_COLUMN
682+ sta COLRAM + (TRACK3_ROW * 40) + HIT_ZONE_COLUMN
567683 sta COLRAM + ((TRACK3_ROW+1) * 40) + HIT_ZONE_COLUMN
684+ sta COLRAM + ((TRACK3_ROW+2) * 40) + HIT_ZONE_COLUMN
568685
569686 rts
570687
...
584701 bne draw_title
585702 draw_title_done:
586703
587- lda #$1A ; Z
704+ ; Track labels - use standard characters for letters
705+ lda #$1A ; Z (screen code)
588706 sta SCREEN + (TRACK1_ROW * 40)
589707 lda #TRACK1_NOTE_COL
590708 sta COLRAM + (TRACK1_ROW * 40)
...
603721 draw_instr:
604722 lda instr_text,x
605723 beq draw_instr_done
606- sta SCREEN + (23 * 40) + 8,x
724+ sta SCREEN + (23 * 40) + 6,x
607725 lda #TRACK_LINE_COL
608- sta COLRAM + (23 * 40) + 8,x
726+ sta COLRAM + (23 * 40) + 6,x
609727 inx
610728 bne draw_instr
611729 draw_instr_done:
...
617735 !byte 0
618736
619737 instr_text:
620- !scr "customise me!"
738+ !scr "custom graphics active"
621739 !byte 0
622740
623741 ; ----------------------------------------------------------------------------
624-; Initialize SID - Uses customisation constants
742+; Initialize SID
625743 ; ----------------------------------------------------------------------------
626744
627745 init_sid:
...
635753 lda #$0F
636754 sta SID_VOLUME
637755
638- ; Voice 1 - uses VOICE1_* constants
639756 lda #$00
640757 sta SID_V1_FREQ_LO
641758 lda #VOICE1_FREQ
...
647764 lda #VOICE_SR
648765 sta SID_V1_SR
649766
650- ; Voice 2 - uses VOICE2_* constants
651767 lda #$00
652768 sta SID_V2_FREQ_LO
653769 lda #VOICE2_FREQ
...
659775 lda #VOICE_SR
660776 sta SID_V2_SR
661777
662- ; Voice 3 - uses VOICE3_* constants
663778 lda #$00
664779 sta SID_V3_FREQ_LO
665780 lda #VOICE3_FREQ
...
771886 rts
772887
773888 ; ----------------------------------------------------------------------------
774-; Play Voices - Uses customisation waveforms
889+; Play Voices
775890 ; ----------------------------------------------------------------------------
776891
777892 play_voice1:
778893 lda #VOICE1_WAVE
779- ora #$01 ; Add gate bit
894+ ora #$01
780895 sta SID_V1_CTRL
781896 rts
782897
...
793908 rts
794909
795910 ; ----------------------------------------------------------------------------
796-; Flash Tracks - Uses customisation colours
911+; Flash Tracks
797912 ; ----------------------------------------------------------------------------
798913
799914 flash_track1:
...
804919 inx
805920 cpx #38
806921 bne flash_t1_loop
807- lda #1 ; White label
922+ lda #1
808923 sta COLRAM + (TRACK1_ROW * 40)
809924 rts
810925