Offline

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
Offline
Adelaide, Australia

Nice intro!

I don't understand assembly language but that player/music code looks like a beautiful (and very useful) thing.

I might have to try it out somehow! Thanks! :-]