Skip to content

Instantly share code, notes, and snippets.

@cesarmiquel
Created March 9, 2020 03:06
Show Gist options
  • Save cesarmiquel/ea30327f2249460bba384b90f6c5b685 to your computer and use it in GitHub Desktop.
Save cesarmiquel/ea30327f2249460bba384b90f6c5b685 to your computer and use it in GitHub Desktop.
; Source code to 303 dmeo by 4mat 2020 (http://4matprojects.blogspot.com/2020/03/c64-303.html)
; 303 style by 4mat 2020
; Type sys 49152 to play.
; Written using Dasm assembler. Should be mostly compatible with other assemblers except:
; + The processor line is probably Dasm only unless your assembler handles multiple processors.
; + org might be replaced by * or something else.
; + Some assemblers use !byte or something else instead of .byte, see your docs.
; Memory map at bottom of source.
; Set start address to $c000. (49152)
org $c000
processor 6502
; Init Intro.
; Disable interrupts while we do our setup.
sei
; Copy song data to zero page.
; This saves a bit of memory as some assembler commands will only use 2 bytes instead
; of 3 if they were in 'normal' memory.
ldy #$00
ldx #$30
argha
tya
sta $60,x
lda datas-1,x
sta $0f,x
dex
bne argha
; Setup soundchip.
; By default I've set all channels to use triangle and sync/ring-mod for the first cycle.
setsound
lda #$17
sta 54276,y
lda #$08
sta 54275,y
lda #$00
sta 54277,y
lda $1d,x
sta 54278,y
lda $10,x
sta $6b,x
lda $12,x
sta 54295,x
inx
clc
tya
adc #$07
tay
cpy #$15
bne setsound
; Point hardware interrupt at the music player section.
lda #<musicloop
sta $0314
lda #>musicloop
sta $0315
; Enable interrupts so we're good to start.
cli
; Main loop.
; Only the visuals are updated here. This part runs as fast as the spare CPU
; time will allow.
loop
; Take the current filter cut-off value and add 65 to it (so it's in the Petscii area
; of the character set)
lda $62
adc #$41
; Use the random number generator to read the oscillator value from channel 3 and use
; this as the X offset position. Because channel 3 is where the drums are, for snares
; and hihats this will be using the noise waveform.
ldx $d41b
; Store the petscii char into the screen area. This is repeated 4 times to fill the 4
; pages of the screen. I used a slight offset from the usual $0400 start position to
; move the visuals around a bit.
sta $03e8,x
sta $04e8,x
sta $05e8,x
sta $06e8,x
jmp loop
; Music player.
musicloop
; Decrease tempo tick, if it goes minus (#$ff) we need a new note, otherwise we can jump
; to the instrument update part.
dec $60
bmi musicloop2
bpl updatedrums
; Get new note in the pattern.
musicloop2
; Reset tempo tick to full. (in our case #$06 which is about 125 bpm)
lda #$06
sta $60
; Make new value to add to filter cut-off for this note.
; Using the current raster beam value ($d012) as an offset, read a value from the
; Kernal ROM and use only the lower 4-bit value. Then add the current number of
; loop cycles ($6b) to make each value slightly different.
ldy $d012
lda $e144,y
and #$0f
adc $6b
sta filtsweep+$01
; Get new note values, the current pattern position stored in $61.
ldy $61
; First the main melody, using the lower 4-bits from some of the Kernal ROM to only
; use bass values between 0 (silence) and 15. The extra 'eor #$03' is more for
; personal taste with the different melodies.
chan1
lda $f704,y
and #$0f
eor #$03
sta 54273
; Now the sync/ring-mod channel, using data from the BASIC ROM, but only taking
; the low 6-bits.
chan2
lda $a340,y
and #$3f
sta 54273+7
; Set new Drum
; This checks against 4 possible BIT values to see if a drum needs to be played.
; ($40 = Bass Drum, $08 = Snare , $02 = Hihat , $01 = Rest)
noresetsq
ldx #$00
drumcheck
; Get next position value from drum rhythm table.
lda $30,y
; Check if value matches the current BIT value indexed.
and $20,x
beq nobit
; If it does this means we have a new drum to play.
; Firstly set the waveform from that table. Also store it at $66 so we can apply
; note off later.
lda $24,x
sta 54276+14
sta $66
; Set the drum pitch, this is placed directly in a variable as we do work on this
; value when adding the pitch sweep.
lda $27,x
sta $67
; Set the drum pitch sweep.
lda $2a,x
sta $68
; Finally set the timer value before applying the note-off on the drum.
lda $2d,x
sta $69
nobit
dex
bpl drumcheck
; Increase the pattern position by 1 and AND the value by #$0f so it's always
; between 00-15.
iny
tya
and #$0f
sta $61
; Check if the value is 00, if not we don't need to decrease the amount of pattern
; cycles yet.
bne noupdate
; Decrease the amount of pattern cycles and check if this is #$ff yet. If not we
; don't need to create a new pattern yet.
dec $6b
bpl noupdate
; When the pattern cycles are complete it's time to create a new pattern.
; Firstly we do some self-modifying code to the initial value of the drum check loop.
; This means we don't always get the same drum beat by dropping out checks for the
; snare and hi-hats.
dec noresetsq+$01
lda noresetsq+$01
and #$03
sta noresetsq+$01
; We also use this value to change the melody line's waveform, from the table at $15-$18.
tax
lda $15,x
sta 54276
; We also change the amount of pattern cycles for the next pattern from the
; table at $19-$1c.
lda $19,x
sta $6b
; Now we do some more self-modifying code to change the memory position to read the
; note data from. This increase the high memory value by one for the main melody and
; the ring-tone channel. This does mean that eventually both values will reach past the
; end of memory and reset back to $0000. As mentioned in the docs as this was for a video
; I didn't add any checking for this occurance, however the older version of the player resets
; the machine to avoid this happening.
inc chan1+$02
inc chan2+$02
; This resets the starting value of the filter cut-off value. It works very similar to
; the filter sweep setup though we start with a 7-bit value, divide it by half and then add
; the current pattern position value to it.
noupdate
ldy $d012
adc $e948,y
and #$7f
lsr
adc $61
sta $62
; This is where the player falls through to on every frame. This updates the filter and
; drums and then sends an acknowledgement to the timer system that this routine has finished.
; Check the tempo tick against the current drum's note-off value. If it's the same switch off
; the waveform's gate (bit 1) so the release part of the ADSR gets activated.
updatedrums
lda $60
cmp $69
bne nodrumgate
dec $66
lda $66
sta 54276+14
nodrumgate
; Add the drum's pitch sweep value to the current pitch and store it in channel 3's high pitch
; register. Note that we don't do the math directly on the register because the SID is write
; only when enabled.
clc
lda $67
adc $68
sta $67
sta 54273+14
; Set current filter cut-off value to the cut-off register.
lda $62
sta 54294
; Decrease the cut-off value by the current filter sweep value. (note this was set in
; self-modifying code earlier) If the value is already below zero don't store it in the
; variable so it only stays at this value.
filtsweep
sbc #$00
bmi filtnot
sta $62
; End of music driver, call to IO system that we've ended our routine.
; As we have the full default system enabled we need to use $ea31 rather than the
; less cpu-heavy $ea81
filtnot
jmp $ea31
datas
.byte $05,$07
siddata
.byte $f3,$1f,$00
basswaves
.byte $11,$21,$41,$21
length
.byte $01,$03,$03,$03
vols
.byte $a9,$3c,$79
btt
.byte $40,$02,$08,$00
wav
.byte $41,$81,$81
not
.byte $0a,$ff,$20
plu
.byte $ff,$fe,$fc
del
.byte $03,$02,$01
beat
.byte $40,$01,$02,$01,$08,$01,$02,$01,$40,$01,$02,$01,$08,$01,$02,$40
; Memory map:
; $10 = Initial pattern cycles value for first pattern. (datas)
; $12 = SID Filter values for filter type/volume and resonance/channel allocation. (siddata)
; $15 = Melody line waveform table. (basswaves)
; $19 = Melody loop cycle length table. (length)
; $1d = SID Channel sustain and release values. (Attack/Decay are always zero) (vols)
; $20 = Drum BIT value check table. (btt)
; $24 = Drum Waveform table. (silence isn't stored in drum values) (wav)
; $27 = Drum starting Pitch value table. (not)
; $2a = Drum Pitch addition signed value table. (plu)
; $2d = Drum ticks before note off value table. (del)
; $30 = Drum beat pattern table. (beat)
; $60 = Current note timing tick.
; $61 = Current note position in pattern.
; $62 = Filter cut-off value.
; $66 = Drum Waveform setting for use with note-off.
; $67 = Current drum pitch.
; $68 = Pitch value to add to drum pitch every frame. (signed value)
; $69 = Drum note-off timer.
; $6b = number of cycles to loop the current pattern
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment