;;-----------------------------LICENSE NOTICE------------------------------------ ;; This file is part of CPCtelera: An Amstrad CPC Game Engine ;; Copyright (C) 2009 Targhan / Arkos ;; Copyright (C) 2015 ronaldo / Fremos / Cheesetea / ByteRealms (@FranGallegoBR) ;; ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU Lesser General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WArraNTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU Lesser General Public License for more details. ;; ;; You should have received a copy of the GNU Lesser General Public License ;; along with this program. If not, see . ;;------------------------------------------------------------------------------- .module cpct_audio ;; ;; Title: Arkos Player Functions ;; ;;------------------------------------------------------------------------------------------------------ ;;--- PLAYER CONFIGURATION CONSTANTS ;;------------------------------------------------------------------------------------------------------ ;; ;; Constants: Arkos Player Compilation Constants ;; ;; Constants used to control which features are enabled / disabled in the Arkos Tracker ;; Player code. Changing them requires recompiling CPCtelera's library to take effect. ;; ;; PLY_UseSoundEffects - Set to 1 if you want to use Sound Effects in your player. ;; Both CPU and memory consuming. It is set to 1 by default in CPCtelera. ;; PLY_UseFades - Set to 1 to allow fades in / out. A little CPU and memory ;; consuming. _cpct_akp_setFadeVolume becomes available. ;; PLY_SystemFriendly - Set to 1 if you want to save the Registers used by AMSDOS ;; (AF', BC', IX, IY) which allows you to call this player in BASIC. As this option is ;; system-friendly, it cuts interruptions, and restores them ONLY IF NECESSARY. It is set ;; to 0 by default. However, IX and IY registers are always saved. ;; PLY_RetrigValue - Value used to trigger the Re-trig of Register 13. 0xFE ;; corresponds to 'CP xx'. Do not change it! ;; .equ PLY_UseSoundEffects, 1 .equ PLY_UseFades , 0 .equ PLY_SystemFriendly , 0 .equ PLY_RetrigValue , #0xFE ;;------------------------------------------------------------------------------------------------------ ;;--- PLAYER CODE START ;;------------------------------------------------------------------------------------------------------ ;; Digidrum Status ;; Read here to know if a Digidrum has been played (0=no). _cpct_akp_digidrumStatus:: .db 0 ;; Loop times ;; Read here to know the number of times a song has looped _cpct_akp_songLoopTimes:: .db 0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Function: cpct_akp_musicPlay ;; ;; Plays next music cycle of the present song with Arkos Tracker Player. Song ;; has had to be previously established with . ;; ;; C Definition: ;; void () ;; ;; Assembly call (Input parameters on registers): ;; > call cpct_akp_musicPlay_asm ;; ;; Known limitations: ;; * This function *will not work from ROM*, as it uses self-modifying code. ;; ;; Details: ;; This function is to be called to start and continue playing the presently ;; selected song with Arkos Tracker Player. Depending on the frequency at which ;; the song were created, this function should be called 12, 25, 50, 100, 200 ;; or 300 times per second. ;; ;; Each time you call the function, it plays 1/frequency seconds. This means ;; that you have to manually synchronize your calls to this function to have ;; a stable music playing. If you call too fast or too slow you will either ;; interrupt sound or have sound valleys. Therefore, you are responsible for ;; calling this function with the most accurate timing possible, to get best ;; sound results. ;; ;; Destroyed Register values: ;; AF, AF', BC, DE, HL, IX, IY ;; ;; Required memory: ;; 1794 bytes ;; ;; However, take into account that all of Arkos Tracker Player's ;; functions are linked and included, because they depend on each other. Total ;; memory requirement is around 2089 bytes. ;; ;; Time Measures: ;; (start code) ;; To be done ;; (end code) ;; ;; Credits: ;; This is a modification of the original ;; code from Targhan / Arkos. Madram / Overlander and Grim / Arkos have also ;; contributed to this source. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; _cpct_akp_musicPlay:: cpct_akp_musicPlay_asm:: ;; Entry point for assembly calls PLY_Play: ;***** Player System Friendly has to restore registers ***** .if PLY_SystemFriendly call PLY_DisableInterruptions ex af, af' exx push af push bc .endif push ix push iy xor a ld (_cpct_akp_digidrumStatus), a ;Reset the Digidrum flag. ;Manage Speed. If Speed counter is over, we have to read the Pattern further. PLY_SpeedCpt: ld a, #1 dec a jp nz, PLY_SpeedEnd ;Moving forward in the Pattern. Test if it is not over. PLY_HeightCpt: ld a, #1 dec a jr nz, PLY_HeightEnd ;Pattern Over. We have to read the Linker. ;Get the Transpositions, if they have changed, or detect the Song Ending ! PLY_Linker_PT: ld hl, #0 ld a, (hl) inc hl rra jr nc, PLY_SongNotOver ;Song over ! ;; Increment song loop times ld a, (_cpct_akp_songLoopTimes) inc a ld (_cpct_akp_songLoopTimes), a ;; We read the address of the Loop point. ld a, (hl) inc hl ld h, (hl) ld l, a ld a, (hl) ;We know the Song won't restart now, so we can skip the first bit. inc hl rra PLY_SongNotOver: rra jr nc, PLY_NoNewTransposition1 ld de, #PLY_Transposition1 + 1 ldi PLY_NoNewTransposition1: rra jr nc, PLY_NoNewTransposition2 ld de, #PLY_Transposition2 + 1 ldi PLY_NoNewTransposition2: rra jr nc, PLY_NoNewTransposition3 ld de, #PLY_Transposition3 + 1 ldi PLY_NoNewTransposition3: ;Get the Tracks addresses. ld de, #PLY_Track1_PT + 1 ldi ldi ld de, #PLY_Track2_PT + 1 ldi ldi ld de, #PLY_Track3_PT + 1 ldi ldi ;Get the Special Track address, if it has changed. rra jr nc, PLY_NoNewHeight ld de, #PLY_Height + 1 ldi PLY_NoNewHeight: rra jr nc, PLY_NoNewSpecialTrack PLY_NewSpecialTrack: ld e, (hl) inc hl ld d, (hl) inc hl ld (PLY_SaveSpecialTrack + 1), de PLY_NoNewSpecialTrack: ld (PLY_Linker_PT + 1), hl PLY_SaveSpecialTrack: ld hl, #0 ld (PLY_SpecialTrack_PT + 1), hl ;Reset the SpecialTrack/Tracks line counter. ;We can't rely on the song data, because the Pattern Height is not related to the Tracks Height. ld a, #1 ld (PLY_SpecialTrack_WaitCounter + 1), a ld (PLY_Track1_WaitCounter + 1), a ld (PLY_Track2_WaitCounter + 1), a ld (PLY_Track3_WaitCounter + 1), a PLY_Height: ld a, #1 PLY_HeightEnd: ld (PLY_HeightCpt + 1), a ;Read the Special Track/Tracks. ;------------------------------ ;Read the Special Track. PLY_SpecialTrack_WaitCounter: ld a, #1 dec a jr nz, PLY_SpecialTrack_Wait PLY_SpecialTrack_PT: ld hl, #0 ld a, (hl) inc hl srl a ;Data (1) or Wait (0) ? jr nc, PLY_SpecialTrack_NewWait ;If Wait, A contains the Wait value. ;Data. Effect Type ? srl a ;Speed (0) or Digidrum (1) ? ;First, we don't test the Effect Type, but only the Escape Code (=0) jr nz, PLY_SpecialTrack_NoEscapeCode ld a, (hl) inc hl PLY_SpecialTrack_NoEscapeCode: ;Now, we test the Effect type, since the Carry didn't change. jr nc,PLY_SpecialTrack_Speed ld (_cpct_akp_digidrumStatus), a jr PLY_PT_SpecialTrack_EndData PLY_SpecialTrack_Speed: ld (PLY_Speed + 1), a PLY_PT_SpecialTrack_EndData: ld a, #1 PLY_SpecialTrack_NewWait: ld (PLY_SpecialTrack_PT + 1), hl PLY_SpecialTrack_Wait: ld (PLY_SpecialTrack_WaitCounter + 1), a ;Read the Track 1. ;----------------- ;Store the parameters, because the player below is called every frame, but the Read Track isn't. PLY_Track1_WaitCounter: ld a, #1 dec a jr nz, PLY_Track1_NewInstrument_SetWait PLY_Track1_PT: ld hl, #0 call PLY_ReadTrack ld (PLY_Track1_PT + 1), hl jr c, PLY_Track1_NewInstrument_SetWait ;No Wait command. Can be a Note and/or Effects. ;Make a copy of the flags+Volume in a, not to temper with the original. ld a, d rra ;Volume ? If bit 4 was 1, then volume exists on b3-b0 jr nc, PLY_Track1_SameVolume and #0b1111 ld (PLY_Track1_Volume), a PLY_Track1_SameVolume: rl d ;New Pitch ? jr nc, PLY_Track1_NoNewPitch ld (PLY_Track1_PitchAdd + 1), ix PLY_Track1_NoNewPitch: rl d ;Note ? If no Note, we don't have to test if a new Instrument is here. jr nc, PLY_Track1_NoNoteGiven ld a, e PLY_Transposition1: add a, #0 ;Transpose Note according to the Transposition in the Linker. ld (PLY_Track1_Note), a ld hl, #0 ;Reset the TrackPitch. ld (PLY_Track1_Pitch + 1), hl rl d ;New Instrument ? jr c, PLY_Track1_NewInstrument PLY_Track1_SavePTInstrument: ld hl, #0 ;Same Instrument. We recover its address to restart it. ld a, (PLY_Track1_InstrumentSpeed + 1) ;Reset the Instrument Speed Counter. Never seemed useful... ld (PLY_Track1_InstrumentSpeedCpt + 1), a jr PLY_Track1_InstrumentResetPT PLY_Track1_NewInstrument: ;New Instrument. We have to get its new address, and Speed. ld l, b ;H is already set to 0 before. add hl, hl PLY_Track1_InstrumentsTablePT: ld bc, #0 add hl, bc ld a, (hl) ;Get Instrument address. inc hl ld h, (hl) ld l, a ld a, (hl) ;Get Instrument speed. inc hl ld (PLY_Track1_InstrumentSpeed + 1), a ld (PLY_Track1_InstrumentSpeedCpt + 1), a ld a, (hl) or a ;Get IsRetrig?. Code it only if different to 0, else next Instruments are jr z, .+5 ; going to overwrite it. ld (PLY_PSGReg13_Retrig + 1), a inc hl ld (PLY_Track1_SavePTInstrument + 1), hl ;When using the Instrument again, no need to give the Speed, it is skipped. PLY_Track1_InstrumentResetPT: ld (PLY_Track1_Instrument + 1),hl PLY_Track1_NoNoteGiven: ld a, #1 PLY_Track1_NewInstrument_SetWait: ld (PLY_Track1_WaitCounter + 1), a ;Read the Track 2. ;----------------- ;Store the parameters, because the player below is called every frame, but the Read Track isn't. PLY_Track2_WaitCounter: ld a, #1 dec a jr nz, PLY_Track2_NewInstrument_SetWait PLY_Track2_PT: ld hl, #0 call PLY_ReadTrack ld (PLY_Track2_PT + 1), hl jr c, PLY_Track2_NewInstrument_SetWait ;No Wait command. Can be a Note and/or Effects. ;Make a copy of the flags+Volume in a, not to temper with the original. ld a, d rra ;Volume ? If bit 4 was 1, then volume exists on b3-b0 jr nc, PLY_Track2_SameVolume and #0b1111 ld (PLY_Track2_Volume), a PLY_Track2_SameVolume: rl d ;New Pitch ? jr nc, PLY_Track2_NoNewPitch ld (PLY_Track2_PitchAdd + 1), ix PLY_Track2_NoNewPitch: rl d ;Note ? If no Note, we don't have to test if a new Instrument is here. jr nc,PLY_Track2_NoNoteGiven ld a, e PLY_Transposition2: add a, #0 ;Transpose Note according to the Transposition in the Linker. ld (PLY_Track2_Note), a ld hl, #0 ;Reset the TrackPitch. ld (PLY_Track2_Pitch + 1), hl rl d ;New Instrument ? jr c, PLY_Track2_NewInstrument PLY_Track2_SavePTInstrument: ld hl, #0 ;Same Instrument. We recover its address to restart it. ld a, (PLY_Track2_InstrumentSpeed + 1) ;Reset the Instrument Speed Counter. Never seemed useful... ld (PLY_Track2_InstrumentSpeedCpt + 1), a jr PLY_Track2_InstrumentResetPT PLY_Track2_NewInstrument: ;New Instrument. We have to get its new address, and Speed. ld l, b ;H is already set to 0 before. add hl, hl PLY_Track2_InstrumentsTablePT: ld bc, #0 add hl, bc ld a, (hl) ;Get Instrument address. inc hl ld h, (hl) ld l, a ld a, (hl) ;Get Instrument speed. inc hl ld (PLY_Track2_InstrumentSpeed + 1), a ld (PLY_Track2_InstrumentSpeedCpt + 1), a ld a, (hl) or a ;Get IsRetrig?. Code it only if different to 0, else next Instruments are going to overwrite it. jr z, .+5 ld (PLY_PSGReg13_Retrig + 1), a inc hl ld (PLY_Track2_SavePTInstrument + 1), hl ;When using the Instrument again, no need to give the Speed, it is skipped. PLY_Track2_InstrumentResetPT: ld (PLY_Track2_Instrument + 1), hl PLY_Track2_NoNoteGiven: ld a, #1 PLY_Track2_NewInstrument_SetWait: ld (PLY_Track2_WaitCounter + 1), a ;Read the Track 3. ;----------------- ;Store the parameters, because the player below is called every frame, but the Read Track isn't. PLY_Track3_WaitCounter: ld a, #1 dec a jr nz, PLY_Track3_NewInstrument_SetWait PLY_Track3_PT: ld hl, #0 call PLY_ReadTrack ld (PLY_Track3_PT + 1), hl jr c, PLY_Track3_NewInstrument_SetWait ;No Wait command. Can be a Note and/or Effects. ;Make a copy of the flags+Volume in a, not to temper with the original. ld a, d rra ;Volume ? If bit 4 was 1, then volume exists on b3-b0 jr nc, PLY_Track3_SameVolume and #0b1111 ld (PLY_Track3_Volume), a PLY_Track3_SameVolume: rl d ;New Pitch ? jr nc,PLY_Track3_NoNewPitch ld (PLY_Track3_PitchAdd + 1), ix PLY_Track3_NoNewPitch: rl d ;Note ? If no Note, we don't have to test if a new Instrument is here. jr nc, PLY_Track3_NoNoteGiven ld a, e PLY_Transposition3: add a, #0 ;Transpose Note according to the Transposition in the Linker. ld (PLY_Track3_Note), a ld hl, #0 ;Reset the TrackPitch. ld (PLY_Track3_Pitch + 1), hl rl d ;New Instrument ? jr c, PLY_Track3_NewInstrument PLY_Track3_SavePTInstrument: ld hl, #0 ;Same Instrument. We recover its address to restart it. ld a, (PLY_Track3_InstrumentSpeed + 1) ;Reset the Instrument Speed Counter. Never seemed useful... ld (PLY_Track3_InstrumentSpeedCpt + 1), a jr PLY_Track3_InstrumentResetPT PLY_Track3_NewInstrument: ;New Instrument. We have to get its new address, and Speed. ld l, b ;H is already set to 0 before. add hl, hl PLY_Track3_InstrumentsTablePT: ld bc, #0 add hl, bc ld a, (hl) ;Get Instrument address. inc hl ld h, (hl) ld l, a ld a, (hl) ;Get Instrument speed. inc hl ld (PLY_Track3_InstrumentSpeed + 1), a ld (PLY_Track3_InstrumentSpeedCpt + 1), a ld a, (hl) or a ;Get IsRetrig?. Code it only if different to 0, else next Instruments are going to overwrite it. jr z, .+5 ld (PLY_PSGReg13_Retrig + 1), a inc hl ld (PLY_Track3_SavePTInstrument + 1), hl ;When using the Instrument again, no need to give the Speed, it is skipped. PLY_Track3_InstrumentResetPT: ld (PLY_Track3_Instrument + 1), hl PLY_Track3_NoNoteGiven: ld a, #1 PLY_Track3_NewInstrument_SetWait: ld (PLY_Track3_WaitCounter + 1), a PLY_Speed: ld a, #1 PLY_SpeedEnd: ld (PLY_SpeedCpt + 1), a ;Play the Sound on Track 3 ;------------------------- ;Plays the sound on each frame, but only save the forwarded Instrument pointer when Instrument Speed is reached. ;This is needed because TrackPitch is involved in the Software Frequency/Hardware Frequency calculation, ;and is calculated every frame. ld iy, #PLY_PSGRegistersArray + 4 PLY_Track3_Pitch: ld hl, #0 PLY_Track3_PitchAdd: ld de, #0 add hl, de ld (PLY_Track3_Pitch + 1), hl sra h ;Shift the Pitch to slow its speed. rr l sra h rr l ex de, hl exx .equ PLY_Track3_Volume, .+2 .equ PLY_Track3_Note, .+1 ld de, #0 ;D=Inverted Volume E=Note PLY_Track3_Instrument: ld hl, #0 call PLY_PlaySound PLY_Track3_InstrumentSpeedCpt: ld a, #1 dec a jr nz, PLY_Track3_PlayNoForward ld (PLY_Track3_Instrument + 1), hl PLY_Track3_InstrumentSpeed: ld a, #6 PLY_Track3_PlayNoForward: ld (PLY_Track3_InstrumentSpeedCpt + 1), a ;*************************************** ;Play Sound Effects on Track 3 (If activated) ;*************************************** .if PLY_UseSoundEffects PLY_SFX_Track3_Pitch: ld de, #0 exx .equ PLY_SFX_Track3_Volume, .+2 .equ PLY_SFX_Track3_Note, .+1 ld de, #0 ;D=Inverted Volume E=Note PLY_SFX_Track3_Instrument: ld hl, #0 ;If 0, no sound effect. ld a, l or h jr z, PLY_SFX_Track3_End ld a, #1 ld (PLY_PS_EndSound_SFX + 1), a call PLY_PlaySound xor a ld (PLY_PS_EndSound_SFX + 1), a ld a, l ;If the new address is 0, the instrument is over. or h ;Speed is set in the process, we don't care. jr z, PLY_SFX_Track3_Instrument_SetAddress PLY_SFX_Track3_InstrumentSpeedCpt: ld a, #1 dec a jr nz, PLY_SFX_Track3_PlayNoForward PLY_SFX_Track3_Instrument_SetAddress: ld (PLY_SFX_Track3_Instrument + 1), hl PLY_SFX_Track3_InstrumentSpeed: ld a, #6 PLY_SFX_Track3_PlayNoForward: ld (PLY_SFX_Track3_InstrumentSpeedCpt + 1), a PLY_SFX_Track3_End: .endif ;****************************************** .dw #0x7DDD ; ld a, ixl ;Save the Register 7 of the Track 3. ex af, af' ;Play the Sound on Track 2 ;------------------------- ld iy, #PLY_PSGRegistersArray + 2 PLY_Track2_Pitch: ld hl, #0 PLY_Track2_PitchAdd: ld de, #0 add hl, de ld (PLY_Track2_Pitch + 1), hl sra h ;Shift the Pitch to slow its speed. rr l sra h rr l ex de, hl exx .equ PLY_Track2_Volume, .+2 .equ PLY_Track2_Note, .+1 ld de, #0 ;D=Inverted Volume E=Note PLY_Track2_Instrument: ld hl, #0 call PLY_PlaySound PLY_Track2_InstrumentSpeedCpt: ld a, #1 dec a jr nz, PLY_Track2_PlayNoForward ld (PLY_Track2_Instrument + 1), hl PLY_Track2_InstrumentSpeed: ld a, #6 PLY_Track2_PlayNoForward: ld (PLY_Track2_InstrumentSpeedCpt + 1), a ;*************************************** ;Play Sound Effects on Track 2 (If activated) ;*************************************** .if PLY_UseSoundEffects PLY_SFX_Track2_Pitch: ld de, #0 exx .equ PLY_SFX_Track2_Volume, .+2 .equ PLY_SFX_Track2_Note, .+1 ld de, #0 ;D=Inverted Volume E=Note PLY_SFX_Track2_Instrument: ld hl, #0 ;If 0, no sound effect. ld a, l or h jr z, PLY_SFX_Track2_End ld a, #1 ld (PLY_PS_EndSound_SFX + 1), a call PLY_PlaySound xor a ld (PLY_PS_EndSound_SFX + 1), a ld a, l ;If the new address is 0, the instrument is over. or h ;Speed is set in the process, we don't care. jr z, PLY_SFX_Track2_Instrument_SetAddress PLY_SFX_Track2_InstrumentSpeedCpt: ld a, #1 dec a jr nz, PLY_SFX_Track2_PlayNoForward PLY_SFX_Track2_Instrument_SetAddress: ld (PLY_SFX_Track2_Instrument + 1), hl PLY_SFX_Track2_InstrumentSpeed: ld a, #6 PLY_SFX_Track2_PlayNoForward: ld (PLY_SFX_Track2_InstrumentSpeedCpt + 1), a PLY_SFX_Track2_End: .endif ;****************************************** ex af, af' add a, a ;Mix Reg7 from Track2 with Track3, making room first. .dw #0xB5DD ; or ixl rla ex af, af' ;Play the Sound on Track 1 ;------------------------- ld iy, #PLY_PSGRegistersArray PLY_Track1_Pitch: ld hl, #0 PLY_Track1_PitchAdd: ld de, #0 add hl, de ld (PLY_Track1_Pitch + 1), hl sra h ;Shift the Pitch to slow its speed. rr l sra h rr l ex de, hl exx .equ PLY_Track1_Volume, .+2 .equ PLY_Track1_Note, .+1 ld de, #0 ;D=Inverted Volume E=Note PLY_Track1_Instrument: ld hl, #0 call PLY_PlaySound PLY_Track1_InstrumentSpeedCpt: ld a, #1 dec a jr nz, PLY_Track1_PlayNoForward ld (PLY_Track1_Instrument + 1), hl PLY_Track1_InstrumentSpeed: ld a, #6 PLY_Track1_PlayNoForward: ld (PLY_Track1_InstrumentSpeedCpt + 1), a ;*************************************** ;Play Sound Effects on Track 1 (If activated) ;*************************************** .if PLY_UseSoundEffects PLY_SFX_Track1_Pitch: ld de, #0 exx .equ PLY_SFX_Track1_Volume, .+2 .equ PLY_SFX_Track1_Note, .+1 ld de, #0 ;D=Inverted Volume E=Note PLY_SFX_Track1_Instrument: ld hl, #0 ;If 0, no sound effect. ld a, l or h jr z, PLY_SFX_Track1_End ld a, #1 ld (PLY_PS_EndSound_SFX + 1), a call PLY_PlaySound xor a ld (PLY_PS_EndSound_SFX + 1), a ld a, l ;If the new address is 0, the instrument is over. or h ;Speed is set in the process, we don't care. jr z, PLY_SFX_Track1_Instrument_SetAddress PLY_SFX_Track1_InstrumentSpeedCpt: ld a, #1 dec a jr nz, PLY_SFX_Track1_PlayNoForward PLY_SFX_Track1_Instrument_SetAddress: ld (PLY_SFX_Track1_Instrument + 1), hl PLY_SFX_Track1_InstrumentSpeed: ld a, #6 PLY_SFX_Track1_PlayNoForward: ld (PLY_SFX_Track1_InstrumentSpeedCpt + 1), a PLY_SFX_Track1_End: .endif ;*********************************** ex af, af' .dw #0xB5DD ; or ixl ;Mix Reg7 from Track3 with Track2+1. ;Send the registers to PSG. Various codes according to the machine used. PLY_SendRegisters: ;A=Register 7 ld de, #0xC080 ld b, #0xF6 out (c),d ;#f6c0 exx ld hl, #PLY_PSGRegistersArray ld e, #0xF6 ld bc, #0xF401 ;Register 0 .dw #0x71ED ;out(c), 0 ; #0xF400+Register ld b, e .dw #0x71ED ;out(c), 0 ; #0xF600 dec b outi ; #0xF400+value exx out (c), e ; #0xF680 out (c), d ; #0xF6C0 exx ;Register 1 out (c),c ld b, e .dw #0x71ED ;out(c), 0 dec b outi exx out (c), e out (c), d exx inc c ;Register 2 out (c),c ld b, e .dw #0x71ED ;out(c), 0 dec b outi exx out (c), e out (c), d exx inc c ;Register 3 out (c),c ld b, e .dw #0x71ED ;out(c), 0 dec b outi exx out (c), e out (c), d exx inc c ;Register 4 out (c),c ld b, e .dw #0x71ED ;out(c), 0 dec b outi exx out (c), e out (c), d exx inc c ;Register 5 out (c),c ld b, e .dw #0x71ED ;out(c), 0 dec b outi exx out (c), e out (c), d exx inc c ;Register 6 out (c),c ld b, e .dw #0x71ED ;out(c), 0 dec b outi exx out (c), e out (c), d exx inc c ;Register 7 out (c),c ld b, e .dw #0x71ED ;out(c), 0 dec b dec b out (c), a ;Read A register instead of the list. exx out (c), e out (c), d exx inc c ;Register 8 out (c), c ld b, e .dw #0x71ED ;out(c), 0 dec b .if PLY_UseFades dec b ld a, (hl) PLY_Channel1_FadeValue: sub #0 ;Set a value from 0 (full volume) to 16 or more (volume to 0). jr nc, .+6 .dw #0x71ED ;out(c), 0 jr .+4 out (c), a inc hl .else outi .endif exx out (c), e out (c), d exx inc c inc hl ;Skip unused byte. ;Register 9 out (c), c ld b, e .dw #0x71ED ;out(c), 0 dec b .if PLY_UseFades ;If PLY_UseFades is set to 1, we manage the volume fade. dec b ld a, (hl) PLY_Channel2_FadeValue: sub #0 ;Set a value from 0 (full volume) to 16 or more (volume to 0). jr nc, .+6 .dw #0x71ED ;out(c), 0 jr .+4 out (c), a inc hl .else outi .endif exx out (c), e out (c), d exx inc c inc hl ;Skip unused byte. ;Register 10 out (c), c ld b, e .dw #0x71ED ;out(c), 0 dec b .if PLY_UseFades dec b ld a, (hl) PLY_Channel3_FadeValue: sub #0 ;Set a value from 0 (full volume) to 16 or more (volume to 0). jr nc, .+6 .dw #0x71ED ;out(c), 0 jr .+4 out (c), a inc hl .else outi .endif exx out (c), e out (c), d exx inc c ;Register 11 out (c),c ld b, e .dw #0x71ED ;out(c), 0 dec b outi exx out (c), e out (c), d exx inc c ;Register 12 out (c),c ld b, e .dw #0x71ED ;out(c), 0 dec b outi exx out (c), e out (c), d exx inc c ;Register 13 .if PLY_SystemFriendly call PLY_PSGReg13_Code .endif PLY_PSGREG13_RecoverSystemRegisters: pop iy pop ix .if PLY_SystemFriendly pop bc pop af exx ex af, af' ;Restore Interrupt status PLY_RestoreInterruption: nop ;Will be automodified to an DI/EI. ret .endif PLY_PSGReg13_Code: ld a, (hl) PLY_PSGReg13_Retrig: cp #255 ;If IsRetrig?, force the R13 to be triggered. ret z ld (PLY_PSGReg13_Retrig + 1), a out (c),c ld b, e .dw #0x71ED ;out(c), 0 dec b outi exx out (c), e out (c), d ret ;There are two holes in the list, because the Volume registers are set relatively to ;the Frequency of the same Channel (+7, always). Also, the Reg7 is passed as a register, so is not kept in the memory. PLY_PSGRegistersArray: PLY_PSGReg0: .db 0 PLY_PSGReg1: .db 0 PLY_PSGReg2: .db 0 PLY_PSGReg3: .db 0 PLY_PSGReg4: .db 0 PLY_PSGReg5: .db 0 PLY_PSGReg6: .db 0 PLY_PSGReg8: .db 0 ;+7 .db 0 PLY_PSGReg9: .db 0 ;+9 .db 0 PLY_PSGReg10: .db 0 ;+11 PLY_PSGReg11: .db 0 PLY_PSGReg12: .db 0 PLY_PSGReg13: .db 0 PLY_PSGRegistersArray_End: ;Plays a sound stream. ;hl=Pointer on Instrument Data ;IY=Pointer on Register code (volume, frequency). ;E=Note ;D=Inverted Volume ;DE'=TrackPitch ;RET= ;hl=New Instrument pointer. ;IXL=Reg7 mask (x00x) ;Also used inside = ;B,C=read byte/second byte. ;IXH=Save original Note (only used for Independant mode). PLY_PlaySound: ld b, (hl) inc hl rr b jp c, PLY_PS_Hard ;************** ;Software Sound ;************** ;Second Byte needed ? rr b jr c, PLY_PS_S_SecondByteNeeded ;No second byte needed. We need to check if Volume is null or not. ld a, b and #0b1111 jr nz, PLY_PS_S_SoundOn ;Null Volume. It means no Sound. We stop the Sound, the Noise, and it's over. ;We have to make the volume to 0, because if a bass Hard was activated before, we have to stop it. ld 7(iy), a .db #0xDD, #0x2E, #0b1001 ; ld ixl,%1001 ret PLY_PS_S_SoundOn: ;Volume is here, no Second Byte needed. It means we have a simple Software sound (Sound = On, Noise = Off) ;We have to test Arpeggio and Pitch, however. .db #0xDD, #0x2E, #0b1000 ; ld ixl,%1000 sub d ;Code Volume. jr nc, .+3 xor a ld 7(iy), a rr b ;Needed for the subroutine to get the good flags. call PLY_PS_CalculateFrequency ld 0(iy), l ;Code Frequency. ld 1(iy), h exx ret PLY_PS_S_SecondByteNeeded: .db #0xDD, #0x2E, #0b1000 ; ld ixl,%1000 ;By defaut, No Noise, Sound. ;Second Byte needed. ld c, (hl) inc hl ;Noise ? ld a, c and #0b11111 jr z, PLY_PS_S_SBN_NoNoise ld (PLY_PSGReg6), a .db #0xDD, #0x2E, #0b0000 ; ld ixl,%0000 ;Open Noise Channel. PLY_PS_S_SBN_NoNoise: ;Here we have either Volume and/or Sound. So first we need to read the Volume. ld a, b and #0b1111 sub d ;Code Volume. jr nc, .+3 xor a ld 7(iy), a ;Sound ? bit 5, c jr nz, PLY_PS_S_SBN_Sound ;No Sound. Stop here. .dw #0x2CDD ; inc ixl ;Set Sound bit to stop the Sound. ret PLY_PS_S_SBN_Sound: ;Manual Frequency ? rr b ;Needed for the subroutine to get the good flags. bit 6, c call PLY_PS_CalculateFrequency_TestManualFrequency ld 0(iy), l ;Code Frequency. ld 1(iy), h exx ret ;********** ;Hard Sound ;********** PLY_PS_Hard: ;We don't set the Volume to 16 now because we may have reached the end of the sound ! rr b ;Test Retrig here, it is common to every Hard sounds. jr nc, PLY_PS_Hard_NoRetrig ld a, (PLY_Track1_InstrumentSpeedCpt + 1) ;Retrig only if it is the first step in this line of Instrument ! ld c, a ld a, (PLY_Track1_InstrumentSpeed + 1) cp c jr nz, PLY_PS_Hard_NoRetrig ld a, #PLY_RetrigValue ld (PLY_PSGReg13_Retrig + 1), a PLY_PS_Hard_NoRetrig: ;Independant/Loop or Software/Hardware Dependent ? bit 1, b ;We don't shift the bits, so that we can use the same code jp nz, PLY_PS_Hard_LoopOrIndependent ;(Frequency calculation) several times. ;Hardware Sound. ld 7(iy), #16 ;Set Volume .db #0xDD, #0x2E, #0b1000 ; ld ixl,%1000 ;Sound is always On here (only Independence mode can switch it off). ;This code is common to both Software and Hardware Dependent. ld c, (hl) ;Get Second Byte. inc hl ld a, c ;Get the Hardware Envelope waveform. and #0b1111 ;We don't care about the bit 7-4, but we have to clear them, ld (PLY_PSGReg13), a ;else the waveform might be reset. bit 0, b jr z, PLY_PS_HardwareDependent ;****************** ;Software Dependent ;****************** ;Calculate the Software frequency bit 4-2, b ;Manual Frequency ? -2 Because the byte has been shifted previously. call PLY_PS_CalculateFrequency_TestManualFrequency ld 0(iy), l ;Code Software Frequency. ld 1(iy), h exx ;Shift the Frequency. ld a, c rra rra ;Shift=Shift*4. The shift is inverted in memory (7 - Editor Shift). and #0b11100 ld (PLY_PS_SD_Shift + 1), a ld a, b ;Used to get the HardwarePitch flag within the second registers set. exx PLY_PS_SD_Shift: jr .+2 srl h rr l srl h rr l srl h rr l srl h rr l srl h rr l srl h rr l srl h rr l jr nc, .+3 inc hl ;Hardware Pitch ? bit 7-2, a jr z, PLY_PS_SD_NoHardwarePitch exx ;Get Pitch and add it to the just calculated Hardware Frequency. ld a, (hl) inc hl exx add a, l ;Slow. Can be optimised ? Probably never used anyway..... ld l, a exx ld a, (hl) inc hl exx adc a, h ld h, a PLY_PS_SD_NoHardwarePitch: ld (PLY_PSGReg11), hl exx ;This code is also used by Hardware Dependent. PLY_PS_SD_Noise: ;Noise ? bit 7, c ret z ld a, (hl) inc hl ld (PLY_PSGReg6), a .db #0xDD, #0x2E, #0b0000 ; ld ixl,%0000 ret ;****************** ;Hardware Dependent ;****************** PLY_PS_HardwareDependent: ;Calculate the Hardware frequency bit 4-2, b ;Manual Hardware Frequency ? -2 Because the byte has been shifted previously. call PLY_PS_CalculateFrequency_TestManualFrequency ld (PLY_PSGReg11),hl ;Code Hardware Frequency. exx ;Shift the Hardware Frequency. ld a, c rra rra ;Shift=Shift*4. The shift is inverted in memory (7 - Editor Shift). and #0b11100 ld (PLY_PS_HD_Shift + 1), a ld a, b ;Used to get the Software flag within the second registers set. exx PLY_PS_HD_Shift: jr .+2 sla l rl h sla l rl h sla l rl h sla l rl h sla l rl h sla l rl h sla l rl h ;Software Pitch ? bit 7-2, a jr z, PLY_PS_HD_NoSoftwarePitch exx ;Get Pitch and add it to the just calculated Software Frequency. ld a, (hl) inc hl exx add a, l ld l, a ;Slow. Can be optimised ? Probably never used anyway..... exx ld a, (hl) inc hl exx adc a, h ld h, a PLY_PS_HD_NoSoftwarePitch: ld 0(iy), l ;Code Frequency. ld 1(iy), h exx ;Go to manage Noise, common to Software Dependent. jr PLY_PS_SD_Noise PLY_PS_Hard_LoopOrIndependent: bit 0, b ;We mustn't shift it to get the result in the Carry, as it would be mess jr z, PLY_PS_Independent ;the structure of the flags, making it uncompatible with the common code. ;The sound has ended. ;If Sound Effects activated, we mark the "end of sound" by returning a 0 as an address. .if PLY_UseSoundEffects PLY_PS_EndSound_SFX: ld a, #0 ; Is the sound played is a SFX (1) or a normal sound (0) ? or a jr z, PLY_PS_EndSound_NotASFX ld hl, #0 ret PLY_PS_EndSound_NotASFX: .endif ;The sound has ended. Read the new pointer and restart instrument. ld a, (hl) inc hl ld h, (hl) ld l, a jp PLY_PlaySound ;*********** ;Independent ;*********** PLY_PS_Independent: ld 7(iy), #16 ;Set Volume ;Sound ? bit 7-2, b ;-2 Because the byte has been shifted previously. jr nz, PLY_PS_I_SoundOn ;No Sound ! It means we don't care about the software frequency (manual frequency, arpeggio, pitch). .db #0xDD, #0x2E, #0b1001 ; ld ixl,%1001 jr PLY_PS_I_SkipSoftwareFrequencyCalculation PLY_PS_I_SoundOn: .db #0xDD, #0x2E, #0b1000 ; ld ixl, %1000 ;Sound is on. .dw #0x63DD ; ld ixh, e ;Save the original note for the Hardware frequency, ;because a Software Arpeggio will modify it. ;Calculate the Software frequency bit 4-2, b ;Manual Frequency ? -2 Because the byte has been shifted previously. call PLY_PS_CalculateFrequency_TestManualFrequency ld 0(iy), l ;Code Software Frequency. ld 1(iy), h exx .dw #0x5CDD ; ld e, ixh PLY_PS_I_SkipSoftwareFrequencyCalculation: ld b, (hl) ;Get Second Byte. inc hl ld a, b ;Get the Hardware Envelope waveform. and #0b1111 ;We don't care about the bit 7-4, but we have to clear them, ld (PLY_PSGReg13), a ;else the waveform might be reset. ;Calculate the Hardware frequency rr b ;Must shift it to match the expected data of the subroutine. rr b bit 4-2, b ;Manual Hardware Frequency ? -2 Because the byte has been shifted previously. call PLY_PS_CalculateFrequency_TestManualFrequency ld (PLY_PSGReg11), hl ;Code Hardware Frequency. exx ;Noise ? We can't use the previous common code, because the setting of the Noise is different, ;since Independent can have no Sound. bit 7-2, b ret z ld a, (hl) inc hl ld (PLY_PSGReg6), a .dw #0x7DDD ; ld a, ixl ;Set the Noise bit. res 3, a .dw #0x6FDD ; ld ixl, a ret ;Subroutine that = ;If Manual Frequency? (Flag Z off), read frequency (Word) and adds the TrackPitch (DE'). ;Else, Auto Frequency. ; if Arpeggio? = 1 (bit 3 from B), read it (Byte). ; if Pitch? = 1 (bit 4 from B), read it (Word). ; Calculate the frequency according to the Note (E) + Arpeggio + TrackPitch (DE'). ;hl = Pointer on Instrument data. ;DE'= TrackPitch. ;RET= ;hl = Pointer on Instrument moved forward. ;hl'= Frequency ; RETURN IN AUXILIARY REGISTERS PLY_PS_CalculateFrequency_TestManualFrequency: jr z, PLY_PS_CalculateFrequency ;Manual Frequency. We read it, no need to read Pitch and Arpeggio. ;However, we add TrackPitch to the read Frequency, and that's all. ld a, (hl) inc hl exx add a, e ;Add TrackPitch LSB. ld l, a exx ld a, (hl) inc hl exx adc a, d ;Add TrackPitch HSB. ld h, a ret PLY_PS_CalculateFrequency: ;Pitch ? bit 5-1, b jr z, PLY_PS_S_SoundOn_NoPitch ld a, (hl) inc hl exx add a, e ;If Pitch found, add it directly to the TrackPitch. ld e, a exx ld a, (hl) inc hl exx adc a, d ld d, a exx PLY_PS_S_SoundOn_NoPitch: ;Arpeggio ? ld a, e bit 4-1,b jr z, PLY_PS_S_SoundOn_ArpeggioEnd add a, (hl) ;Add Arpeggio to Note. inc hl cp #144 jr c, .+4 ld a, #143 PLY_PS_S_SoundOn_ArpeggioEnd: ;Frequency calculation. exx ld l, a ld h, #0 add hl, hl ld bc, #PLY_FrequencyTable add hl, bc ld a, (hl) inc hl ld h, (hl) ld l, a add hl, de ;Add TrackPitch + InstrumentPitch (if any). ret ;Read one Track. ;hl=Track Pointer. ;Ret = ;hl=New Track Pointer. ;Carry = 1 = Wait A lines. Carry=0=Line not empty. ;A=Wait (0(=256)-127), if Carry. ;D=Parameters + Volume. ;E=Note ;B=Instrument. 0=RST ;IX=PitchAdd. Only used if Pitch? = 1. PLY_ReadTrack: ld a, (hl) inc hl srl a ;Full Optimisation ? If yes = Note only, no Pitch, no Volume, Same Instrument. jr c, PLY_ReadTrack_FullOptimisation sub #32 ;0-31 = Wait. jr c, PLY_ReadTrack_Wait jr z, PLY_ReadTrack_NoOptimisation_EscapeCode dec a ;0 (32-32) = Escape Code for more Notes (parameters will be read) ;Note. Parameters are present. But the note is only present if Note? flag is 1. ld e, a ;Save Note. ;Read Parameters PLY_ReadTrack_ReadParameters: ld a, (hl) ld d, a ;Save Parameters. inc hl rla ;Pitch ? jr nc, PLY_ReadTrack_Pitch_End ld b, (hl) ;Get PitchAdd .dw #0x68DD ; ld ixl, b inc hl ld b, (hl) .dw #0x60DD ; ld ixh, b inc hl PLY_ReadTrack_Pitch_End: rla ;Skip IsNote? flag. rla ;New Instrument ? ret nc ld b, (hl) inc hl or a ;Remove Carry, as the player interpret it as a Wait command. ret ;Escape code, read the Note and returns to read the Parameters. PLY_ReadTrack_NoOptimisation_EscapeCode: ld e, (hl) inc hl jr PLY_ReadTrack_ReadParameters PLY_ReadTrack_FullOptimisation: ;Note only, no Pitch, no Volume, Same Instrument. ld d, #0b01000000 ;Note only. sub #1 ld e, a ret nc ld e, (hl) ;Escape Code found (0). Read Note. inc hl or a ret PLY_ReadTrack_Wait: add a, #32 ret PLY_FrequencyTable: .dw 3822,3608,3405,3214,3034,2863,2703,2551,2408,2273,2145,2025 .dw 1911,1804,1703,1607,1517,1432,1351,1276,1204,1136,1073,1012 .dw 956,902,851,804,758,716,676,638,602,568,536,506 .dw 478,451,426,402,379,358,338,319,301,284,268,253 .dw 239,225,213,201,190,179,169,159,150,142,134,127 .dw 119,113,106,100,95,89,84,80,75,71,67,63 .dw 60,56,53,50,47,45,42,40,38,36,34,32 .dw 30,28,27,25,24,22,21,20,19,18,17,16 .dw 15,14,13,13,12,11,11,10,9,9,8,8 .dw 7,7,7,6,6,6,5,5,5,4,4,4 .dw 4,4,3,3,3,3,3,2,2,2,2,2 .dw 2,2,2,2,1,1,1,1,1,1,1,1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Function: cpct_akp_musicInit ;; ;; Sets up a music into Arkos Tracker Player to be played later on with ;; . ;; ;; C Definition: ;; void (void* *songdata*) ;; ;; Input Parameters (2 bytes): ;; (2B DE) songdata - Pointer to the start of the array containing song's data in AKS binary format ;; ;; Assembly call (Input parameters on registers): ;; > call cpct_akp_musicInit_asm ;; ;; Parameter Restrictions: ;; * *songdata* must be an array containing dong's data in AKS binary format. ;; Take into account that AKS binary format enforces a concrete start location in ;; memory. Therefore, song must have been created in Arkos Tracker and exported ;; to the same memory location that *songdata* points to. If you fail to ;; locate the song at the same memory location it was exported for in Arkos ;; Tracker, unexpected results will happen (Typically, noise will be played but, ;; occasionally your program may hang or crash). ;; ;; Known limitations: ;; * *songdata* must be the same memory address that the one given to Arkos ;; Tracker when exporting song's binary. Arkos Tracker songs are created to ;; be at a concrete memory location, due to optimization constraints. Therefore, ;; this must be taken into account. If you wanted to change the memory location ;; of the song, you should first open the song into Arkos Tracker and export ;; it again with the new desired memory location. ;; * This function *will not work from ROM*, as it uses self-modifying code. ;; ;; Details: ;; This function should be called fist to initialize the song that is to be ;; played. The function reads the song header and sets up the player to start ;; playing it. Once this process is done, should be called ;; at the required frequency to continuously play the song. ;; ;; Destroyed Register values: ;; AF, AF', BC, DE, HL, IX, IY ;; ;; Required memory: ;; 92 bytes ;; ;; However, take into account that all of Arkos Tracker Player's ;; functions are linked and included, because they depend on each other. Total ;; memory requirement is around 2089 bytes. ;; ;; Time Measures: ;; (start code) ;; To be done ;; (end code) ;; ;; Credits: ;; This is a modification of the original ;; code from Targhan / Arkos. Madram / Overlander and Grim / Arkos have also ;; contributed to this source. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; _cpct_akp_musicInit:: ld hl, #2 ;; [10] Retrieve parameters from stack add hl, sp ;; [11] ld e, (hl) ;; [ 7] DE = Pointer to the start of music inc hl ;; [ 6] ld d, (hl) ;; [ 7] cpct_akp_musicInit_asm:: ;; Entry point for assembly calls using registers for parameter passing ;; First, set song loop times to 0 when we start xor a ;; A = 0 ld (_cpct_akp_songLoopTimes), a ;; _cpct_akp_songLoopTimes = 0 PLY_Init: ld hl, #9 ;Skip Header, SampleChannel, YM Clock (DB*3), and Replay Frequency. add hl, de ld de, #PLY_Speed + 1 ldi ;Copy Speed. ld c, (hl) ;Get Instruments chunk size. inc hl ld b, (hl) inc hl ld (PLY_Track1_InstrumentsTablePT + 1), hl ld (PLY_Track2_InstrumentsTablePT + 1), hl ld (PLY_Track3_InstrumentsTablePT + 1), hl add hl, bc ;Skip Instruments to go to the Linker address. ;Get the pre-Linker information of the first pattern. ld de, #PLY_Height + 1 ldi ld de, #PLY_Transposition1 + 1 ldi ld de, #PLY_Transposition2 + 1 ldi ld de, #PLY_Transposition3 + 1 ldi ld de, #PLY_SaveSpecialTrack + 1 ldi ldi ld (PLY_Linker_PT + 1), hl ;Get the Linker address. ld a, #1 ld (PLY_SpeedCpt + 1), a ld (PLY_HeightCpt + 1), a ld a, #0xFF ld (PLY_PSGReg13), a ;Set the Instruments pointers to Instrument 0 data (Header has to be skipped). ld hl, (PLY_Track1_InstrumentsTablePT + 1) ld e, (hl) inc hl ld d, (hl) ex de, hl inc hl ;Skip Instrument 0 Header. inc hl ld (PLY_Track1_Instrument + 1), hl ld (PLY_Track2_Instrument + 1), hl ld (PLY_Track3_Instrument + 1), hl ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Function: cpct_akp_stop ;; ;; Stops playing musing and sound effects on all 3 channels. ;; ;; C Definition: ;; void () ;; ;; Assembly call (Input parameters on registers): ;; > call cpct_akp_stop_asm ;; ;; Known limitations: ;; * This function *will not work from ROM*, as it uses self-modifying code. ;; ;; Details: ;; This function stops the music and sound effects playing in the 3 channels. ;; It can be later continued again calling . Please, take ;; into account that sound effects cannot be played while music is stopped, as ;; code for sound effects and music play is integrated. ;; ;; Destroyed Register values: ;; AF, AF', BC, DE, HL, IX, IY ;; ;; Required memory: ;; 19 bytes ;; ;; However, take into account that all of Arkos Tracker Player's ;; functions are linked and included, because they depend on each other. Total ;; memory requirement is around 2089 bytes. ;; ;; Time Measures: ;; (start code) ;; To be done ;; (end code) ;; ;; Credits: ;; This is a modification of the original ;; code from Targhan / Arkos. Madram / Overlander and Grim / Arkos have also ;; contributed to this source. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;Stop the music, cut the channels. _cpct_akp_stop:: cpct_akp_stop_asm:: ;; Entry point for assembly calls PLY_Stop: .if PLY_SystemFriendly call PLY_DisableInterruptions ex af, af' exx push af push bc .endif push ix push iy ld hl, #PLY_PSGReg8 ld bc, #0x0500 ld (hl), c inc hl djnz .-2 ld a, #0b00111111 jp PLY_SendRegisters .if PLY_UseSoundEffects ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Function: cpct_akp_SFXGetInstrument ;; ;; Returns the number of the instrument which is playing SFX in the selected ;; channel (0 = Channel free / not playing SFX). ;; ;; C Definition: ;; ( *channel_bitmask*) ;; ;; Assembly call (Input parameters on registers): ;; > call cpct_akp_SFXGetInstrument_asm ;; ;; Details: ;; This function returns the number of the instrumeent which is playing in ;; the given channel. You may use it to check the status of an FX reproduction. ;; If this function returns a 0 it means that no SFX is playing on the channel ;; (i.e. the channel is free for reproducing new SFXs). ;; ;; Destroyed Register values: ;; AF, HL ;; ;; Required memory: ;; 25 bytes ;; ;; However, take into account that all of Arkos Tracker Player's ;; functions are linked and included, because they depend on each other. Total ;; memory requirement is around 2089 bytes. ;; ;; Time Measures: ;; (start code) ;; Case | Cycles | microSecs (us) ;; -------------------------------------- ;; Best | 68 | 17.00 ;; -------------------------------------- ;; Worst | 82 | 20.50 ;; -------------------------------------- ;; Asm saving | -28 | -7.00 ;; -------------------------------------- ;; (end code) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; _cpct_akp_SFXGetInstrument:: ;; Get Parameter from Stack ld hl, #2 ;; [10] HL points to the start of parameters in the stack add hl, sp ;; [11] ld a, (hl) ;; [ 7] A = Selected Channel Bitmask cpct_akp_SFXGetInstrument_asm:: ;; Entry point for assembly calls using registers for parameter passing ;; Check the selected audio channel depending on bitmask rrca ;; [ 4] Rotate A right to test bit0 (Check for channel 0 / A) jp c, sfcb_channel_a ;; [10] if Carry=1, bit0 was 1, Channel A selected rrca ;; [ 4] Rotate A right to test bit1 (Check for channel 1 / B) jp c, sfcb_channel_b ;; [10] if Carry=1, bit1 was 1, Channel B selected sfcb_channel_c: ;; If it is not channel B or C, is channel 0 / A (bit 0) ld hl, (PLY_SFX_Track3_Instrument + 1) ;; [16] HL = SFX Track 3 (Channel 2 / C) Pitch Status ret ;; [10] return sfcb_channel_b: ld hl, (PLY_SFX_Track2_Instrument + 1) ;; [16] HL = SFX Track 2 (Channel 1 / B) Pitch Status ret ;; [10] return sfcb_channel_a: ld hl, (PLY_SFX_Track1_Instrument + 1) ;; [16] HL = SFX Track 1 (Channel 0 / C) Pitch Status ret ;; [10] return ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Function: cpct_akp_SFXInit ;; ;; Initializes sound effect instruments to be able to play sound effects. ;; ;; C Definition: ;; void (void* *sfx_song_data*) ;; ;; Input Parameters (2 bytes): ;; (2B DE) sfx_song_data - Pointer to the start of a song file containing instrument data for SFX ;; ;; Assembly call (Input parameters on registers): ;; > call cpct_akp_SFXInit_asm ;; ;; Known limitations: ;; * This function *will not work from ROM*, as it uses self-modifying code. ;; ;; Parameter Restrictions: ;; * *sfx_song_data* must be a song in binary AKS format. However, this song ;; only needs to have instruments defined (there is not need for an actual "song" ;; as a list of notes, patterns and others). Instruments will be set up for ;; playing SFX later on with functions like . ;; ;; Details: ;; This function initializes instruments that will be used later on to play ;; FX sounds at will using . In order for the instruments to ;; be initialized, *sfx_song_data* must point to a song defined in AKS format. ;; For the purpose of this function, the song only requires to have instruments ;; defined in it, as patterns, notes and other information is not used for FX ;; sounds. ;; ;; You may use instruments from another song or a specific song containing ;; instrument data only. Any song with instruments defined in it is valid to ;; set up SFX with Arkos Tracker Player. ;; ;; Destroyed Register values: ;; AF, DE, HL ;; ;; Required memory: ;; 14 bytes (+13 bytes from that comes next to this ;; function and is used in initialization) ;; ;; However, take into account that all of Arkos Tracker Player's ;; functions are linked and included, because they depend on each other. Total ;; memory requirement is around 2089 bytes. ;; ;; Time Measures: ;; (start code) ;; Case | Cycles | microSecs (us) ;; -------------------------------- ;; Any | 146 | 36.50 ;; -------------------------------- ;; Asm saving | -41 | -10.25 ;; ------------------------------- ;; (end code) ;; ;; Credits: ;; This is a modification of the original ;; code from Targhan / Arkos. Madram / Overlander and Grim / Arkos have also ;; contributed to this source. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; PLY_SFX_Init: _cpct_akp_SFXInit:: ld hl, #2 ;; [10] Get Parameter from Stack add hl, sp ;; [11] ld e, (hl) ;; [ 7] inc hl ;; [ 6] ld d, (hl) ;; [ 7] DE = Pointer to the SFX "Song" cpct_akp_SFXInit_asm:: ;; Entry point for assembly calls using registers for parameter passing ;Find the Instrument Table. ld hl, #12 ;; [10] add hl, de ;; [11] ld (PLY_SFX_Play_InstrumentTable + 1), hl ;; [16] ;; Initialization continues clearing sound effects from the 3 channels ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Function: cpct_akp_SFXStopAll ;; ;; Stops the reproduction of any sound effect in the 3 channels. ;; ;; C Definition: ;; void () ;; ;; Assembly call (Input parameters on registers): ;; > call cpct_akp_SFXStopAll_asm ;; ;; Known limitations: ;; * This function *will not work from ROM*, as it uses self-modifying code. ;; ;; Details: ;; This function stops all sound FX reproduction on all the 3 sound channels. ;; ;; Destroyed Register values: ;; HL ;; ;; Required memory: ;; 13 bytes ;; ;; However, take into account that all of Arkos Tracker Player's ;; functions are linked and included, because they depend on each other. Total ;; memory requirement is around 2089 bytes. ;; ;; Time Measures: ;; (start code) ;; Case | Cycles | microSecs (us) ;; -------------------------------- ;; Any | 68 | 15.50 ;; -------------------------------- ;; Asm saving | 0 | 0 ;; ------------------------------- ;; (end code) ;; ;; Credits: ;; This is a modification of the original ;; code from Targhan / Arkos. Madram / Overlander and Grim / Arkos have also ;; contributed to this source. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; _cpct_akp_SFXStopAll:: cpct_akp_SFXStopAll_asm:: ;; Entry point for assembly calls PLY_SFX_StopAll: ;Clear the three channels of any sound effect. ld hl, #0 ;; [10] ld (PLY_SFX_Track1_Instrument + 1), hl ;; [16] ld (PLY_SFX_Track2_Instrument + 1), hl ;; [16] ld (PLY_SFX_Track3_Instrument + 1), hl ;; [16] ret ;; [10] .equ PLY_SFX_OffsetPitch, 0 .equ PLY_SFX_OffsetVolume, PLY_SFX_Track1_Volume - PLY_SFX_Track1_Pitch .equ PLY_SFX_OffsetNote, PLY_SFX_Track1_Note - PLY_SFX_Track1_Pitch .equ PLY_SFX_OffsetInstrument, PLY_SFX_Track1_Instrument - PLY_SFX_Track1_Pitch .equ PLY_SFX_OffsetSpeed, PLY_SFX_Track1_InstrumentSpeed - PLY_SFX_Track1_Pitch .equ PLY_SFX_OffsetSpeedCpt, PLY_SFX_Track1_InstrumentSpeedCpt - PLY_SFX_Track1_Pitch ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Function: cpct_akp_SFXPlay ;; ;; Plays a concrete sound effect, using the instruments of the "SFX song" ;; given to when initializing sound effects. ;; ;; C Definition: ;; void ( *sfx_num*, *volume*, *note*, ;; *speed*, *inverted_pitch*, *channel_bitmask*) ;; ;; Input Parameters (7 bytes): ;; (1B L ) sfx_num - Number of the instrument in the SFX Song (>0), same as the number given to the ;; instrument in Arkos Tracker. ;; (1B H ) volume - Volume [0-15], 0 = off, 15 = maximum volume. ;; (1B E ) note - Note to be played with the given instrument [0-143] ;; (1B D ) speed - Speed (0 = As original, [1-255] = new Speed (1 is fastest)) ;; (2B BC) inverted_pitch - Inverted Pitch (-0xFFFF -> 0xFFFF). 0 is no pitch. The higher the pitch, the lower the sound. ;; (1B A ) channel_bitmask - Bitmask representing channels to use for reproducing the sound (Ch.A = 001 (1), Ch.B = 010 (2), Ch.C = 100 (4)) ;; ;; Assembly call (Input parameters on registers): ;; > call cpct_akp_SFXPlay_asm ;; ;; Known limitations: ;; * This function *will not work from ROM*, as it uses self-modifying code. ;; ;; Details: ;; Plays a given sound effect, along with the music, in a concrete channel ;; and with some parameters (Volume, Note, Speed, Inverted Pitch). This lets ;; you create lots of different (and potentially complex) sound effects ;; from a set of instruments. In fact, you could play a song made of sound ;; effect calls. ;; ;; Destroyed Register values: ;; AF, BC, DE, HL, ;; ;; Required memory: ;; 95 bytes ;; ;; However, take into account that all of Arkos Tracker Player's ;; functions are linked and included, because they depend on each other. Total ;; memory requirement is around 2089 bytes. ;; ;; Time Measures: ;; (start code) ;; Case | Cycles | microSecs (us) ;; -------------------------------------- ;; Any | To be done ;; -------------------------------------- ;; Asm saving | -141 | -45.25 ;; -------------------------------------- ;; (end code) ;; ;; Credits: ;; This is a modification of the original ;; code from Targhan / Arkos. Madram / Overlander and Grim / Arkos have also ;; contributed to this source. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; _cpct_akp_SFXPlay:: PLY_SFX_Play: ld (PLY_SFX_Recover_IX+2), ix ;; [20] Save IX value (cannot use push as parameters are on the stack) pop af ;; [10] pop hl ;; [10] pop de ;; [10] pop bc ;; [10] pop ix ;; [14] push ix ;; [15] push bc ;; [11] push de ;; [11] push hl ;; [11] push af ;; [11] .dw #0x7DDD ; ld a, ixl ;; [ 8] A = Channel number cpct_akp_SFXPlay_asm:: ;; Entry point for assembly calls using registers for parameter passing ;; Pick up the selected audio channel using bitmasks ;; to reproduce the sound effect rrca ;; [ 4] Check for channel 0 / A (bit 0) jp c, sfp_channel_a ;; [10] rrca ;; [ 4] Check for channel 1 / B (bit 1) jp c, sfp_channel_b ;; [10] sfp_channel_c: ;; If it is not channel A or B, is channel 2 / C (bit 2) ld ix, #PLY_SFX_Track3_Pitch ;; [14] IX = Track 3 (channel 2 / C) jp PLY_SFX_Play_Selected ;; [10] sfp_channel_b: ld ix, #PLY_SFX_Track2_Pitch ;; [14] IX = Track 2 (channel 1 / B) jp PLY_SFX_Play_Selected ;; [10] sfp_channel_a: ld ix, #PLY_SFX_Track1_Pitch ;; [14] IX = Track 1 (Channel 0 / A) PLY_SFX_Play_Selected: ld PLY_SFX_OffsetPitch + 1(ix), c ;Set Pitch ld PLY_SFX_OffsetPitch + 2(ix), b ld a, e ;Set Note ld PLY_SFX_OffsetNote (ix), a ld a, #15 ;Set Volume sub h ld PLY_SFX_OffsetVolume (ix), a ld h, #0 ;Set Instrument Address add hl, hl PLY_SFX_Play_InstrumentTable: ld bc, #0 add hl, bc ld a, (hl) inc hl ld h, (hl) ld l, a ld a, d ;Read Speed or use the user's one ? or a jr nz, PLY_SFX_Play_UserSpeed ld a, (hl) ;Get Speed PLY_SFX_Play_UserSpeed: ld PLY_SFX_OffsetSpeed + 1 (ix), a ld PLY_SFX_OffsetSpeedCpt + 1 (ix), a inc hl ;Skip Re-trig inc hl ld PLY_SFX_OffsetInstrument + 1 (ix), l ld PLY_SFX_OffsetInstrument + 2 (ix), h PLY_SFX_Recover_IX: ld ix, #0 ; [14] 0 is a placeholder to save the previous value of IX ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Function: cpct_akp_SFXStop ;; ;; Stops the reproduction sound FX on given channels ;; ;; C Definition: ;; void ( *stop_bitmask*) ;; ;; Input Parameters (1 byte): ;; (1B A) stop_bitmask - A value where the 3 Least Significant Bits represent which channels to stop ;; (bits enabled = channels to stop) ;; ;; Assembly call (Input parameters on registers): ;; > call cpct_akp_SFXStop_asm ;; ;; Known limitations: ;; * This function *will not work from ROM*, as it uses self-modifying code. ;; ;; Parameter Restrictions: ;; * *stop_bitmask* must be a value that operates as a set of enabled / ;; disabled bits (a bitmask). Concretely, the 3 Least Significant bits, 2, 1 ;; and 0 (xxxxx210) refer to Channels C, B and A respectively. Rest of the ;; bits are ignored. Bits set to 1 mean that those channels will be stopped. ;; Bits set to 0 mean that those channels are to be left as they are. ;; ;; Details: ;; This function lets you selectively stop sound FX reproduction on one, two ;; or the 3 available channels. A *stop_bitmask* is given as parameter containing ;; the information about what channels shall be stopped. ;; ;; Destroyed Register values: ;; AF, HL ;; ;; Required memory: ;; 30 bytes ;; ;; However, take into account that all of Arkos Tracker Player's ;; functions are linked and included, because they depend on each other. Total ;; memory requirement is around 2082 bytes. ;; ;; Time Measures: ;; (start code) ;; Case | Cycles | microSecs (us) ;; -------------------------------- ;; Best (0) | 101 | 25.25 ;; -------------------------------- ;; Best (1) | 149 | 37.25 ;; -------------------------------- ;; Asm saving | -28 | -7.00 ;; -------------------------------- ;; (end code) ;; ;; Credits: ;; This is a modification of the original ;; code from Targhan / Arkos. Madram / Overlander and Grim / Arkos have also ;; contributed to this source. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; _cpct_akp_SFXStop:: PLY_SFX_Stop: ld hl, #2 ;; [10] Get Parameter from Stack add hl, sp ;; [11] ld a, (hl) ;; [ 7] A = Channel number to be stopped cpct_akp_SFXStop_asm:: ;; Entry point for assembly calls using registers for parameter passing ld hl, #0 ;; [10] Value 0 to stop SFX in a channel rrca ;; [ 4] Test bit 0 (00000001) to know if channel 0 has to be stopped jp nc, PLY_SFSStop_no1 ;; [10] If Carry=0, channel 2 is left as is. ld (PLY_SFX_Track1_Instrument + 1), hl ;; [16] Stop Channel 0 PLY_SFSStop_no1: rrca ;; [ 4] Test bit 1 (00000010) to know if channel 1 has to be stopped jp nc, PLY_SFSStop_no2 ;; [10] If Carry=0, channel 1 is left as is. ld (PLY_SFX_Track2_Instrument + 1), hl ;; [16] Stop Channel 1 PLY_SFSStop_no2: rrca ;; [ 4] Test bit 2 (00000100) to know if channel 0 has to be stopped jp z, PLY_SFSStop_no3 ;; [10] If Carry=0, channel 0 is left as is. ld (PLY_SFX_Track3_Instrument + 1), hl ;; [16] Stop Channel 2 PLY_SFSStop_no3: ret ;; [10] Return .endif .if PLY_UseFades ;E = Fade value (0 = full volume, 16 or more = no volume). ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Function: cpct_akp_setFadeVolume ;; ;; Sets global volumes for creating fade in / out effects. ;; ;; C Definition: ;; void ( *volume*) ;; ;; Input Parameters (1 byte): ;; (1B A) volume - Global volume for all channels, [0-15]. 0 = max volume, 16 or more = no volume ;; ;; Assembly call (Input parameters on registers): ;; > call cpct_akp_setFadeVolume_asm ;; ;; Known limitations: ;; * This function *will not work from ROM*, as it uses self-modifying code. ;; ;; Parameter Restrictions: ;; * *volume* must be a value in the range [0-15] for some meaning. Values ;; greater than 15 mean no volume at all. 0 is the maximum volume, 15 the minimum. ;; ;; Details: ;; This function controls the global reproduction volume for all channels. This ;; lets you do Fade in / out effects, by making the volume progressively go up ;; or down. ;; ;; Destroyed Register values: ;; AF, HL ;; ;; Required memory: ;; 15 bytes ;; ;; However, take into account that all of Arkos Tracker Player's ;; functions are linked and included, because they depend on each other. Total ;; memory requirement is around 2082 bytes. ;; ;; Time Measures: ;; (start code) ;; Case | Cycles | microSecs (us) ;; ------------------------------------- ;; Any | 51 | 12.75 ;; ------------------------------------- ;; Asm saving | -28 | 7.00 ;; ------------------------------------- ;; (end code) ;; ;; Credits: ;; This is a modification of the original ;; code from Targhan / Arkos. Madram / Overlander and Grim / Arkos have also ;; contributed to this source. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; _cpct_akp_setFadeVolume:: ;; Get parameter from the stack ld hl, #2 ;; [10] add hl, sp ;; [11] ld a, (hl) ;; [ 7] A = Fade Volume cpct_akp_setFadeVolume_asm:: ;; ASM Entry point for setFadeVolume PLY_SetFadeValue: ld (PLY_Channel1_FadeValue + 1), a ;; [ 7] Set fade volume for channel A (0) ld (PLY_Channel2_FadeValue + 1), a ;; [ 7] Set fade volume for channel B (1) ld (PLY_Channel3_FadeValue + 1), a ;; [ 7] Set fade volume for channel C (2) ret ;; [10] .endif .if PLY_SystemFriendly ;Save Interrupt status and Disable Interruptions PLY_DisableInterruptions: ld a, i di ;IFF in P/V flag. ;Prepare opcode for DI. ld a, #0xF3 jp po, PLY_DisableInterruptions_Set_Opcode ;Opcode for EI. ld a, #0xFB PLY_DisableInterruptions_Set_Opcode: ld (PLY_RestoreInterruption), a ret .endif