; *** Paul Slocum Music Kit 2.2 *** ; Song Player ; changes compared to Paul Slocum Music Kit 2.0 ; - ported from dasm to ca65 ; - split of words in tables containing hi/lo-byte, doubling #patterns ; - shifted bit 7 of patternid to bit 6 to indicate low volume pattern ; - bit 7 of patternid for voice0 now disables hihat for that pattern ; v2.2 by Thomas Jentzsch (TJ), 2015 ; changes compared to Paul Slocum Music Kit 2.1 ; - changed back to DASM :) ; - new, slightly more compact song format ; - added space counting ; - some space optimizations ; - replaced some magic numbers with constants ; - fixed accent values (now correctly considering carry flag state) ; - everything is a macro now, this allows more flexible space usage ; - added option to mute note with 0 instead of 255 ; - several new assembler switches to en/disable player functionality MAC PSMK_SONG_PLAYER ; {4 bytes temporary RAM} ; assembler switches: SHADOW_REGISTERS = 0 ; copy sound values into shadow register (6-14 bytes) ENABLE_ENDMARKER = 1 ; check for a song endmarker (7 bytes) (TJ: how should this work without???) ENABLE_HIHAT = 0 ; automatic hihats (39..53 bytes) SUPPRESS_HIHAT = 0 ; suppress hihats for defined patterns (7 bytes; needs ENABLE_HIHAT = 1) ENABLE_PERCCUTTER = 0 ; better percussions (10 bytes) PERCCUTTER_START = 0 ; start pattern number for better percussion (6 bytes; needs ENABLE_PERCCUTTER = 1) ENABLE_ACCENT = 0 ; accentuate (+2) individual notes (11/19 bytes) ENABLE_LOWVOL = 0 ; attenuated, lower (-4) volume pattern (15 bytes) ENABLE_FRAC_TEMPO = 1 ; fractional speed (+/-1 bytes) ENABLE_ZERO_MUTE = 1 ; use 0 (instead of 255) for mute notes (-3 bytes) SONG_RESTART = 0 ; pattern number for song loop start (1 byte) ; total: 106..228 ; debugging constants: ENABLE_VOICE0 = 1 ; play voice 0 ENABLE_VOICE1 = 1 ; play voice 1 (+ hihats) ; player constants: SONG_END = $ff .IF ENABLE_ZERO_MUTE MUTE_NOTE = 0 .ELSE MUTE_NOTE = $ff ; 2.1 format .ENDIF ACCENT_OFFSET = 8 HIHAT_FLAG = $80 ATTENUATION_FLAG = $40 ;-------------------------------------------------------------------------- ; PsmkPlayer ;-------------------------------------------------------------------------- ; Plays up to two pre-programmed patterns simlutaneously. ; ; Call this once per screen-draw. ;-------------------------------------------------------------------------- PsmkPlayer SUBROUTINE ;-------------------------------------------------------------------------- ; Generates tempo based on TEMPODELAY ;-------------------------------------------------------------------------- ldy psmkPatternIdx .IF ENABLE_FRAC_TEMPO = 0 inc psmkTempoCount lda psmkTempoCount .IF ENABLE_HIHAT && SUPPRESS_HIHAT and #~HIHAT_FLAG .ENDIF .IFCONST TEMPODELAY eor #TEMPODELAY .ELSE eor TEMPODELAY .ENDIF bne .quitTempo sta psmkTempoCount ; A == 0 .ELSE ; ENABLE_FRAC_TEMPO lda psmkTempoCount clc .IFCONST TEMPODELAY adc #TEMPODELAY .ELSE adc TEMPODELAY .ENDIF sta psmkTempoCount bcc .quitTempo IF TEMPODELAY = 68 inc psmkTempoCount ; correction for TEMPODELAY 68.27 (Coke Zero) ENDIF .ENDIF ; /ENABLE_FRAC_TEMPO inc psmkBeatIdx lda psmkBeatIdx eor #$20 bne .quitTempo sta psmkBeatIdx ; A == 0 iny .IF ENABLE_ENDMARKER ldx PsmkSong1,y ; Check to see if the end of the song was reached inx ; SONG_END? bne .notEndOfSong ; Go back to the first measure .IF SONG_RESTART = 0 tay ; A == 0 .ELSE ldy #SONG_RESTART .ENDIF .notEndOfSong .ENDIF ; /ENABLE_ENDMARKER sty psmkPatternIdx .quitTempo ;-------------------------------------------------------------------------- ; Get Song Pattern ;-------------------------------------------------------------------------- lax PsmkSong1,y .IF ENABLE_HIHAT && SUPPRESS_HIHAT and #HIHAT_FLAG ora psmkTempoCount sta psmkTempoCount txa .ENDIF ; /SUPPRESS_HIHAT .IF ENABLE_VOICE0 ldx #0 ; first voice jsr PlayPattern .ENDIF .IF ENABLE_VOICE1 ldy psmkPatternIdx lda PsmkSong2,y ; second voice .IF ENABLE_HIHAT || ENABLE_VOICE0 = 0 ldx #1 .ELSE inx ; -> x = 1 .ENDIF .ELSE rts .ENDIF ; slip through ;-------------------------------------------------------------------------- ; PlayPattern ;-------------------------------------------------------------------------- ; Plays a pattern ; ; - A should contain the offset in the patternArray of the pattern to play ; - X should contain the oscillator to be used (0 or 1) ; ;-------------------------------------------------------------------------- PlayPattern SUBROUTINE ; temporary local variables .temp16 = {1} .osc = {1}+2 .attenuation = {1}+3 ; save osc number stx .osc ; save bit6 (ATTENUATION_FLAG) as carry flag asl asl .IF ENABLE_LOWVOL php .ENDIF sta .temp16+1 ; custom code to allow 1 quarter note per measure (Thrust): ; use beat to determine extra offset within pattern array lda psmkBeatIdx and #%00011000 lsr lsr lsr ; add in original offset ora .temp16+1 tax ; This mod allows for high and low volume patterns. ; Patterns of offset greater than 64 are read from ; a different array and play lower. ; Loud version ; Get id of selected pattern ldy PsmkHiVolPatternArrays,x .IF ENABLE_LOWVOL ; Set 0 attenuation lda #0 ; check ATTENUATION_FLAG plp bcc .loudPattern ; Soft version ; Get id of selected pattern ldy PsmkLoVolPatternArrays,x ; Set -4 attenuation lda #4 .loudPattern sta .attenuation .ENDIF ; Get address of selected pattern lda PsmkPatternsLo,y sta .temp16+0 lda PsmkPatternsHi,y sta .temp16+1 ; The variable, beat, contains the 32nd note ; that the beat is currently on. lda psmkBeatIdx ; modification for 1 quarter per measure (Thrust) and #%00000111 tay ; Get sound/note data .IF ENABLE_ZERO_MUTE lda (.temp16),y beq .muteNote .ELSE lax (.temp16),y eor #MUTE_NOTE beq .muteNote txa .ENDIF ;-------------------------------------------------------------------------- ; Extract Pattern Data ;-------------------------------------------------------------------------- ; Each byte of pattern data contains the frequency and ; sound type data. This function separates and decodes them. ; ; The encoding is: the 3 high bits contain the encoded sound ; type and the lower 5 bits contain the freq data. ; ; - ACC must contain pattern byte ; ; = ACC will return the freq ; = X will return the sound type ; ; changes ACC,X ;-------------------------------------------------------------------------- ; Extract freq data and push it and #%00011111 pha eor (.temp16),y asl rol rol rol ; -> CF == 0! tax ;----------------------- .IF ENABLE_LOWVOL lda .attenuation adc PsmkSoundAttenArray,x .ELSE lda PsmkSoundAttenArray,x .ENDIF sta .attenuation ;----------------------- lda PsmkSoundTypeArray,x ;-------------------------------------------------------------------------- ; Get the osc number again ldx .osc sta AUDC0,x .IF SHADOW_REGISTERS sta audc0,x .ENDIF pla sta AUDF0,x .IF SHADOW_REGISTERS sta audf0,x .ENDIF ;-------------------------------------------------------------------------- ; Accent Reader ;-------------------------------------------------------------------------- ; Each set of pattern data is followed by 1 accept byte. ; Each bit in order represents the accent (on or off) ; of its corresponding 8th note. This function ; returns the attenuation of a note in a pattern. ; ; - .temp16 must contain an indirect pointer to the pattern data ; - (X)Y must contain the beat && %00000111) ; ; = will return the volume in ACC ; ; changes X,Y,ACC ;-------------------------------------------------------------------------- .IF ENABLE_ACCENT ; Accent offset is always 8 for Thrust mod lda BitMaskArray,y ; Y = beat & #%111 ldy #ACCENT_OFFSET and (.temp16),y beq .noAccent ; this fixes the carry issue! ; It's an Accent, so don't attenuate lda #$10^$0e ; == 15 .noAccent ; No accent, so use a lower volume eor #$0e ; == 13 .ELSE lda #$0e ; == 13 .ENDIF ;-------------------------------------------------------------------------- sbc .attenuation ; CF == 0! -> -1..14/0..15 (old/new code) .muteNote ldx .osc ; Get the osc number again sta AUDV0,x .IF SHADOW_REGISTERS sta audv0,x .ENDIF ;-------------------------------------------------------------------------- ; Super High Hat (TM) ;-------------------------------------------------------------------------- ; This plays the high hat sound on the first frame of each beat indicated ; in hatPattern ;-------------------------------------------------------------------------- .IF ENABLE_HIHAT beq .noHat ; .osc flag is still set! .IF HATSTART > 0 ; Only play hat after HATSTART initial patterns lda psmkPatternIdx cmp #HATSTART bcc .noHat .ENDIF ; Only play hat on first frame (if not supressed) lda psmkTempoCount .IF ENABLE_FRAC_TEMPO = 0 bne .noHat .ELSE .IFCONST TEMPODELAY cmp #TEMPODELAY .ELSE cmp TEMPODELAY .ENDIF bcs .noHat .ENDIF .IF ENABLE_ACCENT lax psmkBeatIdx and #%00000111 tay txa .ELSE lda psmkBeatIdx ; Y is still beat & #%111 .ENDIF lsr lsr lsr tax lda PsmkHatPattern,x and BitMaskArray,y beq .noHat ; Play hat .IFCONST HATPITCH lda #HATPITCH .ELSE lda HATPITCH .ENDIF sta AUDF1 .IF SHADOW_REGISTERS sta audf0+1 .ENDIF .IFCONST HATSOUND lda #HATSOUND .ELSE lda HATSOUND .ENDIF sta AUDC1 .IF SHADOW_REGISTERS sta audc0+1 .ENDIF .IFCONST HATVOLUME lda #HATVOLUME .ELSE lda HATVOLUME .ENDIF sta AUDV1 .IF SHADOW_REGISTERS sta audv0+1 .ENDIF .noHat .ENDIF ; /ENABLE_HIHAT ;-------------------------------------------------------------------------- ; Percussion cutter ;-------------------------------------------------------------------------- ; This code cuts off the sound for better percussive sounds. You ; can set it to start working at a certain measure. ;-------------------------------------------------------------------------- .IF ENABLE_PERCCUTTER .IF PERCCUTTER_START > 0 lda psmkPatternIdx cmp #PERCCUTTER_START ; start pattern bcc .noCut .ENDIF lda psmkTempoCount .IF ENABLE_HIHAT && SUPPRESS_HIHAT and #~(HIHAT_FLAG|1) ; TJ TODO: code ignores ENABLE_FRAC_TEMPO ; not sure if the perccutter ever worked well at all .ELSE lsr .ENDIF beq .noCut ; cut off the sound lda #0 sta AUDV0 .IF SHADOW_REGISTERS sta audv0 .ENDIF .noCut .ENDIF ; /PERCCUTTER rts ; PlayPattern OTHER RTS IN FUNCTION ;-------------------------------------------------------------------------- .IF ENABLE_HIHAT || ENABLE_ACCENT ; Note: This table can be moved everywhere! BitMaskArray: .byte %10000000 .byte %01000000 .byte %00100000 .byte %00010000 .byte %00001000 .byte %00000100 .byte %00000010 .byte %00000001 .ENDIF ECHO "PsmkPlayer:", (. - PsmkPlayer)d, "bytes" ENDM ; PSMK_SONG_PLAYER