diff --git a/compile.bat b/compile.bat index 75a8bad..3d13932 100644 --- a/compile.bat +++ b/compile.bat @@ -2,11 +2,20 @@ 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 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/music/music.ftm b/music/music.ftm new file mode 100644 index 0000000..6850a16 Binary files /dev/null and b/music/music.ftm differ diff --git a/nrom_32k_vert.cfg b/nrom_32k_vert.cfg index b48cf87..f54e37b 100644 --- a/nrom_32k_vert.cfg +++ b/nrom_32k_vert.cfg @@ -1,65 +1,44 @@ MEMORY { -#RAM Addresses: - # Zero page - ZP: start = $00, size = $100, type = rw, define = yes; - #note, the c compiler + neslib + famitone2 use about 60 zp addresses, I think - - #note OAM: start = $0200, size = $0100, define = yes; - #note, sprites stored here in the RAM - - RAM: start = $0300, size = $0400, define = yes; - #note VRAM_BUFFER: start = $700, size = 128, define = yes; - -#INES Header: - HEADER: start = $0, size = $10, file = %O ,fill = yes; - - -#ROM Addresses: - PRG: start = $8000, size = $8000, file = %O ,fill = yes, define = yes; - - -#1 Bank of 8K CHR ROM - CHR: start = $0000, size = $2000, file = %O, fill = yes; + # Zero page + ZP: start = $0000, size = $0100, type = rw, define = yes; + # 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; + + 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, run = RAM, type = rw, define = yes; - CHARS: load = CHR, type = rw; - BSS: load = RAM, type = bss, define = yes; - HEAP: load = RAM, type = bss, optional = yes; - ZEROPAGE: load = ZP, type = zp; - ONCE: load = PRG, type = ro, define = yes, optional = yes; - - SAMPLES: load = PRG, start = $f000, type = ro, optional = yes; - VECTORS: load = PRG, start = $fffa, 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 - - - SYMBOLS { + __STACKSIZE__: type = weak, value = $0100; # 1 page stack + __STACK_START__: type = weak, value = $0700; - __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 } - diff --git a/src/bss.h b/src/bss.h index e50f4cf..bc45e55 100644 --- a/src/bss.h +++ b/src/bss.h @@ -21,6 +21,33 @@ #include "screens/title.h" #include "sprites.h" +// Current level when the game is in playing state. +unsigned char current_level; + +// Count of balls displayed on the playfield section of whichever screen is +// loaded. +unsigned char current_ball_count; + +// Which playfield pattern is currently being displayed. +unsigned char current_playfield_pattern; + +// Number of players currently playing. +unsigned char player_count; + +// Count of lives remaining. +unsigned char lives_count; + +// Percentage of the playfield which has been cleared. +// Note: This is calculated in update_hud() only because it's expensive. +unsigned char cleared_tile_percentage; + +// How many playfield tiles have been cleared. This is used to compute the +// percentage |cleared_tile_percentage|. +unsigned int cleared_tile_count; + +// Current score counter. +unsigned int score; + unsigned char playfield[PLAYFIELD_WIDTH * PLAYFIELD_HEIGHT]; #if ENABLE_CHEATS 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/crt0.s b/src/crt0.s index 87dc821..1dacac9 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 @@ -70,10 +68,10 @@ NAME_UPD_ENABLE: .res 1 PAL_UPDATE: .res 1 PAL_BG_PTR: .res 2 PAL_SPR_PTR: .res 2 -SCROLL_X: .res 1 -SCROLL_Y: .res 1 -SCROLL_X1: .res 1 -SCROLL_Y1: .res 1 +;SCROLL_X: .res 1 +;SCROLL_Y: .res 1 +;SCROLL_X1: .res 1 +;SCROLL_Y1: .res 1 PAD_STATE: .res 2 ;one byte per controller PAD_STATEP: .res 2 PAD_STATET: .res 2 @@ -83,18 +81,41 @@ PPU_MASK_VAR: .res 1 RAND_SEED: .res 2 FT_TEMP: .res 3 -TEMP: .res 11 +MUSIC_PLAY: .res 1 +ft_music_addr: .res 2 + +BUF_4000: .res 1 +BUF_4001: .res 1 +BUF_4002: .res 1 +BUF_4003: .res 1 +BUF_4004: .res 1 +BUF_4005: .res 1 +BUF_4006: .res 1 +BUF_4007: .res 1 +BUF_4008: .res 1 +BUF_4009: .res 1 +BUF_400A: .res 1 +BUF_400B: .res 1 +BUF_400C: .res 1 +BUF_400D: .res 1 +BUF_400E: .res 1 +BUF_400F: .res 1 + +PREV_4003: .res 1 +PREV_4007: .res 1 + +TEMP: .res 10 SPRID: .res 1 PAD_BUF =TEMP+1 PTR =TEMP ;word LEN =TEMP+2 ;word -NEXTSPR =TEMP+4 -SCRX =TEMP+5 -SCRY =TEMP+6 -SRC =TEMP+7 ;word -DST =TEMP+9 ;word +;NEXTSPR =TEMP+4 ; NEXTSPR removed +SCRX =TEMP+4 +SCRY =TEMP+5 +SRC =TEMP+6 ;word +DST =TEMP+8 ;word RLE_LOW =TEMP RLE_HIGH =TEMP+1 @@ -103,8 +124,8 @@ RLE_BYTE =TEMP+3 ;nesdoug code requires VRAM_INDEX: .res 1 -META_PTR: .res 2 -DATA_PTR: .res 2 +;META_PTR: .res 2 ; META_PTR removed +;DATA_PTR: .res 2 ; DATA_PTR removed @@ -231,10 +252,16 @@ detectNTSC: ldx #0 jsr _set_vram_update - ldx #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<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/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 diff --git a/src/lib/nesdoug.s b/src/lib/nesdoug.s index f1820a6..0c18733 100644 --- a/src/lib/nesdoug.s +++ b/src/lib/nesdoug.s @@ -276,7 +276,7 @@ _pal_fade_to: ;void __fastcall__ set_scroll_x(unsigned int x); _set_scroll_x: - sta 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 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 > 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 { diff --git a/src/zeropage.h b/src/zeropage.h index d20c395..46154c4 100644 --- a/src/zeropage.h +++ b/src/zeropage.h @@ -17,7 +17,7 @@ // clang-format on // Placeholder to track how many bytes are unused in the zeropage. -unsigned char unused_zp_bytes[8]; +unsigned char unused_zp_bytes[5]; // Controller state storage. unsigned char pads[MAX_PLAYERS]; @@ -35,33 +35,6 @@ struct Line lines[MAX_PLAYERS]; // Which state the game is in (ie: playing, paused, title screen, etc). unsigned char game_state; -// Current level when the game is in playing state. -unsigned char current_level; - -// Count of balls displayed on the playfield section of whichever screen is -// loaded. -unsigned char current_ball_count; - -// Which playfield pattern is currently being displayed. -unsigned char current_playfield_pattern; - -// Number of players currently playing. -unsigned char player_count; - -// Count of lives remaining. -unsigned char lives_count; - -// Percentage of the playfield which has been cleared. -// Note: This is calculated in update_hud() only because it's expensive. -unsigned char cleared_tile_percentage; - -// How many playfield tiles have been cleared. This is used to compute the -// percentage |cleared_tile_percentage|. -unsigned int cleared_tile_count; - -// Current score counter. -unsigned int score; - // Below temps may be used anywhere and should be prefered over function-local // storage or passing arguments to function calls. unsigned char temp_byte_1; @@ -70,9 +43,8 @@ unsigned char temp_byte_3; unsigned char temp_byte_4; unsigned char temp_byte_5; unsigned char temp_byte_6; - -signed char temp_signed_byte_1; -signed char temp_signed_byte_2; +unsigned char temp_byte_7; +unsigned char temp_byte_8; int temp_int_1; int temp_int_2;