From 79a4868e5c3de361d59af561ce4cd19320b57dc5 Mon Sep 17 00:00:00 2001 From: Taylor Woll Date: Thu, 8 Jun 2023 14:53:28 -0700 Subject: [PATCH 1/8] Add music and move from famitone2 to famitone5 --- music/music.ftm | Bin 0 -> 38693 bytes src/crt0.s | 4 +- src/jeznes.c | 21 + src/music/TestMusic3.s | 231 ---- src/music/{famitone2.s => famitone5.s} | 1152 +++++++++++++++--- src/music/music.h | 15 + src/music/music.s | 1488 ++++++++++++++++++++++++ 7 files changed, 2521 insertions(+), 390 deletions(-) create mode 100644 music/music.ftm delete mode 100644 src/music/TestMusic3.s rename src/music/{famitone2.s => famitone5.s} (51%) create mode 100644 src/music/music.h create mode 100644 src/music/music.s diff --git a/music/music.ftm b/music/music.ftm new file mode 100644 index 0000000000000000000000000000000000000000..7e3c3fc5c4305c0f63a12a26fe62ab451efd0103 GIT binary patch literal 38693 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
    ^BBWC!x%;lW6&AK648KR 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 literal 0 HcmV?d00001 diff --git a/src/crt0.s b/src/crt0.s index 87dc821..89b9b89 100644 --- a/src/crt0.s +++ b/src/crt0.s @@ -254,14 +254,14 @@ detectNTSC: .include "lib/neslib.s" .include "lib/nesdoug.s" - .include "music/famitone2.s" + .include "music/famitone5.s" .segment "RODATA" music_data: - .include "music/TestMusic3.s" + .include "music/music.s" .if(FT_SFX_ENABLE) sounds_data: diff --git a/src/jeznes.c b/src/jeznes.c index f794c10..da8515a 100644 --- a/src/jeznes.c +++ b/src/jeznes.c @@ -18,6 +18,7 @@ #include "flood_fill.h" #include "lib/nesdoug.h" #include "lib/neslib.h" +#include "music/music.h" #include "music/sfx.h" #include "scoring.h" #include "types.h" @@ -273,6 +274,9 @@ void init_title(void) { // Starting or returning to the title screen. game_state = GAME_STATE_TITLE; + + // Make sure we aren't playing any music. + music_stop(); } unsigned char title_press_start(void) { @@ -385,6 +389,8 @@ void init_game(void) { // Always loads |get_playfield_pattern()| reset_playfield(); + + music_play(MUSIC_JAZZ); } void load_playfield(void) { @@ -479,6 +485,9 @@ void do_level_up(void) { // Reset state to playing the game. game_state = GAME_STATE_PLAYING; + + // Unpause the music for the playfield. + music_pause(FALSE); } void change_to_level_up(void) { @@ -517,6 +526,9 @@ void change_to_level_up(void) { // Transition state to level-up. game_state = GAME_STATE_LEVEL_UP; + + // Pause the playfield music while on the level up screen. + music_pause(TRUE); } void update_hud_level_up(void) { @@ -626,6 +638,9 @@ unsigned char game_over_press_start(void) { ppu_on_all(); game_state = GAME_STATE_PLAYING; + + // Restart playing the music. + music_pause(FALSE); } else { // get_game_over_mode() == GAME_OVER_QUIT @@ -659,12 +674,18 @@ unsigned char pause_press_start(unsigned char player_index) { pal_fade_to(4, 4); game_state = GAME_STATE_PAUSED; + + // Pause the music, too. + music_pause(TRUE); } else { // game_state == GAME_STATE_PAUSED // Back to normal brightness pal_bright(4); game_state = GAME_STATE_PLAYING; + + // Unpause the music, too. + music_pause(FALSE); } return TRUE; diff --git a/src/music/TestMusic3.s b/src/music/TestMusic3.s deleted file mode 100644 index d9fd790..0000000 --- a/src/music/TestMusic3.s +++ /dev/null @@ -1,231 +0,0 @@ -;this file for FamiTone2 library generated by text2data tool - -TestMusic3_music_data: - .byte 2 - .word @instruments - .word @samples-3 - .word @song0ch0,@song0ch1,@song0ch2,@song0ch3,@song0ch4,307,256 - .word @song1ch0,@song1ch1,@song1ch2,@song1ch3,@song1ch4,307,256 - -@instruments: - .byte $b0 ;instrument $00 - .word @env1,@env0,@env0 - .byte $00 - .byte $b0 ;instrument $01 - .word @env2,@env0,@env0 - .byte $00 - .byte $b0 ;instrument $02 - .word @env3,@env0,@env0 - .byte $00 - .byte $b0 ;instrument $03 - .word @env4,@env0,@env0 - .byte $00 - -@samples: -@env0: - .byte $c0,$00,$00 -@env1: - .byte $c6,$cf,$cc,$c9,$c7,$c6,$c5,$c4,$c2,$00,$08 -@env2: - .byte $c3,$c7,$c5,$c4,$c3,$c2,$c1,$c1,$c0,$00,$08 -@env3: - .byte $c4,$c3,$c2,$c1,$c0,$00,$04 -@env4: - .byte $c2,$06,$c1,$00,$02 - - -@song0ch0: - .byte $fb,$07 -@song0ch0loop: -@ref0: - .byte $80,$3a,$85,$84,$30,$80,$32,$83,$2c,$91,$2d,$33,$37,$38,$87,$36 - .byte $83 -@ref1: - .byte $3a,$85,$84,$30,$80,$32,$83,$2c,$91,$33,$33,$33,$30,$30,$83,$30 - .byte $85 -@ref2: - .byte $3a,$85,$84,$30,$80,$32,$83,$2c,$91,$2d,$33,$37,$38,$87,$36,$83 -@ref3: - .byte $81,$32,$33,$33,$33,$30,$85,$31,$29,$36,$37,$37,$37,$32,$83,$31 - .byte $29,$30,$81 -@ref4: - .byte $33,$00,$85,$31,$33,$00,$85,$31,$33,$00,$85,$31,$32,$85,$36,$85 -@ref5: - .byte $33,$00,$85,$31,$33,$00,$85,$31,$33,$00,$85,$31,$33,$29,$29,$82 - .byte $28,$81 - .byte $ff,$0e - .word @ref0 - .byte $ff,$0f - .word @ref1 - .byte $ff,$0e - .word @ref2 - .byte $ff,$13 - .word @ref3 - .byte $ff,$10 - .word @ref4 - .byte $ff,$11 - .word @ref5 - .byte $fd - .word @song0ch0loop - -@song0ch1: -@song0ch1loop: -@ref12: - .byte $00,$85,$82,$14,$85,$00,$85,$15,$00,$89,$14,$85,$1b,$01,$1f,$00 - .byte $81 -@ref13: - .byte $00,$85,$14,$85,$00,$85,$15,$00,$89,$14,$85,$1b,$01,$1f,$00,$81 - .byte $ff,$10 - .word @ref13 - .byte $ff,$10 - .word @ref13 - .byte $ff,$10 - .word @ref13 - .byte $ff,$10 - .word @ref13 -@ref18: - .byte $81,$84,$52,$87,$4a,$83,$52,$87,$4a,$83,$52,$87,$4a,$83,$4e,$87 - .byte $4e,$81 -@ref19: - .byte $81,$52,$87,$4a,$83,$52,$87,$4a,$83,$52,$87,$4a,$85,$49,$48,$85 -@ref20: - .byte $81,$52,$87,$4a,$83,$52,$87,$4a,$83,$52,$87,$4a,$83,$4e,$87,$4e - .byte $81 -@ref21: - .byte $81,$52,$53,$53,$53,$4e,$85,$4f,$49,$4e,$4f,$4f,$4f,$48,$83,$49 - .byte $40,$85 - .byte $ff,$10 - .word @ref12 - .byte $ff,$10 - .word @ref13 - .byte $fd - .word @song0ch1loop - -@song0ch2: -@song0ch2loop: -@ref24: - .byte $80,$4b,$3b,$01,$3b,$4b,$3b,$01,$3b,$4b,$3b,$01,$3b,$4b,$3b,$01 - .byte $3a,$81 -@ref25: - .byte $4b,$3b,$01,$3b,$4b,$3b,$01,$3b,$4b,$3b,$01,$3b,$4b,$3b,$01,$3a - .byte $81 - .byte $ff,$11 - .word @ref25 - .byte $ff,$11 - .word @ref25 - .byte $ff,$11 - .word @ref25 - .byte $ff,$11 - .word @ref25 - .byte $ff,$11 - .word @ref25 - .byte $ff,$11 - .word @ref25 - .byte $ff,$11 - .word @ref25 - .byte $ff,$11 - .word @ref25 - .byte $ff,$11 - .word @ref25 - .byte $ff,$11 - .word @ref25 - .byte $fd - .word @song0ch2loop - -@song0ch3: -@song0ch3loop: -@ref36: - .byte $84,$13,$13,$00,$82,$16,$86,$1b,$84,$13,$13,$00,$82,$16,$86,$1b - .byte $84,$13,$13,$00,$82,$16,$86,$1b,$84,$13,$13,$00,$82,$16,$86,$1a - .byte $81 - .byte $ff,$15 - .word @ref36 - .byte $ff,$15 - .word @ref36 - .byte $ff,$15 - .word @ref36 - .byte $ff,$15 - .word @ref36 - .byte $ff,$15 - .word @ref36 - .byte $ff,$15 - .word @ref36 - .byte $ff,$15 - .word @ref36 - .byte $ff,$15 - .word @ref36 - .byte $ff,$15 - .word @ref36 - .byte $ff,$15 - .word @ref36 - .byte $ff,$15 - .word @ref36 - .byte $fd - .word @song0ch3loop - -@song0ch4: -@song0ch4loop: -@ref48: - .byte $bf -@ref49: - .byte $bf -@ref50: - .byte $bf -@ref51: - .byte $bf -@ref52: - .byte $bf -@ref53: - .byte $bf -@ref54: - .byte $bf -@ref55: - .byte $bf -@ref56: - .byte $bf -@ref57: - .byte $bf -@ref58: - .byte $bf -@ref59: - .byte $bf - .byte $fd - .word @song0ch4loop - - -@song1ch0: - .byte $fb,$06 -@song1ch0loop: -@ref60: - .byte $bf - .byte $fd - .word @song1ch0loop - -@song1ch1: -@song1ch1loop: -@ref61: - .byte $bf - .byte $fd - .word @song1ch1loop - -@song1ch2: -@song1ch2loop: -@ref62: - .byte $82,$33,$00,$89,$33,$00,$89,$33,$00,$89,$2d,$01,$2c,$85 - .byte $fd - .word @song1ch2loop - -@song1ch3: -@song1ch3loop: -@ref63: - .byte $84,$16,$00,$83,$16,$00,$83,$16,$00,$83,$12,$00,$83,$16,$00,$83 - .byte $16,$00,$83,$16,$00,$83,$12,$00,$83 - .byte $fd - .word @song1ch3loop - -@song1ch4: -@song1ch4loop: -@ref64: - .byte $bf - .byte $fd - .word @song1ch4loop diff --git a/src/music/famitone2.s b/src/music/famitone5.s similarity index 51% rename from src/music/famitone2.s rename to src/music/famitone5.s index 1422a16..75a0901 100644 --- a/src/music/famitone2.s +++ b/src/music/famitone5.s @@ -1,21 +1,43 @@ -;FamiTone2 v1.12 +;FamiTone5.2023.Feb +;fork of Famitone2 v1.15 by Shiru 04'17 +;for ca65 +;Revision 1-21-2021, Doug Fraker, to be used with text2vol5 +;added volume column and support for all NES notes +;added support for 1xx,2xx,3xx,4xx,Qxx,Rxx effects +;added support for duty envelopes and sound fx > 256 bytes +;Pal support fixed, volume table exact now +;Nov 2021, fixed bug, disabling FT_SFX_ENABLE was broken +;2022.Mar.12 moved variables to be contiguous +;2023.Feb fixed Qxx/Rxx without note on same line -;settings, uncomment or put them into your main program; the latter makes possible updates easier +.export FamiToneInit, FamiToneMusicPlay, FamiToneUpdate + +;.segment "ZEROPAGE" +;FT_TEMP: .res 3 + +;variables moved below + +MAX_NOTE = 88 -; FT_BASE_ADR = $0300 ;page in the RAM used for FT2 variables, should be $xx00 -; FT_TEMP = $fd ;3 bytes in zeropage used by the library as a scratchpad -; FT_DPCM_OFF = $fc00 ;$c000..$ffc0, 64-byte steps -; FT_SFX_STREAMS = 1 ;number of sound effects played at once, 1..4 +.segment "CODE" -; FT_DPCM_ENABLE = 1 ;undefine to exclude all DMC code -; FT_SFX_ENABLE = 1 ;undefine to exclude all sound effects code -; FT_THREAD = 1 ;undefine if you are calling sound effects from the same thread as the sound update call -; FT_PAL_SUPPORT = 1 ;undefine to exclude PAL support -; FT_NTSC_SUPPORT = 1 ;undefine to exclude NTSC support +;settings, uncomment or put them into your main program; the latter makes possible updates easier + +;FT_BASE_ADR = $0300 ;page in the RAM used for FT2 variables, should be $xx00 +;FT_DPCM_OFF = $fc00 ;$c000..$ffc0, 64-byte steps +;FT_SFX_STREAMS = 2 ;number of sound effects played at once, 1..4 +;FT_DPCM_ENABLE = 0 ;undefine to exclude all DMC code +;FT_SFX_ENABLE = 1 ;undefine to exclude all sound effects code +;FT_THREAD = 1 ;undefine if you are calling sound effects from the same thread as the sound update call +;FT_PAL_SUPPORT = 1 ;undefine to exclude PAL support +;FT_NTSC_SUPPORT = 1 ;undefine to exclude NTSC support + .if(FT_SFX_ENABLE) +.export FamiToneSfxPlay, FamiToneSfxInit + .endif ;internal defines @@ -39,7 +61,8 @@ FT_TEMP_SIZE = 3 ;envelope structure offsets, 5 bytes per envelope, grouped by variable type -FT_ENVELOPES_ALL = 3+3+3+2 ;3 for the pulse and triangle channels, 2 for the noise channel +FT_ENVELOPES_ALL = 4+4+3+3 ;4 for the pulse and 3 for the triangle and noise channel + ;adding duty envelope to pulse and noise FT_ENV_STRUCT_SIZE = 5 FT_ENV_VALUE = FT_BASE_ADR+0*FT_ENVELOPES_ALL @@ -69,9 +92,9 @@ FT_CHN_DUTY = FT_BASE_ADR+8*FT_CHANNELS_ALL FT_ENVELOPES = FT_BASE_ADR FT_CH1_ENVS = FT_ENVELOPES+0 -FT_CH2_ENVS = FT_ENVELOPES+3 -FT_CH3_ENVS = FT_ENVELOPES+6 -FT_CH4_ENVS = FT_ENVELOPES+9 +FT_CH2_ENVS = FT_ENVELOPES+4 +FT_CH3_ENVS = FT_ENVELOPES+8 +FT_CH4_ENVS = FT_ENVELOPES+11 FT_CHANNELS = FT_ENVELOPES+FT_ENVELOPES_ALL*FT_ENV_STRUCT_SIZE FT_CH1_VARS = FT_CHANNELS+0 @@ -93,11 +116,7 @@ FT_CH3_INSTRUMENT = FT_CH3_VARS+.lobyte(FT_CHN_INSTRUMENT) FT_CH4_INSTRUMENT = FT_CH4_VARS+.lobyte(FT_CHN_INSTRUMENT) FT_CH5_INSTRUMENT = FT_CH5_VARS+.lobyte(FT_CHN_INSTRUMENT) -FT_CH1_DUTY = FT_CH1_VARS+.lobyte(FT_CHN_DUTY) -FT_CH2_DUTY = FT_CH2_VARS+.lobyte(FT_CHN_DUTY) -FT_CH3_DUTY = FT_CH3_VARS+.lobyte(FT_CHN_DUTY) -FT_CH4_DUTY = FT_CH4_VARS+.lobyte(FT_CHN_DUTY) -FT_CH5_DUTY = FT_CH5_VARS+.lobyte(FT_CHN_DUTY) + FT_CH1_VOLUME = FT_CH1_ENVS+.lobyte(FT_ENV_VALUE)+0 FT_CH2_VOLUME = FT_CH2_ENVS+.lobyte(FT_ENV_VALUE)+0 @@ -113,6 +132,17 @@ FT_CH1_PITCH_OFF = FT_CH1_ENVS+.lobyte(FT_ENV_VALUE)+2 FT_CH2_PITCH_OFF = FT_CH2_ENVS+.lobyte(FT_ENV_VALUE)+2 FT_CH3_PITCH_OFF = FT_CH3_ENVS+.lobyte(FT_ENV_VALUE)+2 +FT_CH1_DUTY = FT_CH1_ENVS+.lobyte(FT_ENV_VALUE)+3 +FT_CH2_DUTY = FT_CH2_ENVS+.lobyte(FT_ENV_VALUE)+3 +FT_CH3_DUTY = FT_CH3_VARS+.lobyte(FT_CHN_DUTY) ;see FT_PULSE1_PREV below +FT_CH4_DUTY = FT_CH4_ENVS+.lobyte(FT_ENV_VALUE)+2 ;!! +FT_CH5_DUTY = FT_CH5_VARS+.lobyte(FT_CHN_DUTY) ;see FT_PULSE2_PREV below + + +;FT_EN1_DUTY = FT_CH1_ENVS+.lobyte(FT_ENV_VALUE)+2 +;FT_EN2_DUTY = FT_CH2_ENVS+.lobyte(FT_ENV_VALUE)+2 +;FT_EN4_DUTY = FT_CH4_ENVS+.lobyte(FT_ENV_VALUE)+2 + FT_VARS = FT_CHANNELS+FT_CHANNELS_ALL*FT_CHN_STRUCT_SIZE @@ -156,6 +186,7 @@ FT_SFX_CH0 = FT_SFX_STRUCT_SIZE*0 FT_SFX_CH1 = FT_SFX_STRUCT_SIZE*1 FT_SFX_CH2 = FT_SFX_STRUCT_SIZE*2 FT_SFX_CH3 = FT_SFX_STRUCT_SIZE*3 +SIZE_FT_SFX = FT_SFX_STRUCT_SIZE*FT_SFX_STREAMS ;aliases for the APU registers @@ -183,19 +214,19 @@ APU_SND_CHN = $4015 ;aliases for the APU registers in the output buffer - .if(!FT_SFX_ENABLE) ;if sound effects are disabled, write to the APU directly -FT_MR_PULSE1_V = APU_PL1_VOL -FT_MR_PULSE1_L = APU_PL1_LO -FT_MR_PULSE1_H = APU_PL1_HI -FT_MR_PULSE2_V = APU_PL2_VOL -FT_MR_PULSE2_L = APU_PL2_LO -FT_MR_PULSE2_H = APU_PL2_HI -FT_MR_TRI_V = APU_TRI_LINEAR -FT_MR_TRI_L = APU_TRI_LO -FT_MR_TRI_H = APU_TRI_HI -FT_MR_NOISE_V = APU_NOISE_VOL -FT_MR_NOISE_F = APU_NOISE_LO - .else ;otherwise write to the output buffer +; .if(!FT_SFX_ENABLE) ;if sound effects are disabled, write to the APU directly +;FT_MR_PULSE1_V = APU_PL1_VOL +;FT_MR_PULSE1_L = APU_PL1_LO +;FT_MR_PULSE1_H = APU_PL1_HI +;FT_MR_PULSE2_V = APU_PL2_VOL +;FT_MR_PULSE2_L = APU_PL2_LO +;FT_MR_PULSE2_H = APU_PL2_HI +;FT_MR_TRI_V = APU_TRI_LINEAR +;FT_MR_TRI_L = APU_TRI_LO +;FT_MR_TRI_H = APU_TRI_HI +;FT_MR_NOISE_V = APU_NOISE_VOL +;FT_MR_NOISE_F = APU_NOISE_LO +; .else ;otherwise write to the output buffer FT_MR_PULSE1_V = FT_OUT_BUF FT_MR_PULSE1_L = FT_OUT_BUF+1 FT_MR_PULSE1_H = FT_OUT_BUF+2 @@ -207,8 +238,50 @@ FT_MR_TRI_L = FT_OUT_BUF+7 FT_MR_TRI_H = FT_OUT_BUF+8 FT_MR_NOISE_V = FT_OUT_BUF+9 FT_MR_NOISE_F = FT_OUT_BUF+10 - .endif - +; .endif + +FT_EXTRA = FT_SFX_BASE_ADR+SIZE_FT_SFX +volume_Sq1 = FT_EXTRA +volume_Sq2 = FT_EXTRA+1 +volume_Nz = FT_EXTRA+2 +vol_change = FT_EXTRA+3 +multiple1 = FT_EXTRA+4 + +vibrato_depth1 = FT_EXTRA+5 ;zero = off +vibrato_depth2 = FT_EXTRA+6 +vibrato_depth3 = FT_EXTRA+7 +vibrato_count = FT_EXTRA+8 ;goes up every frame, shared by all + +slide_mode1 = FT_EXTRA+9 ;0 = off, 1 = up, 2 = down, 3 = portamento, 4 q/r +slide_mode2 = FT_EXTRA+10 +slide_mode3 = FT_EXTRA+11 +slide_speed1 = FT_EXTRA+12 ;how much each frame, zero = off +slide_speed2 = FT_EXTRA+13 +slide_speed3 = FT_EXTRA+14 +slide_count_low1 = FT_EXTRA+15 ;how much to add / subtract from low byte - cumulative +slide_count_low2 = FT_EXTRA+16 +slide_count_low3 = FT_EXTRA+17 +slide_count_high1 = FT_EXTRA+18 ; how much to add / subtract from high byte +slide_count_high2 = FT_EXTRA+19 +slide_count_high3 = FT_EXTRA+20 + +temp_low = FT_EXTRA+21 ;low byte of frequency *** +temp_high = FT_EXTRA+22 +channel = FT_EXTRA+23 + +temp_duty = FT_EXTRA+24 +qr_flag = FT_EXTRA+25 +qr_offset = FT_EXTRA+26 +qr_rate = FT_EXTRA+27 +zero_flag1 = FT_EXTRA+28 ;for remembering if 100,200,300 +zero_flag2 = FT_EXTRA+29 +zero_flag3 = FT_EXTRA+30 ;31 new variables + +POST_FT = FT_EXTRA+31 +LAST_FT = POST_FT-1 + +.out .sprintf("last FT variable at %x", LAST_FT) +.out .sprintf("safe to use at %x", POST_FT) ;------------------------------------------------------------------------------ @@ -216,7 +289,7 @@ FT_MR_NOISE_F = FT_OUT_BUF+10 ; in: A 0 for PAL, not 0 for NTSC ; X,Y pointer to music data ;------------------------------------------------------------------------------ - + FamiToneInit: stx FT_SONG_LIST_L ;store music data pointer for further use @@ -227,14 +300,14 @@ FamiToneInit: .if(FT_PITCH_FIX) tax ;set SZ flags for A beq @pal - lda #64 + lda #(NoteTable_Count-_FT2NoteTableLSB) ;64 @pal: .else .if(FT_PAL_SUPPORT) lda #0 .endif .if(FT_NTSC_SUPPORT) - lda #64 + lda #(NoteTable_Count-_FT2NoteTableLSB) ;64 .endif .endif sta FT_PAL_ADJUST @@ -264,6 +337,10 @@ FamiToneInit: sta APU_TRI_LINEAR lda #$00 ;load noise length sta APU_NOISE_HI +; lda #0 ;change to 63 for medium, change to 127 for quiet +; ;also, change to 63 if using DPCM effects +; sta APU_DMC_RAW ; , for louder Triangle Channel +; removed, not needed, should be zero on reset lda #$30 ;volumes to 0 sta APU_PL1_VOL @@ -329,6 +406,23 @@ FamiToneMusicStop: FamiToneMusicPlay: + ldx #$0f ; full volume to start + stx volume_Sq1 + stx volume_Sq2 + stx volume_Nz + + ldx #0 + stx vibrato_depth1 ; turn off by default + stx vibrato_depth2 + stx vibrato_depth3 + stx slide_speed1 + stx slide_speed2 + stx slide_speed3 + stx slide_mode1 + stx slide_mode2 + stx slide_mode3 + ;note, slide_count_low/high are reset on each new note + ldx FT_SONG_LIST_L stx output note frequency = silence + rts +: + lda zero_flag1, y ;keep the current slide value until a new note + beq :+ + lda slide_count_high1, y + sta temp_high + lda slide_count_low1, y + sta temp_low + jmp Vib_Effects +: + + + + lda slide_mode1, y + bne :+ + jmp Vib_Effects +; lda #0 ;already zero +; sta slide_speed1, y +; beq Apply_Slide_Up ; this is a waste of CPU, but needed + ; in case you freeze a slide with 100,200,300 + ; without a new note +: + cmp #1 + beq Apply_Slide_Up + cmp #2 + beq Apply_Slide_Down + ;3 = port, same code for 4 Qxx/Rxx + jmp Apply_Portamento + +Apply_Slide_Down: +;add to the base note + + ldx slide_count_high1, y + lda slide_count_low1, y + clc + adc slide_speed1, y ;downward in frequency is adding to the low frequency + bcc Slide_Down2 + inx ;high byte +Slide_Down2: + sta slide_count_low1, y + txa + cmp #8 + bcc Slide_Down3 + lda #$ff + sta slide_count_low1, y + lda #7 +Slide_Down3: + sta slide_count_high1, y ;stx address, y doesn't exist + sta temp_high + lda slide_count_low1, y + sta temp_low + jmp Vib_Effects + + + + + +Apply_Slide_Up: + ldx slide_count_high1, y + lda slide_count_low1, y + sec + sbc slide_speed1, y ;downward in frequency is adding to the low frequency + bcs Slide_Up2 + dex ;high byte + bmi Slide_Too_Far +Slide_Up2: + sta slide_count_low1, y + sta temp_low + txa + sta slide_count_high1, y ;stx address, y doesn't exist + sta temp_high + jmp Vib_Effects + +Slide_Too_Far: + lda #0 + sta FT_CH1_NOTE, y ;too far, end note + sta slide_count_low1, y ;zero these for later + sta slide_count_high1, y + tax ; now a and x are zero => output note frequency = silence + rts ; and exit + + + +Apply_Portamento: +;if slide is at 0,0, something is wrong (maybe first note of song) +;and make sure it's something real + lda slide_count_low1, y + ora slide_count_high1, y + bne :+ + lda FT_CH1_NOTE, y ;note + jsr get_freq ;returns a low, x high + sta slide_count_low1, y + txa ;no stx abs, y opcode + sta slide_count_high1, y +: + + jsr Use_Note + jsr Compare_Sub + beq @go_up + dex + beq @go_down + jmp Vib_Effects ;exactly equal, skip + +@go_up: + ldx slide_count_high1, y + lda slide_count_low1, y + sec + sbc slide_speed1, y ;downward in frequency is adding to the low frequency + bcs @go_up2 + dex ;high byte + bmi @too_far +@go_up2: + sta slide_count_low1, y + txa + sta slide_count_high1, y ;stx address, y doesn't exist + jsr Compare_Sub + cpx #1 + beq @too_far + +@still_ok: + lda slide_count_low1, y + sta temp_low + lda slide_count_high1, y + sta temp_high + jmp Vib_Effects + +@too_far: + jsr Use_Note + lda temp_low + sta slide_count_low1, y + lda temp_high + sta slide_count_high1, y + + lda slide_mode1, y + cmp #4 ;Qxx/Rxx + bne @too_far2 + lda #0 ;end the Qxx/Rxx, the destination has been reached + sta slide_mode1, y +@too_far2: + jmp Vib_Effects + +@go_down: + ldx slide_count_high1, y + lda slide_count_low1, y + clc + adc slide_speed1, y ;downward in frequency is adding to the low frequency + bcc @go_down2 + inx ;high byte +@go_down2: + sta slide_count_low1, y + txa + sta slide_count_high1, y ;stx address, y doesn't exist + jsr Compare_Sub + beq @too_far + bne @still_ok + + +;returns 0 = less than, 1 = more than, 2 = equal +Compare_Sub: + lda slide_count_high1, y + tax ;save for later + cmp temp_high + bcc @go_down + bne @go_up +;equal, check low byte + lda slide_count_low1, y + cmp temp_low + bcc @go_down + bne @go_up + ldx #2 ;equal + rts + +@go_up: ;in freq + ldx #0 + rts + +@go_down: ;in freq + ldx #1 + rts + + +Use_Note: + lda FT_CH1_NOTE, y + jsr get_freq ;returns a low, x high + sta temp_low + stx temp_high + rts + + +Vib_Effects: + ldx vibrato_depth1, y + beq Vib_Skip ; if zero, off + lda Vib_Offset, x + clc + adc vibrato_count ; this increments every frame + tax + lda Vib_Table, x + bmi Vib_Neg +Vib_Pos: ; a = offset amount + clc + adc temp_low + bcc Vib_Done + lda #$ff ;if overflow, just use max low byte + bne Vib_Done + +Vib_Neg: + clc + adc temp_low + bcs Vib_Done + lda #$00 ;if underflow, just use min low byte + +Vib_Done: + sta temp_low +Vib_Skip: + lda temp_low ; pass the final frequency back to the music routine + ldx temp_high + rts + + +Vib_Offset: ;zero skipped, here for filler +;speed 6 +.byte 0,0,11,22,33,44,55,66,77,88,99,110 + + +Vib_Table: ; vibrato + +;speed 6 +.byte 0,1,1,1,1, 0,0,256-1,256-1,256-1, 256-1 ;1 +.byte 0,1,2,2,1, 0,0,256-1,256-2,256-2, 256-1 ;2 +.byte 0,2,3,3,2, 1,256-1,256-3,256-4,256-4, 256-2 ;3 +.byte 0,3,5,6,5, 2,256-2,256-5,256-6,256-5, 256-3 ;4 +.byte 0,3,6,6,5, 2,256-2,256-5,256-6,256-6, 256-3 ;5 +.byte 0,5,8,9,7, 3,256-3,256-7,256-9,256-8, 256-5 ;6 +.byte 0,6,10,11,8, 3,256-3,256-8,256-11,256-10, 256-6 ;7 +.byte 0,7,12,13,10, 4,256-4,256-10,256-13,256-12, 256-7 ;8 +.byte 0,9,15,16,12, 4,256-4,256-12,256-16,256-15, 256-9 ;9 +.byte 0,10,17,19,14, 5,256-5,256-14,256-19,256-17, 256-10 ;A + + + + + + + + + + ;internal routine, sets up envelopes of a channel according to current instrument ;in X envelope group offset, A instrument number - -_FT2SetInstrument: +;lots changed here z50 +_FT2SetInstrument: asl a ;instrument number is pre multiplied by 4 tay lda FT_INSTRUMENT_H @@ -773,53 +1290,76 @@ _FT2SetInstrument: sta = 256, need to update the pointer to keep going + inc Date: Thu, 8 Jun 2023 15:40:19 -0700 Subject: [PATCH 2/8] Update compile.bat to rebuild the music/sfx asm files --- compile.bat | 11 ++++++++++- src/crt0.s | 4 +--- src/jeznes.c | 3 ++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/compile.bat b/compile.bat index 75a8bad..1af5851 100644 --- a/compile.bat +++ b/compile.bat @@ -2,8 +2,17 @@ set name="jeznes" +set famitone_home=..\famitone5.0\ +set nsf2data_home=%famitone_home%\nsf2data +set text2data_home=%famitone_home%\text2data set CC65_HOME=..\cc65\bin\ -set path=%path%;%CC65_HOME% +set path=%path%;%CC65_HOME%;%nsf2data_home%;%text2data_home% + +nsf2data5.exe music/sfx.nsf -ca65 +cp music/sfx.s src/music/sfx.s + +text2vol5.exe music/music.txt -ca65 +cp music/music.s src/music/music.s cc65 -Oirs src/%name%.c -o build/%name%.s -g --add-source ca65 src/crt0.s -o build/crt0.o -g diff --git a/src/crt0.s b/src/crt0.s index 89b9b89..597fd4e 100644 --- a/src/crt0.s +++ b/src/crt0.s @@ -3,10 +3,9 @@ -FT_BASE_ADR = $0100 ;page in RAM, should be $xx00 +FT_BASE_ADR = $0600 ;page in RAM, should be $xx00 FT_DPCM_OFF = $f000 ;$c000..$ffc0, 64-byte steps FT_SFX_STREAMS = 2 ;number of sound effects played at once, 1..4 - FT_THREAD = 1 ;undefine if you call sound effects in the same thread as sound update FT_PAL_SUPPORT = 1 ;undefine to exclude PAL support FT_NTSC_SUPPORT = 1 ;undefine to exclude NTSC support @@ -16,7 +15,6 @@ FT_SFX_ENABLE = 1 ;undefine to exclude all sound effects code - ;REMOVED initlib ;this called the CONDES function diff --git a/src/jeznes.c b/src/jeznes.c index da8515a..b609ce5 100644 --- a/src/jeznes.c +++ b/src/jeznes.c @@ -387,9 +387,10 @@ void init_game(void) { set_player_orientation_flag(temp_byte_1, ORIENTATION_HORIZ); } - // Always loads |get_playfield_pattern()| + // Always loads |get_playfield_pattern()|. reset_playfield(); + // Start playing the in-game music. music_play(MUSIC_JAZZ); } From 6be49aa08117ab489be338515b09a4c096f2acd5 Mon Sep 17 00:00:00 2001 From: Taylor Woll Date: Thu, 15 Jun 2023 12:35:45 -0700 Subject: [PATCH 3/8] Change Ball data structure to save 20 bytes in the zero page --- src/constants/game.h | 3 +- src/data.h | 21 ++++++-- src/jeznes.c | 113 ++++++++++++++++++++++++++-------------- src/screens/playfield.h | 2 +- src/types.h | 11 ++-- 5 files changed, 102 insertions(+), 48 deletions(-) diff --git a/src/constants/game.h b/src/constants/game.h index 0ef5e47..f1ed139 100644 --- a/src/constants/game.h +++ b/src/constants/game.h @@ -11,8 +11,7 @@ #define PLAYER_SPEED 0x2 #define MAX_BALLS 20 -#define BALL_SPEED_POSITIVE 1 -#define BALL_SPEED_NEGATIVE (-1) +#define BALL_SPEED 1 #define BALL_WIDTH 8 #define BALL_HEIGHT 8 diff --git a/src/data.h b/src/data.h index 7e0dbd4..92f3e5b 100644 --- a/src/data.h +++ b/src/data.h @@ -56,17 +56,30 @@ #define get_positive_line_segment_origin() (temp_int_2) #define set_positive_line_segment_origin(a) (temp_int_2 = (a)) -#define get_x_velocity() (temp_signed_byte_1) -#define set_x_velocity(a) (temp_signed_byte_1 = (a)) -#define get_y_velocity() (temp_signed_byte_2) -#define set_y_velocity(a) (temp_signed_byte_2 = (a)) +#define get_flags_byte() (temp_byte_6) +#define set_flags_byte(a) (temp_byte_6 = (a)) + +#define get_x_direction() (temp_byte_5) +#define set_x_direction(a) (temp_byte_5 = (a)) +#define get_y_direction() (temp_byte_5) +#define set_y_direction(a) (temp_byte_5 = (a)) + #define get_x_candidate_pixel_coord() (temp_byte_2) #define set_x_candidate_pixel_coord(a) (temp_byte_2 = (a)) +#define inc_x_candidate_pixel_coord() (++temp_byte_2) +#define dec_x_candidate_pixel_coord() (--temp_byte_2) + #define get_y_candidate_pixel_coord() (temp_byte_3) #define set_y_candidate_pixel_coord(a) (temp_byte_3 = (a)) +#define inc_y_candidate_pixel_coord() (++temp_byte_3) +#define dec_y_candidate_pixel_coord() (--temp_byte_3) + #define get_x_compare_pixel_coord() (temp_byte_4) #define set_x_compare_pixel_coord(a) (temp_byte_4 = (a)) #define get_y_compare_pixel_coord() (temp_byte_4) #define set_y_compare_pixel_coord(a) (temp_byte_4 = (a)) +#define get_playfield_tile_value() (temp_byte_4) +#define set_playfield_tile_value(a) (temp_byte_4 = (a)) + #endif // __JEZNES_DATA_H__ diff --git a/src/jeznes.c b/src/jeznes.c index b609ce5..1d2fead 100644 --- a/src/jeznes.c +++ b/src/jeznes.c @@ -12,6 +12,7 @@ #include "constants/tiles.h" #include "data.h" #include "debug.h" +#include "flags/ball.h" #include "flags/line.h" #include "flags/player.h" #include "flags/playfield.h" @@ -233,15 +234,12 @@ void init_balls(void) { rand8() % playfield_pattern_valid_ball_height_in_pixels [get_playfield_pattern()] + playfield_pattern_valid_ball_start_pixel_y[get_playfield_pattern()]; + if (rand2()) { - balls[temp_byte_1].x_velocity = BALL_SPEED_POSITIVE; - } else { - balls[temp_byte_1].x_velocity = BALL_SPEED_NEGATIVE; + set_ball_x_direction(temp_byte_1); } if (rand2()) { - balls[temp_byte_1].y_velocity = BALL_SPEED_POSITIVE; - } else { - balls[temp_byte_1].y_velocity = BALL_SPEED_NEGATIVE; + set_ball_y_direction(temp_byte_1); } } } @@ -817,67 +815,106 @@ void move_and_draw_balls(void) { } void move_ball() { + // Stash flags byte. + set_flags_byte(get_temp_ptr(struct Ball)->flags); + // Keep track of candidate new pixel coord. + set_x_candidate_pixel_coord(get_temp_ptr(struct Ball)->x); + set_y_candidate_pixel_coord(get_temp_ptr(struct Ball)->y); + // Consider moving right or left first. - set_x_velocity(get_temp_ptr(struct Ball)->x_velocity); - set_x_candidate_pixel_coord(get_temp_ptr(struct Ball)->x + get_x_velocity()); - set_x_compare_pixel_coord(get_x_candidate_pixel_coord()); - // Moving right - if (get_x_velocity() == BALL_SPEED_POSITIVE) { + set_x_direction(get_ball_x_direction_flag_from_byte(get_flags_byte())); + if (get_x_direction() == BALL_DIRECTION_POSITIVE) { + // Moving right.. + // TODO: If ball velocity is >1 we need to rethink this. + inc_x_candidate_pixel_coord(); // Balls are 8 pixels wide, compare to the right-edge. - set_x_compare_pixel_coord(get_x_compare_pixel_coord() + BALL_WIDTH); + set_x_compare_pixel_coord(get_x_candidate_pixel_coord() + BALL_WIDTH); + } else { + // Moving left.. + // TODO: If ball velocity is >1 we need to rethink this. + dec_x_candidate_pixel_coord(); + // Compare to left-edge. + set_x_compare_pixel_coord(get_x_candidate_pixel_coord()); } // Find x-direction candidate playfield tile index. - temp_int_1 = playfield_tile_from_pixel_coords(get_x_compare_pixel_coord(), - get_temp_ptr(struct Ball)->y); + set_current_playfield_index(playfield_tile_from_pixel_coords(get_x_compare_pixel_coord(), get_y_candidate_pixel_coord())); + set_playfield_tile_value(playfield[get_current_playfield_index()]); // Bounce off a left or right wall tile. - if (playfield[temp_int_1] == PLAYFIELD_WALL) { + if (get_playfield_tile_value() == PLAYFIELD_WALL) { // Reverse x-direction. - set_x_velocity(get_x_velocity() * -1); + set_x_direction(get_x_direction() ^ 1); + set_ball_x_direction_flag_in_byte(get_flags_byte(), get_x_direction()); // Move the ball such that it's in the non-wall tile opposite the candidate. - set_x_candidate_pixel_coord(get_temp_ptr(struct Ball)->x + - get_x_velocity()); - // Update the ball velocity. - get_temp_ptr(struct Ball)->x_velocity = get_x_velocity(); + if (get_x_direction() == BALL_DIRECTION_POSITIVE) { + // We dec'd the candidate pixel coord above so now inc it twice to move in the opposite direction. + // TODO: If ball velocity is >1 we need to rethink this. + inc_x_candidate_pixel_coord(); + inc_x_candidate_pixel_coord(); + } else { + // We inc'd the candidate pixel coord above so now dec it twice to move in the opposite direction. + // TODO: If ball velocity is >1 we need to rethink this. + dec_x_candidate_pixel_coord(); + dec_x_candidate_pixel_coord(); + } + // Update the ball direction flag. + get_temp_ptr(struct Ball)->flags = get_flags_byte(); // Play a sound effect. if (game_state == GAME_STATE_PLAYING) { sfx_play(SFX_BALL_BOUNCE, 0); } } + // Update ball x-coord in pixel space. get_temp_ptr(struct Ball)->x = get_x_candidate_pixel_coord(); // Consider moving up or down next (we already moved right/left). - set_y_velocity(get_temp_ptr(struct Ball)->y_velocity); - set_y_candidate_pixel_coord(get_temp_ptr(struct Ball)->y + get_y_velocity()); - set_y_compare_pixel_coord(get_y_candidate_pixel_coord()); - // Moving down - if (get_y_velocity() == BALL_SPEED_POSITIVE) { + set_y_direction(get_ball_y_direction_flag_from_byte(get_flags_byte())); + if (get_y_direction() == BALL_DIRECTION_POSITIVE) { + // Moving down.. + // TODO: If ball velocity is >1 we need to rethink this. + inc_y_candidate_pixel_coord(); // Balls are 8 pixels tall, compare to the bottom edge. - set_y_compare_pixel_coord(get_y_compare_pixel_coord() + BALL_HEIGHT); + set_y_compare_pixel_coord(get_y_candidate_pixel_coord() + BALL_HEIGHT); + } else { + // Moving up.. + // TODO: If ball velocity is >1 we need to rethink this. + dec_y_candidate_pixel_coord(); + // Compare to top edge. + set_y_compare_pixel_coord(get_y_candidate_pixel_coord()); } // Find y-direction candidate playfield tile index. - temp_int_2 = playfield_tile_from_pixel_coords(get_x_candidate_pixel_coord(), - get_y_compare_pixel_coord()); + set_current_playfield_index(playfield_tile_from_pixel_coords(get_x_candidate_pixel_coord(), get_y_compare_pixel_coord())); + set_playfield_tile_value(playfield[get_current_playfield_index()]); // Bounce off a top or bottom wall tile. - if (playfield[temp_int_2] == PLAYFIELD_WALL) { + if (get_playfield_tile_value() == PLAYFIELD_WALL) { // Reverse y-direction. - set_y_velocity(get_y_velocity() * -1); + set_y_direction(get_y_direction() ^ 1); + set_ball_y_direction_flag_in_byte(get_flags_byte(), get_y_direction()); // Move the ball such that it's in the non-wall tile opposite the candidate. - set_y_candidate_pixel_coord(get_temp_ptr(struct Ball)->y + - get_y_velocity()); - // Update the ball velocity. - get_temp_ptr(struct Ball)->y_velocity = get_y_velocity(); + if (get_y_direction() == BALL_DIRECTION_POSITIVE) { + // We dec'd the candidate pixel coord above so now inc it twice to move in the opposite direction. + // TODO: If ball velocity is >1 we need to rethink this. + inc_y_candidate_pixel_coord(); + inc_y_candidate_pixel_coord(); + } else { + // We inc'd the candidate pixel coord above so now dec it twice to move in the opposite direction. + // TODO: If ball velocity is >1 we need to rethink this. + dec_y_candidate_pixel_coord(); + dec_y_candidate_pixel_coord(); + } + // Update the ball direction flag. + get_temp_ptr(struct Ball)->flags = get_flags_byte(); // Play a sound effect. if (game_state == GAME_STATE_PLAYING) { sfx_play(SFX_BALL_BOUNCE, 0); } } + // Update ball y-coord in pixel space. get_temp_ptr(struct Ball)->y = get_y_candidate_pixel_coord(); // Update nearest playfield tile - center of the ball. - temp_byte_2 = get_x_candidate_pixel_coord() + 4; - temp_byte_3 = get_y_candidate_pixel_coord() + 4; - get_temp_ptr(struct Ball)->nearest_playfield_tile = - playfield_tile_from_pixel_coords(temp_byte_2, temp_byte_3); + set_x_candidate_pixel_coord(get_x_candidate_pixel_coord() + 4); + set_y_candidate_pixel_coord(get_y_candidate_pixel_coord() + 4); + get_temp_ptr(struct Ball)->nearest_playfield_tile = playfield_tile_from_pixel_coords(get_x_candidate_pixel_coord(), get_y_candidate_pixel_coord()); } void draw_player(void) { diff --git a/src/screens/playfield.h b/src/screens/playfield.h index 9640708..05104f1 100644 --- a/src/screens/playfield.h +++ b/src/screens/playfield.h @@ -38,7 +38,7 @@ // Calculate the playfield tile index from (x,y) pixel coords. #define playfield_tile_from_pixel_coords(x, y) \ - (((x) >> 3) + (((y) >> 3) * 32) - PLAYFIELD_FIRST_TILE_INDEX) + (((x) >> 3) + (((y) >> 3) << 5) - PLAYFIELD_FIRST_TILE_INDEX) // Calculate the playfield tile position in (x,y) of the playfield tile |i|. #define playfield_index_x(i) ((i) % 32) diff --git a/src/types.h b/src/types.h index f4362ef..41dced1 100644 --- a/src/types.h +++ b/src/types.h @@ -28,6 +28,11 @@ enum { GAME_OVER_RETRY, GAME_OVER_QUIT }; enum { TITLE_1_PLAYER, TITLE_2_PLAYERS }; +enum { + BALL_DIRECTION_POSITIVE = 0, + BALL_DIRECTION_NEGATIVE, +}; + struct Player { // Player metasprite location in pixel-coords unsigned char x; @@ -49,11 +54,11 @@ struct Ball { unsigned char x; unsigned char y; - signed char x_velocity; - signed char y_velocity; - // Playfield tile index for nearest tile int nearest_playfield_tile; + + // Hold bit-flags used to track state of this ball. + unsigned char flags; }; struct Line { From c4f19da01738ea318d85983ae7fac0c7fd8373cd Mon Sep 17 00:00:00 2001 From: Taylor Woll Date: Fri, 16 Jun 2023 01:53:39 -0700 Subject: [PATCH 4/8] wip --- music/music.ftm | Bin 38693 -> 38693 bytes nrom_32k_vert.cfg | 86 ++++++++++---------------- src/bss.h | 27 +++++++++ src/crt0.s | 93 +++++++++++++++++++--------- src/data.h | 8 +-- src/flags/ball.h | 54 +++++++++++++++++ src/flood_fill.h | 8 +-- src/lib/nesdoug.s | 50 ++++++++------- src/lib/neslib.s | 137 ++++++++++++++++++++++++++++++++++++++---- src/music/famitone5.s | 6 +- src/zeropage.h | 34 +---------- 11 files changed, 344 insertions(+), 159 deletions(-) create mode 100644 src/flags/ball.h diff --git a/music/music.ftm b/music/music.ftm index 7e3c3fc5c4305c0f63a12a26fe62ab451efd0103..6850a16f8e37afeeff1649431e2d800d0fefbf56 100644 GIT binary patch delta 147 zcmZ3wj%n#SrVSBHEQ}0{44Vzrr5GnWFtTl~VHOYq@_R*ZL;@a0twobdV{6RC+{!)1!irUTu>u8xwbqHE^ahAwBizkw>iF& V1!5LRgZ<=NRl9)}O{?yg1^`V>Gj0F? delta 147 zcmZ3wj%n#SrVSBHEDQ{c44Vzrr5GnWFtTl~VHOZ#WZc{music_data - lda music_data + ; lda >BALL_BIT_DIRECTION_Y) + +// Sets the ball y-direction |direction| which must be either BALL_DIRECTION_POSITIVE or BALL_DIRECTION_NEGATIVE +#define set_ball_y_direction_flag_in_byte(flags_byte, direction) \ + ((flags_byte) = (flags_byte) & (~BALL_BIT_DIRECTION_Y | (direction<0 + ldx #FT_SFX_CH0 + jsr FamiToneUpdate + .endif + .if FT_SFX_STREAMS>1 + ldx #FT_SFX_CH1 + jsr FamiToneUpdate + .endif + .if FT_SFX_STREAMS>2 + ldx #FT_SFX_CH2 + jsr FamiToneUpdate + .endif + .if FT_SFX_STREAMS>3 + ldx #FT_SFX_CH3 jsr FamiToneUpdate + .endif + + .endif + + ;send data from the output buffer to the APU + + lda music_data + stx music_dummy_data + stx Date: Fri, 16 Jun 2023 01:56:29 -0700 Subject: [PATCH 5/8] Add Famitracker lib --- src/lib/ft_drv/apu.s | 521 +++++++++++++++ src/lib/ft_drv/driver.s | 606 ++++++++++++++++++ src/lib/ft_drv/effects.s | 714 +++++++++++++++++++++ src/lib/ft_drv/init.s | 596 +++++++++++++++++ src/lib/ft_drv/instrument.s | 668 +++++++++++++++++++ src/lib/ft_drv/periods.s | 65 ++ src/lib/ft_drv/player.s | 1204 +++++++++++++++++++++++++++++++++++ 7 files changed, 4374 insertions(+) create mode 100644 src/lib/ft_drv/apu.s create mode 100644 src/lib/ft_drv/driver.s create mode 100644 src/lib/ft_drv/effects.s create mode 100644 src/lib/ft_drv/init.s create mode 100644 src/lib/ft_drv/instrument.s create mode 100644 src/lib/ft_drv/periods.s create mode 100644 src/lib/ft_drv/player.s diff --git a/src/lib/ft_drv/apu.s b/src/lib/ft_drv/apu.s new file mode 100644 index 0000000..97849d4 --- /dev/null +++ b/src/lib/ft_drv/apu.s @@ -0,0 +1,521 @@ +; +; Updates the APU registers. x and y are free to use +; + +.if 0 +; Found this on nesdev bbs by blargg, +; this can replace the volume table but takes a little more CPU +ft_get_volume: + + lda var_ch_VolColumn, x + lsr a + lsr a + lsr a + sta var_Temp + lda var_ch_Volume, x + sta var_Temp2 + + lda var_Temp ; 4x4 multiplication + lsr var_Temp2 + bcs :+ + lsr a +: lsr var_Temp2 + bcc :+ + adc var_Temp +: lsr a + lsr var_Temp2 + bcc :+ + adc var_Temp +: lsr a + lsr var_Temp2 + bcc :+ + adc var_Temp +: lsr a + beq :+ + rts +: lda var_Temp + ora var_ch_Volume, x + beq :+ + lda #$01 ; Round up to 1 +: rts +.endif + +ft_update_apu: + lda var_PlayerFlags + bne @Play + lda #$00 ; Kill all channels + sta $4015 + rts +@KillSweepUnit: ; Reset sweep unit to avoid strange problems + lda #$C0 + sta $4017 + lda #$40 + sta $4017 + rts +@Play: + +; ============================================================================== +; Square 1 +; ============================================================================== + lda var_Channels + and #$01 + bne :+ + jmp @Square2 +: lda var_ch_Note ; Kill channel if note = off + beq @KillSquare1 + + ; Calculate volume +.if 0 + ldx #$00 + jsr ft_get_volume + beq @KillSquare1 +.endif + ; Calculate volume + lda var_ch_VolColumn + 0 ; Kill channel if volume column = 0 + asl a + beq @KillSquare1 + and #$F0 + sta var_Temp + lda var_ch_Volume + 0 + beq @KillSquare1 + ora var_Temp + tax + lda ft_volume_table, x + sec + sbc var_ch_TremoloResult + bpl :+ + lda #$00 +: bne :+ + lda var_ch_VolColumn + 0 + beq :+ + lda #$01 +: + + ; Write to registers + pha + lda var_ch_DutyCycle + and #$03 + tax + pla + ora ft_duty_table, x ; Add volume + ora #$30 ; And disable length counter and envelope + ;sta $4000 + sta BUF_4000 + ; Period table isn't limited to $7FF anymore + lda var_ch_PeriodCalcHi + and #$F8 + beq @TimerOverflow1 + lda #$07 + sta var_ch_PeriodCalcHi + lda #$FF + sta var_ch_PeriodCalcLo +@TimerOverflow1: + + lda var_ch_Sweep ; Check if sweep is active + beq @NoSquare1Sweep + and #$80 + beq @Square2 ; See if sweep is triggered, if then don't touch sound registers until next note + + lda var_ch_Sweep ; Trigger sweep + ;sta $4001 + sta BUF_4001 + and #$7F + sta var_ch_Sweep + + jsr @KillSweepUnit + + lda var_ch_PeriodCalcLo + ;sta $4002 + sta BUF_4002 + lda var_ch_PeriodCalcHi + ;sta $4003 + sta BUF_4003 + ;lda #$FF + ;sta var_ch_PrevFreqHigh + + jmp @Square2 + +@KillSquare1: + lda #$30 + ;sta $4000 + sta BUF_4000 + jmp @Square2 + +@NoSquare1Sweep: ; No Sweep + lda #$08 + ;sta $4001 + sta BUF_4001 + jsr @KillSweepUnit + lda var_ch_PeriodCalcLo + ;sta $4002 + sta BUF_4002 + lda var_ch_PeriodCalcHi + ;cmp var_ch_PrevFreqHigh + ;beq @SkipHighPartSq1 + ;sta $4003 + sta BUF_4003 + ;sta var_ch_PrevFreqHigh +@SkipHighPartSq1: +; jmp @Square2 + +; ============================================================================== +; Square 2 +; ============================================================================== +@Square2: + lda var_Channels + and #$02 + bne :+ + jmp @Triangle +: lda var_ch_Note + 1 + beq @KillSquare2 + + ; Calculate volume +.if 0 + ldx #$01 + jsr ft_get_volume + beq @KillSquare2 +.endif + + lda var_ch_VolColumn + 1 ; Kill channel if volume column = 0 + asl a + beq @KillSquare2 + and #$F0 + sta var_Temp + lda var_ch_Volume + 1 + beq @KillSquare2 + ora var_Temp + tax + lda ft_volume_table, x + sec + sbc var_ch_TremoloResult + 1 + bpl :+ + lda #$00 +: bne :+ + lda var_ch_VolColumn + 1 + beq :+ + lda #$01 +: + + ; Write to registers + pha + lda var_ch_DutyCycle + 1 + and #$03 + tax + pla + ora ft_duty_table, x + ora #$30 + ;sta $4004 + sta BUF_4004 + ; Period table isn't limited to $7FF anymore + lda var_ch_PeriodCalcHi + 1 + and #$F8 + beq @TimerOverflow2 + lda #$07 + sta var_ch_PeriodCalcHi + 1 + lda #$FF + sta var_ch_PeriodCalcLo + 1 +@TimerOverflow2: + + lda var_ch_Sweep + 1 ; Check if there should be sweep + beq @NoSquare2Sweep + and #$80 + beq @Triangle ; See if sweep is triggered + lda var_ch_Sweep + 1 ; Trigger sweep + ;sta $4005 + sta BUF_4005 + and #$7F + sta var_ch_Sweep + 1 + + jsr @KillSweepUnit + + lda var_ch_PeriodCalcLo + 1 ; Could this be done by that below? I don't know + ;sta $4006 + sta BUF_4006 + lda var_ch_PeriodCalcHi + 1 + ;sta $4007 + sta BUF_4007 + ;lda #$FF + ;sta var_ch_PrevFreqHigh + 1 + + jmp @Triangle + +@KillSquare2: + lda #$30 + ;sta $4004 + sta BUF_4004 + jmp @Triangle + +@NoSquare2Sweep: ; No Sweep + lda #$08 + ;sta $4005 + sta BUF_4005 + jsr @KillSweepUnit + lda var_ch_PeriodCalcLo + 1 + ;sta $4006 + sta BUF_4006 + lda var_ch_PeriodCalcHi + 1 + ;cmp var_ch_PrevFreqHigh + 1 + ;beq @SkipHighPartSq2 + ;sta $4007 + sta BUF_4007 + ;sta var_ch_PrevFreqHigh + 1 +@SkipHighPartSq2: + +@Triangle: + lda var_Channels + and #$04 + beq @Noise + +; ============================================================================== +; Triangle +; ============================================================================== + lda var_ch_Volume + 2 + beq @KillTriangle + lda var_ch_VolColumn + 2 + beq @KillTriangle + lda var_ch_Note + 2 + beq @KillTriangle + lda #$81 + ;sta $4008 + sta BUF_4008 + ; Period table isn't limited to $7FF anymore + lda var_ch_PeriodCalcHi + 2 + and #$F8 + beq @TimerOverflow3 + lda #$07 + sta var_ch_PeriodCalcHi + 2 + lda #$FF + sta var_ch_PeriodCalcLo + 2 +@TimerOverflow3: +; lda #$08 +; sta $4009 + lda var_ch_PeriodCalcLo + 2 + ;sta $400A + sta BUF_400A + lda var_ch_PeriodCalcHi + 2 + ;sta $400B + sta BUF_400B + jmp @SkipTriangleKill +@KillTriangle: + lda #$00 + ;sta $4008 + sta BUF_4008 +@SkipTriangleKill: + +; ============================================================================== +; Noise +; ============================================================================== +@Noise: + lda var_Channels + and #$08 + beq @DPCM + + lda var_ch_Note + 3 + beq @KillNoise + + ; Calculate volume + lda var_ch_VolColumn + 3 ; Kill channel if volume column = 0 + asl a + beq @KillNoise + and #$F0 + sta var_Temp + lda var_ch_Volume + 3 + beq @KillNoise + ora var_Temp + tax + lda ft_volume_table, x + sec + sbc var_ch_TremoloResult + 3 + bpl :+ + lda #$00 +: bne :+ + lda var_ch_VolColumn + 3 + beq :+ + lda #$01 +: + + ; Write to registers + ora #$30 + ;sta $400C + sta BUF_400C + lda #$00 + ;sta $400D + sta BUF_400D + lda var_ch_DutyCycle + 3 +; and #$01 + ror a + ror a + and #$80 + sta var_Temp +.if 0 +.ifdef SCALE_NOISE + ; Divide noise period by 16 + lda var_ch_PeriodCalcLo + 3 + lsr a + lsr a + lsr a + lsr a +.else + ; Limit noise period to range 0 - 15 + lda var_ch_PeriodCalcHi + 3 + bne :+ + lda var_ch_PeriodCalcLo + 3 + cmp #$10 + bcc :++ +: lda #$0F +: eor #$0F +.endif +.else +; No limit + lda var_ch_PeriodCalcLo + 3 + and #$0F + eor #$0F +.endif + ora var_Temp + ;sta $400E + sta BUF_400E + lda #$00 + ;sta $400F + sta BUF_400F + beq @DPCM +@KillNoise: + lda #$30 + ;sta $400C + sta BUF_400C +@DPCM: + +; ============================================================================== +; DPCM +; ============================================================================== +.ifdef USE_DPCM + lda var_Channels + and #$10 + bne :+ + rts ; Skip DPCM + ;beq @Return +: +.ifdef USE_N163 + ldx var_AllChannels + dex +.else + ldx #DPCM_CHANNEL +.endif + lda var_ch_DPCM_Retrig ; Retrigger + beq @SkipRetrigger + dec var_ch_DPCM_RetrigCntr + bne @SkipRetrigger + sta var_ch_DPCM_RetrigCntr + lda #$01 + sta var_ch_Note, x +@SkipRetrigger: + + lda var_ch_DPCMDAC ; See if delta counter should be updated + bmi @SkipDAC + sta $4011 +@SkipDAC: + lda #$80 ; store a negative value to mark that it's already updated + sta var_ch_DPCMDAC + + lda var_ch_Note, x + beq @KillDPCM + bmi @SkipDPCM + lda var_ch_SamplePitch + and #$40 + sta var_Temp + lda var_ch_DPCM_EffPitch + bpl :+ + lda var_ch_SamplePitch +: ora var_Temp + sta $4010 + lda #$80 + sta var_ch_DPCM_EffPitch + + + ; Setup sample bank (if used) + .ifdef USE_BANKSWITCH + lda var_ch_SampleBank + beq :+ + clc + sta $5FFC ; Always last bank + adc #$01 + sta $5FFD + adc #$01 + sta $5FFE +; adc #$01 +; sta $5FFF +: +.endif + + ; Sample position (add sample offset) + clc + lda var_ch_SamplePtr + adc var_ch_DPCM_Offset + +;extra DPCM offset, samples now could be included not only at $c000 + +.import __DMC_START__ + clc + adc #<((__DMC_START__-$c000)/64) + + sta $4012 + + ; Sample length (remove sample offset) + lda var_ch_DPCM_Offset + asl a + asl a + sta var_Temp + sec + lda var_ch_SampleLen + sbc var_Temp + sta $4013 + lda #$80 + sta var_ch_Note, x + lda #$0F + sta $4015 + lda #$1F + sta $4015 + rts +@SkipDPCM: + cmp #$FF + beq @ReleaseDPCM + rts +@ReleaseDPCM: +; todo + lda #$0F + sta $4015 + lda #$80 + sta var_ch_Note, x + rts +@KillDPCM: + lda #$0F + sta $4015 + lda #$80 + sta $4011 + sta var_ch_Note, x +.endif +@Return: + rts + +; Lookup tables + +ft_duty_table: + .byte $00, $40, $80, $C0 + +; Volume table: (column volume) * (instrument volume) +ft_volume_table: + .byte 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + .byte 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + .byte 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2 + .byte 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3 + .byte 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4 + .byte 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5 + .byte 0, 1, 1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6 + .byte 0, 1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7 + .byte 0, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 8 + .byte 0, 1, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 7, 8, 9 + .byte 0, 1, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 10 + .byte 0, 1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 8, 9, 10, 11 + .byte 0, 1, 1, 2, 3, 4, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12 + .byte 0, 1, 1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 10, 11, 12, 13 + .byte 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 + .byte 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 diff --git a/src/lib/ft_drv/driver.s b/src/lib/ft_drv/driver.s new file mode 100644 index 0000000..afa1d26 --- /dev/null +++ b/src/lib/ft_drv/driver.s @@ -0,0 +1,606 @@ +; +; The NSF music driver for FamiTracker +; Version 2.11 +; By jsr (jsr@famitracker.com) +; assemble with ca65 +; +; Documentation is in readme.txt +; +; Tab stop is 4 +; +; +; ToDo; +; - Sunsoft +; - Support for multiple chips +; +; +; Known bugs: +; + + + +; +; Assembler code switches +; + +USE_BANKSWITCH = 0 ; Enable bankswitching code +USE_DPCM = 1 ; Enable DPCM channel (currently broken, leave enabled to avoid trouble). + ; Also leave enabled when using expansion chips + +;INC_MUSIC_ASM = 1 ; Music is in assembler style +RELOCATE_MUSIC= 1 ; Enable if music data must be relocated + +NTSC_PERIOD_TABLE = 1 ; Enable this to include the NTSC period table +PAL_PERIOD_TABLE = 1 ; Enable this to include the PAL period table + +ENABLE_ROW_SKIP = 1 ; Enable this to add code for seeking to a row > 0 when using skip command + +;USE_VRC6 = 1 ; Enable this to include VRC6 code +;USE_MMC5 = 1 ; Enable this to include MMC5 code +;USE_VRC7 = 1 ; Enable this to include VRC7 code +;USE_FDS = 1 ; Enable this to include FDS code +;USE_N163 = 1 ; Enable this to include N163 code +;USE_5B = 1 ; Enable this to include 5B code + +;ENABLE_SFX = 1 ; Enable this to enable sound effect support (not yet working) + +SPEED_SPLIT_POINT = 32 ; Speed/tempo split-point. Patched by the NSF exporter + +USE_EXP = 0 ; Enable expansion chips + +;SCALE_NOISE = 1 ; Enable 4 bit noise period scaling + +;CHANNEL_CONTROL = 1 ; Enable to access channel enable/disable routines + + +; +; Constants +; +TUNE_PATTERN_LENGTH = $00 +TUNE_FRAME_LIST_POINTER = $01 + +; Setup the pattern number -> channel mapping, as exported by the tracker + +.if .defined(USE_VRC6) ; 2A03 + VRC6 channels + CHANNELS = 8 + DPCM_CHANNEL = 7 + VRC6_CHANNELS = 4 ; Start of VRC channels + SAW_CHANNEL = VRC6_CHANNELS + 2 ; Saw channel + WAVE_CHANS = CHANNELS - 1 + VRC6_PERIOD_TABLE = 1 +.elseif .defined(USE_VRC7) ; 2A03 + VRC7 channels + CHANNELS = 11 + DPCM_CHANNEL = 10 + WAVE_CHANS = 4 + VRC7_CHANNEL = 4 +.elseif .defined(USE_MMC5) ; 2A03 + MMC5 channels + CHANNELS = 7 + DPCM_CHANNEL = 6 + WAVE_CHANS = CHANNELS - 1 +.elseif .defined(USE_FDS) ; 2A03 + FDS + CHANNELS = 6 + DPCM_CHANNEL = 5 + FDS_CHANNEL = 4 + WAVE_CHANS = CHANNELS - 1 + FDS_PERIOD_TABLE = 1 +.elseif .defined(USE_N163) + CHANNELS = 13 + WAVE_CHANS = CHANNELS - 1 + DPCM_CHANNEL = CHANNELS - 1 + + N163_OFFSET = 4 + + N163_PERIOD_TABLE = 1 + +.else ; 2A03 channels + .ifdef USE_DPCM + CHANNELS = 5 + DPCM_CHANNEL = 4 + WAVE_CHANS = CHANNELS - 1 + .else + CHANNELS = 4 + WAVE_CHANS = CHANNELS + .endif +.endif + +EFF_CHANS = CHANNELS - 1 ; # of channels using vibrato & arpeggio effects (not used by DPCM) + +; Number of wave channels +;.ifdef USE_DPCM +; WAVE_CHANS = CHANNELS - 1 +;.else +; WAVE_CHANS = CHANNELS +;.endif + +; Noise channel +NOISE_CHANNEL = 3 + +;.if .defined(ENABLE_SFX) +; SFX_CHANS = CHANNELS * 2 +; SFX_WAVE_CHANS = WAVE_CHANS * 2 +;.else + SFX_CHANS = CHANNELS + SFX_WAVE_CHANS = WAVE_CHANS +;.endif + +CHAN_2A03 = 0 +CHAN_VRC6 = 2 +CHAN_VRC7 = 4 +CHAN_FDS = 6 +CHAN_MMC5 = 8 +CHAN_N163 = 10 +CHAN_S5B = 12 + +CHAN_2A03_PULSE1 = 1 +CHAN_2A03_PULSE2 = 2 +CHAN_2A03_TRIANGLE = 3 +CHAN_2A03_NOISE = 4 +CHAN_2A03_DPCM = 5 +CHAN_VRC6_PULSE1 = 6 +CHAN_VRC6_PULSE2 = 7 +CHAN_VRC6_SAWTOOTH = 8 +CHAN_VRC7_CHANNEL1 = 9 +CHAN_VRC7_CHANNEL2 = 10 +CHAN_VRC7_CHANNEL3 = 11 +CHAN_VRC7_CHANNEL4 = 12 +CHAN_VRC7_CHANNEL5 = 13 +CHAN_VRC7_CHANNEL6 = 14 +CHAN_FDS_CHANNEL = 15 +CHAN_MMC5_PULSE1 = 16 +CHAN_MMC5_PULSE2 = 17 +CHAN_N163_CHANNEL1 = 18 +CHAN_N163_CHANNEL2 = 19 +CHAN_N163_CHANNEL3 = 20 +CHAN_N163_CHANNEL4 = 21 +CHAN_N163_CHANNEL5 = 22 +CHAN_N163_CHANNEL6 = 23 +CHAN_N163_CHANNEL7 = 24 +CHAN_N163_CHANNEL8 = 25 +CHAN_S5B_SQUARE1 = 26 +CHAN_S5B_SQUARE2 = 27 +CHAN_S5B_SQUARE3 = 28 + +; Header item offsets +HEAD_SPEED = 11 +HEAD_TEMPO = 12 + +EFF_NONE = 0 +EFF_ARPEGGIO = 1 +EFF_PORTAMENTO = 2 +EFF_PORTA_UP = 3 +EFF_PORTA_DOWN = 4 +EFF_SLIDE_UP_LOAD = 5 +EFF_SLIDE_UP = 6 +EFF_SLIDE_DOWN_LOAD = 7 +EFF_SLIDE_DOWN = 8 + +.segment "ZEROPAGE" + +; +; Variables that must be on zero-page +; +var_Temp: .res 1 ; Temporary 8-bit +var_Temp2: .res 1 +var_Temp3: .res 1 +var_Temp4: .res 1 +var_Temp16: .res 2 ; Temporary 16-bit +var_Temp_Pointer: .res 2 ; Temporary +var_Temp_Pointer2: .res 2 +var_Temp_Pattern: .res 2 ; Pattern address (temporary) +var_Note_Table: .res 2 + +ACC: .res 2 ; Used by division routine +AUX: .res 2 +EXT: .res 2 + +.ifdef USE_FDS +var_Wave_pointer: .res 2 +.endif + +.ifdef USE_VRC7 +;var_Period: .res 2 +var_CustomPatchPtr: .res 2 +.endif + +last_zp_var: .res 1 ; Not used + + +.segment "BSS" + +; +; Driver variables +; + +; Song header (necessary to be in order) +var_Song_list: .res 2 ; Song list address +var_Instrument_list: .res 2 ; Instrument list address +.ifdef USE_DPCM +var_dpcm_inst_list: .res 2 ; DPCM instruments +var_dpcm_pointers: .res 2 ; DPCM sample pointers +.endif +var_SongFlags: .res 1 ; Song flags, bit 0 = bankswitched, bit 1 = old vibrato, bit 2 - 7 = unused +.ifdef USE_FDS +var_Wavetables: .res 2 ; FDS waves +.endif + +var_Channels: .res 1 ; Channel enable/disable + +; Track header (necessary to be in order) +var_Frame_List: .res 2 ; Pattern list address +var_Frame_Count: .res 1 ; Number of frames +var_Pattern_Length: .res 1 ; Global pattern length +var_Speed: .res 1 ; Speed setting +var_Tempo: .res 1 ; Tempo setting +var_InitialBank: .res 1 + +; General +var_PlayerFlags: .res 1 ; Player flags, bit 0 = playing, bit 1 - 7 unused +var_Pattern_Pos: .res 1 ; Global pattern row +var_Current_Frame: .res 1 ; Current frame +var_Load_Frame: .res 1 ; 1 if new frame should be loaded + +var_Tempo_Accum: .res 2 ; Variables for speed division +var_Tempo_Count: .res 2 ; (if tempo support is not needed then this can be optimized) +var_Tempo_Dec: .res 2 +var_VolTemp: .res 1 ; So the Exx command will work +var_Sweep: .res 1 ; This has to be saved + +.ifdef USE_BANKSWITCH +var_Bank: .res 1 +.endif +var_Jump: .res 1 ; If a Jump should be executed +var_Skip: .res 1 ; If a Skip should be executed +.ifdef ENABLE_ROW_SKIP +var_SkipTo: .res 1 ; Skip to row number +.endif + +var_sequence_ptr: .res 1 +var_sequence_result: .res 1 + +;var_enabled_channels: .res 1 + +; Channel variables + +; General channel variables, used by the pattern reader (all channels) +var_ch_PatternAddrLo: .res CHANNELS ; Holds current pattern position +var_ch_PatternAddrHi: .res CHANNELS +.ifdef USE_BANKSWITCH +var_ch_Bank: .res CHANNELS ; Pattern bank +.endif +var_ch_Note: .res CHANNELS ; Current channel note +var_ch_VolColumn: .res CHANNELS ; Volume column +var_ch_Delay: .res CHANNELS ; Delay command +var_ch_NoteCut: .res CHANNELS +var_ch_State: .res CHANNELS +var_ch_FinePitch: .res CHANNELS ; Fine pitch setting + +var_ch_NoteDelay: .res CHANNELS ; Delay in rows until next note +var_ch_DefaultDelay: .res CHANNELS ; Default row delay, if exists + +; Following is specific to chip channels (2A03, VRC6...) + +var_ch_TimerPeriodHi: .res EFF_CHANS ; Current channel note period +var_ch_TimerPeriodLo: .res EFF_CHANS +var_ch_PeriodCalcLo: .res EFF_CHANS ; Frequency after fine pitch and vibrato has been applied +var_ch_PeriodCalcHi: .res EFF_CHANS +;var_ch_OutVolume: .res CHANNELS ; Volume for the APU +var_ch_VolSlide: .res CHANNELS ; Volume slide + +; --- Testing --- +;var_ch_LoopCounter: .res CHANNELS +; --- Testing --- + +; Square 1 & 2 variables +var_ch_Sweep: .res 2 ; Hardware sweep +;var_ch_PrevFreqHigh: .res 2 ; Used only by 2A03 pulse channels + +; Sequence variables +var_ch_SeqVolume: .res SFX_WAVE_CHANS * 2 ; Sequence 1: Volume +var_ch_SeqArpeggio: .res SFX_WAVE_CHANS * 2 ; Sequence 2: Arpeggio +var_ch_SeqPitch: .res SFX_WAVE_CHANS * 2 ; Sequence 3: Pitch bend +var_ch_SeqHiPitch: .res SFX_WAVE_CHANS * 2 ; Sequence 4: High speed pitch bend +var_ch_SeqDutyCycle: .res SFX_WAVE_CHANS * 2 ; Sequence 5: Duty cycle / Noise Mode +var_ch_Volume: .res SFX_WAVE_CHANS ; Output volume +var_ch_DutyCycle: .res SFX_WAVE_CHANS ; Duty cycle / Noise mode +var_ch_SequencePtr1: .res SFX_WAVE_CHANS ; Index pointers for sequences +var_ch_SequencePtr2: .res SFX_WAVE_CHANS +var_ch_SequencePtr3: .res SFX_WAVE_CHANS +var_ch_SequencePtr4: .res SFX_WAVE_CHANS +var_ch_SequencePtr5: .res SFX_WAVE_CHANS + +;var_ch_fixed: .res SFX_WAVE_CHANS + +var_ch_ArpFixed: .res EFF_CHANS + +; Track variables for effects +var_ch_Effect: .res EFF_CHANS ; Arpeggio & portamento +var_ch_EffParam: .res EFF_CHANS ; Effect parameter (used by portamento and arpeggio) + +var_ch_PortaToHi: .res EFF_CHANS ; Portamento +var_ch_PortaToLo: .res EFF_CHANS +var_ch_ArpeggioCycle: .res EFF_CHANS ; Arpeggio cycle + +var_ch_VibratoPos: .res EFF_CHANS ; Vibrato +var_ch_VibratoDepth: .res EFF_CHANS +var_ch_VibratoSpeed: .res EFF_CHANS +var_ch_TremoloPos: .res EFF_CHANS ; Tremolo +var_ch_TremoloDepth: .res EFF_CHANS ; combine these +var_ch_TremoloSpeed: .res EFF_CHANS +var_ch_TremoloResult: .res EFF_CHANS +;var_ch_VibratoParam: .res EFF_CHANS +;var_ch_TremoloParam: .res EFF_CHANS + +; DPCM variables +.ifdef USE_DPCM +var_ch_SamplePtr: .res 1 ; DPCM sample pointer +var_ch_SampleLen: .res 1 ; DPCM sample length +var_ch_SampleBank: .res 1 ; DPCM sample bank +var_ch_SamplePitch: .res 1 ; DPCM sample pitch +var_ch_DPCMDAC: .res 1 ; DPCM delta counter setting +var_ch_DPCM_Offset: .res 1 +var_ch_DPCM_Retrig: .res 1 ; DPCM retrigger +var_ch_DPCM_RetrigCntr: .res 1 +var_ch_DPCM_EffPitch: .res 1 +.endif + +.ifdef USE_MMC5 +var_ch_PrevFreqHighMMC5: .res 2 ; MMC5 +.endif + +.ifdef USE_VRC7 +var_ch_vrc7_CustomPatch: .res 1 ; Keep track of the custom patch +var_ch_vrc7_Patch: .res 6 ; VRC7 patch +var_ch_vrc7_DefPatch: .res 6 +var_ch_vrc7_FnumLo: .res 6 +var_ch_vrc7_FnumHi: .res 6 +var_ch_vrc7_Bnum: .res 6 +var_ch_vrc7_ActiveNote: .res 6 +var_ch_vrc7_Command: .res 6 ; 0 = halt, 1 = trigger, 80 = update +var_ch_vrc7_OldOctave: .res 1 ; Temp variable for old octave when triggering new notes +var_ch_vrc7_EffPatch: .res 1 ; V-command + +var_ch_vrc7_CustomHi: .res 6 +var_ch_vrc7_CustomLo: .res 6 + +.endif + +.ifdef USE_FDS +var_ch_Wave: .res 1 ; Index to wave table +var_ch_ModDelay: .res 1 +var_ch_ModDepth: .res 1 +var_ch_ModRate: .res 2 +var_ch_ModDelayTick: .res 1 +var_ch_ModEffDepth: .res 1 +var_ch_ModEffRateHi: .res 1 +var_ch_ModEffRateLo: .res 1 +var_ch_ModEffWritten: .res 1 +var_ch_ResetMod: .res 1 +.endif + +.ifdef USE_N163 +;var_ch_Wave: .res 8 +var_ch_WavePtrLo: .res 8 +var_ch_WavePtrHi: .res 8 +var_ch_WaveLen: .res 8 +var_ch_WavePos: .res 8 + +var_ch_N163_LastHiFreq: .res 8 + +var_NamcoChannels: .res 1 ; Number of active N163 channels +var_AllChannels: .res 1 +var_EffChannels: .res 1 +var_NamcoChannelsReg: .res 1 + +var_NamcoInstrument: .res 8 +.endif + +; End of variable space +last_bss_var: .res 1 ; Not used + + +.segment "CODE" + +; NSF entry addresses + +.ifdef PACKAGE + .byte $46, $54, $44, $52, $56, $20 + .byte $02, $0A +.endif + +LOAD: +INIT: + jmp ft_music_init +PLAY: + jmp ft_music_play + +.ifdef CHANNEL_CONTROL + +; Disable channel in X, X = {00 : Square 1, 01 : Square 2, 02 : Triangle, 03 : Noise, 04 : DPCM} +ft_disable_channel: + lda ft_channel_mask, x + eor #$FF + and var_Channels + sta var_Channels + rts + +; Enable channel in X +ft_enable_channel: + lda ft_channel_mask, x + ora var_Channels + sta var_Channels + lda #$FF + cpx #$00 + beq :+ + cpx #$01 + beq :++ + rts +: sta var_ch_PrevFreqHigh + rts +: sta var_ch_PrevFreqHigh + 1 + rts + +ft_channel_mask: + .byte $01, $02, $04, $08, $10 + +.endif + +; The rest of the code + .include "init.s" + .include "player.s" + .include "effects.s" + .include "instrument.s" + .include "apu.s" + +.ifdef USE_VRC6 + .include "vrc6.s" +.endif +.ifdef USE_VRC7 + .include "vrc7.s" +.endif +.ifdef USE_MMC5 + .include "mmc5.s" +.endif +.ifdef USE_FDS + .include "fds.s" +.endif +.ifdef USE_N163 + .include "n163.s" +.endif +.ifdef USE_S5B + .include "s5b.s" +.endif + +; +; Channel maps, will be moved to exported data +; + +.if .defined(USE_VRC6) + +; VRC6 +ft_channel_map: + .byte CHAN_2A03_PULSE1, CHAN_2A03_PULSE2, CHAN_2A03_TRIANGLE, CHAN_2A03_NOISE + .byte CHAN_VRC6_PULSE1, CHAN_VRC6_PULSE2, CHAN_VRC6_SAWTOOTH + .byte CHAN_2A03_DPCM +ft_channel_type: + .byte CHAN_2A03, CHAN_2A03, CHAN_2A03, CHAN_2A03 + .byte CHAN_VRC6, CHAN_VRC6, CHAN_VRC6 + .byte CHAN_2A03 + +.elseif .defined(USE_VRC7) + +; VRC7 +ft_channel_map: + .byte CHAN_2A03_PULSE1, CHAN_2A03_PULSE2, CHAN_2A03_TRIANGLE, CHAN_2A03_NOISE + .byte CHAN_VRC7_CHANNEL1, CHAN_VRC7_CHANNEL2, CHAN_VRC7_CHANNEL3, CHAN_VRC7_CHANNEL4, CHAN_VRC7_CHANNEL5, CHAN_VRC7_CHANNEL6 + .byte CHAN_2A03_DPCM +ft_channel_type: + .byte CHAN_2A03, CHAN_2A03, CHAN_2A03, CHAN_2A03 + .byte CHAN_VRC7, CHAN_VRC7, CHAN_VRC7, CHAN_VRC7, CHAN_VRC7, CHAN_VRC7 + .byte CHAN_2A03 + +.elseif .defined(USE_FDS) + +; FDS +ft_channel_map: + .byte CHAN_2A03_PULSE1, CHAN_2A03_PULSE2, CHAN_2A03_TRIANGLE, CHAN_2A03_NOISE + .byte CHAN_FDS_CHANNEL + .byte CHAN_2A03_DPCM +ft_channel_type: + .byte CHAN_2A03, CHAN_2A03, CHAN_2A03, CHAN_2A03 + .byte CHAN_FDS + .byte CHAN_2A03 + +.elseif .defined(USE_MMC5) + +; MMC5 +ft_channel_map: + .byte CHAN_2A03_PULSE1, CHAN_2A03_PULSE2, CHAN_2A03_TRIANGLE, CHAN_2A03_NOISE + .byte CHAN_MMC5_PULSE1, CHAN_MMC5_PULSE2 + .byte CHAN_2A03_DPCM +ft_channel_type: + .byte CHAN_2A03, CHAN_2A03, CHAN_2A03, CHAN_2A03 + .byte CHAN_MMC5, CHAN_MMC5 + .byte CHAN_2A03 + +.elseif .defined(USE_N163) + +; N163 +ft_channel_map: + .byte CHAN_2A03_PULSE1, CHAN_2A03_PULSE2, CHAN_2A03_TRIANGLE, CHAN_2A03_NOISE + .byte CHAN_N163_CHANNEL1, CHAN_N163_CHANNEL2, CHAN_N163_CHANNEL3, CHAN_N163_CHANNEL4, CHAN_N163_CHANNEL5, CHAN_N163_CHANNEL6, CHAN_N163_CHANNEL7, CHAN_N163_CHANNEL8 + .byte CHAN_2A03_DPCM +ft_channel_type: + .byte CHAN_2A03, CHAN_2A03, CHAN_2A03, CHAN_2A03 + .byte CHAN_N163, CHAN_N163, CHAN_N163, CHAN_N163, CHAN_N163, CHAN_N163, CHAN_N163, CHAN_N163 + .byte CHAN_2A03 + +.elseif .defined(USE_S5B) + +; todo +ft_channel_map: + .byte CHAN_2A03_PULSE1, CHAN_2A03_PULSE2, CHAN_2A03_TRIANGLE, CHAN_2A03_NOISE + .byte CHAN_2A03_DPCM +ft_channel_type: + .byte CHAN_2A03, CHAN_2A03, CHAN_2A03, CHAN_2A03 + .byte CHAN_2A03 + +.else + +; 2A03/2A07 +ft_channel_map: + .byte CHAN_2A03_PULSE1, CHAN_2A03_PULSE2, CHAN_2A03_TRIANGLE, CHAN_2A03_NOISE, CHAN_2A03_DPCM +ft_channel_type: + .byte CHAN_2A03, CHAN_2A03, CHAN_2A03, CHAN_2A03, CHAN_2A03 + +.endif + +; Include period tables +.include "periods.s" + +; Vibrato table (256 bytes) +ft_vibrato_table: + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01 + .byte $00, $00, $00, $00, $01, $01, $01, $01, $02, $02, $02, $02, $02, $02, $02, $02 + .byte $00, $00, $00, $01, $01, $01, $02, $02, $02, $03, $03, $03, $03, $03, $03, $03 + .byte $00, $00, $00, $01, $01, $02, $02, $03, $03, $03, $04, $04, $04, $04, $04, $04 + .byte $00, $00, $01, $02, $02, $03, $03, $04, $04, $05, $05, $06, $06, $06, $06, $06 + .byte $00, $00, $01, $02, $03, $04, $05, $06, $07, $07, $08, $08, $09, $09, $09, $09 + .byte $00, $01, $02, $03, $04, $05, $06, $07, $08, $09, $09, $0A, $0B, $0B, $0B, $0B + .byte $00, $01, $02, $04, $05, $06, $07, $08, $09, $0A, $0B, $0C, $0C, $0D, $0D, $0D + .byte $00, $01, $03, $04, $06, $08, $09, $0A, $0C, $0D, $0E, $0E, $0F, $10, $10, $10 + .byte $00, $02, $04, $06, $08, $0A, $0C, $0D, $0F, $11, $12, $13, $14, $15, $15, $15 + .byte $00, $02, $05, $08, $0B, $0E, $10, $13, $15, $17, $18, $1A, $1B, $1C, $1D, $1D + .byte $00, $04, $08, $0C, $10, $14, $18, $1B, $1F, $22, $24, $26, $28, $2A, $2B, $2B + .byte $00, $06, $0C, $12, $18, $1E, $23, $28, $2D, $31, $35, $38, $3B, $3D, $3E, $3F + .byte $00, $09, $12, $1B, $24, $2D, $35, $3C, $43, $4A, $4F, $54, $58, $5B, $5E, $5F + .byte $00, $0C, $18, $25, $30, $3C, $47, $51, $5A, $62, $6A, $70, $76, $7A, $7D, $7F + +; Todo: update the NSF exporter if offset to ft_vibrato_table is changed + +; +; An example of including music follows +; + +; The label that contains a pointer to the music data +; A simple way to handle multiple songs is to move this +; to RAM and setup a table of pointers to music data +;ft_music_addr: +; .word * + 2 ; This is the point where music data is stored + + +.ifdef INC_MUSIC + + ; Include music +.ifdef INC_MUSIC_ASM + ; Included assembly file music, DPCM included + .include "music.asm" +.else + ; Binary chunk music + .incbin "music.bin" ; Music data +.ifdef USE_DPCM + .segment "DPCM" ; DPCM samples goes here + .incbin "samples.bin" +.endif +.endif +.endif diff --git a/src/lib/ft_drv/effects.s b/src/lib/ft_drv/effects.s new file mode 100644 index 0000000..887456d --- /dev/null +++ b/src/lib/ft_drv/effects.s @@ -0,0 +1,714 @@ +; +; Update track effects +; +ft_run_effects: + + ; Volume slide + lda var_ch_VolSlide, x + beq @NoVolSlide + ; First calculate volume decrease + lda var_ch_VolSlide, x + and #$0F + sta var_Temp + sec + lda var_ch_VolColumn, x + sbc var_Temp + bpl :+ + lda #$00 +: sta var_ch_VolColumn, x + ; Then increase + lda var_ch_VolSlide, x + lsr a + lsr a + lsr a + lsr a + sta var_Temp + clc + lda var_ch_VolColumn, x + adc var_Temp + bpl :+ + lda #$7F +: sta var_ch_VolColumn, x +@NoVolSlide: + +.if 0 + lda var_ch_Effect, x + bne :+ + ; No effect + rts +: asl a + tay + lda ft_effect_table - 2, y + sta var_Temp_Pointer + lda ft_effect_table - 1, y + sta var_Temp_Pointer + 1 + jmp (var_Temp_Pointer) +.endif + +;.if 0 +ft_jump_to_effect: + ; Arpeggio and portamento + lda var_ch_Effect, x + beq @NoEffect + cmp #EFF_ARPEGGIO + beq @EffArpeggio + cmp #EFF_PORTAMENTO + beq @EffPortamento + cmp #EFF_PORTA_UP + beq @EffPortaUp + cmp #EFF_SLIDE_UP + beq @EffSlideUp + cmp #EFF_SLIDE_DOWN + beq @EffSlideDown + + cmp #EFF_SLIDE_UP_LOAD + beq @EffLoadSlide + cmp #EFF_SLIDE_DOWN_LOAD + beq @EffLoadSlide + + jmp ft_portamento_down + +@EffArpeggio: + jmp ft_arpeggio +@EffPortamento: + jmp ft_portamento +@EffPortaUp: + jmp ft_portamento_up +@EffSlideUp: + jmp ft_slide_up +@EffSlideDown: + jmp ft_slide_down +@EffLoadSlide: + jmp ft_load_slide +@NoEffect: +;.endif +ft_post_effects: + rts + +.if 0 +ft_effect_table: + .word ft_arpeggio, ft_portamento, ft_portamento_up, ft_portamento_down + .word ft_load_slide, ft_slide_up, ft_load_slide, ft_slide_down +.endif + +ft_load_slide: +.ifdef USE_VRC7 + cpx #VRC7_CHANNEL + bcc :+ ; < + cpx #VRC7_CHANNEL + 6 + bcs :+ ; > + jmp ft_vrc7_load_slide +: ; VRC7 skip +.endif + + lda var_ch_TimerPeriodLo, x + pha + lda var_ch_TimerPeriodHi, x + pha + ; Load note + lda var_ch_EffParam, x ; Store speed + and #$0F ; Get note + sta var_Temp ; Store note in temp + lda var_ch_Effect, x + cmp #EFF_SLIDE_UP_LOAD + beq @Add + lda var_ch_Note, x + sec + sbc var_Temp + bpl :+ + lda #$01 +: bne :+ + lda #$01 +: jmp @Done +@Add: + lda var_ch_Note, x + clc + adc var_Temp + cmp #96 + bcc @Done + lda #96 +@Done: + sta var_ch_Note, x + jsr ft_translate_freq_only + lda var_ch_TimerPeriodLo, x + sta var_ch_PortaToLo, x + lda var_ch_TimerPeriodHi, x + sta var_ch_PortaToHi, x + ; Store speed + lda var_ch_EffParam, x + lsr a + lsr a + lsr a + ora #$01 + sta var_ch_EffParam, x + ; Load old period + pla + sta var_ch_TimerPeriodHi, x + pla + sta var_ch_TimerPeriodLo, x + ; change mode to sliding + clc + lda var_ch_Effect, x + adc #01 +.ifdef USE_FDS + ; FDS's frequency reg is inverted + cpx #FDS_CHANNEL + bne :++ + cmp #EFF_SLIDE_UP + bne :+ + lda #EFF_SLIDE_DOWN + jmp :++ +: lda #EFF_SLIDE_UP +: +.endif + sta var_ch_Effect, x +.ifdef USE_N163 + lda ft_channel_type, x + cmp #CHAN_N163 + bne :++ + asl var_ch_EffParam, x + asl var_ch_EffParam, x + ; Invert + lda var_ch_Effect, x + cmp #EFF_SLIDE_UP + beq :+ + lda #EFF_SLIDE_UP + sta var_ch_Effect, x + jmp ft_jump_to_effect +: lda #EFF_SLIDE_DOWN + sta var_ch_Effect, x +: +.endif + ; Work-around for noise + cpx #$03 + bne :++ + cmp #EFF_SLIDE_UP + beq :+ + lda #EFF_SLIDE_UP + sta var_ch_Effect, x + jmp ft_jump_to_effect + +; rts +: lda #EFF_SLIDE_DOWN + sta var_ch_Effect, x +: ;rts + jmp ft_jump_to_effect + +ft_calc_period: + + ; Load period + lda var_ch_TimerPeriodLo, x + sta var_ch_PeriodCalcLo, x + lda var_ch_TimerPeriodHi, x + sta var_ch_PeriodCalcHi, x + +.ifdef USE_VRC7 + cpx #VRC7_CHANNEL + bcc :+ + lsr var_ch_PeriodCalcHi, x + ror var_ch_PeriodCalcLo, x + lsr var_ch_PeriodCalcHi, x + ror var_ch_PeriodCalcLo, x +: +.endif + + ; Apply fine pitch + lda var_ch_FinePitch, x + cmp #$80 + beq @Skip + lda var_ch_Note, x ; Skip on note off as well to avoid problems with VRC7 + beq @Skip + +; .if 0 + +.ifdef USE_N163 + lda ft_channel_type, x + cmp #CHAN_N163 + bne :+ + ; N163 pitch + lda #$00 + sta var_Temp16 + 1 + lda var_ch_FinePitch, x + asl a + sta var_Temp16 + rol var_Temp16 + 1 + asl var_Temp16 + rol var_Temp16 + 1 + asl var_Temp16 + rol var_Temp16 + 1 + asl var_Temp16 + rol var_Temp16 + 1 + clc + lda var_ch_PeriodCalcLo, x + adc #$00 + sta var_ch_PeriodCalcLo, x + lda var_ch_PeriodCalcHi, x + adc #$08 + sta var_ch_PeriodCalcHi, x + sec + lda var_ch_PeriodCalcLo, x + sbc var_Temp16 + sta var_ch_PeriodCalcLo, x + lda var_ch_PeriodCalcHi, x + sbc var_Temp16 + 1 + sta var_ch_PeriodCalcHi, x + jmp @Skip +: +.endif + + clc + lda var_ch_PeriodCalcLo, x + adc #$80 + sta var_ch_PeriodCalcLo, x + lda var_ch_PeriodCalcHi, x + adc #$00 + sta var_ch_PeriodCalcHi, x + sec + lda var_ch_PeriodCalcLo, x + sbc var_ch_FinePitch, x + sta var_ch_PeriodCalcLo, x + lda var_ch_PeriodCalcHi, x + sbc #$00 + sta var_ch_PeriodCalcHi, x +@Skip: + + jsr ft_vibrato + jsr ft_tremolo + + rts + + +; +; Portamento +; +ft_portamento: + lda var_ch_EffParam, x ; Check portamento, if speed > 0 + beq @NoPortamento + lda var_ch_PortaToLo, x ; and if freq > 0, else stop + ora var_ch_PortaToHi, x + beq @NoPortamento + lda var_ch_TimerPeriodHi, x ; Compare high byte + cmp var_ch_PortaToHi, x + bcc @Increase + bne @Decrease + lda var_ch_TimerPeriodLo, x ; Compare low byte + cmp var_ch_PortaToLo, x + bcc @Increase + bne @Decrease + ;rts ; done + jmp ft_post_effects + +@Decrease: ; Decrease period + lda var_ch_EffParam, x + sta var_Temp16 + lda #$00 + sta var_Temp16 + 1 + jsr ft_period_remove + +.if 0 + sec + lda var_ch_TimerPeriodLo, x + sbc var_ch_EffParam, x + sta var_ch_TimerPeriodLo, x + lda var_ch_TimerPeriodHi, x + sbc #$00 + sta var_ch_TimerPeriodHi, x +.endif + ; Check if sign bit has changed, if so load the desired period +; lda var_ch_TimerPeriodHi, x ; Compare high byte + cmp var_ch_PortaToHi, x + bcc @LoadPeriod + bmi @LoadPeriod + bne @NoPortamento + lda var_ch_TimerPeriodLo, x ; Compare low byte + cmp var_ch_PortaToLo, x + bcc @LoadPeriod +; rts ; Portamento is done at this point + jmp ft_post_effects + +@Increase: ; Increase period + lda var_ch_EffParam, x + sta var_Temp16 + lda #$00 + sta var_Temp16 + 1 + jsr ft_period_add +.if 0 + clc + lda var_ch_TimerPeriodLo, x + adc var_ch_EffParam, x + sta var_ch_TimerPeriodLo, x + lda var_ch_TimerPeriodHi, x + adc #$00 + sta var_ch_TimerPeriodHi, x +.endif + ; Check if sign bit has changed, if so load the desired period + lda var_ch_PortaToHi, x ; Compare high byte + cmp var_ch_TimerPeriodHi, x + bcc @LoadPeriod + bne @NoPortamento + lda var_ch_PortaToLo, x ; Compare low byte + cmp var_ch_TimerPeriodLo, x + bcc @LoadPeriod +; rts + jmp ft_post_effects + +@LoadPeriod: ; Load the correct period + lda var_ch_PortaToLo, x + sta var_ch_TimerPeriodLo, x + lda var_ch_PortaToHi, x + sta var_ch_TimerPeriodHi, x +@NoPortamento: + jmp ft_post_effects + +ft_portamento_up: + lda var_ch_EffParam, x + sta var_Temp16 + lda #$00 + sta var_Temp16 + 1 + jsr ft_period_remove + jsr ft_limit_freq + jmp ft_post_effects +ft_portamento_down: + lda var_ch_EffParam, x + sta var_Temp16 + lda #$00 + sta var_Temp16 + 1 + jsr ft_period_add + jsr ft_limit_freq + jmp ft_post_effects + +ft_period_add: +.ifdef USE_N163 + lda ft_channel_type, x + cmp #CHAN_N163 + bne :+ + ; Multiply by 4 + asl var_Temp16 + rol var_Temp16 + 1 + asl var_Temp16 + rol var_Temp16 + 1 +: +.endif + clc + lda var_ch_TimerPeriodLo, x + adc var_Temp16 + sta var_ch_TimerPeriodLo, x + lda var_ch_TimerPeriodHi, x + adc var_Temp16 + 1 + sta var_ch_TimerPeriodHi, x + bcc :+ ; Do not wrap + lda #$FF + sta var_ch_TimerPeriodLo, x + sta var_ch_TimerPeriodHi, x +: rts +ft_period_remove: +.ifdef USE_N163 + lda ft_channel_type, x + cmp #CHAN_N163 + bne :+ + ; Multiply by 4 + asl var_Temp16 + rol var_Temp16 + 1 + asl var_Temp16 + rol var_Temp16 + 1 +: +.endif + sec + lda var_ch_TimerPeriodLo, x + sbc var_Temp16 + sta var_ch_TimerPeriodLo, x + lda var_ch_TimerPeriodHi, x + sbc var_Temp16 + 1 + sta var_ch_TimerPeriodHi, x + bcs :+ ; Do not wrap + lda #$00 + sta var_ch_TimerPeriodLo, x + sta var_ch_TimerPeriodHi, x +: rts + +; +; Note slide +; +ft_slide_up: + sec + lda var_ch_TimerPeriodLo, x + sbc var_ch_EffParam, x + sta var_ch_TimerPeriodLo, x + lda var_ch_TimerPeriodHi, x + sbc #$00 + sta var_ch_TimerPeriodHi, x + bmi ft_slide_done + cmp var_ch_PortaToHi, x ; Compare high byte + bcc ft_slide_done + bne ft_slide_not_done + lda var_ch_TimerPeriodLo, x + cmp var_ch_PortaToLo, x ; Compare low byte + bcc ft_slide_done + + jmp ft_post_effects + +ft_slide_down: + clc + lda var_ch_TimerPeriodLo, x + adc var_ch_EffParam, x + sta var_ch_TimerPeriodLo, x + lda var_ch_TimerPeriodHi, x + adc #$00 + sta var_ch_TimerPeriodHi, x + + cmp var_ch_PortaToHi, x ; Compare high byte + bcc ft_slide_not_done + bne ft_slide_done + lda var_ch_TimerPeriodLo, x + cmp var_ch_PortaToLo, x ; Compare low byte + bcs ft_slide_done + jmp ft_post_effects + +ft_slide_done: + lda var_ch_PortaToLo, x + sta var_ch_TimerPeriodLo, x + lda var_ch_PortaToHi, x + sta var_ch_TimerPeriodHi, x + + lda #EFF_NONE ; Reset effect + sta var_ch_Effect, x + sta var_ch_PortaToLo, x + sta var_ch_PortaToHi, x + +ft_slide_not_done: + jmp ft_post_effects + +; +; Arpeggio +; +ft_arpeggio: + lda var_ch_ArpeggioCycle, x + cmp #$01 + beq @LoadSecond + cmp #$02 + beq @LoadThird + lda var_ch_Note, x ; Load first note + jsr ft_translate_freq_only + inc var_ch_ArpeggioCycle, x + jmp ft_post_effects +@LoadSecond: ; Second note (second nybble) + lda var_ch_EffParam, x + lsr a + lsr a + lsr a + lsr a + clc + adc var_ch_Note, x + jsr ft_translate_freq_only + lda var_ch_EffParam, x ; see if cycle should reset here + and #$0F + bne @DoNextStep + sta var_ch_ArpeggioCycle, x + jmp ft_post_effects +@DoNextStep: + inc var_ch_ArpeggioCycle, x + jmp ft_post_effects +@LoadThird: ; Third note (first nybble) + lda var_ch_EffParam, x + and #$0F + clc + adc var_ch_Note, x + jsr ft_translate_freq_only + lda #$00 + sta var_ch_ArpeggioCycle, x + jmp ft_post_effects + +; Vibrato calculation +; +ft_vibrato: + lda var_ch_VibratoSpeed, x + bne :+ + rts +: clc + adc var_ch_VibratoPos, x ; Get next position + and #$3F + sta var_ch_VibratoPos, x + cmp #$10 + bcc @Phase1 + cmp #$20 + bcc @Phase2 + cmp #$30 + bcc @Phase3 + ; Phase 4: - 15 - (Phase - 48) + depth + sec + sbc #$30 + sta var_Temp + sec + lda #$0F + sbc var_Temp + ora var_ch_VibratoDepth, x + tay + lda ft_vibrato_table, y + jmp @Negate +@Phase1: + ; Phase 1: Phase + depth + ora var_ch_VibratoDepth, x + tay + lda ft_vibrato_table, y + sta var_Temp16 + lda #$00 + sta var_Temp16 + 1 + jmp @Calculate +@Phase2: + ; Phase 2: 15 - (Phase - 16) + depth + sec + sbc #$10 + sta var_Temp + sec + lda #$0F + sbc var_Temp + ora var_ch_VibratoDepth, x + tay + lda ft_vibrato_table, y + sta var_Temp16 + lda #$00 + sta var_Temp16 + 1 + jmp @Calculate +@Phase3: + ; Phase 3: - (Phase - 32) + depth + sec + sbc #$20 + ora var_ch_VibratoDepth, x + tay + lda ft_vibrato_table, y + +@Negate: + ; Invert result + eor #$FF + sta var_Temp16 + lda #$FF + sta var_Temp16 + 1 + clc + lda var_Temp16 + adc #$01 + sta var_Temp16 + lda var_Temp16 + 1 + adc #$00 + sta var_Temp16 + 1 + +@Calculate: + + ; Remove this if you don't need support for old vibrato + lda var_SongFlags + and #$02 + beq :+ + lda #$0F + clc + adc var_ch_VibratoDepth, x + tay + clc + lda ft_vibrato_table, y ; add depth + 1 + adc #$01 +; clc + adc var_Temp16 + sta var_Temp16 + lda var_Temp16 + 1 + adc #$00 + sta var_Temp16 + 1 + lsr var_Temp16 + 1 ; divide by 2 + ror var_Temp16 +: + +.ifdef USE_N163 + lda ft_channel_type, x + cmp #CHAN_N163 + bne @SkipN163 + asl var_Temp16 ; Multiply by 16 + rol var_Temp16 + 1 + asl var_Temp16 + rol var_Temp16 + 1 + asl var_Temp16 + rol var_Temp16 + 1 + asl var_Temp16 + rol var_Temp16 + 1 +@SkipN163: ; if (ft_channel_type, x != CHAN_N163) +.endif + +.ifdef USE_EXP + lda ft_channel_type, x + cmp #CHAN_N163 + beq @Inverted + cmp #CHAN_VRC7 + beq @Inverted + cmp #CHAN_FDS + beq @Inverted +.endif + + ; TODO use ft_period_remove + sec + lda var_ch_PeriodCalcLo, x + sbc var_Temp16 + sta var_ch_PeriodCalcLo, x + lda var_ch_PeriodCalcHi, x + sbc var_Temp16 + 1 + sta var_ch_PeriodCalcHi, x + rts + +@Inverted: + clc + lda var_ch_PeriodCalcLo, x + adc var_Temp16 + sta var_ch_PeriodCalcLo, x + lda var_ch_PeriodCalcHi, x + adc var_Temp16 + 1 + sta var_ch_PeriodCalcHi, x + rts + +; Tremolo calculation +; +ft_tremolo: + lda var_ch_TremoloSpeed, x + bne @DoTremolo +; lda var_ch_Volume, x +; sta var_ch_OutVolume, x + lda #$00 + sta var_ch_TremoloResult, x + rts +@DoTremolo: + clc + adc var_ch_TremoloPos, x ; Get next position + and #$3F + sta var_ch_TremoloPos, x + lsr a ; Divide by 2 + cmp #$10 + bcc @Phase1 +; Phase 2 + ; 15 - (Phase - 16) + depth + sec + sbc #$10 + sta var_Temp + sec + lda #$0F + sbc var_Temp + ora var_ch_TremoloDepth, x + tay + lda ft_vibrato_table, y + lsr a + sta var_Temp + jmp @Calculate +@Phase1: + ; Phase + depth + ora var_ch_TremoloDepth, x + tay + lda ft_vibrato_table, y + lsr a + sta var_Temp +@Calculate: + sta var_ch_TremoloResult, x +.if 0 + sec + lda var_ch_Volume, x + sbc var_Temp + bmi :+ + sta var_ch_OutVolume, x + rts +: lda #$00 + sta var_ch_OutVolume, x +.endif + rts + diff --git a/src/lib/ft_drv/init.s b/src/lib/ft_drv/init.s new file mode 100644 index 0000000..f78e32f --- /dev/null +++ b/src/lib/ft_drv/init.s @@ -0,0 +1,596 @@ +; +; ft_sound_init +; +; Initializes the player and song number +; a = song number +; x = ntsc/pal +; +ft_music_init: + asl a + jsr ft_load_song + ; Kill APU registers + ; lda #$00 + ; ldx #$0B +; @LoadRegs: + ; sta $4000, x + ; dex + ; bne @LoadRegs + ; ldx #$06 +; @LoadRegs2: + ; sta $400D, x + ; dex + ; bne @LoadRegs2 + + lda #0 + tax +: + sta BUF_4000,x + inx + cpx #$10 + bne :- + + lda #$30 ; noise is special + ;sta $400C + sta BUF_400C + lda #$0F + sta $4015 ; APU control + lda #$08 + ;sta $4001 + sta BUF_4001 + ;sta $4005 + sta BUF_4005 + lda #$C0 + sta $4017 + lda #$40 + sta $4017 ; Disable frame IRQs + + lda #$FF ; Enable all channels + sta var_Channels + + sta var_ch_DPCM_EffPitch + sta var_ch_DPCMDAC + + ; Reset some variables for the wave channels + lda #$00 + tax +: sta var_ch_NoteCut, x + sta var_ch_Effect, x + sta var_ch_EffParam, x + sta var_ch_PortaToLo, x + sta var_ch_PortaToHi, x + sta var_ch_TimerPeriodLo, x + sta var_ch_TimerPeriodHi, x + inx + cpx #WAVE_CHANS + bne :- + + lda var_SongFlags + and #$02 + beq :++ + lda #48 + ldx #$00 +: sta var_ch_VibratoPos, x + inx + cpx #WAVE_CHANS + bne :- + lda #$00 +: + + ; DPCM + sta var_ch_NoteCut + (CHANNELS - 1) + +.ifdef USE_DPCM +.ifdef USE_N163 + ldx var_AllChannels + dex +.else + ldx #DPCM_CHANNEL +.endif + lda #$80 + sta var_ch_Note, x +.endif + +.ifdef USE_VRC6 + lda #$00 + sta $9003 +.endif + +.ifdef USE_MMC5 + lda #$03 + sta $5015 ; Enable channels +.endif + +.ifdef USE_N163 + jsr ft_init_n163 +.endif + +.ifdef USE_VRC7 + jsr ft_init_vrc7 +.endif + +.ifdef USE_FDS + jsr ft_init_fds +.endif + + rts + +; +; Prepare the player for a song +; +; NSF music data header: +; +; - Song list, 2 bytes +; - Instrument list, 2 bytes +; - DPCM instrument list, 2 bytes +; - DPCM sample list, 2 bytes +; - Flags, 1 byte +; - Pointer to wave tables, 2 bytes, if FDS is enabled +; - NTSC speed divider +; - PAL speed divider +; +ft_load_song: + pha + ; Get the header + lda ft_music_addr + sta var_Temp_Pointer + lda ft_music_addr + 1 + sta var_Temp_Pointer + 1 + + ; Read the header and store in RAM + ldy #$00 +@LoadAddresses: +.ifdef RELOCATE_MUSIC + clc + lda (var_Temp_Pointer), y + adc ft_music_addr + sta var_Song_list, y + iny + lda (var_Temp_Pointer), y ; Song list offset, high addr + adc ft_music_addr + 1 + sta var_Song_list, y +.else + lda (var_Temp_Pointer), y + sta var_Song_list, y + iny + lda (var_Temp_Pointer), y ; Song list offset, high addr + sta var_Song_list, y +.endif + iny + cpy #$08 ; 4 items + bne @LoadAddresses + + lda (var_Temp_Pointer), y ; Flags, 1 byte + sta var_SongFlags + iny + +.ifdef USE_FDS + ; Load FDS wave table pointer +.ifdef RELOCATE_MUSIC + clc + lda (var_Temp_Pointer), y + adc ft_music_addr + sta var_Wavetables + iny + lda (var_Temp_Pointer), y + adc ft_music_addr + 1 + sta var_Wavetables + 1 +.else + lda (var_Temp_Pointer), y + sta var_Wavetables + iny + lda (var_Temp_Pointer), y + sta var_Wavetables + 1 +.endif + iny +.endif + + cpx #$01 ; PAL / NTSC flag + beq @LoadPAL +.ifdef NTSC_PERIOD_TABLE + ; Load NTSC speed divider and frequency table + lda (var_Temp_Pointer), y + iny + sta var_Tempo_Dec + lda (var_Temp_Pointer), y + iny + sta var_Tempo_Dec + 1 + lda #ft_periods_ntsc + sta var_Note_Table + 1 +.ifdef USE_N163 + iny + iny +.endif +.endif + jmp @LoadDone +@LoadPAL: +.ifdef PAL_PERIOD_TABLE + ; Load PAL speed divider and frequency table + iny + iny + lda (var_Temp_Pointer), y + iny + sta var_Tempo_Dec + lda (var_Temp_Pointer), y + iny + sta var_Tempo_Dec + 1 + lda #ft_periods_pal + sta var_Note_Table + 1 +.endif + @LoadDone: + .ifdef USE_N163 + ; N163 channel count + lda (var_Temp_Pointer), y + iny + sta var_NamcoChannels + clc + adc #$04 ; TODO fix this, should not be hardcoded + sta var_EffChannels + adc #$01 + sta var_AllChannels + + ldx var_NamcoChannels + dex + txa + asl a + asl a + asl a + asl a + sta var_NamcoChannelsReg + + .endif + pla + tay + ; Load the song + jsr ft_load_track + + ; Clear variables to zero + ; Important! + ldx #$01 + stx var_PlayerFlags ; Player flags, bit 0 = playing + dex +@ClearChannels2: ; This clears the first four channels + lda #$7F + sta var_ch_VolColumn, x + lda #$80 + sta var_ch_FinePitch, x + lda #$00 + ; + ;lda #$00 + sta var_ch_VibratoSpeed, x + sta var_ch_TremoloSpeed, x + sta var_ch_Effect, x + sta var_ch_VolSlide, x + sta var_ch_NoteDelay, x + sta var_ch_ArpeggioCycle, x + ; + sta var_ch_Note, x + inx + +.ifdef USE_N163 + cpx var_EffChannels +.else + cpx #(CHANNELS - 1) +.endif + bne @ClearChannels2 + + ldx #$FF + ;stx var_ch_PrevFreqHigh ; Set prev freq to FF for Sq1 & 2 + ;stx var_ch_PrevFreqHigh + 1 + +.ifdef USE_DPCM + lda #$00 + sta var_ch_DPCM_Offset +.endif +.ifdef USE_MMC5 + stx var_ch_PrevFreqHighMMC5 + stx var_ch_PrevFreqHighMMC5 + 1 +.endif +.ifdef USE_VRC7 + stx var_ch_vrc7_CustomPatch +.endif + + inx ; Jump to the first frame + stx var_Current_Frame + jsr ft_load_frame + + jsr ft_calculate_speed + ;jsr ft_restore_speed + + lda #$00 + sta var_Tempo_Accum + sta var_Tempo_Accum + 1 + + rts + +; +; Load the track number in A +; +; Track headers: +; +; - Frame list address, 2 bytes +; - Number of frames, 1 byte +; - Pattern length, 1 byte +; - Speed, 1 byte +; - Tempo, 1 byte +; +ft_load_track: + ; Load track header address + lda var_Song_list + sta var_Temp16 + lda var_Song_list + 1 + sta var_Temp16 + 1 + + ; Get the real address, song number * 2 will be in Y here +.ifdef RELOCATE_MUSIC + clc + lda (var_Temp16), y + adc ft_music_addr + sta var_Temp_Pointer + iny + lda (var_Temp16), y + adc ft_music_addr + 1 + sta var_Temp_Pointer + 1 +.else + lda (var_Temp16), y + sta var_Temp_Pointer + iny + lda (var_Temp16), y + sta var_Temp_Pointer + 1 +.endif + + ; Read header + lda #$00 + tax + tay +.ifdef RELOCATE_MUSIC + clc + lda (var_Temp_Pointer), y ; Frame offset, low addr + adc ft_music_addr + sta var_Frame_List + iny + lda (var_Temp_Pointer), y ; Frame offset, high addr + adc ft_music_addr + 1 + sta var_Frame_List + 1 +.else + lda (var_Temp_Pointer), y ; Frame offset, low addr + sta var_Frame_List + iny + lda (var_Temp_Pointer), y ; Frame offset, high addr + sta var_Frame_List + 1 +.endif + iny +@ReadLoop: + lda (var_Temp_Pointer), y ; Frame count + sta var_Frame_Count, x + iny + inx + cpx #$06 + bne @ReadLoop + + rts + +; +; Load the frame in A for all channels +; +ft_load_frame: +.ifdef USE_BANKSWITCH + pha ; Frame bank + lda var_InitialBank + beq :+ +; sta $5FFA + sta $5FFB +: pla +.endif + + ; Get the entry in the frame list + asl A ; Multiply by two + clc ; And add the frame list addr to get + adc var_Frame_List ; the pattern list addr + sta var_Temp16 + lda #$00 + tay + tax + adc var_Frame_List + 1 + sta var_Temp16 + 1 + ; Get the entry in the pattern list +.ifdef RELOCATE_MUSIC + clc + lda (var_Temp16), y + adc ft_music_addr + sta var_Temp_Pointer + iny + lda (var_Temp16), y + adc ft_music_addr + 1 + sta var_Temp_Pointer + 1 +.else + lda (var_Temp16), y + sta var_Temp_Pointer + iny + lda (var_Temp16), y + sta var_Temp_Pointer + 1 +.endif + ; Iterate through the channels, x = channel + ldy #$00 ; Y = address + stx var_Pattern_Pos +@LoadPatternAddr: +.ifdef RELOCATE_MUSIC + clc + lda (var_Temp_Pointer), y ; Load the pattern address for the channel + adc ft_music_addr + sta var_ch_PatternAddrLo, x + iny + lda (var_Temp_Pointer), y ; Pattern address, high byte + adc ft_music_addr + 1 + sta var_ch_PatternAddrHi, x + iny +.else + lda (var_Temp_Pointer), y ; Load the pattern address for the channel + sta var_ch_PatternAddrLo, x + iny + lda (var_Temp_Pointer), y ; Pattern address, high byte + sta var_ch_PatternAddrHi, x + iny +.endif + lda #$00 + sta var_ch_NoteDelay, x + sta var_ch_Delay, x +; sta var_ch_LoopCounter, x + lda #$FF + sta var_ch_DefaultDelay, x + inx +.ifdef USE_N163 + cpx var_AllChannels +.else + cpx #CHANNELS +.endif + bne @LoadPatternAddr +; Bankswitch values +.ifdef USE_BANKSWITCH + lda var_SongFlags ; Check bankswitch flag + and #$01 + beq @SkipBankValues ; Skip if no bankswitch info is stored + ldx #$00 +@LoadBankValues: + lda (var_Temp_Pointer), y ; Pattern bank number + sta var_ch_Bank, x + iny + inx +.ifdef USE_N163 + cpx var_AllChannels +.else + cpx #CHANNELS +.endif + bne @LoadBankValues +@SkipBankValues: +.endif + + lda #$00 + sta var_Jump + sta var_Skip + +.ifdef ENABLE_ROW_SKIP + lda var_SkipTo + bne ft_SkipToRow + rts +; +; Skip to a certain row, this is NOT recommended in songs when CPU time is critical!! +; +ft_SkipToRow: + sta var_Pattern_Pos + ldx #$00 ; x = channel +@ChannelLoop: + lda var_Pattern_Pos + sta var_Temp2 ; Restore row count + lda #$00 + sta var_ch_NoteDelay, x + +@RowLoop: + ldy #$00 + lda var_ch_PatternAddrLo, x + sta var_Temp_Pattern + lda var_ch_PatternAddrHi, x + sta var_Temp_Pattern + 1 + +@ReadNote: + lda var_ch_NoteDelay, x ; First check if in the middle of a row delay + beq @NoRowDelay + dec var_ch_NoteDelay, x ; Decrease one + jmp @RowIsDone + +@NoRowDelay: + ; Read a row + lda (var_Temp_Pattern), y + bmi @Effect + + lda var_ch_DefaultDelay, x + cmp #$FF + bne @LoadDefaultDelay + iny + lda (var_Temp_Pattern), y + iny + + sta var_ch_NoteDelay, x + jmp @RowIsDone +@LoadDefaultDelay: + iny + sta var_ch_NoteDelay, x ; Store default delay +@RowIsDone: + ; Save the new address + clc + tya + adc var_Temp_Pattern + sta var_ch_PatternAddrLo, x + lda #$00 + adc var_Temp_Pattern + 1 + sta var_ch_PatternAddrHi, x + + dec var_Temp2 ; Next row + bne @RowLoop + + inx ; Next channel +.ifdef USE_N163 + cpx var_AllChannels +.else + cpx #CHANNELS +.endif + bne @ChannelLoop + lda #$00 + sta var_SkipTo + rts + +@Effect: + cmp #$80 + beq @LoadInstCmd + cmp #$82 + beq @EffectDuration + cmp #$84 + beq @EffectNoDuration + pha + cmp #$8E + beq @OneByteCommand + cmp #$92 + beq @OneByteCommand + cmp #$A2 + beq @OneByteCommand + and #$F0 + cmp #$F0 ; See if volume + beq @OneByteCommand + cmp #$E0 ; See if a quick instrument command + beq @LoadInst + iny ; Command takes two bytes +@OneByteCommand: ; Command takes one byte + iny + pla + jmp @ReadNote ; A new command or note is immediately following +@EffectDuration: + iny + lda (var_Temp_Pattern), y + iny + sta var_ch_DefaultDelay, x + jmp @ReadNote +@EffectNoDuration: + iny + lda #$FF + sta var_ch_DefaultDelay, x + jmp @ReadNote +@LoadInstCmd: ; mult-byte + iny + lda (var_Temp_Pattern), y + iny + jsr ft_load_instrument + jmp @ReadNote +@LoadInst: ; single byte + iny + pla + and #$0F + asl a + jsr ft_load_instrument + jmp @ReadNote + +.else ; ENABLE_ROW_SKIP + rts +.endif ; ENABLE_ROW_SKIP diff --git a/src/lib/ft_drv/instrument.s b/src/lib/ft_drv/instrument.s new file mode 100644 index 0000000..8cdc448 --- /dev/null +++ b/src/lib/ft_drv/instrument.s @@ -0,0 +1,668 @@ +; Update the instrument for channel X +; +; I might consider storing the sequence address variables in ZP?? +; +ft_run_instrument: +.ifdef USE_VRC7 + lda ft_channel_type, x + cmp #CHAN_VRC7 + bne :+ + rts +: +.endif + ; Volume + ; + lda var_ch_SeqVolume + SFX_WAVE_CHANS, x ; High part of address = 0 mean sequence is disabled + beq @SkipVolumeUpdate + sta var_Temp_Pointer + 1 + lda var_ch_SeqVolume, x ; Store the sequence address in a zp variable + sta var_Temp_Pointer + lda var_ch_SequencePtr1, x ; Sequence item index + cmp #$FF + beq @SkipVolumeUpdate ; Skip if end is reached + jsr ft_run_sequence ; Run an item in the sequence + sta var_ch_SequencePtr1, x ; Store new index + lda var_sequence_result ; Take care of the result + sta var_ch_Volume, x +@SkipVolumeUpdate: + + ; Arpeggio + ; + lda var_ch_SeqArpeggio + SFX_WAVE_CHANS, x + beq @SkipArpeggioUpdate + sta var_Temp_Pointer + 1 + lda var_ch_SeqArpeggio, x + sta var_Temp_Pointer + lda var_ch_SequencePtr2, x + cmp #$FF + beq @RestoreArpeggio;@SkipArpeggioUpdate + jsr ft_run_sequence + sta var_ch_SequencePtr2, x + lda var_ch_Note, x ; No arp if no note + beq @SkipArpeggioUpdate + + ldy #$03 + lda (var_Temp_Pointer), y + beq @Absolute + cmp #$01 + beq @Fixed +@Relative: + ; Relative + clc + lda var_ch_Note, x + adc var_sequence_result + cmp #$01 + bcc :+ + cmp #$5F + bcc :++ + lda #$5F + bne :++ +: lda #$01 +: sta var_ch_Note, x + jmp @ArpDone +@Fixed: + ; Fixed + lda var_sequence_result + clc + adc #$01 + jmp @ArpDone +@Absolute: + ; Absolute + clc + lda var_ch_Note, x + adc var_sequence_result + beq :+ + bpl :++ +: lda #$01 +: cmp #$60 + bcc :+ + lda #$60 +: +@ArpDone: + jsr ft_translate_freq_only + lda #$01 + sta var_ch_ArpFixed, x + jmp @SkipArpeggioUpdate + +@RestoreArpeggio: + ldy #$03 + lda (var_Temp_Pointer), y + beq @SkipArpeggioUpdate + lda var_ch_ArpFixed, x + beq @SkipArpeggioUpdate + lda var_ch_Note, x ; No arp if no note + jsr ft_translate_freq_only + lda #$00 + sta var_ch_ArpFixed, x + +@SkipArpeggioUpdate: + + ; Pitch bend + ; + lda var_ch_SeqPitch + SFX_WAVE_CHANS, x + beq @SkipPitchUpdate + sta var_Temp_Pointer + 1 + lda var_ch_SeqPitch, x + sta var_Temp_Pointer + lda var_ch_SequencePtr3, x + cmp #$FF + beq @SkipPitchUpdate + jsr ft_run_sequence + sta var_ch_SequencePtr3, x + + ; Check this + clc + lda var_sequence_result + adc var_ch_TimerPeriodLo, x + sta var_ch_TimerPeriodLo, x + lda var_sequence_result + bpl @NoNegativePitch + lda #$FF + bmi @LoadLowPitch +@NoNegativePitch: + lda #$00 +@LoadLowPitch: + adc var_ch_TimerPeriodHi, x + sta var_ch_TimerPeriodHi, x + jsr ft_limit_freq + ; ^^^^^^^^^^ + + ; Save pitch +@SkipPitchUpdate: + ; HiPitch bend + ; + lda var_ch_SeqHiPitch + SFX_WAVE_CHANS, x + beq @SkipHiPitchUpdate + sta var_Temp_Pointer + 1 + lda var_ch_SeqHiPitch, x + sta var_Temp_Pointer + lda var_ch_SequencePtr4, x + cmp #$FF + beq @SkipHiPitchUpdate + jsr ft_run_sequence + sta var_ch_SequencePtr4, x + + ; Check this + lda var_sequence_result + sta var_Temp16 + rol a + bcc @AddHiPitch + lda #$FF + sta var_Temp16 + 1 + jmp @StoreHiPitch +@AddHiPitch: + lda #$00 + sta var_Temp16 + 1 +@StoreHiPitch: + ldy #$04 +: clc + rol var_Temp16 ; multiply by 2 + rol var_Temp16 + 1 + dey + bne :- + + clc + lda var_Temp16 + adc var_ch_TimerPeriodLo, x + sta var_ch_TimerPeriodLo, x + lda var_Temp16 + 1 + adc var_ch_TimerPeriodHi, x + sta var_ch_TimerPeriodHi, x + jsr ft_limit_freq + ; ^^^^^^^^^^ + +@SkipHiPitchUpdate: + ; Duty cycle/noise mode + ; + lda var_ch_SeqDutyCycle + SFX_WAVE_CHANS, x + beq @SkipDutyUpdate + sta var_Temp_Pointer + 1 + lda var_ch_SeqDutyCycle, x + sta var_Temp_Pointer + lda var_ch_SequencePtr5, x + cmp #$FF + beq @SkipDutyUpdate + jsr ft_run_sequence + sta var_ch_SequencePtr5, x + lda var_sequence_result + pha + lda var_ch_DutyCycle, x + and #$F0 + sta var_ch_DutyCycle, x + pla + ora var_ch_DutyCycle, x + sta var_ch_DutyCycle, x +.ifdef USE_N163 + lda ft_channel_type, x + cmp #CHAN_N163 + bne :+ + jsr ft_n163_load_wave2 +: +.endif + ; Save pitch +@SkipDutyUpdate: + rts + + +; +; Process a sequence, next position is returned in A +; +; In: A = Sequence index +; Out: A = New sequence index +; +ft_run_sequence: + clc + adc #$04 ; Offset is 4 items + tay + lda (var_Temp_Pointer), y + sta var_sequence_result + dey + dey + dey ; (remove) + tya + ldy #$00 ; Check if halt point + cmp (var_Temp_Pointer), y + beq @HaltSequence + ldy #$02 ; Check release point + cmp (var_Temp_Pointer), y + beq @ReleasePoint + rts +@HaltSequence: ; Stop the sequence + iny + lda (var_Temp_Pointer), y ; Check loop point + cmp #$FF + bne @LoopSequence +; lda #$FF ; Disable sequence by loading $FF into length + rts +@LoopSequence: ; Just return A + pha + lda var_ch_State, x + bne :+ + pla + rts ; Return new index +: ldy #$02 ; Check release point + lda (var_Temp_Pointer), y + bne :+ + pla ; Release point not found, loop + rts +: pla ; Release point found, don't loop + lda #$FF + rts +@ReleasePoint: ; Release point has been reached + sta var_Temp ; Save index + lda var_ch_State, x + bne @Releasing + dey + lda (var_Temp_Pointer), y ; Check loop point + cmp #$FF + bne @LoopSequence + lda var_Temp + sec ; Step back one step + sbc #$01 + rts +@Releasing: ; Run release sequence + lda var_Temp + rts + +.macro release sequence, sequence_pointer + lda sequence + SFX_WAVE_CHANS, x + beq :+;+ + sta var_Temp_Pointer + 1 + lda sequence, x + sta var_Temp_Pointer + ldy #$02 + lda (var_Temp_Pointer), y + beq :+ ; Release not available + sec + sbc #$01 + sta sequence_pointer, x +: +.endmacro + +; Called on note release instruction +; +ft_instrument_release: + tya + pha + + release var_ch_SeqVolume, var_ch_SequencePtr1 + release var_ch_SeqArpeggio, var_ch_SequencePtr2 + release var_ch_SeqPitch, var_ch_SequencePtr3 + release var_ch_SeqHiPitch, var_ch_SequencePtr4 + release var_ch_SeqDutyCycle, var_ch_SequencePtr5 + + pla + tay + rts + +; Reset instrument sequences +; +ft_reset_instrument: + +.ifdef USE_FDS + cpx #FDS_CHANNEL + bne :+ + lda var_ch_ModDelay + sta var_ch_ModDelayTick +; lda #$00 +; sta $4085 +; lda #$80 +; sta $4087 +; rts +: +.endif + + lda #$00 + sta var_ch_SequencePtr1, x + sta var_ch_SequencePtr2, x + sta var_ch_SequencePtr3, x + sta var_ch_SequencePtr4, x + sta var_ch_SequencePtr5, x + rts + +; Macros + + +; Macro used to load instrument envelopes +.macro load_inst seq_addr, seq_ptr + + ror var_Temp3 + bcc :++ +.ifdef RELOCATE_MUSIC + clc + lda (var_Temp_Pointer), y + adc ft_music_addr + sta var_Temp16 + iny + lda (var_Temp_Pointer), y + adc ft_music_addr + 1 + sta var_Temp16 + 1 + iny +.else + lda (var_Temp_Pointer), y + sta var_Temp16 + iny + lda (var_Temp_Pointer), y + sta var_Temp16 + 1 + iny +.endif + + lda var_Temp16 + cmp seq_addr, x + bne :+ + lda var_Temp16 + 1 + cmp seq_addr + SFX_WAVE_CHANS, x + bne :+ + + ; Both equal, do not touch anything + jmp :+++ + +: lda var_Temp16 + sta seq_addr, x + lda var_Temp16 + 1 + sta seq_addr + SFX_WAVE_CHANS, x + lda #$00 + sta seq_ptr, x + jmp :++ ; branch always + +: lda #$00 + sta seq_addr, x + sta seq_addr + SFX_WAVE_CHANS, x +: + +.endmacro + +; +; Load instrument (y = saved in var_Temp) +; +; A = instrument number +; +ft_load_instrument: + + ; Instrument_pointer_list + a => instrument_address + ; instrument_address + ft_music_addr => instrument_data + + sta var_Temp3 ; used by VRC7 + + ; Get the instrument data pointer + sty var_Temp + ldy #$00 + clc + adc var_Instrument_list + sta var_Temp16 + tya + adc var_Instrument_list + 1 + sta var_Temp16 + 1 + + ; Get the instrument +.ifdef RELOCATE_MUSIC + clc + lda (var_Temp16), y + adc ft_music_addr + sta var_Temp_Pointer + iny + lda (var_Temp16), y + adc ft_music_addr + 1 + sta var_Temp_Pointer + 1 +.else + lda (var_Temp16), y + sta var_Temp_Pointer + iny + lda (var_Temp16), y + sta var_Temp_Pointer + 1 +.endif + + ; Jump to the instrument setup routine +; ldy var_Temp + lda ft_channel_type, x + tay + lda ft_load_inst_pointers, y + sta var_Temp16 + iny + lda ft_load_inst_pointers, y + sta var_Temp16 + 1 + ldy #$00 + jmp (var_Temp16) + +ft_load_inst_pointers: + .word ft_load_instrument_2a03 ; 2A03 + .word ft_load_instrument_2a03 ; VRC6 + .word ft_load_instrument_vrc7 ; VRC7 + .word ft_load_instrument_fds ; FDS + .word ft_load_instrument_2a03 ; MMC5 + .word ft_load_instrument_n163 ; N163 + +; Load 2A03 instrument +ft_load_instrument_2a03: + ; Read instrument data, var_Temp_Pointer points to instrument data + lda (var_Temp_Pointer), y ; sequence switch + sta var_Temp3 + iny + + load_inst var_ch_SeqVolume, var_ch_SequencePtr1 + load_inst var_ch_SeqArpeggio, var_ch_SequencePtr2 + load_inst var_ch_SeqPitch, var_ch_SequencePtr3 + load_inst var_ch_SeqHiPitch, var_ch_SequencePtr4 + load_inst var_ch_SeqDutyCycle, var_ch_SequencePtr5 + + ldy var_Temp + rts + +; Load FDS instrument +ft_load_instrument_fds: +.ifdef USE_FDS + ; Read FDS instrument + ldy #$00 + lda (var_Temp_Pointer), y ; Load wave index + iny + pha + + ; Load modulation table + jsr ft_reset_modtable +: + lda (var_Temp_Pointer), y + pha + and #$07 + sta $4088 + pla + lsr a + lsr a + lsr a + sta $4088 + iny + cpy #$11 + bne :- + + lda (var_Temp_Pointer), y ; Modulation delay + iny + sta var_ch_ModDelay + lda (var_Temp_Pointer), y ; Modulation depth + iny + sta var_ch_ModDepth + lda (var_Temp_Pointer), y ; Modulation freq low + iny + sta var_ch_ModRate + lda (var_Temp_Pointer), y ; Modulation freq high + sta var_ch_ModRate + 1 + + pla ; Get wave index + jsr ft_load_fds_wave + + ; Finish by loading sequences + ldy #$15 + jmp ft_load_instrument_2a03 +.endif + +; Load VRC7 instrument +ft_load_instrument_vrc7: +.ifdef USE_VRC7 + ; Read VRC7 instrument + ldy #$00 + lda (var_Temp_Pointer), y ; Load patch number + sta var_ch_vrc7_Patch - VRC7_CHANNEL, x ; vrc7 channel offset + sta var_ch_vrc7_DefPatch - VRC7_CHANNEL, x + bne :+ ; Skip custom settings if patch > 0 + + ; Store path to custom patch settings + clc + lda var_Temp_Pointer + adc #$01 + sta var_ch_vrc7_CustomLo - VRC7_CHANNEL, x + lda var_Temp_Pointer + 1 + adc #$00 + sta var_ch_vrc7_CustomHi - VRC7_CHANNEL, x + +: ldy var_Temp + rts +.endif + +; Load N163 instrument +ft_load_instrument_n163: +.ifdef USE_N163 + ldy #$00 + lda (var_Temp_Pointer), y + sta var_ch_WaveLen - N163_OFFSET, x + iny + lda (var_Temp_Pointer), y + sta var_ch_WavePos - N163_OFFSET, x + iny +.ifdef RELOCATE_MUSIC + clc + lda (var_Temp_Pointer), y + adc ft_music_addr + sta var_ch_WavePtrLo - N163_OFFSET, x + iny + lda (var_Temp_Pointer), y + adc ft_music_addr + 1 + sta var_ch_WavePtrHi - N163_OFFSET, x + iny +.else + lda (var_Temp_Pointer), y + sta var_ch_WavePtrLo - N163_OFFSET, x + iny + lda (var_Temp_Pointer), y + sta var_ch_WavePtrHi - N163_OFFSET, x + iny +.endif + lda var_NamcoInstrument, x + cmp var_Temp3 + beq :+ + lda #$00 ; reset wave + sta var_ch_DutyCycle, x + lda var_Temp3 + ; Load N163 wave +; jsr ft_n163_load_wave +: sta var_NamcoInstrument, x + jsr ft_load_instrument_2a03 + jsr ft_n163_load_wave2 + ldy var_Temp + rts +.endif + +; Make sure the period doesn't exceed max or min +ft_limit_freq: + + ; Jump to the instrument setup routine + lda ft_channel_type, x + tay + lda ft_limit_pointers, y + sta var_Temp16 + iny + lda ft_limit_pointers, y + sta var_Temp16 + 1 + ldy #$00 + jmp (var_Temp16) + + +ft_limit_pointers: + .word ft_limit_period_2a03 ; 2A03 + .word ft_limit_period_vrc6 ; VRC6 + .word ft_limit_period_vrc7 ; VRC7 + .word ft_limit_period_vrc6 ; FDS + .word ft_limit_period_2a03 ; MMC5 + .word ft_limit_period_n163 ; N163 + +; N163: no limits +ft_limit_period_n163: +ft_limit_period_vrc7: + rts + +; 2A03: period is between 0 to $7FF +ft_limit_period_2a03: + lda var_ch_TimerPeriodHi, x + bmi @LimitMin + cmp #$08 + bcc @NoLimit + lda #$07 + sta var_ch_TimerPeriodHi, x + lda #$FF + sta var_ch_TimerPeriodLo, x +@NoLimit: + rts +@LimitMin: + lda #$00 + sta var_ch_TimerPeriodLo, x + sta var_ch_TimerPeriodHi, x + rts + +; VRC6: period is between 0 to $FFF +ft_limit_period_vrc6: + lda var_ch_TimerPeriodHi, x + bmi @LimitMin + cmp #$10 + bcc @NoLimit + lda #$0F + sta var_ch_TimerPeriodHi, x + lda #$FF + sta var_ch_TimerPeriodLo, x +@NoLimit: + rts +@LimitMin: + lda #$00 + sta var_ch_TimerPeriodLo, x + sta var_ch_TimerPeriodHi, x + rts + +.if 0 + + lda var_ch_TimerPeriodHi, x + bmi @LimitMin ; period < 0 +.ifdef USE_VRC6 + cpx #VRC6_CHANNELS + bcc :+ + cmp #$10 ; period > $FFF + bcc @NoLimit + lda #$0F + sta var_ch_TimerPeriodHi, x + lda #$FF + sta var_ch_TimerPeriodLo, x + rts +: +.endif +.ifdef USE_FDS + cpx #FDS_CHANNEL + bne :+ + cmp #$11 ; period > $1000? + bcc @NoLimit + lda #$10 + sta var_ch_TimerPeriodHi, x + lda #$FF + sta var_ch_TimerPeriodLo, x + rts +: +.endif + cmp #$08 ; period > $7FF + bcc @NoLimit + lda #$07 + sta var_ch_TimerPeriodHi, x + lda #$FF + sta var_ch_TimerPeriodLo, x +@NoLimit: + rts +@LimitMin: + lda #$00 + sta var_ch_TimerPeriodLo, x + sta var_ch_TimerPeriodHi, x + rts + +.endif \ No newline at end of file diff --git a/src/lib/ft_drv/periods.s b/src/lib/ft_drv/periods.s new file mode 100644 index 0000000..cf47249 --- /dev/null +++ b/src/lib/ft_drv/periods.s @@ -0,0 +1,65 @@ +; 2A03 NTSC +.ifdef NTSC_PERIOD_TABLE +ft_periods_ntsc: + .word $0D5B, $0C9C, $0BE6, $0B3B, $0A9A, $0A01, $0972, $08EA, $086A, $07F1, $077F, $0713 + .word $06AD, $064D, $05F3, $059D, $054C, $0500, $04B8, $0474, $0434, $03F8, $03BF, $0389 + .word $0356, $0326, $02F9, $02CE, $02A6, $0280, $025C, $023A, $021A, $01FB, $01DF, $01C4 + .word $01AB, $0193, $017C, $0167, $0152, $013F, $012D, $011C, $010C, $00FD, $00EF, $00E1 + .word $00D5, $00C9, $00BD, $00B3, $00A9, $009F, $0096, $008E, $0086, $007E, $0077, $0070 + .word $006A, $0064, $005E, $0059, $0054, $004F, $004B, $0046, $0042, $003F, $003B, $0038 + .word $0034, $0031, $002F, $002C, $0029, $0027, $0025, $0023, $0021, $001F, $001D, $001B + .word $001A, $0018, $0017, $0015, $0014, $0013, $0012, $0011, $0010, $000F, $000E, $000D +.endif + +; 2A03 PAL +.ifdef PAL_PERIOD_TABLE +ft_periods_pal: + .word $0C68, $0BB6, $0B0E, $0A6F, $09D9, $094B, $08C6, $0848, $07D1, $0760, $06F6, $0692 + .word $0634, $05DB, $0586, $0537, $04EC, $04A5, $0462, $0423, $03E8, $03B0, $037B, $0349 + .word $0319, $02ED, $02C3, $029B, $0275, $0252, $0231, $0211, $01F3, $01D7, $01BD, $01A4 + .word $018C, $0176, $0161, $014D, $013A, $0129, $0118, $0108, $00F9, $00EB, $00DE, $00D1 + .word $00C6, $00BA, $00B0, $00A6, $009D, $0094, $008B, $0084, $007C, $0075, $006E, $0068 + .word $0062, $005D, $0057, $0052, $004E, $0049, $0045, $0041, $003E, $003A, $0037, $0034 + .word $0031, $002E, $002B, $0029, $0026, $0024, $0022, $0020, $001E, $001D, $001B, $0019 + .word $0018, $0016, $0015, $0014, $0013, $0012, $0011, $0010, $000F, $000E, $000D, $000C +.endif + +; VRC6 Sawtooth +.ifdef VRC6_PERIOD_TABLE +ft_periods_sawtooth: + .word $0F44, $0E69, $0D9A, $0CD6, $0C1E, $0B70, $0ACB, $0A30, $099E, $0913, $0891, $0816 + .word $07A2, $0734, $06CC, $066B, $060E, $05B7, $0565, $0518, $04CE, $0489, $0448, $040A + .word $03D0, $0399, $0366, $0335, $0307, $02DB, $02B2, $028B, $0267, $0244, $0223, $0205 + .word $01E8, $01CC, $01B2, $019A, $0183, $016D, $0159, $0145, $0133, $0122, $0111, $0102 + .word $00F3, $00E6, $00D9, $00CC, $00C1, $00B6, $00AC, $00A2, $0099, $0090, $0088, $0080 + .word $0079, $0072, $006C, $0066, $0060, $005B, $0055, $0051, $004C, $0048, $0044, $0040 + .word $003C, $0039, $0035, $0032, $002F, $002D, $002A, $0028, $0025, $0023, $0021, $001F + .word $001E, $001C, $001A, $0019, $0017, $0016, $0015, $0013, $0012, $0011, $0010, $000F +.endif + +; FDS +.ifdef FDS_PERIOD_TABLE +ft_periods_fds: + .word $0013, $0014, $0016, $0017, $0018, $001A, $001B, $001D, $001E, $0020, $0022, $0024 + .word $0026, $0029, $002B, $002E, $0030, $0033, $0036, $0039, $003D, $0040, $0044, $0048 + .word $004D, $0051, $0056, $005B, $0061, $0066, $006C, $0073, $007A, $0081, $0089, $0091 + .word $0099, $00A2, $00AC, $00B6, $00C1, $00CD, $00D9, $00E6, $00F3, $0102, $0111, $0121 + .word $0133, $0145, $0158, $016D, $0182, $0199, $01B2, $01CB, $01E7, $0204, $0222, $0243 + .word $0265, $028A, $02B0, $02D9, $0304, $0332, $0363, $0397, $03CD, $0407, $0444, $0485 + .word $04CA, $0513, $0560, $05B2, $0609, $0665, $06C6, $072D, $079B, $080E, $0889, $090B + .word $0994, $0A26, $0AC1, $0B64, $0C12, $0CCA, $0D8C, $0E5B, $0F35, $101D, $1112, $1216 +.endif + +; N163 +.ifdef N163_PERIOD_TABLE +ft_periods_n163: + .word $023E, $0260, $0285, $02AB, $02D4, $02FF, $032C, $035D, $0390, $03C6, $0400, $043D + .word $047D, $04C1, $050A, $0557, $05A8, $05FE, $0659, $06BA, $0720, $078D, $0800, $087A + .word $08FB, $0983, $0A14, $0AAE, $0B50, $0BFD, $0CB3, $0D74, $0E41, $0F1A, $1000, $10F4 + .word $11F6, $1307, $1429, $155C, $16A1, $17FA, $1967, $1AE9, $1C83, $1E35, $2001, $21E8 + .word $23EC, $260F, $2852, $2AB8, $2D43, $2FF4, $32CE, $35D3, $3906, $3C6A, $4002, $43D1 + .word $47D9, $4C1F, $50A5, $5571, $5A86, $5FE8, $659C, $6BA7, $720D, $78D5, $8005, $87A2 + .word $8FB2, $983E, $A14B, $AAE3, $B50C, $BFD0, $CB38, $D74E, $E41B, $F1AB, $FFFF, $FFFF + .word $FFFF, $FFFF, $FFFF, $FFFF, $FFFF, $FFFF, $FFFF, $FFFF, $FFFF, $FFFF, $FFFF, $FFFF +.endif + diff --git a/src/lib/ft_drv/player.s b/src/lib/ft_drv/player.s new file mode 100644 index 0000000..307f68c --- /dev/null +++ b/src/lib/ft_drv/player.s @@ -0,0 +1,1204 @@ +; +; ft_music_play +; +; The player routine +; +ft_music_play: + lda var_PlayerFlags ; Skip if player is disabled + bne :+ + rts ; Not playing, return +: + +.ifdef USE_FDS + lda #$00 + sta var_ch_ModEffWritten +.endif + + ; Run delayed channels + ldx #$00 +@ChanLoop: + lda var_ch_Delay, x + beq @SkipDelay + sec + sbc #$01 + sta var_ch_Delay, x + bne @SkipDelay + jsr ft_read_pattern ; Read the delayed note + lda var_ch_NoteCut, x + and #$7F + sta var_ch_NoteCut, x +@SkipDelay: + inx +.ifdef USE_N163 + cpx var_AllChannels +.else + cpx #CHANNELS +.endif + bne @ChanLoop + +.ifdef USE_FDS + jsr ft_check_fds_effects +.endif + + ; Speed division + lda var_Tempo_Accum + 1 + bmi ft_do_row_update ; Counter < 0 + ora var_Tempo_Accum + beq ft_do_row_update ; Counter = 0 + jmp ft_skip_row_update + ; Read a row +ft_do_row_update: + +.ifdef USE_DPCM + lda #$00 + sta var_ch_DPCM_Retrig +.endif + + ; Switches to new frames are delayed to next row to resolve issues with delayed notes. + ; It won't work if new pattern adresses are loaded before the delayed note is played + lda var_Load_Frame + beq @SkipFrameLoad + lda #$00 + sta var_Load_Frame + lda var_Current_Frame + jsr ft_load_frame +@SkipFrameLoad: + + ; Read one row from all patterns + ldx #$00 +ft_read_channels: +@UpdateChan: + lda var_ch_Delay, x + beq :+ + lda #$00 + sta var_ch_Delay, x + jsr ft_read_pattern ; In case a delayed note has not been played, skip it to get next note +: jsr ft_read_pattern ; Get new notes + lda var_ch_NoteCut, x + and #$7F + sta var_ch_NoteCut, x + inx + +.ifdef USE_N163 + cpx var_AllChannels +.else + cpx #CHANNELS +.endif + + bne ft_read_channels + +.ifdef USE_FDS + jsr ft_check_fds_effects +.endif + + ; Should jump? + lda var_Jump + beq @NoJump + ; Yes, jump + sec + sbc #$01 + sta var_Current_Frame +; jsr ft_load_frame + lda #$01 + sta var_Load_Frame + + jmp @NoPatternEnd +@NoJump: + ; Should skip? + lda var_Skip + beq @NoSkip + ; Yes, skip + sec + sbc #$01 +.ifdef ENABLE_ROW_SKIP + ; Store next row number in Temp2 + sta var_SkipTo +.endif + inc var_Current_Frame + lda var_Current_Frame + cmp var_Frame_Count + beq @RestartSong +; jsr ft_load_frame + lda #$01 + sta var_Load_Frame + + jmp @NoPatternEnd +@RestartSong: + lda #$00 + sta var_Current_Frame +; jsr ft_load_frame + lda #$01 + sta var_Load_Frame + + jmp @NoPatternEnd +@NoSkip: + ; Current row in all channels are processed, update info + inc var_Pattern_Pos + lda var_Pattern_Pos ; See if end is reached + cmp var_Pattern_Length + bne @NoPatternEnd + ; End of current frame, load next + inc var_Current_Frame + lda var_Current_Frame + cmp var_Frame_Count + beq @ResetFrame + sta var_Load_Frame + jmp @NoPatternEnd +@ResetFrame: + ldx #$00 + stx var_Current_Frame + inx + stx var_Load_Frame + +@NoPatternEnd: + jsr ft_restore_speed ; Reset frame divider counter +ft_skip_row_update: + ; Speed division + sec + lda var_Tempo_Accum ; Decrement speed counter + sbc var_Tempo_Count + sta var_Tempo_Accum + lda var_Tempo_Accum + 1 + sbc var_Tempo_Count + 1 + sta var_Tempo_Accum + 1 + + ; Note cut effect (Sxx) + ldx #$00 +: lda var_ch_NoteCut, x + beq :+ + sec + sbc #$01 + sta var_ch_NoteCut, x + bne :+ + sta var_ch_Note, x ; todo: make a subroutine for note cut + sta var_ch_PortaToLo, x + sta var_ch_PortaToHi, x + sta var_ch_TimerPeriodLo, x + sta var_ch_TimerPeriodHi, x +.ifdef USE_VRC7 + lda ft_channel_type, x + cmp #CHAN_VRC7 + bne :+ + lda #$00 ; Halt VRC7 channel + sta var_ch_vrc7_Command - VRC7_CHANNEL, x +.endif + ; End VRC7 +: inx +.ifdef USE_N163 + cpx var_AllChannels +.else + cpx #CHANNELS +.endif + bne :-- + + ; Update channel instruments and effects + ldx #$00 + +; Loop through wave channels +ft_loop_channels: + + ; Do channel effects, like portamento and vibrato + jsr ft_run_effects + + ; Instrument sequences + lda var_ch_Note, x + beq :+ + jsr ft_run_instrument ; Update instruments +: jsr ft_calc_period + + inx + ;cpx #WAVE_CHANS ; Skip DPCM +.ifdef USE_N163 + cpx var_EffChannels +.else + cpx #EFF_CHANS +.endif + bne ft_loop_channels + + ; Finally update APU and expansion chip registers + jsr ft_update_apu +.ifdef USE_VRC6 + jsr ft_update_vrc6 +.endif +.ifdef USE_MMC5 + jsr ft_update_mmc5 +.endif +.ifdef USE_VRC7 + jsr ft_update_vrc7 +.endif +.ifdef USE_FDS + jsr ft_update_fds +.endif +.ifdef USE_N163 + jsr ft_update_n163 +.endif + + ; End of music routine, return + rts + + +; Process a pattern row in channel X +ft_read_pattern: + ldy var_ch_NoteDelay, x ; First check if in the middle of a row delay + beq :+ + dey + tya + sta var_ch_NoteDelay, x + rts ; And skip +: sty var_Sweep ; Y = 0 +.ifdef USE_BANKSWITCH + ; First setup the bank + lda var_ch_Bank, x + beq :+ + sta $5FFB ; Patterns are located @ $B000-$BFFF +: ; Go on +.endif +.ifdef USE_FDS +; lda #$0F ; Default max vol is 15 +; cpx #FDS_CHANNEL + lda ft_channel_type, x + cmp #CHAN_FDS + bne :+ + lda #$1F ; FDS max vol is 31 + jmp :++ +: lda #$0F +: +.else + lda #$0F ; Default max vol is 15 +.endif + sta var_VolTemp + lda var_ch_PatternAddrLo, x ; Load pattern address + sta var_Temp_Pattern + lda var_ch_PatternAddrHi, x + sta var_Temp_Pattern + 1 +.ifdef USE_VRC7 + lda #$FF + sta var_ch_vrc7_EffPatch +.endif + +ft_read_note: + lda (var_Temp_Pattern), y ; Read pattern command + bpl :+ + jmp @Effect +: beq @JumpToDone ; Rest + cmp #$7F +; beq @NoteOff ; Note off + bne :+ + jmp @NoteOff +: cmp #$7E +; beq @NoteRelease ; Note release + bne :+ + jmp @NoteRelease +: + ; Read a note + sta var_ch_Note, x ; Note on + jsr ft_translate_freq + + lda var_ch_NoteCut, x + bmi :+ + lda #$00 + sta var_ch_NoteCut, x ; Reset note cuts +: +.ifdef USE_DPCM + lda ft_channel_map, x + cmp #CHAN_2A03_DPCM + bne :+ + jmp @ReadIsDone +: ; DPCM skip +.endif +.ifdef USE_VRC7 + lda ft_channel_type, x + cmp #CHAN_VRC7 + bne :+ + jsr ft_vrc7_trigger + jmp @ResetSlide +: ; VRC7 skip +.endif +.ifdef USE_FDS + lda ft_channel_type, x + cmp #CHAN_FDS + bne :+ + lda #$01 + sta var_ch_ResetMod +: +.endif + jsr ft_reset_instrument + lda #$00 + sta var_ch_State, x + lda var_VolTemp + sta var_ch_Volume, x + lda #$00 +; sta var_ch_ArpeggioCycle, x + + lda var_ch_DutyCycle, x + and #$F0 + sta var_ch_DutyCycle, x + lsr a + lsr a + lsr a + lsr a + ora var_ch_DutyCycle, x + sta var_ch_DutyCycle, x + +@ResetSlide: + ; Clear the slide effect on new notes + lda var_ch_Effect, x + cmp #EFF_SLIDE_UP + beq :+ + cmp #EFF_SLIDE_DOWN + bne :++ +: lda #EFF_NONE + sta var_ch_Effect, x +: + + cpx #$02 ; Skip if not square + bcc :+ + jmp @ReadIsDone +: lda #$00 + sta var_ch_Sweep, x ; Reset sweep +@JumpToDone: + jmp @ReadIsDone +@NoteRelease: + lda var_ch_State, x + cmp #$01 + beq @JumpToDone + lda #$01 + sta var_ch_State, x +.ifdef USE_DPCM + lda ft_channel_map, x + cmp #CHAN_2A03_DPCM + bne :+ + lda #$FF + sta var_ch_Note, x + jmp @ReadIsDone +: +.endif +.ifdef USE_VRC7 + cpx #VRC7_CHANNEL + bcs @JumpToDone +.endif + jsr ft_instrument_release + jmp @ReadIsDone +@NoteOff: + lda #$00 + sta var_ch_Note, x +.ifdef USE_DPCM + lda ft_channel_map, x + cmp #CHAN_2A03_DPCM + bne :+ + jmp @ReadIsDone +: lda #$00 +.endif +.ifdef USE_VRC7 + lda ft_channel_type, x + cmp #CHAN_VRC7 + bne :+ + lda #$00 ; Halt VRC7 channel + sta var_ch_vrc7_Command - VRC7_CHANNEL, x + sta var_ch_PortaToLo, x + sta var_ch_PortaToHi, x + sta var_ch_TimerPeriodLo, x + sta var_ch_TimerPeriodHi, x + jmp @ReadIsDone +: lda #$00 +.endif + sta var_ch_Volume, x + sta var_ch_PortaToLo, x + sta var_ch_PortaToHi, x + sta var_ch_TimerPeriodLo, x + sta var_ch_TimerPeriodHi, x + cpx #$02 ; Skip all but the square channels + bcs :+ + ;lda #$FF + ;sta var_ch_PrevFreqHigh, x +: jmp @ReadIsDone +@VolumeCommand: ; Handle volume + pla + asl a + asl a + asl a + ;asl a + and #$78 + sta var_ch_VolColumn, x + iny + jmp ft_read_note +@InstCommand: ; Instrument change + pla + and #$0F + asl a + jsr ft_load_instrument + iny + jmp ft_read_note +@Effect: + pha + and #$F0 + cmp #$F0 ; See if volume + beq @VolumeCommand + cmp #$E0 ; See if a quick instrument command + beq @InstCommand + pla + and #$7F ; Look up the command address + sty var_Temp ; from the command table + tay + lda ft_command_table, y + sta var_Temp_Pointer + iny + lda ft_command_table, y + sta var_Temp_Pointer + 1 + ldy var_Temp + iny + jmp (var_Temp_Pointer) ; And jump there +@LoadDefaultDelay: + sta var_ch_NoteDelay, x ; Store default delay + jmp ft_read_is_done +@ReadIsDone: + lda var_ch_DefaultDelay, x ; See if there's a default delay + cmp #$FF + bne @LoadDefaultDelay ; If so then use it + iny + lda (var_Temp_Pattern), y ; A note is immediately followed by the amount of rows until next note + sta var_ch_NoteDelay, x +ft_read_is_done: + clc ; Store pattern address + iny + tya + adc var_Temp_Pattern + sta var_ch_PatternAddrLo, x + lda #$00 + adc var_Temp_Pattern + 1 + sta var_ch_PatternAddrHi, x + + lda var_Sweep ; Check sweep + beq @EndPatternFetch + sta var_ch_Sweep, x ; Store sweep, only used for square 1 and 2 + lda #$00 + sta var_Sweep + ;sta var_ch_PrevFreqHigh, x +@EndPatternFetch: + rts + +; Read pattern to A and move to next byte +ft_get_pattern_byte: + lda (var_Temp_Pattern), y ; Get the instrument number + pha + iny + pla + rts + +; +; Command table +; +ft_command_table: + .word ft_cmd_instrument + .word ft_cmd_duration + .word ft_cmd_noduration + .word ft_cmd_speed + .word ft_cmd_tempo + .word ft_cmd_jump + .word ft_cmd_skip + .word ft_cmd_halt + .word ft_cmd_effvolume + .word ft_cmd_clear + .word ft_cmd_porta_up + .word ft_cmd_porta_down + .word ft_cmd_portamento + .word ft_cmd_arpeggio + .word ft_cmd_vibrato + .word ft_cmd_tremolo + .word ft_cmd_pitch + .word ft_cmd_reset_pitch + .word ft_cmd_duty + .word ft_cmd_delay + .word ft_cmd_sweep + .word ft_cmd_dac + .word ft_cmd_sample_offset + .word ft_cmd_slide_up + .word ft_cmd_slide_down + .word ft_cmd_vol_slide + .word ft_cmd_note_cut + .word ft_cmd_retrigger + .word ft_cmd_dpcm_pitch +.ifdef USE_FDS + .word ft_cmd_fds_mod_depth + .word ft_cmd_fds_mod_rate_hi + .word ft_cmd_fds_mod_rate_lo +.endif +.ifdef USE_VRC7 + .word ft_cmd_vrc7_patch_change +.endif +; .word ft_cmd_expand + +; +; Command functions +; + +.if 0 +; Loop expansion +ft_cmd_expand: + lda var_ch_LoopCounter, x ; See if already looping + bne :+ + ; Load new loop + jsr ft_get_pattern_byte ; number of loops + sta var_ch_LoopCounter, x + jsr ft_get_pattern_byte ; length in bytes + sta var_Temp + ; Calculate pattern pointer + sec + lda var_Temp_Pattern + sbc var_Temp + sta var_Temp_Pattern + lda var_Temp_Pattern + 1 + sbc #$00 + sta var_Temp_Pattern + 1 + ldy #$00 + jmp ft_read_note +: ; Already looping + sec + sbc #$01 + beq :+ ; Check if done + sta var_ch_LoopCounter, x + iny ; number of loops, ignore + jsr ft_get_pattern_byte ; length in bytes + sta var_Temp + ; Calculate pattern pointer + sec + lda var_Temp_Pattern + sbc var_Temp + sta var_Temp_Pattern + lda var_Temp_Pattern + 1 + sbc #$00 + sta var_Temp_Pattern + 1 + ldy #$00 + jmp ft_read_note +: ; Loop is done + sta var_ch_LoopCounter, x + iny ; number of loops, ignore + iny ; length in bytes, ignore + jmp ft_read_note +.endif + +; Change instrument +ft_cmd_instrument: + jsr ft_get_pattern_byte + jsr ft_load_instrument + jmp ft_read_note +; Set default note duration +ft_cmd_duration: + jsr ft_get_pattern_byte + sta var_ch_DefaultDelay, x + jmp ft_read_note +; No default note duration +ft_cmd_noduration: + lda #$FF + sta var_ch_DefaultDelay, x + jmp ft_read_note +; Effect: Speed (Fxx) +ft_cmd_speed: + jsr ft_get_pattern_byte + sta var_Speed + jsr ft_calculate_speed + jmp ft_read_note +; Effect: Tempo (Fxx) +ft_cmd_tempo: + jsr ft_get_pattern_byte + sta var_Tempo + jsr ft_calculate_speed + jmp ft_read_note +; Effect: Jump (Bxx) +ft_cmd_jump: + jsr ft_get_pattern_byte + sta var_Jump + jmp ft_read_note +; Effect: Skip (Dxx) +ft_cmd_skip: + jsr ft_get_pattern_byte + sta var_Skip + jmp ft_read_note +; Effect: Halt (Cxx) +ft_cmd_halt: + jsr ft_get_pattern_byte + lda #$00 + sta var_PlayerFlags + jmp ft_read_note +; Effect: Volume (Exx) +ft_cmd_effvolume: + jsr ft_get_pattern_byte + sta var_VolTemp + sta var_ch_Volume, x + jmp ft_read_note +; Effect: Portamento (3xx) +ft_cmd_portamento: + jsr ft_get_pattern_byte + sta var_ch_EffParam, x + lda #EFF_PORTAMENTO + sta var_ch_Effect, x + jmp ft_read_note +; Effect: Portamento up (1xx) +ft_cmd_porta_up: + jsr ft_get_pattern_byte + sta var_ch_EffParam, x + lda #EFF_PORTA_UP + sta var_ch_Effect, x + jmp ft_read_note +; Effect: Portamento down (2xx) +ft_cmd_porta_down: + jsr ft_get_pattern_byte + sta var_ch_EffParam, x + lda #EFF_PORTA_DOWN + sta var_ch_Effect, x + jmp ft_read_note +; Effect: Arpeggio (0xy) +ft_cmd_arpeggio: + jsr ft_get_pattern_byte + sta var_ch_EffParam, x + lda #$00 + sta var_ch_ArpeggioCycle, x + lda #EFF_ARPEGGIO + sta var_ch_Effect, x + jmp ft_read_note +ft_cmd_clear: + lda #$00 + sta var_ch_EffParam, x + sta var_ch_Effect, x + sta var_ch_PortaToLo, x + sta var_ch_PortaToHi, x + jmp ft_read_note +; Effect: Hardware sweep (Hxy / Ixy) +ft_cmd_sweep: + jsr ft_get_pattern_byte + sta var_Sweep + jmp ft_read_note +; Effect: Vibrato (4xy) +ft_cmd_vibrato: + jsr ft_get_pattern_byte + pha + lda var_ch_VibratoSpeed, x + bne :++ + lda var_SongFlags + and #$02 + beq :+ + lda #48 +: sta var_ch_VibratoPos, x +: pla + pha + and #$F0 + sta var_ch_VibratoDepth, x + pla + and #$0F + sta var_ch_VibratoSpeed, x + jmp ft_read_note +; Effect: Tremolo (7xy) +ft_cmd_tremolo: + jsr ft_get_pattern_byte + pha + and #$F0 + sta var_ch_TremoloDepth, x + pla + and #$0F + sta var_ch_TremoloSpeed, x + cmp #$00 + beq @ResetTremolo + jmp ft_read_note +@ResetTremolo: ; Clear tremolo + sta var_ch_TremoloPos, x + jmp ft_read_note +; Effect: Pitch (Pxx) +ft_cmd_pitch: + jsr ft_get_pattern_byte + sta var_ch_FinePitch, x + jmp ft_read_note +ft_cmd_reset_pitch: + lda #$80 + sta var_ch_FinePitch, x + jmp ft_read_note +; Effect: Delay (Gxx) +ft_cmd_delay: + jsr ft_get_pattern_byte + sta var_ch_Delay, x + dey + jmp ft_read_is_done +; Effect: delta counter setting (Zxx) +ft_cmd_dac: +.ifdef USE_DPCM + jsr ft_get_pattern_byte + sta var_ch_DPCMDAC + jmp ft_read_note +.endif +; Effect: Duty cycle (Vxx) +ft_cmd_duty: + jsr ft_get_pattern_byte + sta var_ch_DutyCycle, x ; xxxxyyyy: xxxx = default value, yyyy = current value + clc + asl a + asl a + asl a + asl a + ora var_ch_DutyCycle, x + sta var_ch_DutyCycle, x +.ifdef USE_N163 + lda ft_channel_type, x + cmp #CHAN_N163 + bne :+ + jsr ft_n163_load_wave2 +: +.endif + jmp ft_read_note +; Effect: Sample offset +ft_cmd_sample_offset: +.ifdef USE_DPCM + jsr ft_get_pattern_byte + sta var_ch_DPCM_Offset + jmp ft_read_note +.endif +; Effect: Slide pitch up +ft_cmd_slide_up: + jsr ft_get_pattern_byte ; Fetch speed / note + sta var_ch_EffParam, x + lda #EFF_SLIDE_UP_LOAD + sta var_ch_Effect, x + jmp ft_read_note +; Effect: Slide pitch down +ft_cmd_slide_down: + jsr ft_get_pattern_byte ; Fetch speed / note + sta var_ch_EffParam, x + lda #EFF_SLIDE_DOWN_LOAD + sta var_ch_Effect, x + jmp ft_read_note +; Effect: Volume slide +ft_cmd_vol_slide: + jsr ft_get_pattern_byte ; Fetch speed / note + sta var_ch_VolSlide, x + jmp ft_read_note +; Effect: Note cut (Sxx) +ft_cmd_note_cut: + jsr ft_get_pattern_byte + ora #$80 + sta var_ch_NoteCut, x + jmp ft_read_note +; Effect: Retrigger +ft_cmd_retrigger: +.ifdef USE_DPCM + jsr ft_get_pattern_byte + sta var_ch_DPCM_Retrig + lda var_ch_DPCM_RetrigCntr + bne :+ + lda var_ch_DPCM_Retrig + sta var_ch_DPCM_RetrigCntr +: jmp ft_read_note +.endif +; Effect: DPCM pitch setting +ft_cmd_dpcm_pitch: +.ifdef USE_DPCM + jsr ft_get_pattern_byte + sta var_ch_DPCM_EffPitch + jmp ft_read_note +.endif +; End of effect column commands +; FDS + +.ifdef USE_FDS + +ft_cmd_fds_mod_depth: + jsr ft_get_pattern_byte + sta var_ch_ModEffDepth + lda var_ch_ModEffWritten + ora #$01 + sta var_ch_ModEffWritten + jmp ft_read_note +ft_cmd_fds_mod_rate_hi: + jsr ft_get_pattern_byte + sta var_ch_ModEffRateHi + lda var_ch_ModEffWritten + ora #$02 + sta var_ch_ModEffWritten + jmp ft_read_note +ft_cmd_fds_mod_rate_lo: + jsr ft_get_pattern_byte + sta var_ch_ModEffRateLo + lda var_ch_ModEffWritten + ora #$04 + sta var_ch_ModEffWritten + jmp ft_read_note + +.endif + +; VRC7 +.ifdef USE_VRC7 +ft_cmd_vrc7_patch_change: + jsr ft_get_pattern_byte + sta var_ch_vrc7_EffPatch + sta var_ch_vrc7_Patch - VRC7_CHANNEL, x + jmp ft_read_note +.endif + +; +; End of commands +; + +.ifdef USE_VRC6 +ft_load_vrc6_saw_table: + cpx #SAW_CHANNEL + bne :+ + pha ; Load VRC6 sawtooth table + lda #ft_periods_sawtooth + sta var_Note_Table + 1 + pla + rts +: pha ; Load 2A03 table + lda #ft_periods_ntsc + sta var_Note_Table + 1 + pla + rts +.endif + +.ifdef USE_FDS +ft_load_fds_table: + pha +; cpx #FDS_CHANNEL + lda ft_channel_type, x + cmp #CHAN_FDS + bne :+ + lda #ft_periods_fds + sta var_Note_Table + 1 + pla + rts +: lda #ft_periods_ntsc + sta var_Note_Table + 1 + pla + rts +.endif + +.ifdef USE_N163 +ft_load_n163_table: + pha + lda ft_channel_type, x + cmp #CHAN_N163 + bne :+ + lda #ft_periods_n163 + sta var_Note_Table + 1 + pla + rts +: lda #ft_periods_ntsc + sta var_Note_Table + 1 + pla + rts +.endif + +; +; Translate the note in A to a frequency and stores in current channel +; Don't care if portamento is enabled +; +ft_translate_freq_only: + + sec + sbc #$01 + +.ifdef USE_VRC7 + pha + lda ft_channel_type, x + cmp #CHAN_VRC7 + bne :+ + pla + sta var_ch_vrc7_ActiveNote - VRC7_CHANNEL, x + jsr ft_vrc7_get_freq_only + rts +: pla +.endif + + + cpx #NOISE_CHANNEL ; Check if noise + beq StoreNoise2 + +.ifdef USE_VRC6 + jsr ft_load_vrc6_saw_table +.endif +.ifdef USE_FDS + jsr ft_load_fds_table +.endif +.ifdef USE_N163 + jsr ft_load_n163_table +.endif + + asl a + sty var_Temp + + tay +LoadFrequency: + lda (var_Note_Table), y + sta var_ch_TimerPeriodLo, x + iny + lda (var_Note_Table), y + sta var_ch_TimerPeriodHi, x + ldy var_Temp + rts + +StoreNoise2: +; eor #$0F +.ifdef SCALE_NOISE + asl a + asl a + asl a + asl a +.endif + and #$0F + ora #$10 + sta var_ch_TimerPeriodLo, x + lda #$00 + sta var_ch_TimerPeriodHi, x + rts + +; +; Translate the note in A to a frequency and stores in current channel +; If portamento is enabled, store in PortaTo +; + +ft_translate_freq: + + sec + sbc #$01 + +.ifdef USE_DPCM + pha + lda ft_channel_map, x + cmp #CHAN_2A03_DPCM ; Check if DPCM + bne :+ + jmp StoreDPCM +: pla +.endif + +.ifdef USE_VRC7 + pha + lda ft_channel_type, x + cmp #CHAN_VRC7 + bne :+ + pla + sta var_ch_vrc7_ActiveNote - VRC7_CHANNEL, x + jsr ft_vrc7_get_freq + rts +: pla +.endif + + cpx #NOISE_CHANNEL ; Check if noise + beq StoreNoise + +.ifdef USE_VRC6 + jsr ft_load_vrc6_saw_table +.endif +.ifdef USE_FDS + jsr ft_load_fds_table +.endif +.ifdef USE_N163 + jsr ft_load_n163_table +.endif + + asl a + sty var_Temp + tay + ; Check portamento + lda var_ch_Effect, x + cmp #EFF_PORTAMENTO + bne @NoPorta + ; Load portamento + lda (var_Note_Table), y + sta var_ch_PortaToLo, x + iny + lda (var_Note_Table), y + sta var_ch_PortaToHi, x + + ldy var_Temp + lda var_ch_TimerPeriodLo, x + ora var_ch_TimerPeriodHi, x + bne @Return + lda var_ch_PortaToLo, x + sta var_ch_TimerPeriodLo, x + lda var_ch_PortaToHi, x + sta var_ch_TimerPeriodHi, x +@Return: + rts +@NoPorta: + jmp LoadFrequency + rts +StoreNoise: ; Special case for noise +; eor #$0F +.ifdef SCALE_NOISE + asl a + asl a + asl a + asl a +.endif + ora #$10 + pha + lda var_ch_Effect, x + cmp #EFF_PORTAMENTO + bne @NoPorta + pla + sta var_ch_PortaToLo, x + lda #$00 + sta var_ch_PortaToHi, x + lda var_ch_TimerPeriodLo, x + ora var_ch_TimerPeriodHi, x + bne @Return + lda var_ch_PortaToLo, x + sta var_ch_TimerPeriodLo, x + lda var_ch_PortaToHi, x + sta var_ch_TimerPeriodHi, x +@Return: + rts +@NoPorta: + pla + sta var_ch_TimerPeriodLo, x + lda #$00 + sta var_ch_TimerPeriodHi, x + rts + +.ifdef USE_DPCM +StoreDPCM: ; Special case for DPCM + + clc ; Multiply the DPCM instrument index by 3 + pla ; and store in Temp16 + pha + asl a + adc var_dpcm_inst_list + sta var_Temp16 + lda #$00 + adc var_dpcm_inst_list + 1 + sta var_Temp16 + 1 + clc + pla + adc var_Temp16 + sta var_Temp16 + lda #$00 + adc var_Temp16 + 1 + sta var_Temp16 + 1 + + sty var_Temp + ldy #$00 + + lda (var_Temp16), y ; Read pitch + sta var_ch_SamplePitch + iny + lda var_ch_DPCMDAC + bpl :+ + lda (var_Temp16), y ; Read delta value + bmi :+ + sta var_ch_DPCMDAC +: iny + lda (var_Temp16), y ; Read sample + tay + + lda var_dpcm_pointers ; Load sample pointer list + sta var_Temp16 + lda var_dpcm_pointers + 1 + sta var_Temp16 + 1 + + lda (var_Temp16), y ; Sample address + sta var_ch_SamplePtr + iny + lda (var_Temp16), y ; Sample size + sta var_ch_SampleLen + iny + lda (var_Temp16), y ; Sample bank + sta var_ch_SampleBank + + ldy var_Temp + + ; Reload retrigger counter + lda var_ch_DPCM_Retrig + sta var_ch_DPCM_RetrigCntr + + rts +.endif + +; Reload speed division counter +ft_restore_speed: + clc + lda var_Tempo_Accum + adc var_Tempo_Dec + sta var_Tempo_Accum + lda var_Tempo_Accum + 1 + adc var_Tempo_Dec + 1 + sta var_Tempo_Accum + 1 + rts + +; Calculate frame division from the speed and tempo settings +ft_calculate_speed: + tya + pha + + ; Multiply by 24 + lda var_Tempo + sta AUX + lda #$00 + sta AUX + 1 + ldy #$03 +: asl AUX + rol AUX + 1 + dey + bne :- + lda AUX + sta ACC + lda AUX + 1 + tay + asl AUX + rol AUX + 1 + clc + lda ACC + adc AUX + sta ACC + tya + adc AUX + 1 + sta ACC + 1 + + ; divide by speed + lda var_Speed + sta AUX + lda #$00 + sta AUX + 1 + jsr DIV ; ACC/AUX -> ACC, remainder in EXT + lda ACC + sta var_Tempo_Count + lda ACC + 1 + sta var_Tempo_Count + 1 + pla + tay + + rts + +; If anyone knows a way to calculate speed without using +; multiplication or division, please contact me + +; ACC/AUX -> ACC, remainder in EXT +DIV: LDA #0 + STA EXT+1 + LDY #$10 +LOOP2: ASL ACC + ROL ACC+1 + ROL + ROL EXT+1 + PHA + CMP AUX + LDA EXT+1 + SBC AUX+1 + BCC DIV2 + STA EXT+1 + PLA + SBC AUX + PHA + INC ACC +DIV2: PLA + DEY + BNE LOOP2 + STA EXT + RTS From c07fad5781a59711845820901734ad3e208338fc Mon Sep 17 00:00:00 2001 From: Taylor Woll Date: Fri, 16 Jun 2023 11:17:23 -0700 Subject: [PATCH 6/8] Format nrom cfg file a bit --- nrom_32k_vert.cfg | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/nrom_32k_vert.cfg b/nrom_32k_vert.cfg index 1abfe43..4caa991 100644 --- a/nrom_32k_vert.cfg +++ b/nrom_32k_vert.cfg @@ -7,27 +7,30 @@ MEMORY { # ROM # iNES Header HEADER: start = $0000, size = $0010, file = %O, fill = yes; + # Program ROM PRG: start = $8000, size = $7d00, file = %O, fill = yes, define = yes; # 1 Bank of 8K CHR ROM CHR: start = $0000, size = $2000, file = %O, fill = yes; + DMC: start = $fd00, size = $02fa, file = %O, fill = yes, define = yes; VECTORS: start = $fffa, size = $0006, file = %O, fill = yes; } SEGMENTS { - HEADER: load = HEADER, type = ro; - STARTUP: load = PRG, type = ro, define = yes; - LOWCODE: load = PRG, type = ro, optional = yes; - INIT: load = PRG, type = ro, define = yes, optional = yes; - CODE: load = PRG, type = ro, define = yes; - RODATA: load = PRG, type = ro, define = yes; - DATA: load = PRG, run = RAM, type = rw, define = yes; - VECTORS: load = VECTORS, type = ro; - DMC: load = DMC, type = ro; - CHR: load = CHR, type = ro; - BSS: load = RAM, type = bss, define = yes; - HEAP: load = RAM, type = bss, optional = yes; - ZEROPAGE: load = ZP, type = zp; + HEADER: load = HEADER, type = ro; + STARTUP: load = PRG, type = ro, define = yes; + LOWCODE: load = PRG, type = ro, optional = yes; + INIT: load = PRG, type = ro, define = yes, optional = yes; + CODE: load = PRG, type = ro, define = yes; + RODATA: load = PRG, type = ro, define = yes; + DATA: load = PRG, type = rw, define = yes, run = RAM; + CHR: load = CHR, type = rw; + BSS: load = RAM, type = bss, define = yes; + HEAP: load = RAM, type = bss, optional = yes; + ZEROPAGE: load = ZP, type = zp; + + DMC: load = DMC, type = ro, optional = yes; + VECTORS: load = VECTORS,type = ro; } #removed CONDES features @@ -36,8 +39,8 @@ SYMBOLS { __STACKSIZE__: type = weak, value = $0100; # 1 page stack __STACK_START__: type = weak, value = $0700; - NES_MAPPER: type = weak, value = 0; # mapper number, 0 = NROM - NES_PRG_BANKS: type = weak, value = 2; # number of 16K PRG banks, change to 2 for NROM256 - NES_CHR_BANKS: type = weak, value = 1; # number of 8K CHR banks - NES_MIRRORING: type = weak, value = 1; # 0 horizontal, 1 vertical, 8 four screen + NES_MAPPER: type = weak, value = 0; # mapper number, 0 = NROM + NES_PRG_BANKS: type = weak, value = 2; # number of 16K PRG banks, change to 2 for NROM256 + NES_CHR_BANKS: type = weak, value = 1; # number of 8K CHR banks + NES_MIRRORING: type = weak, value = 1; # 0 horizontal, 1 vertical, 8 four screen } From e9f8b4eb831bd26027ba57c9db5f59b0c5535f54 Mon Sep 17 00:00:00 2001 From: Taylor Woll Date: Fri, 16 Jun 2023 11:26:34 -0700 Subject: [PATCH 7/8] Format cfg --- nrom_32k_vert.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nrom_32k_vert.cfg b/nrom_32k_vert.cfg index 4caa991..d1c0617 100644 --- a/nrom_32k_vert.cfg +++ b/nrom_32k_vert.cfg @@ -36,7 +36,7 @@ SEGMENTS { #removed CONDES features SYMBOLS { - __STACKSIZE__: type = weak, value = $0100; # 1 page stack + __STACKSIZE__: type = weak, value = $0100; # 1 page stack __STACK_START__: type = weak, value = $0700; NES_MAPPER: type = weak, value = 0; # mapper number, 0 = NROM From 92677b13bc66828cd79b0a3974e52250286a7fd2 Mon Sep 17 00:00:00 2001 From: Taylor Woll Date: Fri, 16 Jun 2023 11:37:31 -0700 Subject: [PATCH 8/8] Re-order segments and memory to get the rom layout correct --- compile.bat | 2 +- nrom_32k_vert.cfg | 38 ++++++++++++++++++-------------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/compile.bat b/compile.bat index 1af5851..3d13932 100644 --- a/compile.bat +++ b/compile.bat @@ -18,4 +18,4 @@ cc65 -Oirs src/%name%.c -o build/%name%.s -g --add-source ca65 src/crt0.s -o build/crt0.o -g ca65 build/%name%.s -o build/%name%.o -g -ld65 -C nrom_32k_vert.cfg -o build/%name%.nes build/crt0.o build/%name%.o nes.lib -Ln build/%name%.labels.txt --dbgfile build/%name%.dbg +ld65 -C nrom_32k_vert.cfg -o build/%name%.nes build/crt0.o build/%name%.o nes.lib -Ln build/%name%.labels.txt --dbgfile build/%name%.dbg -v diff --git a/nrom_32k_vert.cfg b/nrom_32k_vert.cfg index d1c0617..f54e37b 100644 --- a/nrom_32k_vert.cfg +++ b/nrom_32k_vert.cfg @@ -1,36 +1,34 @@ MEMORY { -# RAM # Zero page ZP: start = $0000, size = $0100, type = rw, define = yes; - RAM: start = $0300, size = $0500, define = yes; - -# ROM # iNES Header HEADER: start = $0000, size = $0010, file = %O, fill = yes; # Program ROM PRG: start = $8000, size = $7d00, file = %O, fill = yes, define = yes; + # Samples ROM + DMC: start = $fd00, size = $02fa, file = %O, fill = yes, define = yes; + # IRQ Vectors + VECTORS: start = $fffa, size = $0006, file = %O, fill = yes; # 1 Bank of 8K CHR ROM CHR: start = $0000, size = $2000, file = %O, fill = yes; - DMC: start = $fd00, size = $02fa, file = %O, fill = yes, define = yes; - VECTORS: start = $fffa, size = $0006, file = %O, fill = yes; + RAM: start = $0300, size = $0500, define = yes; } SEGMENTS { - HEADER: load = HEADER, type = ro; - STARTUP: load = PRG, type = ro, define = yes; - LOWCODE: load = PRG, type = ro, optional = yes; - INIT: load = PRG, type = ro, define = yes, optional = yes; - CODE: load = PRG, type = ro, define = yes; - RODATA: load = PRG, type = ro, define = yes; - DATA: load = PRG, type = rw, define = yes, run = RAM; - CHR: load = CHR, type = rw; - BSS: load = RAM, type = bss, define = yes; - HEAP: load = RAM, type = bss, optional = yes; - ZEROPAGE: load = ZP, type = zp; - - DMC: load = DMC, type = ro, optional = yes; - VECTORS: load = VECTORS,type = ro; + HEADER: load = HEADER, type = ro; + STARTUP: load = PRG, type = ro, define = yes; + LOWCODE: load = PRG, type = ro, optional = yes; + INIT: load = PRG, type = ro, define = yes, optional = yes; + CODE: load = PRG, type = ro, define = yes; + RODATA: load = PRG, type = ro, define = yes; + DATA: load = PRG, type = rw, define = yes, run = RAM; + VECTORS: load = VECTORS, type = ro; + DMC: load = DMC, type = ro, optional = yes; + CHR: load = CHR, type = rw; + BSS: load = RAM, type = bss, define = yes; + HEAP: load = RAM, type = bss, optional = yes; + ZEROPAGE: load = ZP, type = zp; } #removed CONDES features