At the Revision party over the weekend Rez and myself won the 4k oldskool competition.
Below is the source code to the small music driver, written way back in 2003 but I only just found a use for it. Anyway, has instructions if anyone wants to try it out. You'll need Dasm to compile, has a built-in front-end for testing.
; "1K PLAY" music driver for reasonably small c64 songs. Used in Razor 1911's "The Best Intro ever" at Revision 2016.
; code & music by 4mat / ate bit + orb
; There are some questionable design decisions in the code :), but if you want 1kb-ish songs with filters and some fx it'll do that.
; Main work done in 2003
; Some small fixes in 2016 (actually the first time I've used it in a prod)
; Requires : DASM Assembler
; Useage:
; -------
; DEBUGMODE = 0 - just build driver for use in production.
; Standard calls: Init Player - JSR ADDRESS , Call update each frame - JSR ADDRESS+$03
; DEBUGMODE = 1 - build with front-end. (for writing tracks/testing)
; Press SPACE to ffwd through song.
; Press FIRE (JOY #2) to reset max rastertime counter. (you'll need to do that when using ffwd as well)
; In this mode the song and pattern position offsets are AUTOMATICALLY CALCULATED, when building just the driver you need to insert these values into the tables at the end.
; You can find the calculated tables in memory at $0340 - song positions (3 bytes) , $0380 - pattern positions (however many patterns you have)
DEBUGMODE = 1
; Relocation/Footprint
; --------------------
; On top of the driver size player uses some ram during run-time: 256 bytes for the generated frequency table and $35 bytes of space for variables.
; You can relocate the player, freq. table and variables here:
ADDRESS = $1000 ; Player start address
freqtablo = $0f00 ; Frequency table start address (256 bytes)
songpos = $40 ; Variables start address ($35 bytes)
; Obviously putting the player variables outside zero page will increase the size of the player at run-time.
; Saving more ram
; ---------------
; There are a couple of player functions that can be disabled to save more ram if you don't use them:
FUNKTEMPO = 0 ; If using one constant tempo (rather than 'funk tempo' on alternate frames) set this to 0.
SONGREPEAT = 1 ; If you aren't using the Ex command in your song data (repeat next pattern) set this to 0.
; Writing songs
; --------------
; Scroll to bottom of source for instructions. (search for Music Data)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Debug display
.IF DEBUGMODE = 1
processor 6502
org $0801
.byte $0b,$08,$ef,$00,$9e,$32,$30,$36,$32,0,0,0,0
org $080e
start
jsr $e544
sei
jsr init
ldy #$00
ldx #$00
showtext
lda screengfx,x
sta $0400,x
lda screencol,y
sta $d800,x
sta $d900,x
iny
cpy #$50
bne notapage
ldy #$00
notapage
inx
bne showtext
clc
lda #>ADDRESS
jsr gethex
lda hexdisp
sta $04fb
lda hexdisp+$01
sta $04fc
lda #<ADDRESS
jsr gethex
lda hexdisp
sta $04fd
lda hexdisp+$01
sta $04fe
lda #>codeend
jsr gethex
lda hexdisp
sta $0500
lda hexdisp+$01
sta $0501
lda #<codeend
jsr gethex
lda hexdisp
sta $0502
lda hexdisp+$01
sta $0503
loop
lda $d012
cmp #$40
bne loop
sta rasterstart
skiprast
inc $d020
jsr playframe
lda $d012
sta rasternow
dec $d020
clc
lda rasternow
sbc rasterstart
sta rastercurrent
cmp rastermax
bcc notnewraster
sta rastermax
notnewraster
jsr displaydata
lda $dc01
cmp #$ef
bne noskiprast
jmp skiprast
noskiprast
lda $dc00
cmp #$6f
bne loop
lda #$00
sta rastermax
jmp loop
displaydata
ldx #$00
display
lda dispreglo,x
sta readreg+$01
lda dispreghi,x
sta readreg+$02
ldy dispoffset,x
readreg
lda $4000,y
jsr gethex
lda displo,x
sta print1+$01
clc
adc #$01
sta print2+$01
lda hexdisp
print1
sta $0400
lda hexdisp+$01
print2
sta $0428
inx
cpx #$0f
bne display
rts
rasterstart
.byte $00
rastermax
.byte $00
rasternow
.byte $00
rastercurrent
.byte $00
dispreglo
.byte <#songpos,#<songpos,#<songpos
.byte <#pattpos,#<pattpos,#<pattpos
.byte <#note,#<note,<#note
.byte <#instnum,#<instnum,#<instnum
.byte <#ticks
.byte <#rastercurrent
.byte <#rastermax
dispreghi
.byte >#songpos,#>songpos,#>songpos
.byte >#pattpos,#>pattpos,#>pattpos
.byte >#note,#>note,>#note
.byte >#instnum,#>instnum,#>instnum
.byte >#ticks
.byte >#rastercurrent
.byte >#rastermax
dispoffset
.byte $00,$01,$02
.byte $00,$01,$02
.byte $00,$01,$02
.byte $00,$01,$02
.byte $00
.byte $00
.byte $00
displo
.byte $18,$1c,$20
.byte $40,$44,$48
.byte $68,$6c,$70
.byte $90,$94,$98
.byte $b8
.byte $e0,$e3
screengfx
.scrl "song pos "
.scrl "pattern pos "
.scrl "note "
.scrl "instrument "
.scrl "frame tick "
.scrl "rastertime/max time / 1k*play"
.scrl "song size $ -"
screencol
.byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$00,$00,$0d,$0d,$01,$01,$0d,$0d,$01,$01,$0d,$0d,$01,$01,$07,$03,$0d,$01
.byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$00,$00,$07,$07,$01,$07,$07,$07,$01,$01,$07,$07,$01,$01,$07,$03,$0d,$01
gethex
pha
and #$0f
tay
lda hexchars,y
sta hexdisp+$01
pla
lsr
lsr
lsr
lsr
tay
lda hexchars,y
sta hexdisp
rts
hexdisp
.byte $00,$00
hexchars .byte $30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$01,$02,$03,$04,$05,$06
autofind
ldy #$01
ldx #$00
songloop
lda songtab,x
inx
cmp #$ff
bne songloop
txa
sta songstart,y
sta $0340,y
iny
cpy #$03
beq dothepat
jmp songloop
dothepat
ldy #$01
ldx #$00
pattloop
lda patttab,x
cmp #$7f
bne notpatt
inx
txa
sta pattstart,y
sta $0380,y
iny
cpy #$10
beq dotherest
jmp pattloop
notpatt
inx
bne pattloop
dotherest
rts
.ENDIF
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Driver
freqtabhi = freqtablo+$80
pattpos = songpos+$03
transpose = songpos+$06
note = songpos+$09
instnum = songpos+$0c
delay = songpos+$0f
repeaty = songpos+$12
yahoo = songpos+$15
instfreq = songpos+$18
insthigh = songpos+$1b
notelen = songpos+$1e
patwait = songpos+$21
resetwav = songpos+$24
patrepeat = songpos+$27
transtemp = songpos+$2a
filtset = songpos+$2d
ticks = songpos+$2e
tempocnt = songpos+$2f
temp = songpos+$34
temp2 = songpos+$33
temp3 = songpos+$32
temp4 = songpos+$31
temp5 = songpos+$30
org ADDRESS
jmp init
jmp playframe
init
ldx #$00
stx freqtablo
freqloop
lda #$01
pha
txa
tay
freqset
pla
sta freqtablo+$01,y
asl
pha
lda freqsource,x
sta freqtabhi+$01,y
rol
sta freqsource,x
bcc notinc
pla
adc #$00
pha
notinc
clc
tya
adc #$0c
tay
bpl freqset
pla
inx
cpx #$0c
bne freqloop
.IF DEBUGMODE = 1
jsr autofind
.ENDIF
ldx #$34
lda #$00
cleardata
sta songpos,x
dex
bpl cleardata
ldx #$02
resetgo
lda songstart,x
sta songpos,x
dex
bpl resetgo
rts
playframe
ldx #$00
stx temp2
noteloop
stx temp
lda ticks
beq dothis
jmp update
dothis
dec delay,x
bmi newnote
jmp update
newnote
lda transtemp,x
sta transpose,x
ldy pattpos,x
lda patttab,y
nonewpat
cmp #$ef
bcc justnote
instrument
and #$0f
sta instnum,x
fubme
inc pattpos,x
jmp newnote
justnote
bpl regularnote
and #$0f
sta notelen,x
bpl fubme
regularnote
pha
ldy instnum,x
lda notelen,x
sta delay,x
inc pattpos,x
pla
cmp #$00
beq update
sta note,x
doit
lda instoff,y
sta yahoo,x
lda resetwav,x
cmp instnum,x
bne havetoreset
lda instdec,y
and #$f0
bne nowavreset
havetoreset
lda instpul,y
and #$0f
sta insthigh,x
lda #$00
sta instfreq,x
nowavreset
lda instfilt,y
beq nofilt
sta filtset
lda filtres,y
sta $d417
lda volume,y
sta $d418
nofilt
tya
sta resetwav,x
ldx temp2
lda #$09
sta $d404,x
lda instadc,y
sta $d405,x
lda instsus,y
sta $d406,x
update
ldx temp
lda instnum,x
sta temp3
ldy yahoo,x
lda instnote,y
ldy temp2
cmp #$00
beq playusual
cmp #$f0
bcc playthis
and #$0f
bpl playusual
playthis
sta temp5
jmp playcont
playusual
clc
adc note,x
adc transpose,x
playnowt
tax
lda freqtablo,x
sta temp5
lda freqtabhi,x
sta temp4
ldx temp
ldy temp3
lda instdec,y
and #$01
beq nowave
lda temp5
adc instfreq,x
sta temp5
nowave
ldy temp2
playcont
lda temp5
sta $d401,y
lda temp4
sta $d400,y
lda instfreq,x
sta $d402,y
lda insthigh,x
sta $d403,y
lda yahoo,x
tax
lda insttab,x
sta $d404,y
nogate
ldx temp
ldy temp3
lda instfreq,x
adc instadd,y
sta instfreq,x
lda instfreq,x
cmp instadd,y
bcs noise
inc insthigh,x
noise
inc yahoo,x
ldy yahoo,x
lda insttab,y
cmp #$ef
bcc noinstres
and #$0f
sta temp
lda yahoo,x
sbc temp
sta yahoo,x
noinstres
clc
lda temp2
adc #$07
sta temp2
ldy pattpos,x
lda patttab,y
cmp #$7f
bne notfin
getpat
ldy songpos,x
lda songtab,y
cmp #$ff
bne nopatreset
lda songstart,x
sta songpos,x
jmp getpat
nopatreset
.IF SONGREPEAT = 1
cmp #$df
bcc nopatloop
yesrepeat
and #$0f
sta patwait,x
inc songpos,x
jmp getpat
.ENDIF
nopatloop
sta temp
and #$0f
tay
lda pattstart,y
sta pattpos,x
lda temp
and #$f0
lsr
lsr
lsr
lsr
sta transtemp,x
dec patwait,x
bpl notfin
inc songpos,x
lda #$00
sta patwait,x
notfin
inx
cpx #$03
beq updatend
jmp noteloop
updatend
lda ticks
bne noreset
.IF FUNKTEMPO = 1
inc tempocnt
lda tempocnt
and #$01
sta tempocnt
tax
lda tempo,x
.ELSE
lda tempo
.ENDIF
sta ticks
ldy temp3
lda filtset
adc instfila,y
sta filtset
sta $d416
noreset
dec ticks
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Music Data
; ----------
; A song can have:
; 256 bytes total of song positon data.
; 256 bytes total of pattern data.
; 15 patterns (pattern 0 must stay blank for init)
; 16 instruments
; Single or dual 'funk' tempo. (flipping between these values each beat if you have FUNKTEMPO enabled. Can also be used for intermediate tempos (eg: $06,$07)
; Tempo settings. (amount of ticks per beat like other trackers)
tempo
.byte $06,$06
; Song table offset positions for each channel. If in DEBUG mode look at memory $0340-$0342 for the offset positions you need to put here when building song in non debug mode.
songstart
.byte $00,$2b,$44
; Song data table. Song format is:
; $XY = Transpose ($0-$D) / Pattern number ($1-$F) - eg: $01 = No transpose & play pattern 1 , $75 = +7 Transpose and play pattern 5.
; $Ex = Repeat following pattern x times - eg: $E7 = Repeat next pattern 7 times.
; $FF = End song channel. (must have 3 channels setup in a song)
songtab
; channel 1
.byte $04,$06,$ef,$02 ; intro
.byte $e3,$02,$e3,$32,$e3,$52,$e3,$82 ; lead
.byte $52,$52,$02,$02,$e3,$32,$e3,$52,$82,$82,$72,$72 ; lead
.byte $e7,$02 ; post lead
.byte $e7,$02,$e3,$92,$e3,$82,$e7,$02 ; mid
.byte $e7,$02,$e3,$92,$e3,$82,$c4,$04 ; mid
.byte $ff
; channel 2
.byte $ef,$03,$e3,$08 ; intro
.byte $e3,$c3,$e3,$03,$e7,$08 ; lead
.byte $e3,$c3 ; post lead
.byte $e3,$08,$e3,$c3,$e7,$08 ; mid
.byte $e3,$08,$e3,$c3,$e3,$08 ; mid
.byte $ff
; channel 3
.byte $01,$05,$07,$07 ; intro
.byte $09,$0a,$0b,$0a,$0c,$c9,$ca,$cb,$ca,$cc ;lead
.byte $07 ; post lead
.byte $c4,$c5,$07 ; mid
.byte $05,$07 ; mid
.byte $ff
; Pattern table offset positions.
; As with song data if in DEBUG mode look at memory $0380-$038F for offset positions you need to put here. When in debug mode leave this table 16 bytes long
; so the auto-find code doesn't overwrite your other data.
pattstart .byte $00,$02,$09,$18,$2b,$32,$3e,$47,$4e,$69,$9f,$b1,$ba,$c2,$00,$00
; Pattern data table. Pattern format is:
; $00 - Blank note. Will leave previous note playing if one is enabled.
; $01-$60 - Play note. See table below.
; $8x - Set duration for all following notes. (+1 for length) - eg: $87 = All following notes will play for 8 beats.
; $Fx - Set instrument for all following notes. - eg: $F5 = All following notes will use instrument 5.
; $7F - End pattern.
; Always leave pattern 0 blank, the default below is smallest you need.
; Note table guide
; c c# d d# e f f# g g# a a# b
; 1 2 3 4 5 6 7 8 9 a b c
; d e f 10 11 12 13 14 15 16 17 18
; 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24
; 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30
; 31 32 33 34 35 36 37 38 39 3a 3b 3c
; 3d 3e 3f 40 41 42 43 44 45 46 47 48
; 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54
; 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60
patttab
.byte $00,$7f ; ALWAYS LEAVE THIS PATTERN (0) AS IS
.byte $f1,$8f,$01,$00,$00,$00,$7f ; 1 spesh fx
.byte $80,$f3,$18,$f2,$18,$18,$18,$81,$f4,$18,$80,$f2,$18,$18,$7f ; 2 bass and drums
.byte $80,$f5,$30,$30,$24,$30,$2e,$30,$24,$35,$24,$30,$24,$2b,$2e,$30,$24,$37,$7f ; 3 accomp
.byte $f6,$8f,$0c,$00,$00,$00,$7f ; 4
.byte $f7,$8f,$24,$28,$2c,$30,$00,$00,$f8,$20,$00,$7f ; 5 intro lead
.byte $18,$18,$18,$15,$14,$20,$1f,$13,$7f ; 6 intro long bass
.byte $f0,$8f,$15,$00,$00,$00,$7f ; 7 crash
.byte $80,$f5,$30,$30,$fa,$38,$f5,$30,$2e,$fa,$38,$f5,$24,$35,$f9,$37,$f5,$30,$24,$f9,$37,$f5,$2e,$30,$24,$37,$7f ; 8 accomp with arps
.byte $fb,$83,$2c,$81,$33,$fc,$2c,$fb,$32,$fc,$33,$fb,$33,$85,$2b,$81,$33,$fc,$33,$fb
.byte $32,$fc,$33,$fb,$33,$fc,$32,$fb,$83,$2e,$81,$33,$fc,$2e,$fb,$30,$33,$fc,$30,$fb
.byte $35,$fc,$33,$fb,$37,$35,$83,$85,$33,$81,$fb,$30,$33,$7f ; lead
.byte $82,$35,$37,$83,$35,$81,$33,$30,$80,$2e,$82,$30,$fc,$80,$2e,$82,$30,$7f ; more lead
.byte $80,$2e,$84,$30,$fb,$81,$30,$33,$7f
.byte $87,$f8,$35,$80,$f4,$20,$20,$7f
; Instrument settings
; instoff : Offset for instrument into instrument tables (below)
; instadc : $XY : Attack/Decay
; instdec : $XY : X = 1 don't reset pulsewidth modulation subsequent notes using this instrument (Rob Hubbard-style) / Y = 1 means add frequency slide enabled. (for waveforms that aren't pulse)
; instsus : $XY : Sustain/Release
; instpul : Pulsewidth coarse value ($00-$0f)
; instadd : Amount to add to Pulsewidth modulation or pitch frequency if enabled in 'instdec'
; volume : $XY : Filter type / Instrument Volume (see $d418 settings in C64 manual)
; filtres : $XY : Filter resonance / Channel matrix that can use filter (see $d417 settings in C64 manual)
; instfilt : Filter cut-off start position , 00 = no filter enabled on instrument. Last instrument on a beat that has filter enabled will have control of the filter.
; instfila : Amount to add to filter cut-off each tempo pass.
;0 1 2 3 4 5 6 7 8 9 A B C
instoff .byte $3e,$15,$05,$0a,$10,$05,$1b,$26,$15,$30,$37,$43,$43
instadc .byte $10,$87,$10,$10,$10,$10,$10,$10,$87,$10,$10,$00,$10
instdec .byte $01,$11,$10,$00,$00,$10,$10,$00,$11,$10,$10,$00,$00
instsus .byte $bb,$7f,$ca,$ca,$b7,$67,$af,$cd,$7f,$a9,$a9,$9d,$3d
instpul .byte $00,$01,$09,$08,$08,$07,$07,$01,$01,$03,$07,$07,$0d
instadd .byte $7f,$01,$44,$34,$00,$64,$09,$01,$01,$32,$32,$86,$26
volume .byte $0f,$1f,$00,$00,$0f,$00,$00,$3f,$00,$00,$00,$5f,$4f
filtres .byte $00,$c7,$00,$00,$00,$00,$00,$c5,$00,$00,$00,$54,$74
instfilt .byte $01,$01,$00,$00,$01,$00,$00,$07,$00,$00,$00,$ed,$17
instfila .byte $00,$00,$00,$00,$00,$00,$00,$03,$00,$00,$00,$cd,$24
; Instrument waveform table
; Start instruments with $09 for a solid restart.
; Use normal C64 waveform settings for instruments.
; Instruments must end with $fX loop command, where X is the amount of steps to jump back at the end of the instrument. (eg : $F1 = jump back one step , $F4 = jump back four steps like an arpeggio loop)
insttab
.byte $00,$f1
.byte $09,$16,$f1
.byte $09,$81,$41,$40,$f1
.byte $09,$41,$81,$40,$40,$f1
.byte $09,$41,$41,$80,$f1
.byte $09,$81,$15,$80,$14,$f2
.byte $09,$41,$40,$40,$40,$40,$40,$40,$40,$40,$f8
.byte $09,$41,$40,$40,$40,$40,$40,$20,$20,$f7
.byte $09,$81,$40,$40,$20,$40,$f4
.byte $09,$81,$40,$40,$20,$40,$f4
.byte $09,$81,$80,$80,$f2
.byte $09,$41,$40,$f1
; Instrument pitch table.
; Length must match waveform table as same offsets are used for both.
; $00 = Play pitched note
; $01-$ef = Play fixed coarse note pitch. (eg : for drums)
; $f0-$ff = Transpose note $fX amount. (eg : for arpeggios - $f3 = minor key, $f4 = major key)
instnote .byte $00,$00
.byte $00,$00,$00
.byte $00,$ed,$00,$00,$00
.byte $00,$0c,$18,$07,$00,$00
.byte $00,$0c,$12,$ec,$00
.byte $00,$00,$00,$00,$00,$00
.byte $00,$00,$00,$00,$00,$fc,$fc,$fc,$fc,$fc ; yes I know this instrument doesn't match the table above, it sounds better in this song though. :)
.byte $00,$00,$fc,$fc,$00,$00,$00,$00,$00,$00
.byte $00,$ed,$fc,$fc,$f5,$f5,$00
.byte $00,$ed,$fc,$fc,$f4,$f4,$00
.byte $00,$00,$00,$fc,$00
.byte $00,$fc,$00,$00
; Default note pitches for frequency generator.
freqsource
.byte 12,28,45,62,81,102,123,145,169,195,221,250
codeend