diff options
author | Juan J. Martinez <jjm@usebox.net> | 2020-12-30 19:07:31 +0000 |
---|---|---|
committer | Juan J. Martinez <jjm@usebox.net> | 2020-12-30 19:23:41 +0000 |
commit | 2682bc5d1d864341aaeb42a449db73c3ecd16d70 (patch) | |
tree | 9116764364b4ee0ce7f6037305077807b57776de /src/mplayer/akm | |
download | ubox-msx-lib-ca9b663c147340e92804979a96eee4113ab0b27f.tar.gz ubox-msx-lib-ca9b663c147340e92804979a96eee4113ab0b27f.zip |
Initial import1.0
Diffstat (limited to 'src/mplayer/akm')
-rw-r--r-- | src/mplayer/akm/LICENSE.txt | 21 | ||||
-rw-r--r-- | src/mplayer/akm/Makefile | 4 | ||||
-rw-r--r-- | src/mplayer/akm/PlayerAkm.asm | 2328 | ||||
-rw-r--r-- | src/mplayer/akm/PlayerAkm_SoundEffects.asm | 477 | ||||
-rw-r--r-- | src/mplayer/akm/README.md | 10 | ||||
-rw-r--r-- | src/mplayer/akm/akm_ubox.asm | 27 |
6 files changed, 2867 insertions, 0 deletions
diff --git a/src/mplayer/akm/LICENSE.txt b/src/mplayer/akm/LICENSE.txt new file mode 100644 index 0000000..dd2bb6c --- /dev/null +++ b/src/mplayer/akm/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016-2020 Julien Névo (contact@julien-nevo.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
\ No newline at end of file diff --git a/src/mplayer/akm/Makefile b/src/mplayer/akm/Makefile new file mode 100644 index 0000000..65da7f8 --- /dev/null +++ b/src/mplayer/akm/Makefile @@ -0,0 +1,4 @@ +akm_sdcc.z80: main.asm + rasm main.asm -o akm -s -sl -sq + Disark --sourceProfile sdcc --symbolFile akm.sym --src16bitsValuesInHex --src8bitsValuesInHex --undocumentedOpcodesToBytes akm.bin akm_sdcc.z80 + diff --git a/src/mplayer/akm/PlayerAkm.asm b/src/mplayer/akm/PlayerAkm.asm new file mode 100644 index 0000000..5a711f3 --- /dev/null +++ b/src/mplayer/akm/PlayerAkm.asm @@ -0,0 +1,2328 @@ +; Arkos Tracker 2 AKM (Minimalist) player (format V0). +; By Targhan/Arkos. +; +; Thanks to Hicks/Vanity for two small (but relevant!) optimizations. + +; This compiles with RASM. Check the compatibility page on the Arkos Tracker 2 website, it contains a source converter to any Z80 assembler!; + +; This is a Minimalist player. Only a subset of the generic player is used. Use this player for 4k demo or other productions +; with a tight memory limitation. However, this remains a versatile and powerful player, so it may fit any production! +; +; Though the player is optimized in speed, it is much slower than the generic one or the AKY player. +; With effects used at the same time, it can reach 45 scanlines on a CPC, plus some few more if you are using sound effects. +; So it's about as fast as the Soundtrakker 128 player, but smaller and more powerful (so what are you complaining about?). +; +; The player uses the stack for optimizations. Make sure the interruptions are disabled before it is called. +; The stack pointer is saved at the beginning and restored at the end. +; +; Target hardware: +; --------------- +; This code can target Amstrad CPC, MSX, Spectrum and Pentagon. By default, it targets Amstrad CPC. +; Simply use one of the follow line (BEFORE this player): +; PLY_AKM_HARDWARE_CPC = 1 +; PLY_AKM_HARDWARE_MSX = 1 +; PLY_AKM_HARDWARE_SPECTRUM = 1 +; PLY_AKM_HARDWARE_PENTAGON = 1 +; Note that the PRESENCE of this variable is tested, NOT its value. + +; Some severe optimizations of CPU/memory can be performed: +; --------------------------------------------------------- +; - Use the Player Configuration of Arkos Tracker 2 to generate a configuration file to be included at the beginning of this player. +; It will disable useless features according to your songs! Check the manual for more details, or more simply the testers. + +; Sound effects: +; -------------- +; Sound effects are disabled by default. Declare PLY_AKM_MANAGE_SOUND_EFFECTS to enable it: +; PLY_AKM_MANAGE_SOUND_EFFECTS = 1 +; Check the sound effect tester to see how it enables it. +; Note that the PRESENCE of this variable is tested, NOT its value. +; +; ROM +; ---------------------- +; To use a ROM player (no automodification, use of a small buffer to put in RAM): +; PLY_AKM_Rom = 1 +; PLY_AKM_ROM_Buffer = #4000 (or wherever). +; This makes the player a bit slower and slightly bigger. +; The buffer is PLY_AKM_ROM_BufferSize bytes long (199 bytes max). +; +; ------------------------------------------------------- +PLY_AKM_Start: + + + ;Checks the hardware. Only one must be selected. +PLY_AKM_HardwareCounter = 0 + IFDEF PLY_AKM_HARDWARE_CPC + PLY_AKM_HardwareCounter = PLY_AKM_HardwareCounter + 1 + ENDIF + IFDEF PLY_AKM_HARDWARE_MSX + PLY_AKM_HardwareCounter = PLY_AKM_HardwareCounter + 1 + PLY_AKM_HARDWARE_SPECTRUM_OR_MSX = 1 + ENDIF + IFDEF PLY_AKM_HARDWARE_SPECTRUM + PLY_AKM_HardwareCounter = PLY_AKM_HardwareCounter + 1 + PLY_AKM_HARDWARE_SPECTRUM_OR_PENTAGON = 1 + PLY_AKM_HARDWARE_SPECTRUM_OR_MSX = 1 + ENDIF + IFDEF PLY_AKM_HARDWARE_PENTAGON + PLY_AKM_HardwareCounter = PLY_AKM_HardwareCounter + 1 + PLY_AKM_HARDWARE_SPECTRUM_OR_PENTAGON = 1 + ENDIF + IF PLY_AKM_HARDWARECounter > 1 + FAIL 'Only one hardware must be selected!' + ENDIF + ;By default, selects the Amstrad CPC. + IF PLY_AKM_HARDWARECounter == 0 + PLY_AKM_HARDWARE_CPC = 1 + ENDIF + + + ;Disark macro: Word region Start. + disarkCounter = 0 + IFNDEF dkws + MACRO dkws +PLY_AKM_DisarkWordRegionStart_{disarkCounter} + ENDM + ENDIF + ;Disark macro: Word region End. + IFNDEF dkwe + MACRO dkwe +PLY_AKM_DisarkWordRegionEnd_{disarkCounter}: + disarkCounter = disarkCounter + 1 + ENDM + ENDIF + + ;Disark macro: Pointer region Start. + disarkCounter = 0 + IFNDEF dkps + MACRO dkps +PLY_AKM_DisarkPointerRegionStart_{disarkCounter} + ENDM + ENDIF + ;Disark macro: Pointer region End. + IFNDEF dkpe + MACRO dkpe +PLY_AKM_DisarkPointerRegionEnd_{disarkCounter}: + disarkCounter = disarkCounter + 1 + ENDM + ENDIF + + ;Disark macro: Byte region Start. + disarkCounter = 0 + IFNDEF dkbs + MACRO dkbs +PLY_AKM_DisarkByteRegionStart_{disarkCounter} + ENDM + ENDIF + ;Disark macro: Byte region End. + IFNDEF dkbe + MACRO dkbe +PLY_AKM_DisarkByteRegionEnd_{disarkCounter}: + disarkCounter = disarkCounter + 1 + ENDM + ENDIF + + ;Disark macro: Force "No Reference Area" for 3 bytes (ld hl,xxxx). + IFNDEF dknr3 + MACRO dknr3 +PLY_AKM_DisarkForceNonReferenceDuring3_{disarkCounter}: + disarkCounter = disarkCounter + 1 + ENDM + ENDIF + + +PLY_AKM_USE_HOOKS: equ 1 ;Use hooks for external calls? 0 if the Init/Play methods are directly called, will save a few bytes. +PLY_AKM_STOP_SOUNDS: equ 1 ;1 to have the "stop sounds" code. Set it to 0 if you never plan on stopping your music. + + ;Hooks for external calls. Can be removed if not needed. + if PLY_AKM_USE_HOOKS + assert PLY_AKM_Start == $ ;Makes sure no extra byte were inserted before the hooks. + jp PLY_AKM_Init ;Player + 0. + jp PLY_AKM_Play ;Player + 3. + if PLY_AKM_STOP_SOUNDS + jp PLY_AKM_Stop ;Player + 6. + endif + endif + + ;Includes the sound effects player, if wanted. Important to do it as soon as possible, so that + ;its code can react to the Player Configuration and possibly alter it. + IFDEF PLY_AKM_MANAGE_SOUND_EFFECTS + include "PlayerAkm_SoundEffects.asm" + ENDIF + ;[[INSERT_SOUND_EFFECT_SOURCE]] ;A tag for test units. Don't touch or you're dead. + + ;Is there a loaded Player Configuration source? If no, use a default configuration. + IFNDEF PLY_CFG_ConfigurationIsPresent + PLY_CFG_UseTranspositions = 1 + PLY_CFG_UseSpeedTracks = 1 + PLY_CFG_UseEffects = 1 + PLY_CFG_UseHardwareSounds = 1 + PLY_CFG_NoSoftNoHard_Noise = 1 + PLY_CFG_SoftOnly_Noise = 1 + PLY_CFG_SoftOnly_SoftwarePitch = 1 + PLY_CFG_SoftToHard_SoftwarePitch = 1 + PLY_CFG_SoftToHard_SoftwareArpeggio = 1 + PLY_CFG_SoftAndHard_SoftwarePitch = 1 + PLY_CFG_SoftAndHard_SoftwareArpeggio = 1 + PLY_CFG_UseEffect_ArpeggioTable = 1 + PLY_CFG_UseEffect_ForcePitchTableSpeed = 1 + PLY_CFG_UseEffect_ForceArpeggioSpeed = 1 + PLY_CFG_UseEffect_ForceInstrumentSpeed = 1 + PLY_CFG_UseEffect_PitchUp = 1 + PLY_CFG_UseEffect_PitchDown = 1 + PLY_CFG_UseEffect_PitchTable = 1 + PLY_CFG_UseEffect_SetVolume = 1 + PLY_CFG_UseEffect_Reset = 1 + ENDIF + + ;Agglomerates some flags, because they are treated the same way by this player. + ;-------------------------------------------------- + ;Creates a flag for pitch in instrument, and also pitch in hardware. + IFDEF PLY_CFG_SoftOnly_SoftwarePitch + PLY_AKM_PitchInInstrument = 1 + ENDIF + IFDEF PLY_CFG_SoftToHard_SoftwarePitch + PLY_AKM_PitchInInstrument = 1 + PLY_AKM_PitchInHardwareInstrument = 1 + ENDIF + IFDEF PLY_CFG_SoftAndHard_SoftwarePitch + PLY_AKM_PitchInInstrument = 1 + PLY_AKM_PitchInHardwareInstrument = 1 + ENDIF + ;A flag for Arpeggios in Instrument, both in software and hardware. + IFDEF PLY_CFG_SoftOnly_SoftwareArpeggio + PLY_AKM_ArpeggioInSoftwareOrHardwareInstrument = 1 + ENDIF + IFDEF PLY_CFG_SoftToHard_SoftwareArpeggio + PLY_AKM_ArpeggioInSoftwareOrHardwareInstrument = 1 + PLY_AKM_ArpeggioInHardwareInstrument = 1 + ENDIF + IFDEF PLY_CFG_SoftAndHard_SoftwareArpeggio + PLY_AKM_ArpeggioInSoftwareOrHardwareInstrument = 1 + PLY_AKM_ArpeggioInHardwareInstrument = 1 + ENDIF + + ;A flag if noise is used (noise in hardware not tested, not present in this format). + IFDEF PLY_CFG_NoSoftNoHard_Noise + PLY_AKM_USE_Noise = 1 + ENDIF + IFDEF PLY_CFG_SoftOnly_Noise + PLY_AKM_USE_Noise = 1 + ENDIF + ;The noise is managed? Then the noise register access must be compiled. + IFDEF PLY_AKM_USE_Noise + PLY_AKM_USE_NoiseRegister = 1 + ENDIF + + ;Mixing Pitch up/down effects. + IFDEF PLY_CFG_UseEffect_PitchUp + PLY_AKM_USE_EffectPitchUpDown = 1 + ENDIF + IFDEF PLY_CFG_UseEffect_PitchDown + PLY_AKM_USE_EffectPitchUpDown = 1 + ENDIF + + ;If the Force Arpeggio Speed if used, it means the ArpeggioTable effect must also be! + IFDEF PLY_CFG_UseEffect_ForceArpeggioSpeed + PLY_CFG_UseEffect_ArpeggioTable = 1 + ENDIF + ;If the Force Pitch Table Speed if used, it means the PitchTable effect must also be! + IFDEF PLY_CFG_UseEffect_ForcePitchTableSpeed + PLY_CFG_UseEffect_PitchTable = 1 + ENDIF + +;A nice trick to manage the offset using the same instructions, according to the player (ROM or not). + IFDEF PLY_AKM_Rom +PLY_AKM_Offset1b: equ 0 +PLY_AKM_Offset2b: equ 0 ;Used for instructions such as ld iyh,xx + ELSE +PLY_AKM_Offset1b: equ 1 +PLY_AKM_Offset2b: equ 2 + ENDIF + +;Initializes the song. MUST be called before actually playing the song. +;IN: HL = Address of the song. +; A = Index of the subsong to play (>=0). +PLY_AKM_InitDisarkGenerateExternalLabel: +PLY_AKM_Init: + ;Reads the Song header. + ;Reads the pointers to the various index tables. + ld de,PLY_AKM_PtInstruments + PLY_AKM_Offset1b + ldi + ldi + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC + IFDEF PLY_CFG_UseEffect_ArpeggioTable ;CONFIG SPECIFIC + ld de,PLY_AKM_PtArpeggios + PLY_AKM_Offset1b + ldi + ldi + ELSE + inc hl + inc hl + ENDIF ;PLY_CFG_UseEffect_ArpeggioTable + IFDEF PLY_CFG_UseEffect_PitchTable ;CONFIG SPECIFIC + ld de,PLY_AKM_PtPitches + PLY_AKM_Offset1b + ldi + ldi + ELSE + inc hl + inc hl + ENDIF ;PLY_CFG_UseEffect_PitchTable + ELSE +dknr3 (void): ld de,4 + add hl,de + ENDIF ;PLY_CFG_UseEffects + + ;Finds the address of the Subsong. + ;HL points on the table, adds A * 2. + ;Possible optimization: possible to set the Subsong directly. + add a,a + ld e,a + ld d,0 + add hl,de + ld a,(hl) + inc hl + ld h,(hl) + ld l,a + + ;Reads the header of the Subsong, copies the values inside the code via a table. + ld ix,PLY_AKM_InitVars_Start + ld a,(PLY_AKM_InitVars_End - PLY_AKM_InitVars_Start) / 2 +PLY_AKM_InitVars_Loop: + ld e,(ix + 0) + ld d,(ix + 1) + inc ix + inc ix + ldi + dec a + jr nz,PLY_AKM_InitVars_Loop + + ;A is zero, no need to reset it. + ld (PLY_AKM_PatternRemainingHeight + PLY_AKM_Offset1b),a ;Optimization: this line can be removed if there is no need to reset the song (warning, A is used below). + + ;Stores the Linker address, just after. + ex de,hl + ld hl,PLY_AKM_PtLinker + PLY_AKM_Offset1b + ld (hl),e + inc hl + ld (hl),d + + ;A big LDIR to erase all the data blocks. Optimization: can be removed if there is no need to reset the song. + ;A is considered 0! + ld hl,PLY_AKM_Track1_Data + ld de,PLY_AKM_Track1_Data + 1 +dknr3 (void): ld bc,PLY_AKM_Track3_Data_End - PLY_AKM_Track1_Data - 1 + ld (hl),a + ldir + + ;Resets this flag. Especially important for ROM. + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC + ld (PLY_AKM_RT_ReadEffectsFlag + PLY_AKM_Offset1b),a + ENDIF + + ;Forces a new line. + ld a,(PLY_AKM_Speed + PLY_AKM_Offset1b) + dec a + ld (PLY_AKM_TickCounter + PLY_AKM_Offset1b),a + + ;Reads the first instrument, the empty one, and set-ups the pointers to the instrument to read. + ;Optimization: needed if the song doesn't start with an instrument on all the channels. Else, it can be removed. + ld hl,(PLY_AKM_PtInstruments + PLY_AKM_Offset1b) + ld e,(hl) + inc hl + ld d,(hl) + inc de ;Skips the header. + ld (PLY_AKM_Track1_PtInstrument),de + ld (PLY_AKM_Track2_PtInstrument),de + ld (PLY_AKM_Track3_PtInstrument),de + + ;If sound effects, clears the SFX state. + IFDEF PLY_AKM_MANAGE_SOUND_EFFECTS +dknr3 (void): ld hl,0 + ld (PLY_AKM_Channel1_SoundEffectData),hl + ld (PLY_AKM_Channel2_SoundEffectData),hl + ld (PLY_AKM_Channel3_SoundEffectData),hl + ENDIF ;PLY_AKM_MANAGE_SOUND_EFFECTS + + ;For ROM, generates the RET table. + IFDEF PLY_AKM_Rom + ld ix,PLY_AKM_RegistersForRom ;Source. + ld iy,PLY_AKM_Registers_RetTable ;Destination. + ld bc,PLY_AKM_SendPsgRegister +dknr3 (void): ld de,4 +PLY_AKM_InitRom_Loop: + ld a,(ix) ;Gets the register. + ld h,a + inc ix + and %00111111 + ld (iy + 0),a ;Writes the register. + ld (iy + 1),0 ;Value is 0 for now. + ld a,h + and %11000000 + jr nz,PLY_AKM_InitRom_Special + ;Encodes the "normal" SendPsgRegister code address. + ld (iy + 2),c + ld (iy + 3),b + add iy,de + jr PLY_AKM_InitRom_Loop +PLY_AKM_InitRom_Special: + IFDEF PLY_CFG_UseHardwareSounds ;CONFIG SPECIFIC + rl h + jr c,PLY_AKM_InitRom_WriteEndCode + ;Bit 6 must be set if we came here. + ld bc,PLY_AKM_SendPsgRegisterR13 + ld (iy + 2),c + ld (iy + 3),b + ld bc,PLY_AKM_SendPsgRegisterAfterPop ;This one is a trick to send the register after R13 is managed. + ld (iy + 4),c + ld (iy + 5),b + add iy,de ;Only advance of 4, the code belows expects that. + ENDIF ;PLY_CFG_UseHardwareSounds + +PLY_AKM_InitRom_WriteEndCode: + ld bc,PLY_AKM_SendPsgRegisterEnd + ld (iy + 2),c + ld (iy + 3),b + ENDIF + ret + + ;If ROM, the registers to send, IN THE ORDER they are declared in the ROM buffer! + ;Bit 7 if ends (end DW to encode). Exclusive to bit 6. + ;Bit 6 if R13/AfterPop the end DW to encode. Exclusive to bit 7. + IFDEF PLY_AKM_Rom +PLY_AKM_RegistersForRom: +dkbs (void): + db 8, 0, 1, 9, 2, 3, 10, 4, 5 + IFDEF PLY_AKM_USE_NoiseRegister ;CONFIG SPECIFIC + db 6 + ENDIF + IFNDEF PLY_CFG_UseHardwareSounds ;CONFIG SPECIFIC + db 7 + 128 + ELSE + db 7, 11, 12 + 64 ;13 is NOT declared, special case. + ENDIF +dkbe (void): + ENDIF + +;Addresses where to put the header data. +PLY_AKM_InitVars_Start: +dkps (void): + dw PLY_AKM_NoteIndexTable + PLY_AKM_Offset1b + dw PLY_AKM_NoteIndexTable + PLY_AKM_Offset1b + 1 + dw PLY_AKM_TrackIndex + PLY_AKM_Offset1b + dw PLY_AKM_TrackIndex + PLY_AKM_Offset1b + 1 + dw PLY_AKM_Speed + PLY_AKM_Offset1b + dw PLY_AKM_PrimaryInstrument + PLY_AKM_Offset1b + dw PLY_AKM_SecondaryInstrument + PLY_AKM_Offset1b + dw PLY_AKM_PrimaryWait + PLY_AKM_Offset1b + dw PLY_AKM_SecondaryWait + PLY_AKM_Offset1b + dw PLY_AKM_DefaultStartNoteInTracks + PLY_AKM_Offset1b + dw PLY_AKM_DefaultStartInstrumentInTracks + PLY_AKM_Offset1b + dw PLY_AKM_DefaultStartWaitInTracks + PLY_AKM_Offset1b + dw PLY_AKM_FlagNoteAndEffectInCell + PLY_AKM_Offset1b +dkpe (void): +PLY_AKM_InitVars_End: + + +;Cuts the channels, stopping all sounds. + if PLY_AKM_STOP_SOUNDS +PLY_AKM_StopDisarkGenerateExternalLabel: +PLY_AKM_Stop: + ld (PLY_AKM_SaveSP + PLY_AKM_Offset1b),sp + + xor a + ld (PLY_AKM_Track1_Volume),a + ld (PLY_AKM_Track2_Volume),a + ld (PLY_AKM_Track3_Volume),a + IFDEF PLY_AKM_HARDWARE_MSX + ld a,%10111111 ;On MSX, bit 7 must be 1, bit 6 0. + ELSE + ld a,%00111111 ;On CPC, bit 6 must be 0. Other platforms don't care. + ENDIF + ld (PLY_AKM_MixerRegister),a + jp PLY_AKM_SendPsg + endif ;PLY_AKM_STOP_SOUNDS + + + +;Plays one frame of the song. It MUST have been initialized before. +;The stack is saved and restored, but is diverted, so watch out for the interruptions. +PLY_AKM_PlayDisarkGenerateExternalLabel: +PLY_AKM_Play: + ld (PLY_AKM_SaveSP + PLY_AKM_Offset1b),sp + + ;Reads a new line? + IFNDEF PLY_AKM_Rom +PLY_AKM_TickCounter: ld a,0 + inc a +PLY_AKM_Speed: cp 1 ;Speed (>0). + ELSE + ld a,(PLY_AKM_Speed) + ld b,a + ld a,(PLY_AKM_TickCounter) + inc a + cp b + ENDIF + jp nz,PLY_AKM_TickCounterManaged + + ;A new line must be read. But have we reached the end of the Pattern? + IFNDEF PLY_AKM_Rom +PLY_AKM_PatternRemainingHeight: ld a,0 ;Height. If 0, end of the pattern. + ELSE + ld a,(PLY_AKM_PatternRemainingHeight) + ENDIF + sub 1 + jr c,PLY_AKM_Linker + ;Pattern not ended. No need to read the Linker. + ld (PLY_AKM_PatternRemainingHeight + PLY_AKM_Offset1b),a + jr PLY_AKM_ReadLine + + ;New pattern. Reads the Linker. +PLY_AKM_Linker: + IFNDEF PLY_AKM_Rom +dknr3 (void): +PLY_AKM_TrackIndex: ld de,0 ;DE' points on the Track Index. Useful when new Tracks are found. + ELSE + ld de,(PLY_AKM_TrackIndex) + ENDIF + exx + IFNDEF PLY_AKM_Rom +dknr3 (void): +PLY_AKM_PtLinker: ld hl,0 + ELSE + ld hl,(PLY_AKM_PtLinker) + ENDIF +PLY_AKM_LinkerPostPt: + ;Resets the possible empty cell counter of each Track. + xor a + ld (PLY_AKM_Track1_WaitEmptyCell),a + ld (PLY_AKM_Track2_WaitEmptyCell),a + ld (PLY_AKM_Track3_WaitEmptyCell),a + ;On new pattern, the escape note/instrument/wait values are set for each Tracks. + IFNDEF PLY_AKM_Rom +PLY_AKM_DefaultStartNoteInTracks: ld a,0 + ELSE + ld a,(PLY_AKM_DefaultStartNoteInTracks) + ENDIF + ld (PLY_AKM_Track1_EscapeNote),a + ld (PLY_AKM_Track2_EscapeNote),a + ld (PLY_AKM_Track3_EscapeNote),a + IFNDEF PLY_AKM_Rom +PLY_AKM_DefaultStartInstrumentInTracks: ld a,0 + ELSE + ld a,(PLY_AKM_DefaultStartInstrumentInTracks) + ENDIF + ld (PLY_AKM_Track1_EscapeInstrument),a + ld (PLY_AKM_Track2_EscapeInstrument),a + ld (PLY_AKM_Track3_EscapeInstrument),a + IFNDEF PLY_AKM_Rom +PLY_AKM_DefaultStartWaitInTracks: ld a,0 + ELSE + ld a,(PLY_AKM_DefaultStartWaitInTracks) + ENDIF + ld (PLY_AKM_Track1_EscapeWait),a + ld (PLY_AKM_Track2_EscapeWait),a + ld (PLY_AKM_Track3_EscapeWait),a + + ;Reads the state byte of the pattern. + ld b,(hl) + inc hl + rr b ;Speed change or end of song? + jr nc,PLY_AKM_LinkerAfterSpeedChange + ;Next byte is either the speed (>0) or an end of song marker. + ld a,(hl) + inc hl + ;If no speed used, it means "end of song" every time. + IFDEF PLY_CFG_UseSpeedTracks ;CONFIG SPECIFIC + or a ;0 if end of song, else speed. + jr nz,PLY_AKM_LinkerSpeedChange + ENDIF ;PLY_CFG_UseSpeedTracks + ;End of song. + ld a,(hl) ;Reads where to loop in the Linker. + inc hl + ld h,(hl) + ld l,a + jr PLY_AKM_LinkerPostPt + IFDEF PLY_CFG_UseSpeedTracks ;CONFIG SPECIFIC +PLY_AKM_LinkerSpeedChange: + ;Speed change. + ld (PLY_AKM_Speed + PLY_AKM_Offset1b),a + ENDIF ;PLY_CFG_UseSpeedTracks +PLY_AKM_LinkerAfterSpeedChange: + + ;New height? + rr b + jr nc,PLY_AKM_LinkerUsePreviousHeight + ld a,(hl) + inc hl + ld (PLY_AKM_LinkerPreviousRemainingHeight + PLY_AKM_Offset1b),a + jr PLY_AKM_LinkerSetRemainingHeight + ;The same height is used. It was stored before. +PLY_AKM_LinkerUsePreviousHeight: + IFNDEF PLY_AKM_Rom +PLY_AKM_LinkerPreviousRemainingHeight: ld a,0 + ELSE + ld a,(PLY_AKM_LinkerPreviousRemainingHeight) + ENDIF +PLY_AKM_LinkerSetRemainingHeight: + ld (PLY_AKM_PatternRemainingHeight + PLY_AKM_Offset1b),a + + ;New Transposition and Track for channel 1? + ld ix,PLY_AKM_Track1_Data + call PLY_AKM_CheckTranspositionAndTrack + ;New Transposition and Track for channel 2? + ld ix,PLY_AKM_Track2_Data + call PLY_AKM_CheckTranspositionAndTrack + ;New Transposition and Track for channel 3? + ld ix,PLY_AKM_Track3_Data + call PLY_AKM_CheckTranspositionAndTrack + + ld (PLY_AKM_PtLinker + PLY_AKM_Offset1b),hl + + +;Reads the Tracks. +;--------------------------------- +PLY_AKM_ReadLine: + IFNDEF PLY_AKM_Rom +dknr3 (void): +PLY_AKM_PtInstruments: ld de,0 +dknr3 (void): +PLY_AKM_NoteIndexTable: ld bc,0 + ELSE + ld de,(PLY_AKM_PtInstruments) + ld bc,(PLY_AKM_NoteIndexTable) + ENDIF + exx + ld ix,PLY_AKM_Track1_Data + call PLY_AKM_ReadTrack + ld ix,PLY_AKM_Track2_Data + call PLY_AKM_ReadTrack + ld ix,PLY_AKM_Track3_Data + call PLY_AKM_ReadTrack + + xor a +PLY_AKM_TickCounterManaged: + ld (PLY_AKM_TickCounter + PLY_AKM_Offset1b),a + + + +;Plays the sound stream. +;--------------------------------- + ld de,PLY_AKM_PeriodTable + exx + + ld c,%11100000 ;Register 7, shifted of 2 to the left. Bits 2 and 5 will be possibly changed by each iteration. + + ld ix,PLY_AKM_Track1_Data + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC + call PLY_AKM_ManageEffects + ENDIF ;PLY_CFG_UseEffects + ld iy,PLY_AKM_Track1_Registers + call PLY_AKM_PlaySoundStream + + srl c ;Not RR, because we have to make sure the b6 is 0, else no more keyboard (on CPC)! + ;Also, on MSX, bit 6 must be 0. + ld ix,PLY_AKM_Track2_Data + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC + call PLY_AKM_ManageEffects + ENDIF ;PLY_CFG_UseEffects + ld iy,PLY_AKM_Track2_Registers + call PLY_AKM_PlaySoundStream + + IFDEF PLY_AKM_HARDWARE_MSX + scf ;On MSX, bit 7 must be 1. + rr c + ELSE + rr c ;On other platforms, we don't care about b7. + ENDIF + ld ix,PLY_AKM_Track3_Data + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC + call PLY_AKM_ManageEffects + ENDIF ;PLY_CFG_UseEffects + ld iy,PLY_AKM_Track3_Registers + call PLY_AKM_PlaySoundStream + + ld a,c + +;Plays the sound effects, if desired. +;------------------------------------------- + IFDEF PLY_AKM_MANAGE_SOUND_EFFECTS + call PLY_AKM_PlaySoundEffectsStream + ELSE + ld (PLY_AKM_MixerRegister),a + ENDIF ;PLY_AKM_MANAGE_SOUND_EFFECTS + + + +;Sends the values to the PSG. +;--------------------------------- +PLY_AKM_SendPsg: + ld sp,PLY_AKM_Registers_RetTable + + IFDEF PLY_AKM_HARDWARE_CPC +dknr3 (void): ld bc,#f680 + ld a,#c0 +dknr3 (void): ld de,#f4f6 + out (c),a ;#f6c0 ;Madram's trick requires to start with this. out (c),b works, but will activate K7's relay! Not clean. + ENDIF + + IFDEF PLY_AKM_HARDWARE_SPECTRUM_OR_PENTAGON +dknr3 (void): ld de,#bfff + ld c,#fd + ENDIF + +PLY_AKM_SendPsgRegister: + pop hl ;H = value, L = register. +PLY_AKM_SendPsgRegisterAfterPop: + IFDEF PLY_AKM_HARDWARE_CPC + ld b,d + out (c),l ;#f400 + register. + ld b,e + out (c),0 ;#f600 + ld b,d + out (c),h ;#f400 + value. + ld b,e + out (c),c ;#f680 + out (c),a ;#f6c0 + ENDIF + + IFDEF PLY_AKM_HARDWARE_SPECTRUM_OR_PENTAGON + ld b,e + out (c),l ;#fffd + register. + ld b,d + out (c),h ;#bffd + value + ENDIF + + IFDEF PLY_AKM_HARDWARE_MSX + ld a,l ;Register. + out (#a0),a + ld a,h ;Value. + out (#a1),a + ENDIF + ret + + IFDEF PLY_CFG_UseHardwareSounds ;CONFIG SPECIFIC +PLY_AKM_SendPsgRegisterR13: + + ;Should the R13 be played? Yes only if different. No "force retrig" is managed by this player. + IFNDEF PLY_AKM_Rom +PLY_AKM_SetReg13: ld a,0 +PLY_AKM_SetReg13Old: cp 0 + ELSE + ld a,(PLY_AKM_SetReg13Old) + ld b,a + ld a,(PLY_AKM_SetReg13) + cp b + ENDIF + jr z,PLY_AKM_SendPsgRegisterEnd + ;Different. R13 must be played. Updates the old R13 value. + ld (PLY_AKM_SetReg13Old + PLY_AKM_Offset1b),a + + ld h,a + ld l,13 + + IFDEF PLY_AKM_HARDWARE_CPC + ld a,#c0 + ENDIF + + ret ;Sends the 13th registers. + ENDIF ;PLY_CFG_UseHardwareSounds +PLY_AKM_SendPsgRegisterEnd: + + IFNDEF PLY_AKM_Rom +dknr3 (void): +PLY_AKM_SaveSP: ld sp,0 + ELSE + ld sp,(PLY_AKM_SaveSP) + ENDIF + ret + + + + + +;Shifts B to the right, if carry, a transposition is read. +;Shifts B to the right once again, if carry, a new Track is read (may be an index or a track offset). +;IN: HL = where to read the data. +; IX = points on the track data buffer. +; DE'= Track index table +; B = flags. +;OUT: B = shifted of two. +; HL = increased according to read data. +PLY_AKM_CheckTranspositionAndTrack: + ;New transposition? + rr b + IFDEF PLY_CFG_UseTranspositions ;CONFIG SPECIFIC + jr nc,PLY_AKM_CheckTranspositionAndTrack_AfterTransposition + ;Transposition. + ld a,(hl) + ld (ix + PLY_AKM_Data_OffsetTransposition),a + inc hl +PLY_AKM_CheckTranspositionAndTrack_AfterTransposition: + ENDIF ;PLY_CFG_UseTranspositions + ;New Track? + rr b + jr nc,PLY_AKM_CheckTranspositionAndTrack_NoNewTrack + ;New Track. + ld a,(hl) + inc hl + ;Is it a reference? + sla a + jr nc,PLY_AKM_CheckTranspositionAndTrack_TrackOffset + ;Reference. + exx + ld l,a ;A is the track index * 2. + ld h,0 + add hl,de ;HL points on the track address. + ld a,(hl) + ld (ix + PLY_AKM_Data_OffsetPtStartTrack + 0),a + ld (ix + PLY_AKM_Data_OffsetPtTrack + 0),a + inc hl + ld a,(hl) + ld (ix + PLY_AKM_Data_OffsetPtStartTrack + 1),a + ld (ix + PLY_AKM_Data_OffsetPtTrack + 1),a + exx + ret +PLY_AKM_CheckTranspositionAndTrack_TrackOffset: + ;The Track is an offset. Counter the previous shift. + rra ;Carry was 0, so bit 7 is 0. + ld d,a ;D is the MSB of the offset. + ld e,(hl) ;Reads the LSB of the offset. + inc hl + + ld c,l ;Saves HL. + ld a,h + + add hl,de ;HL is now the Track (offset + $ (past offset)); + ld (ix + PLY_AKM_Data_OffsetPtStartTrack + 0),l + ld (ix + PLY_AKM_Data_OffsetPtStartTrack + 1),h + ld (ix + PLY_AKM_Data_OffsetPtTrack + 0),l + ld (ix + PLY_AKM_Data_OffsetPtTrack + 1),h + + ld l,c ;Retrieves HL. + ld h,a + ret +PLY_AKM_CheckTranspositionAndTrack_NoNewTrack: + ;Copies the old Track inside the new Track pointer, as it evolves. + ld a,(ix + PLY_AKM_Data_OffsetPtStartTrack + 0) + ld (ix + PLY_AKM_Data_OffsetPtTrack + 0),a + ld a,(ix + PLY_AKM_Data_OffsetPtStartTrack + 1) + ld (ix + PLY_AKM_Data_OffsetPtTrack + 1),a + ret + + + + + +;Reads a Track. +;IN: IX = Data block of the Track. +; DE'= Instrument table. Do not modify! +; BC'= Note index table. Do not modify! +PLY_AKM_ReadTrack: + ;Are there any empty lines to wait? + ld a,(ix + PLY_AKM_Data_OffsetWaitEmptyCell) + sub 1 + jr c,PLY_AKM_RT_NoEmptyCell + ;Wait! + ld (ix + PLY_AKM_Data_OffsetWaitEmptyCell),a + ret + +PLY_AKM_RT_NoEmptyCell: + ;Reads the Track pointer. + ld l,(ix + PLY_AKM_Data_OffsetPtTrack + 0) + ld h,(ix + PLY_AKM_Data_OffsetPtTrack + 1) +PLY_AKM_RT_GetDataByte: + ld b,(hl) + inc hl + ;First, reads the note/effect flag. + IFDEF PLY_AKM_Rom + ld a,(PLY_AKM_FlagNoteAndEffectInCell) + ld c,a + ENDIF + ld a,b + and %1111 ;Keeps only the note/data. + IFNDEF PLY_AKM_Rom +PLY_AKM_FlagNoteAndEffectInCell: cp 12 ;0-12 = note reference if no effects in the song, or 0-11 if there are effects in the song. + ELSE + cp c + ENDIF + jr c,PLY_AKM_RT_NoteReference + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC + sub 12 ;Can not be optimized with the code above, its value is automodified. + jr z,PLY_AKM_RT_NoteAndEffects + dec a + jr z,PLY_AKM_RT_NoNoteMaybeEffects + ELSE + sub 13 + jr z,PLY_AKM_RT_ReadWaitFlags ;If no effects, directly check the wait flag. + ENDIF ;PLY_CFG_UseEffects + dec a + jr z,PLY_AKM_RT_NewEscapeNote + ;15. Same escape note. + ld a,(ix + PLY_AKM_Data_OffsetEscapeNote) + jr PLY_AKM_RT_AfterNoteRead + +PLY_AKM_RT_NewEscapeNote: + ;Reads the escape note, and stores it, it may be reused by other cells. + ld a,(hl) + ld (ix + PLY_AKM_Data_OffsetEscapeNote),a + inc hl + jr PLY_AKM_RT_AfterNoteRead + + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC +PLY_AKM_RT_NoteAndEffects + ;There is a "note and effects". This is a special case. A new data byte must be read, with the note and the normal flags. + ;However, we use a "force effects" to mark the presence of effects. + dec a ;A is 0, give it any other value. + ld (PLY_AKM_RT_ReadEffectsFlag + PLY_AKM_Offset1b),a + jr PLY_AKM_RT_GetDataByte + +PLY_AKM_RT_NoNoteMaybeEffects + ;Reads flag "instrument" to know what to do. The flags are diverted to indicate whether there are effects. + bit 4,b ;Effects? + jr z,PLY_AKM_RT_ReadWaitFlags ;No effects. As there is no note, logically, there are no instrument to read, so simply reads the Wait value. + ld a,b ;B is not 0, so it works. + ld (PLY_AKM_RT_ReadEffectsFlag + PLY_AKM_Offset1b),a + jr PLY_AKM_RT_ReadWaitFlags + ENDIF ;PLY_CFG_UseEffects + +PLY_AKM_RT_NoteReference: + ;A is the index of the note. + exx + ld l,a + ld h,0 + add hl,bc + ld a,(hl) + exx + + ;A is the right note (0-127). +PLY_AKM_RT_AfterNoteRead: + ;Adds the transposition. + IFDEF PLY_CFG_UseTranspositions ;CONFIG SPECIFIC + add a,(ix + PLY_AKM_Data_OffsetTransposition) + ENDIF ;PLY_CFG_UseTranspositions + ld (ix + PLY_AKM_Data_OffsetBaseNote),a + + ;Reads the instruments flags. + ;------------------ + ld a,b + and %110000 + jr z,PLY_AKM_RT_SameEscapeInstrument + cp %010000 + jr z,PLY_AKM_RT_PrimaryInstrument + cp %100000 + jr z,PLY_AKM_RT_SecondaryInstrument + ;New escape instrument. Reads and stores it, it may be reused by other cells. + ld a,(hl) + inc hl + ld (ix + PLY_AKM_Data_OffsetEscapeInstrument),a + jr PLY_AKM_RT_StoreCurrentInstrument + +PLY_AKM_RT_SameEscapeInstrument: + ;Use the latest escape instrument. + ld a,(ix + PLY_AKM_Data_OffsetEscapeInstrument) + jr PLY_AKM_RT_StoreCurrentInstrument + +PLY_AKM_RT_SecondaryInstrument: + ;Use the secondary instrument. + IFNDEF PLY_AKM_Rom +PLY_AKM_SecondaryInstrument: ld a,0 + ELSE + ld a,(PLY_AKM_SecondaryInstrument) + ENDIF + jr PLY_AKM_RT_StoreCurrentInstrument + +PLY_AKM_RT_PrimaryInstrument: + ;Use the primary instrument. + IFNDEF PLY_AKM_Rom +PLY_AKM_PrimaryInstrument: ld a,0 + ELSE + ld a,(PLY_AKM_PrimaryInstrument) + ENDIF + +PLY_AKM_RT_StoreCurrentInstrument: + ;A is the instrument to play. + exx + ;Gets the address of the Instrument. + add a,a ;Only 127 instruments max. + ld l,a + ld h,0 + add hl,de ;Adds to the Instrument Table. + ld a,(hl) + inc hl + ld h,(hl) + ld l,a + ;Reads the header of the Instrument. + ld a,(hl) ;Speed. + inc hl + ld (ix + PLY_AKM_Data_OffsetInstrumentSpeed),a + ;Stores the pointer on the data of the Instrument. + ld (ix + PLY_AKM_Data_OffsetPtInstrument + 0),l + ld (ix + PLY_AKM_Data_OffsetPtInstrument + 1),h + exx + xor a + ;Resets the step on the Instrument. + ld (ix + PLY_AKM_Data_OffsetInstrumentCurrentStep),a + ;Resets the Track pitch. + IFDEF PLY_AKM_USE_EffectPitchUpDown ;CONFIG SPECIFIC + ld (ix + PLY_AKM_Data_OffsetIsPitchUpDownUsed),a + ld (ix + PLY_AKM_Data_OffsetTrackPitchInteger + 0),a + ld (ix + PLY_AKM_Data_OffsetTrackPitchInteger + 1),a + ;ld (ix + PLY_AKM_Data_OffsetTrackPitchDecimal),a ;Shouldn't be needed, the difference shouldn't be noticeable. + ENDIF ;PLY_AKM_USE_EffectPitchUpDown + + ;Resets the offset on Arpeggio and Pitch tables. + IFDEF PLY_CFG_UseEffect_ArpeggioTable ;CONFIG SPECIFIC + ld (ix + PLY_AKM_Data_OffsetPtArpeggioOffset),a + ld (ix + PLY_AKM_Data_OffsetArpeggioCurrentStep),a + IFDEF PLY_CFG_UseEffect_ForceArpeggioSpeed ;CONFIG SPECIFIC + ld a,(ix + PLY_AKM_Data_OffsetArpeggioOriginalSpeed) ;The arpeggio speed must be reset. + ld (ix + PLY_AKM_Data_OffsetArpeggioCurrentSpeed),a + ENDIF ;PLY_CFG_UseEffect_ForceArpeggioSpeed + ENDIF ;PLY_CFG_UseEffect_ArpeggioTable + + IFDEF PLY_CFG_UseEffect_PitchTable ;CONFIG SPECIFIC + ld (ix + PLY_AKM_Data_OffsetPtPitchOffset),a + ld (ix + PLY_AKM_Data_OffsetPitchCurrentStep),a + IFDEF PLY_CFG_UseEffect_ForcePitchTableSpeed ;CONFIG SPECIFIC + ld a,(ix + PLY_AKM_Data_OffsetPitchOriginalSpeed) ;The pitch speed must be reset. + ld (ix + PLY_AKM_Data_OffsetPitchCurrentSpeed),a + ENDIF ;PLY_CFG_UseEffect_ForcePitchTableSpeed + ENDIF ;PLY_CFG_UseEffect_PitchTable + + + ;Reads the wait flags. + ;---------------------- +PLY_AKM_RT_ReadWaitFlags: + ld a,b + and %11000000 + jr z,PLY_AKM_RT_SameEscapeWait + cp %01000000 + jr z,PLY_AKM_RT_PrimaryWait + cp %10000000 + jr z,PLY_AKM_RT_SecondaryWait + ;New escape wait. Reads and stores it, it may be reused by other cells. + ld a,(hl) + inc hl + ld (ix + PLY_AKM_Data_OffsetEscapeWait),a + jr PLY_AKM_RT_StoreCurrentWait + +PLY_AKM_RT_SameEscapeWait: + ;Use the latest escape wait. + ld a,(ix + PLY_AKM_Data_OffsetEscapeWait) + jr PLY_AKM_RT_StoreCurrentWait + +PLY_AKM_RT_PrimaryWait: + ;Use the primary wait. + IFNDEF PLY_AKM_Rom +PLY_AKM_PrimaryWait: ld a,0 + ELSE + ld a,(PLY_AKM_PrimaryWait) + ENDIF + jr PLY_AKM_RT_StoreCurrentWait + +PLY_AKM_RT_SecondaryWait: + ;Use the secondary wait. + IFNDEF PLY_AKM_Rom +PLY_AKM_SecondaryWait: ld a,0 + ELSE + ld a,(PLY_AKM_SecondaryWait) + ENDIF + +PLY_AKM_RT_StoreCurrentWait: + ;A is the wait to store. + ld (ix + PLY_AKM_Data_OffsetWaitEmptyCell),a + + ;-------------------- + ;Are there effects to read? + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC + IFNDEF PLY_AKM_Rom +PLY_AKM_RT_ReadEffectsFlag: ld a,0 + ELSE + ld a,(PLY_AKM_RT_ReadEffectsFlag) + ENDIF + or a + jr nz,PLY_AKM_RT_ReadEffects +PLY_AKM_RT_AfterEffects: + ENDIF ;PLY_CFG_UseEffects + ;No effects, or after they have been managed. + ;Saves the new pointer on the Track. + ld (ix + PLY_AKM_Data_OffsetPtTrack + 0),l + ld (ix + PLY_AKM_Data_OffsetPtTrack + 1),h + ret + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC +PLY_AKM_RT_ReadEffects: + ;Resets the effect presence flag. + xor a + ld (PLY_AKM_RT_ReadEffectsFlag + PLY_AKM_Offset1b),a + +PLY_AKM_RT_ReadEffect: + ld iy,PLY_AKM_EffectTable + ;Reads effect number and possible data. All effect must jump to PLY_AKM_RT_ReadEffect_Return when finished. + ld b,(hl) + ld a,b + inc hl + + and %1110 + ld e,a + ld d,0 + add iy,de + + ;As a convenience, puts the effect nibble "to the right", for direct use. + ld a,b + rra + rra + rra + rra + and %1111 ;This sets the carry flag, useful for the effects code. + ;Executes the effect code. + jp (iy) +PLY_AKM_RT_ReadEffect_Return: + ;More effects? + bit 0,b + jr nz,PLY_AKM_RT_ReadEffect + jr PLY_AKM_RT_AfterEffects + +PLY_AKM_RT_WaitLong: + ;A 8-bit byte is encoded just after. + ld a,(hl) + inc hl + ld (ix + PLY_AKM_Data_OffsetWaitEmptyCell),a + jr PLY_AKM_RT_CellRead +PLY_AKM_RT_WaitShort: + ;Only a 2-bit value is encoded. + ld a,b + rlca ;Transfers the bit 7/6 to 1/0. Thanks Hicks for the RCLA trick! + rlca + and %11 + ld (ix + PLY_AKM_Data_OffsetWaitEmptyCell),a + ;jr PLY_AKM_RT_CellRead +;Jumped to after the Cell has been read. +;IN: HL = new value of the Track pointer. Must point after the read Cell. +PLY_AKM_RT_CellRead: + ld (ix + PLY_AKM_Data_OffsetPtTrack + 0),l + ld (ix + PLY_AKM_Data_OffsetPtTrack + 1),h + ret + + +;Manages the effects, if any. For the activated effects, modifies the internal data for the Track which data block is given. +;IN: IX = data block of the Track. +;OUT: IX, IY = unmodified. +; C must NOT be modified! +; DE' must NOT be modified! +PLY_AKM_ManageEffects: + IFDEF PLY_AKM_USE_EffectPitchUpDown ;CONFIG SPECIFIC + ;Pitch up/down used? + ld a,(ix + PLY_AKM_Data_OffsetIsPitchUpDownUsed) + or a + jr z,PLY_AKM_ME_PitchUpDownFinished + + ;Adds the LSB of integer part and decimal part, using one 16 bits operation. + ld l,(ix + PLY_AKM_Data_OffsetTrackPitchDecimal) + ld h,(ix + PLY_AKM_Data_OffsetTrackPitchInteger + 0) + + ld e,(ix + PLY_AKM_Data_OffsetTrackPitchSpeed + 0) + ld d,(ix + PLY_AKM_Data_OffsetTrackPitchSpeed + 1) + + ld a,(ix + PLY_AKM_Data_OffsetTrackPitchInteger + 1) + + ;Negative pitch? + bit 7,d + jr nz,PLY_AKM_ME_PitchUpDown_NegativeSpeed + +PLY_AKM_ME_PitchUpDown_PositiveSpeed: + ;Positive speed. Adds it to the LSB of the integer part, and decimal part. + add hl,de + + ;Carry? Transmits it to the MSB of the integer part. + adc 0 + jr PLY_AKM_ME_PitchUpDown_Save +PLY_AKM_ME_PitchUpDown_NegativeSpeed: + ;Negative speed. Resets the sign bit. The encoded pitch IS positive. + ;Subtracts it to the LSB of the integer part, and decimal part. + res 7,d + + or a + sbc hl,de + + ;Carry? Transmits it to the MSB of the integer part. + sbc 0 + +PLY_AKM_ME_PitchUpDown_Save: + ld (ix + PLY_AKM_Data_OffsetTrackPitchInteger + 1),a + + ld (ix + PLY_AKM_Data_OffsetTrackPitchDecimal),l + ld (ix + PLY_AKM_Data_OffsetTrackPitchInteger + 0),h + +PLY_AKM_ME_PitchUpDownFinished: + ENDIF ;PLY_AKM_USE_EffectPitchUpDown + + + + ;Manages the Arpeggio Table effect, if any. + ;------------------------------------------ + IFDEF PLY_CFG_UseEffect_ArpeggioTable ;CONFIG SPECIFIC + ld a,(ix + PLY_AKM_Data_OffsetIsArpeggioTableUsed) + or a + jr z,PLY_AKM_ME_ArpeggioTableFinished + + ;Plays the arpeggio current note. It is suppose to be correct (not a loop). + ;Plays it in any case, in order to manage some corner case with Force Arpeggio Speed. + ld e,(ix + PLY_AKM_Data_OffsetPtArpeggioTable + 0) + ld d,(ix + PLY_AKM_Data_OffsetPtArpeggioTable + 1) + ld l,(ix + PLY_AKM_Data_OffsetPtArpeggioOffset) + ld h,0 + add hl,de + ld a,(hl) ;Gets the Arpeggio value (b1-b7). + sra a ;Carry is 0, because the ADD above surely didn't overflow. + ld (ix + PLY_AKM_Data_OffsetCurrentArpeggioValue),a + + ;Moves forward, if the speed has been reached. + ;Has the speed been reached? + ld a,(ix + PLY_AKM_Data_OffsetArpeggioCurrentStep) + IFDEF PLY_CFG_UseEffect_ForceArpeggioSpeed ;CONFIG SPECIFIC + cp (ix + PLY_AKM_Data_OffsetArpeggioCurrentSpeed) + ELSE + cp (ix + PLY_AKM_Data_OffsetArpeggioOriginalSpeed) + ENDIF ;PLY_CFG_UseEffect_ForceArpeggioSpeed + jr c,PLY_AKM_ME_ArpeggioTable_SpeedNotReached + ;Resets the speed. Reads the next Arpeggio value. + ld (ix + PLY_AKM_Data_OffsetArpeggioCurrentStep),0 + + ;Advances in the Arpeggio. + inc (ix + PLY_AKM_Data_OffsetPtArpeggioOffset) + inc hl ;HL points on the next value. No need to add to the base offset like before, we have it. + ld a,(hl) + ;End of the Arpeggio? + rra ;Carry is 0. + jr nc,PLY_AKM_ME_ArpeggioTableFinished + ;End of the Arpeggio. The loop offset is now in A. + ld l,a + ld (ix + PLY_AKM_Data_OffsetPtArpeggioOffset),a + jr PLY_AKM_ME_ArpeggioTableFinished + +PLY_AKM_ME_ArpeggioTable_SpeedNotReached: + inc a + ld (ix + PLY_AKM_Data_OffsetArpeggioCurrentStep),a + +PLY_AKM_ME_ArpeggioTableFinished: + ENDIF ;PLY_CFG_UseEffect_ArpeggioTable + + + + ;Manages the Pitch Table effect, if any. + ;------------------------------------------ + IFDEF PLY_CFG_UseEffect_PitchTable ;CONFIG SPECIFIC + ld a,(ix + PLY_AKM_Data_OffsetIsPitchTableUsed) + or a + ret z + + ;Plays the Pitch Table current note. It is suppose to be correct (not a loop). + ;Plays it in any case, in order to manage some corner case with Force Pitch Speed. + ;Reads the Pitch Table. Adds the Pitch base address to an offset. + ld l,(ix + PLY_AKM_Data_OffsetPtPitchTable + 0) + ld h,(ix + PLY_AKM_Data_OffsetPtPitchTable + 1) + ld e,(ix + PLY_AKM_Data_OffsetPtPitchOffset) + ld d,0 + add hl,de + ld a,(hl) ;Gets the Pitch value (b1-b7). + sra a + ;A = pitch note. It is converted to 16 bits. + ;D is already 0. + jp p,PLY_AKM_ME_PitchTableEndNotReached_Positive + dec d +PLY_AKM_ME_PitchTableEndNotReached_Positive: + ld (ix + PLY_AKM_Data_OffsetCurrentPitchTableValue + 0),a + ld (ix + PLY_AKM_Data_OffsetCurrentPitchTableValue + 1),d + + ;Moves forward, if the speed has been reached. + ;Has the speed been reached? + ld a,(ix + PLY_AKM_Data_OffsetPitchCurrentStep) + IFDEF PLY_CFG_UseEffect_ForcePitchTableSpeed ;CONFIG SPECIFIC + cp (ix + PLY_AKM_Data_OffsetPitchCurrentSpeed) + ELSE + cp (ix + PLY_AKM_Data_OffsetPitchOriginalSpeed) + ENDIF ;PLY_CFG_UseEffect_ForcePitchTableSpeed + jr c,PLY_AKM_ME_PitchTable_SpeedNotReached + ;Resets the speed, then reads the next Pitch value. + ld (ix + PLY_AKM_Data_OffsetPitchCurrentStep),0 + + ;Advances in the Pitch. + inc (ix + PLY_AKM_Data_OffsetPtPitchOffset) + inc hl ;HL points on the next value. No need to add to the base offset like before, we have it. + ld a,(hl) + ;End of the Pitch? + rra ;Carry is 0. + ret nc + ;End of the Pitch. The loop offset is now in A. + ld l,a + ld (ix + PLY_AKM_Data_OffsetPtPitchOffset),a + ret + +PLY_AKM_ME_PitchTable_SpeedNotReached: + inc a + ld (ix + PLY_AKM_Data_OffsetPitchCurrentStep),a + ENDIF ;PLY_CFG_UseEffect_PitchTable + ret + ENDIF ;PLY_CFG_UseEffects + + + + +;--------------------------------------------------------------------- +;Sound stream. +;--------------------------------------------------------------------- + +;Plays the sound stream, filling the PSG registers table (but not playing it). +;The Instrument pointer must be updated as it evolves inside the Instrument. +;IN: IX = Data block of the Track. +; IY = Points at the beginning of the register structure related to the channel. +; C = R7. Only bit 2 (sound) must be *set* to cut the sound if needed, and bit 5 (noise) must be *reset* if there is noise. +; DE' = Period table. Must not be modified. +PLY_AKM_PlaySoundStream: + ;Gets the pointer on the Instrument, from its base address and the offset. + ld l,(ix + PLY_AKM_Data_OffsetPtInstrument + 0) + ld h,(ix + PLY_AKM_Data_OffsetPtInstrument + 1) + + ;Reads the first byte of the cell of the Instrument. What type? +PLY_AKM_PSS_ReadFirstByte: + ld a,(hl) + ld b,a + inc hl + rra + jr c,PLY_AKM_PSS_SoftOrSoftAndHard + + ;NoSoftNoHard or SoftwareToHardware + rra + IFDEF PLY_CFG_UseHardwareSounds ;CONFIG SPECIFIC + jr c,PLY_AKM_PSS_SoftwareToHardware + ENDIF ;PLY_CFG_UseHardwareSounds + + ;No software no hardware, or end of sound (loop)! + ;End of sound? + rra + jr nc,PLY_AKM_PSS_NSNH_NotEndOfSound + ;The sound loops/ends. Where? + ld a,(hl) + inc hl + ld h,(hl) + ld l,a + ;As a sound always has at least one cell, we should safely be able to read its bytes without storing the instrument pointer. + ;However, we do it anyway to remove the overhead of the Speed management: if looping, the same last line will be read, + ;if several channels do so, it will be costly. So... + ld (ix + PLY_AKM_Data_OffsetPtInstrument + 0),l + ld (ix + PLY_AKM_Data_OffsetPtInstrument + 1),h + jr PLY_AKM_PSS_ReadFirstByte + +PLY_AKM_PSS_NSNH_NotEndOfSound: + ;No software, no hardware. + ;------------------------- + ;Stops the sound. + set 2,c + + ;Volume. A now contains the volume on b0-3. + IFDEF PLY_CFG_UseEffect_SetVolume ;CONFIG SPECIFIC + call PLY_AKM_PSS_Shared_AdjustVolume + ELSE + and %1111 + ENDIF ;PLY_CFG_UseEffect_SetVolume + ld (iy + PLY_AKM_Registers_OffsetVolume),a + + ;Read noise? + rl b + IFDEF PLY_CFG_NoSoftNoHard_Noise ;CONFIG SPECIFIC + call c,PLY_AKM_PSS_ReadNoise + ENDIF ;PLY_CFG_NoSoftNoHard_Noise + jr PLY_AKM_PSS_Shared_StoreInstrumentPointer + + ;Software sound, or Software and Hardware? +PLY_AKM_PSS_SoftOrSoftAndHard: + rra + IFDEF PLY_CFG_UseHardwareSounds ;CONFIG SPECIFIC + jr c,PLY_AKM_PSS_SoftAndHard + ENDIF ;PLY_CFG_UseHardwareSounds + + ;Software sound. + ;----------------- + ;A is the volume. Already shifted twice, so it can be used directly. + IFDEF PLY_CFG_UseEffect_SetVolume ;CONFIG SPECIFIC + call PLY_AKM_PSS_Shared_AdjustVolume + ELSE + and %1111 + ENDIF ;PLY_CFG_UseEffect_SetVolume + ld (iy + PLY_AKM_Registers_OffsetVolume),a + + ;Arp and/or noise? + ld d,0 ;Default arpeggio. + rl b + jr nc,PLY_AKM_PSS_S_AfterArpAndOrNoise + ld a,(hl) + inc hl + ;Noise? + sra a + ;A is now the signed Arpeggio. It must be kept. + ld d,a + ;Now takes care of the noise, if there is a Carry. + IFDEF PLY_CFG_SoftOnly_Noise ;CONFIG SPECIFIC + call c,PLY_AKM_PSS_ReadNoise + ENDIF ;PLY_CFG_SoftOnly_Noise +PLY_AKM_PSS_S_AfterArpAndOrNoise: + + ld a,d ;Gets the instrument arpeggio, if any. + call PLY_AKM_CalculatePeriodForBaseNote + + ;Read pitch? + rl b + IFDEF PLY_CFG_SoftOnly_SoftwarePitch ;CONFIG SPECIFIC + call c,PLY_AKM_ReadPitchAndAddToPeriod + ENDIF ;PLY_CFG_SoftOnly_SoftwarePitch + + ;Stores the new period of this channel. + exx + ld (iy + PLY_AKM_Registers_OffsetSoftwarePeriodLSB),l + ld (iy + PLY_AKM_Registers_OffsetSoftwarePeriodMSB),h + exx + + ;The code below is shared! + ;Stores the new instrument pointer, if Speed allows it. + ;-------------------------------------------------- +PLY_AKM_PSS_Shared_StoreInstrumentPointer: + ;Checks the Instrument speed, and only stores the Instrument new pointer if the speed is reached. + ld a,(ix + PLY_AKM_Data_OffsetInstrumentCurrentStep) + cp (ix + PLY_AKM_Data_OffsetInstrumentSpeed) + jr nc,PLY_AKM_PSS_S_SpeedReached + ;Increases the current step. + inc (ix + PLY_AKM_Data_OffsetInstrumentCurrentStep) + ret +PLY_AKM_PSS_S_SpeedReached: + ;Stores the Instrument new pointer, resets the speed counter. + ld (ix + PLY_AKM_Data_OffsetPtInstrument + 0),l + ld (ix + PLY_AKM_Data_OffsetPtInstrument + 1),h + ld (ix + PLY_AKM_Data_OffsetInstrumentCurrentStep),0 + ret + + + IFDEF PLY_CFG_UseHardwareSounds ;CONFIG SPECIFIC + + ;Software and Hardware. + ;---------------------------- +PLY_AKM_PSS_SoftAndHard: + ;Reads the envelope bit, the possible pitch, and sets the software period accordingly. + call PLY_AKM_PSS_Shared_ReadEnvBitPitchArp_SoftPeriod_HardVol_HardEnv + ;Reads the hardware period. + ld a,(hl) + ld (PLY_AKM_Reg11),a + inc hl + ld a,(hl) + ld (PLY_AKM_Reg12),a + inc hl + + jr PLY_AKM_PSS_Shared_StoreInstrumentPointer + + + ;Software to Hardware. + ;------------------------- +PLY_AKM_PSS_SoftwareToHardware: + call PLY_AKM_PSS_Shared_ReadEnvBitPitchArp_SoftPeriod_HardVol_HardEnv + + ;Now we can calculate the hardware period thanks to the ratio (contray to LW, it is NOT inverted, we can use it as-is). + ld a,b + rlca + rlca + rlca + rlca + and %111 + exx + jr z,PLY_AKM_PSS_STH_RatioEnd +PLY_AKM_PSS_STH_RatioLoop: + srl h + rr l + dec a + jr nz,PLY_AKM_PSS_STH_RatioLoop + ;If carry, rounds the period. + jr nc,PLY_AKM_PSS_STH_RatioEnd + inc hl +PLY_AKM_PSS_STH_RatioEnd: + ld a,l + ld (PLY_AKM_Reg11),a + ld a,h + ld (PLY_AKM_Reg12),a + exx + + jr PLY_AKM_PSS_Shared_StoreInstrumentPointer + +;A shared code for hardware sound. +;Reads the envelope bit in bit 1, arpeggio in bit 7 pitch in bit 2 from A. If pitch present, adds it to BC'. +;Converts the note to period, adds the instrument pitch, sets the software period of the channel. +;Also sets the hardware volume, and sets the hardware curve. +PLY_AKM_PSS_Shared_ReadEnvBitPitchArp_SoftPeriod_HardVol_HardEnv: + ;Envelope bit? R13 = 8 + 2 * (envelope bit?). Allows to have hardware envelope to 8 or 0xa. + ;Shifted by 2 to the right, bit 1 is now envelope bit, which is perfect for us. + and %10 + add a,8 + ld (PLY_AKM_SetReg13 + PLY_AKM_Offset1b),a + + ;Volume to 16 to trigger the hardware envelope. + ld (iy + PLY_AKM_Registers_OffsetVolume),16 + + ;Arpeggio? + xor a ;Default arpeggio. + IFDEF PLY_AKM_ArpeggioInHardwareInstrument ;CONFIG SPECIFIC + bit 7,b ;Not shifted yet. + jr z,PLY_AKM_PSS_Shared_REnvBAP_AfterArpeggio + ;Reads the Arpeggio. + ld a,(hl) + inc hl +PLY_AKM_PSS_Shared_REnvBAP_AfterArpeggio: + ENDIF ;PLY_AKM_ArpeggioInHardwareInstrument + ;Calculates the software period. + call PLY_AKM_CalculatePeriodForBaseNote + + ;Pitch? + IFDEF PLY_AKM_PitchInHardwareInstrument ;CONFIG SPECIFIC + bit 2,b ;Not shifted yet. + call nz,PLY_AKM_ReadPitchAndAddToPeriod + ENDIF ;PLY_AKM_PitchInHardwareInstrument + + ;Stores the new period of this channel. + exx + ld (iy + PLY_AKM_Registers_OffsetSoftwarePeriodLSB),l + ld (iy + PLY_AKM_Registers_OffsetSoftwarePeriodMSB),h + exx + ret + + ENDIF ;PLY_CFG_UseHardwareSounds + + IFDEF PLY_CFG_UseEffect_SetVolume ;CONFIG SPECIFIC +;Decreases the given volume (encoded in possibly more then 4 bits). If <0, forced to 0. +;IN: A = volume, not ANDed. +;OUT: A = new volume. +PLY_AKM_PSS_Shared_AdjustVolume: + and %1111 + sub (ix + PLY_AKM_Data_OffsetTrackInvertedVolume) + ret nc + xor a + ret + ENDIF ;PLY_CFG_UseEffect_SetVolume + +;Reads and stores the noise pointed by HL, opens the noise channel. +;IN: HL = instrument data where the noise is. +;OUT: HL = HL++. +;MOD: A. + IFDEF PLY_AKM_USE_Noise ;CONFIG SPECIFIC +PLY_AKM_PSS_ReadNoise: + ld a,(hl) + inc hl + ld (PLY_AKM_NoiseRegister),a + res 5,c ;Opens the noise channel. + ret + ENDIF ;PLY_AKM_USE_Noise + +;Calculates the period according to the base note and put it in BC'. Used by both software and hardware codes. +;IN: DE' = period table. +; A = instrument arpeggio (0 if not used). +;OUT: HL' = period. +;MOD: A +PLY_AKM_CalculatePeriodForBaseNote: + ;Gets the period from the current note. + exx + ld h,0 + add a,(ix + PLY_AKM_Data_OffsetBaseNote) ;Adds the instrument Arp to the base note (including the transposition). + IFDEF PLY_CFG_UseEffect_ArpeggioTable ;CONFIG SPECIFIC + add (ix + PLY_AKM_Data_OffsetCurrentArpeggioValue) ;Adds the Arpeggio Table effect. + ENDIF ;PLY_CFG_UseEffect_ArpeggioTable + + ;Finds the period from a single line of octave look-up table. This is slow... + ;IN: DE = PeriodTable. + ; A = note (>=0). + ;OUT: HL = period. + ; DE unmodified. + ; BC modified. + + ;Finds the octave. +dknr3 (void): ld bc,255 * 256 + 12 ;B = Octave (>=0). Will be increased just below. +PLY_AKM_FindOctave_Loop: + inc b ;Next octave. + sub c + jr nc,PLY_AKM_FindOctave_Loop + add a,c ;Compensates the first iteration that may not have been useful. + + ;A = note inside the octave. Gets the period for the note, for the lowest octave. + add a,a + ld l,a + ld h,0 + add hl,de ;Points on the period on the lowest octave. + ld a,(hl) + inc hl + ld h,(hl) ;HL is the period on the lowest octave. + ld l,a + ;Divides the period as long as we haven't reached the octave. + ld a,b + or a + jr z,PLY_AKM_FindOctave_OctaveShiftLoop_Finished +PLY_AKM_FindOctave_OctaveShiftLoop: + srl h + rr l + djnz PLY_AKM_FindOctave_OctaveShiftLoop ;Fortunately, does not modify the carry, used below. +PLY_AKM_FindOctave_OctaveShiftLoop_Finished: + ;Rounds the period at the last iteration. + jr nc,PLY_AKM_FindOctave_Finished + inc hl +PLY_AKM_FindOctave_Finished: + + ;Adds the Pitch Table value, if used. + IFDEF PLY_CFG_UseEffect_PitchTable ;CONFIG SPECIFIC + ld a,(ix + PLY_AKM_Data_OffsetIsPitchTableUsed) + or a + jr z,PLY_AKM_CalculatePeriodForBaseNote_NoPitchTable + ld c,(ix + PLY_AKM_Data_OffsetCurrentPitchTableValue + 0) + ld b,(ix + PLY_AKM_Data_OffsetCurrentPitchTableValue + 1) + add hl,bc +PLY_AKM_CalculatePeriodForBaseNote_NoPitchTable: + ENDIF ;PLY_CFG_UseEffect_PitchTable + ;Adds the Track Pitch. + IFDEF PLY_AKM_USE_EffectPitchUpDown ;CONFIG SPECIFIC + ld c,(ix + PLY_AKM_Data_OffsetTrackPitchInteger + 0) + ld b,(ix + PLY_AKM_Data_OffsetTrackPitchInteger + 1) + add hl,bc + ENDIF ;PLY_AKM_USE_EffectPitchUpDown + exx + ret + + IFDEF PLY_AKM_PitchInInstrument ;CONFIG SPECIFIC +;Reads the pitch in the Instruments (16 bits) and adds it to HL', which should contain the software period. +;IN: HL = points on the pitch value. +;OUT: HL = points after the pitch. +;MOD: A, BC', HL' updated. +PLY_AKM_ReadPitchAndAddToPeriod: + ;Reads 2 * 8 bits for the pitch. Slow... + ld a,(hl) + inc hl + exx + ld c,a ;Adds the read pitch to the note period. + exx + ld a,(hl) + inc hl + exx + ld b,a + add hl,bc + exx + ret + ENDIF ;PLY_AKM_PitchInInstrument + + + + + + + +;--------------------------------------------------------------------- +;Effect management. +;--------------------------------------------------------------------- + + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC + +;IN: HL = points after the first byte. +; A = data of the first byte on bits 0-3, the other bits are 0. +; Carry = 0. +; Z flag = 1 if the data is 0. +; DE'= Instrument Table (not useful here). Do not modify! +; IX = data block of the Track. +; B = Do not modify! +;OUT: HL = points after the data of the effect (maybe nothing to do). +; Each effect must jump to PLY_AKM_RT_ReadEffect_Return. + + IFDEF PLY_CFG_UseEffect_Reset ;CONFIG SPECIFIC. +;Clears all the effects (volume, pitch table, arpeggio table). +PLY_AKM_EffectResetWithVolume: + ;Inverted volume. + IFDEF PLY_CFG_UseEffect_SetVolume ;CONFIG SPECIFIC + ld (ix + PLY_AKM_Data_OffsetTrackInvertedVolume),a + ENDIF ;PLY_CFG_UseEffect_SetVolume + xor a + ;The inverted volume is managed above, so don't change it. + IFDEF PLY_AKM_USE_EffectPitchUpDown ;CONFIG SPECIFIC + ld (ix + PLY_AKM_Data_OffsetIsPitchUpDownUsed),a + ENDIF ;PLY_AKM_USE_EffectPitchUpDown + IFDEF PLY_CFG_UseEffect_ArpeggioTable ;CONFIG SPECIFIC + ld (ix + PLY_AKM_Data_OffsetIsArpeggioTableUsed),a + ld (ix + PLY_AKM_Data_OffsetCurrentArpeggioValue),a ;Contrary to the Pitch, the value must be reset. + ENDIF ;PLY_CFG_UseEffect_ArpeggioTable + IFDEF PLY_CFG_UseEffect_PitchTable ;CONFIG SPECIFIC + ld (ix + PLY_AKM_Data_OffsetIsPitchTableUsed),a + ENDIF ;PLY_CFG_UseEffect_PitchTable + jp PLY_AKM_RT_ReadEffect_Return + ENDIF ;PLY_CFG_UseEffect_Reset + + +;Changes the volume. + IFDEF PLY_CFG_UseEffect_SetVolume ;CONFIG SPECIFIC +PLY_AKM_EffectVolume: + ld (ix + PLY_AKM_Data_OffsetTrackInvertedVolume),a + jp PLY_AKM_RT_ReadEffect_Return + ENDIF ;PLY_CFG_UseEffect_SetVolume + + IFDEF PLY_CFG_UseEffect_ForceInstrumentSpeed ;CONFIG SPECIFIC +;Forces the speed of the Instrument. The current step is NOT changed. +PLY_AKM_EffectForceInstrumentSpeed: + call PLY_AKM_EffectReadIfEscape ;Makes sure the data is 0-14, else 15 means: read the next escape value. + ld (ix + PLY_AKM_Data_OffsetInstrumentSpeed),a + + jp PLY_AKM_RT_ReadEffect_Return + ENDIF ;PLY_CFG_UseEffect_ForceInstrumentSpeed + + IFDEF PLY_CFG_UseEffect_ForcePitchTableSpeed ;CONFIG SPECIFIC +;Forces the speed of the Pitch. The current step is NOT changed. +PLY_AKM_EffectForcePitchSpeed: + call PLY_AKM_EffectReadIfEscape ;Makes sure the data is 0-14, else 15 means: read the next escape value. + ld (ix + PLY_AKM_Data_OffsetPitchCurrentSpeed),a + ;ld (ix + PLY_AKM_Data_OffsetPitchCurrentStep),a ;No need to force next note of the Arpeggio. Faster, and more compliant with the C++ player. + + jp PLY_AKM_RT_ReadEffect_Return + ENDIF ;PLY_CFG_UseEffect_ForcePitchTableSpeed + + IFDEF PLY_CFG_UseEffect_ForceArpeggioSpeed ;CONFIG SPECIFIC +;Forces the speed of the Arpeggio. The current step is NOT changed. +PLY_AKM_EffectForceArpeggioSpeed: + call PLY_AKM_EffectReadIfEscape ;Makes sure the data is 0-14, else 15 means: read the next escape value. + ld (ix + PLY_AKM_Data_OffsetArpeggioCurrentSpeed),a + ;ld (ix + PLY_AKM_Data_OffsetArpeggioCurrentStep),a ;No need to force next note of the Arpeggio. Faster, and more compliant with the C++ player. + + jp PLY_AKM_RT_ReadEffect_Return + ENDIF ;PLY_CFG_UseEffect_ForceArpeggioSpeed + + +;Effect table. Each entry jumps to an effect management code. +;Put after the code above so that the JR are within bound. +PLY_AKM_EffectTable: + IFDEF PLY_CFG_UseEffect_Reset ;CONFIG SPECIFIC. + jr PLY_AKM_EffectResetWithVolume ;000 + ELSE + jr $ + ENDIF ;PLY_CFG_UseEffect_Reset + + IFDEF PLY_CFG_UseEffect_SetVolume ;CONFIG SPECIFIC + jr PLY_AKM_EffectVolume ;001 + ELSE + jr $ + ENDIF ;PLY_CFG_UseEffect_SetVolume + + IFDEF PLY_AKM_USE_EffectPitchUpDown ;CONFIG SPECIFIC + jr PLY_AKM_EffectPitchUpDown ;010 + ELSE + jr $ + ENDIF ;PLY_AKM_USE_EffectPitchUpDown + + IFDEF PLY_CFG_UseEffect_ArpeggioTable ;CONFIG SPECIFIC + jr PLY_AKM_EffectArpeggioTable ;011 + ELSE + jr $ + ENDIF ;PLY_CFG_UseEffect_ArpeggioTable + + IFDEF PLY_CFG_UseEffect_PitchTable ;CONFIG SPECIFIC + jr PLY_AKM_EffectPitchTable ;100 + ELSE + jr $ + ENDIF ;PLY_CFG_UseEffect_PitchTable + + IFDEF PLY_CFG_UseEffect_ForceInstrumentSpeed ;CONFIG SPECIFIC + jr PLY_AKM_EffectForceInstrumentSpeed ;101 + ELSE + jr $ + ENDIF ;PLY_CFG_UseEffect_ForceInstrumentSpeed + + IFDEF PLY_CFG_UseEffect_ForceArpeggioSpeed ;CONFIG SPECIFIC + jr PLY_AKM_EffectForceArpeggioSpeed ;110 + ELSE + jr $ + ENDIF ;PLY_CFG_UseEffect_ForceArpeggioSpeed + + IFDEF PLY_CFG_UseEffect_ForcePitchTableSpeed ;CONFIG SPECIFIC + jr PLY_AKM_EffectForcePitchSpeed ;111 + ELSE + ;jr $ ;Last one. No need to encode it. + ENDIF ;PLY_CFG_UseEffect_ForcePitchTableSpeed + + + IFDEF PLY_AKM_USE_EffectPitchUpDown ;CONFIG SPECIFIC +;Pitch up/down effect, activation or stop. +PLY_AKM_EffectPitchUpDown: + rra ;Pitch present or pitch stop? + jr nc,PLY_AKM_EffectPitchUpDown_Deactivated + ;Activates the effect. + ld (ix + PLY_AKM_Data_OffsetIsPitchUpDownUsed),255 + ld a,(hl) + inc hl + ld (ix + PLY_AKM_Data_OffsetTrackPitchSpeed + 0),a + ld a,(hl) + inc hl + ld (ix + PLY_AKM_Data_OffsetTrackPitchSpeed + 1),a + jp PLY_AKM_RT_ReadEffect_Return +PLY_AKM_EffectPitchUpDown_Deactivated: + ;Pitch stop. + ld (ix + PLY_AKM_Data_OffsetIsPitchUpDownUsed),0 + jp PLY_AKM_RT_ReadEffect_Return + ENDIF ;PLY_AKM_USE_EffectPitchUpDown + + + IFDEF PLY_CFG_UseEffect_ArpeggioTable ;CONFIG SPECIFIC +;Arpeggio table effect, activation or stop. +PLY_AKM_EffectArpeggioTable: + call PLY_AKM_EffectReadIfEscape ;Makes sure the data is 0-14, else 15 means: read the next escape value. + ld (ix + PLY_AKM_Data_OffsetIsArpeggioTableUsed),a ;Sets to 0 if the Arpeggio is stopped, or any other value if it starts. + or a + jr z,PLY_AKM_EffectArpeggioTable_Stop + + ;Gets the Arpeggio address. + add a,a + exx + ld l,a + ld h,0 + ;BC is modified, will be restored below. + IFNDEF PLY_AKM_Rom +dknr3 (void): +PLY_AKM_PtArpeggios: ld bc,0 ;Arpeggio table does not encode entry 0, but the pointer points two bytes earlier to compensate. + ELSE + ld bc,(PLY_AKM_PtArpeggios) + ENDIF + add hl,bc + ld a,(hl) + inc hl + ld h,(hl) + ld l,a + ld a,(hl) ;Reads the speed. + inc hl + ld (ix + PLY_AKM_Data_OffsetArpeggioOriginalSpeed),a + IFDEF PLY_CFG_UseEffect_ForceArpeggioSpeed ;CONFIG SPECIFIC + ld (ix + PLY_AKM_Data_OffsetArpeggioCurrentSpeed),a + ENDIF ;PLY_CFG_UseEffect_ForceArpeggioSpeed + ld (ix + PLY_AKM_Data_OffsetPtArpeggioTable + 0),l + ld (ix + PLY_AKM_Data_OffsetPtArpeggioTable + 1),h + + ld bc,(PLY_AKM_NoteIndexTable + PLY_AKM_Offset1b) + exx + + ;Resets the offset of the Arpeggio to restart the Arpeggio, and forces a step to read immediately. + xor a + ld (ix + PLY_AKM_Data_OffsetPtArpeggioOffset),a + ld (ix + PLY_AKM_Data_OffsetArpeggioCurrentStep),a + jp PLY_AKM_RT_ReadEffect_Return +PLY_AKM_EffectArpeggioTable_Stop: + ;Contrary to the Pitch, the Arpeggio must also be set to 0 when stopped. + ld (ix + PLY_AKM_Data_OffsetCurrentArpeggioValue),a + jp PLY_AKM_RT_ReadEffect_Return + ENDIF ;PLY_CFG_UseEffect_ArpeggioTable + + IFDEF PLY_CFG_UseEffect_PitchTable ;CONFIG SPECIFIC +;Pitch table effect, activation or stop. +;This is almost exactly the same code as for the Arpeggio, but I can't find a way to share it... +PLY_AKM_EffectPitchTable: + call PLY_AKM_EffectReadIfEscape ;Makes sure the data is 0-14, else 15 means: read the next escape value. + ld (ix + PLY_AKM_Data_OffsetIsPitchTableUsed),a ;Sets to 0 if the Pitch is stopped, or any other value if it starts. + or a + jp z,PLY_AKM_RT_ReadEffect_Return + + ;Gets the Pitch address. + add a,a + exx + ld l,a + ld h,0 + ;BC is modified, will be restored below. + IFNDEF PLY_AKM_Rom +dknr3 (void): +PLY_AKM_PtPitches: ld bc,0 ;Pitch table does not encode entry 0, but the pointer points two bytes earlier to compensate. + ELSE + ld bc,(PLY_AKM_PtPitches) + ENDIF + add hl,bc + ld a,(hl) + inc hl + ld h,(hl) + ld l,a + ld a,(hl) ;Reads the speed. + inc hl + ld (ix + PLY_AKM_Data_OffsetPitchOriginalSpeed),a + IFDEF PLY_CFG_UseEffect_ForcePitchTableSpeed ;CONFIG SPECIFIC + ld (ix + PLY_AKM_Data_OffsetPitchCurrentSpeed),a + ENDIF ;PLY_CFG_UseEffect_ForcePitchTableSpeed + ld (ix + PLY_AKM_Data_OffsetPtPitchTable + 0),l + ld (ix + PLY_AKM_Data_OffsetPtPitchTable + 1),h + + ld bc,(PLY_AKM_NoteIndexTable + PLY_AKM_Offset1b) + exx + + ;Resets the offset of the Pitch to restart the Pitch, and forces a step to read immediately. + xor a + ld (ix + PLY_AKM_Data_OffsetPtPitchOffset),a + ld (ix + PLY_AKM_Data_OffsetPitchCurrentStep),a + jp PLY_AKM_RT_ReadEffect_Return + ENDIF ;PLY_CFG_UseEffect_PitchTable + + + + + + +;Reads the next escape byte if A is 15, else returns A (0-14). +;IN: HL= data in the effect +; A = 0-15. bit 7-4 must be 0. +;OUT: HL= may be increased if an escape value is read. +; A = the 8-bit value. +PLY_AKM_EffectReadIfEscape: + cp 15 + ret c + ;Reads the escape value. + ld a,(hl) + inc hl + ret + + ENDIF ;PLY_CFG_UseEffects + + +;--------------------------------------------------------------------- +;Data blocks for the three channels. Make sure NOTHING is added between, as the init clears everything! +;--------------------------------------------------------------------- + + counter = 0 + ;Macro to declare a DB if RAM player, or an increasing EQU for ROM player. + MACRO PLY_AKM_db label + IFNDEF PLY_AKM_Rom + dkbs (void) + {label} db 0 + dkbe (void) + ELSE + {label} equ PLY_AKM_ROM_Buffer + counter + counter = counter + 1 + ENDIF + ENDM + + ;Macro to declare a DW if RAM player, or an increasing (of two bytes) EQU for ROM player. + MACRO PLY_AKM_dw label + IFNDEF PLY_AKM_Rom + dkws (void) + {label} dw 0 + dkwe (void) + ELSE + {label} equ PLY_AKM_ROM_Buffer + counter + counter = counter + 2 + ENDIF + ENDM + + + ;Specific generic data for ROM (non-related to channels). + ;Important: must be declared BEFORE the channel-specific data. + IFDEF PLY_AKM_Rom + PLY_AKM_dw PLY_AKM_PtInstruments + PLY_AKM_dw PLY_AKM_PtArpeggios + PLY_AKM_dw PLY_AKM_PtPitches + PLY_AKM_dw PLY_AKM_PtLinker + PLY_AKM_dw PLY_AKM_NoteIndexTable + PLY_AKM_dw PLY_AKM_TrackIndex + PLY_AKM_dw PLY_AKM_SaveSP + + PLY_AKM_db PLY_AKM_DefaultStartNoteInTracks + PLY_AKM_db PLY_AKM_DefaultStartInstrumentInTracks + PLY_AKM_db PLY_AKM_DefaultStartWaitInTracks + PLY_AKM_db PLY_AKM_PrimaryInstrument + PLY_AKM_db PLY_AKM_SecondaryInstrument + PLY_AKM_db PLY_AKM_PrimaryWait + PLY_AKM_db PLY_AKM_SecondaryWait + PLY_AKM_db PLY_AKM_FlagNoteAndEffectInCell + + PLY_AKM_db PLY_AKM_PatternRemainingHeight + PLY_AKM_db PLY_AKM_LinkerPreviousRemainingHeight + PLY_AKM_db PLY_AKM_Speed + PLY_AKM_db PLY_AKM_TickCounter + PLY_AKM_db PLY_AKM_SetReg13Old + PLY_AKM_db PLY_AKM_SetReg13 + PLY_AKM_db PLY_AKM_RT_ReadEffectsFlag + + ;RET table: db register, db value, dw code to jump to once the value is read. + ;MUST be consistent with the RAM buffer! +PLY_AKM_Registers_RetTable: equ PLY_AKM_ROM_Buffer + counter + ;Reg 8. + PLY_AKM_db PLY_AKM_Track1_Registers + PLY_AKM_db PLY_AKM_Track1_Volume + PLY_AKM_dw PLY_AKM_Track1_VolumeRet + ;Reg 0. + PLY_AKM_db PLY_AKM_Track1_SoftwarePeriodLSBRegister + PLY_AKM_db PLY_AKM_Track1_SoftwarePeriodLSB + PLY_AKM_dw PLY_AKM_Track1_SoftwarePeriodLSBRet + ;Reg 1. + PLY_AKM_db PLY_AKM_Track1_SoftwarePeriodMSBRegister + PLY_AKM_db PLY_AKM_Track1_SoftwarePeriodMSB + PLY_AKM_dw PLY_AKM_Track1_SoftwarePeriodMSBRet + + ;Reg 9. + PLY_AKM_db PLY_AKM_Track2_Registers + PLY_AKM_db PLY_AKM_Track2_Volume + PLY_AKM_dw PLY_AKM_Track2_VolumeRet + ;Reg 2. + PLY_AKM_db PLY_AKM_Track2_SoftwarePeriodLSBRegister + PLY_AKM_db PLY_AKM_Track2_SoftwarePeriodLSB + PLY_AKM_dw PLY_AKM_Track2_SoftwarePeriodLSBRet + ;Reg 3. + PLY_AKM_db PLY_AKM_Track2_SoftwarePeriodMSBRegister + PLY_AKM_db PLY_AKM_Track2_SoftwarePeriodMSB + PLY_AKM_dw PLY_AKM_Track2_SoftwarePeriodMSBRet + + ;Reg 10. + PLY_AKM_db PLY_AKM_Track3_Registers + PLY_AKM_db PLY_AKM_Track3_Volume + PLY_AKM_dw PLY_AKM_Track3_VolumeRet + ;Reg 4. + PLY_AKM_db PLY_AKM_Track3_SoftwarePeriodLSBRegister + PLY_AKM_db PLY_AKM_Track3_SoftwarePeriodLSB + PLY_AKM_dw PLY_AKM_Track3_SoftwarePeriodLSBRet + ;Reg 5. + PLY_AKM_db PLY_AKM_Track3_SoftwarePeriodMSBRegister + PLY_AKM_db PLY_AKM_Track3_SoftwarePeriodMSB + PLY_AKM_dw PLY_AKM_Track3_SoftwarePeriodMSBRet + + IFDEF PLY_AKM_USE_NoiseRegister ;CONFIG SPECIFIC + ;Reg 6. + PLY_AKM_db PLY_AKM_NoiseRegisterPlaceholder + PLY_AKM_db PLY_AKM_NoiseRegister ;Misnomer: this is the value. + PLY_AKM_dw PLY_AKM_NoiseRegisterRet + ENDIF + + ;Reg 7. + PLY_AKM_db PLY_AKM_MixerRegisterPlaceholder + PLY_AKM_db PLY_AKM_MixerRegister ;Misnomer: this is the value. + IFDEF PLY_CFG_UseHardwareSounds ;CONFIG SPECIFIC + PLY_AKM_dw PLY_AKM_MixerRegisterRet + ;Reg 11. + PLY_AKM_db PLY_AKM_Reg11Register + PLY_AKM_db PLY_AKM_Reg11 + PLY_AKM_dw PLY_AKM_Reg11Ret + ;Reg 12. + PLY_AKM_db PLY_AKM_Reg12Register + PLY_AKM_db PLY_AKM_Reg12 + PLY_AKM_dw PLY_AKM_Reg12Ret + ;This one is a trick to send the register after R13 is managed. + PLY_AKM_dw PLY_AKM_Reg12Ret2 + ENDIF + + PLY_AKM_dw PLY_AKM_RegsFinalRet + + + ;The buffers for sound effects (if any), for each channel. They are treated apart, because they must be consecutive. + IFDEF PLY_AKM_MANAGE_SOUND_EFFECTS +PLY_AKM_dw PLY_AKM_PtSoundEffectTable + REPEAT 3, channelNumber +PLY_AKM_dw PLY_AKM_Channel{channelNumber}_SoundEffectData +PLY_AKM_db PLY_AKM_Channel{channelNumber}_SoundEffectInvertedVolume +PLY_AKM_db PLY_AKM_Channel{channelNumber}_SoundEffectCurrentStep +PLY_AKM_db PLY_AKM_Channel{channelNumber}_SoundEffectSpeed + if channelNumber != 3 + counter = counter + 3 ;Padding of 3, but only necessary for channel 1 and 2. + endif + REND + ENDIF ;PLY_AKM_MANAGE_SOUND_EFFECTS + + + ENDIF ;PLY_AKM_Rom + +;Data block for channel 1. + IFNDEF PLY_AKM_Rom +PLY_AKM_Track1_Data: + ELSE + counterStartInTrackData = counter ;Duplicates the counter value to determine later the size of the track buffer. + ENDIF + PLY_AKM_db PLY_AKM_Track1_WaitEmptyCell ;How many empty cells have to be waited. 0 = none. + IFDEF PLY_AKM_Rom + PLY_AKM_Track1_Data equ PLY_AKM_Track1_WaitEmptyCell + ENDIF + IFDEF PLY_CFG_UseTranspositions ;CONFIG SPECIFIC + PLY_AKM_db PLY_AKM_Track1_Transposition + ENDIF ;PLY_CFG_UseTranspositions + PLY_AKM_dw PLY_AKM_Track1_PtStartTrack ;Points at the start of the Track to read. Does not change, unless the Track changes. + PLY_AKM_dw PLY_AKM_Track1_PtTrack ;Points on the next Cell of the Track to read. Evolves. + PLY_AKM_db PLY_AKM_Track1_BaseNote ;Base note, such as the note played. The transposition IS included. + PLY_AKM_db PLY_AKM_Track1_EscapeNote ;The escape note. The transposition is NOT included. + PLY_AKM_db PLY_AKM_Track1_EscapeInstrument ;The escape instrument. + PLY_AKM_db PLY_AKM_Track1_EscapeWait ;The escape wait. + PLY_AKM_dw PLY_AKM_Track1_PtInstrument ;Points on the Instrument, evolves. + PLY_AKM_db PLY_AKM_Track1_InstrumentCurrentStep ;The current step on the Instrument (>=0, till it reaches the Speed). + PLY_AKM_db PLY_AKM_Track1_InstrumentSpeed ;The Instrument speed (>=0). + IFDEF PLY_CFG_UseEffect_SetVolume ;CONFIG SPECIFIC + PLY_AKM_db PLY_AKM_Track1_TrackInvertedVolume + ENDIF ;PLY_CFG_UseEffect_SetVolume + IFDEF PLY_AKM_USE_EffectPitchUpDown ;CONFIG SPECIFIC + PLY_AKM_db PLY_AKM_Track1_IsPitchUpDownUsed ;>0 if a Pitch Up/Down is currently in use. + PLY_AKM_dw PLY_AKM_Track1_TrackPitchInteger ;The integer part of the Track pitch. Evolves as the pitch goes up/down. + PLY_AKM_db PLY_AKM_Track1_TrackPitchDecimal ;The decimal part of the Track pitch. Evolves as the pitch goes up/down. + PLY_AKM_dw PLY_AKM_Track1_TrackPitchSpeed ;The integer and decimal part of the Track pitch speed. Is added to the Track Pitch every frame. + ENDIF ;PLY_AKM_USE_EffectPitchUpDown + IFDEF PLY_CFG_UseEffect_ArpeggioTable ;CONFIG SPECIFIC + PLY_AKM_db PLY_AKM_Track1_IsArpeggioTableUsed ;>0 if an Arpeggio Table is currently in use. + PLY_AKM_dw PLY_AKM_Track1_PtArpeggioTable ;Point on the base of the Arpeggio table, does not evolve. + PLY_AKM_db PLY_AKM_Track1_PtArpeggioOffset ;Increases over the Arpeggio. + PLY_AKM_db PLY_AKM_Track1_ArpeggioCurrentStep ;The arpeggio current step (>=0, increases). + IFDEF PLY_CFG_UseEffect_ForceArpeggioSpeed ;CONFIG SPECIFIC + PLY_AKM_db PLY_AKM_Track1_ArpeggioCurrentSpeed ;The arpeggio speed (>=0, may be changed by the Force Arpeggio Speed effect). + ENDIF ;PLY_CFG_UseEffect_ForceArpeggioSpeed + PLY_AKM_db PLY_AKM_Track1_ArpeggioOriginalSpeed ;The arpeggio original speed (>=0, NEVER changes for this arpeggio). + PLY_AKM_db PLY_AKM_Track1_CurrentArpeggioValue ;Value from the Arpeggio to add to the base note. Read even if the Arpeggio effect is deactivated. + ENDIF ;PLY_CFG_UseEffect_ArpeggioTable + IFDEF PLY_CFG_UseEffect_PitchTable ;CONFIG SPECIFIC + PLY_AKM_db PLY_AKM_Track1_IsPitchTableUsed ;>0 if a Pitch Table is currently in use. + PLY_AKM_dw PLY_AKM_Track1_PtPitchTable ;Points on the base of the Pitch table, does not evolve. + PLY_AKM_db PLY_AKM_Track1_PtPitchOffset ;Increases over the Pitch. + PLY_AKM_db PLY_AKM_Track1_PitchCurrentStep ;The Pitch current step (>=0, increases). + IFDEF PLY_CFG_UseEffect_ForcePitchTableSpeed ;CONFIG SPECIFIC + PLY_AKM_db PLY_AKM_Track1_PitchCurrentSpeed ;The Pitch speed (>=0, may be changed by the Force Pitch Speed effect). + ENDIF ;PLY_CFG_UseEffect_ForcePitchTableSpeed + PLY_AKM_db PLY_AKM_Track1_PitchOriginalSpeed ;The Pitch original speed (>=0, NEVER changes for this pitch). + PLY_AKM_dw PLY_AKM_Track1_CurrentPitchTableValue ;16 bit value from the Pitch to add to the base note. Not read if the Pitch effect is deactivated. + + ENDIF ;PLY_CFG_UseEffect_PitchTable + + IFNDEF PLY_AKM_Rom +PLY_AKM_Track1_Data_End: +PLY_AKM_Track1_Data_Size: equ PLY_AKM_Track1_Data_End - PLY_AKM_Track1_Data + ELSE +PLY_AKM_Track1_Data_Size = counter - counterStartInTrackData +PLY_AKM_Track1_Data_End = PLY_AKM_Track1_Data + PLY_AKM_Track1_Data_Size + ENDIF + +PLY_AKM_Data_OffsetWaitEmptyCell: equ PLY_AKM_Track1_WaitEmptyCell - PLY_AKM_Track1_Data + IFDEF PLY_CFG_UseTranspositions ;CONFIG SPECIFIC +PLY_AKM_Data_OffsetTransposition: equ PLY_AKM_Track1_Transposition - PLY_AKM_Track1_Data + ENDIF ;PLY_CFG_UseTranspositions +PLY_AKM_Data_OffsetPtStartTrack: equ PLY_AKM_Track1_PtStartTrack - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetPtTrack: equ PLY_AKM_Track1_PtTrack - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetBaseNote: equ PLY_AKM_Track1_BaseNote - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetEscapeNote: equ PLY_AKM_Track1_EscapeNote - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetEscapeInstrument: equ PLY_AKM_Track1_EscapeInstrument - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetEscapeWait: equ PLY_AKM_Track1_EscapeWait - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetSecondaryInstrument: equ PLY_AKM_Track1_EscapeWait - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetPtInstrument: equ PLY_AKM_Track1_PtInstrument - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetInstrumentCurrentStep: equ PLY_AKM_Track1_InstrumentCurrentStep - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetInstrumentSpeed: equ PLY_AKM_Track1_InstrumentSpeed - PLY_AKM_Track1_Data + IFDEF PLY_CFG_UseEffect_SetVolume ;CONFIG SPECIFIC +PLY_AKM_Data_OffsetTrackInvertedVolume: equ PLY_AKM_Track1_TrackInvertedVolume - PLY_AKM_Track1_Data + ENDIF ;PLY_CFG_UseEffect_SetVolume + IFDEF PLY_AKM_USE_EffectPitchUpDown ;CONFIG SPECIFIC +PLY_AKM_Data_OffsetIsPitchUpDownUsed: equ PLY_AKM_Track1_IsPitchUpDownUsed - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetTrackPitchInteger: equ PLY_AKM_Track1_TrackPitchInteger - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetTrackPitchDecimal: equ PLY_AKM_Track1_TrackPitchDecimal - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetTrackPitchSpeed: equ PLY_AKM_Track1_TrackPitchSpeed - PLY_AKM_Track1_Data + ENDIF ;PLY_AKM_USE_EffectPitchUpDown + IFDEF PLY_CFG_UseEffect_ArpeggioTable ;CONFIG SPECIFIC +PLY_AKM_Data_OffsetIsArpeggioTableUsed: equ PLY_AKM_Track1_IsArpeggioTableUsed - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetPtArpeggioTable: equ PLY_AKM_Track1_PtArpeggioTable - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetPtArpeggioOffset: equ PLY_AKM_Track1_PtArpeggioOffset - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetArpeggioCurrentStep: equ PLY_AKM_Track1_ArpeggioCurrentStep - PLY_AKM_Track1_Data + IFDEF PLY_CFG_UseEffect_ForceArpeggioSpeed ;CONFIG SPECIFIC +PLY_AKM_Data_OffsetArpeggioCurrentSpeed: equ PLY_AKM_Track1_ArpeggioCurrentSpeed - PLY_AKM_Track1_Data + ENDIF ;PLY_CFG_UseEffect_ForceArpeggioSpeed +PLY_AKM_Data_OffsetArpeggioOriginalSpeed: equ PLY_AKM_Track1_ArpeggioOriginalSpeed - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetCurrentArpeggioValue: equ PLY_AKM_Track1_CurrentArpeggioValue - PLY_AKM_Track1_Data + ENDIF ;PLY_CFG_UseEffect_ArpeggioTable + IFDEF PLY_CFG_UseEffect_PitchTable ;CONFIG SPECIFIC +PLY_AKM_Data_OffsetIsPitchTableUsed: equ PLY_AKM_Track1_IsPitchTableUsed - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetPtPitchTable: equ PLY_AKM_Track1_PtPitchTable - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetPtPitchOffset: equ PLY_AKM_Track1_PtPitchOffset - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetPitchCurrentStep: equ PLY_AKM_Track1_PitchCurrentStep - PLY_AKM_Track1_Data + IFDEF PLY_CFG_UseEffect_ForcePitchTableSpeed ;CONFIG SPECIFIC +PLY_AKM_Data_OffsetPitchCurrentSpeed: equ PLY_AKM_Track1_PitchCurrentSpeed - PLY_AKM_Track1_Data + ENDIF ;PLY_CFG_UseEffect_ForcePitchTableSpeed +PLY_AKM_Data_OffsetPitchOriginalSpeed: equ PLY_AKM_Track1_PitchOriginalSpeed - PLY_AKM_Track1_Data +PLY_AKM_Data_OffsetCurrentPitchTableValue: equ PLY_AKM_Track1_CurrentPitchTableValue - PLY_AKM_Track1_Data + ENDIF ;PLY_CFG_UseEffect_PitchTable + +;Data block for channel 2. + IFNDEF PLY_AKM_Rom +PLY_AKM_Track2_Data: +dkbs (void): + ds PLY_AKM_Track1_Data_Size, 0 +dkbe (void): +PLY_AKM_Track2_Data_End: + ELSE +PLY_AKM_Track2_Data: equ PLY_AKM_Track1_Data + PLY_AKM_Track1_Data_Size +PLY_AKM_Track2_Data_End: equ PLY_AKM_Track2_Data + PLY_AKM_Track1_Data_Size + ENDIF +PLY_AKM_Track2_WaitEmptyCell: equ PLY_AKM_Track2_Data + PLY_AKM_Data_OffsetWaitEmptyCell +PLY_AKM_Track2_PtTrack: equ PLY_AKM_Track2_Data + PLY_AKM_Data_OffsetPtTrack +PLY_AKM_Track2_PtInstrument: equ PLY_AKM_Track2_Data + PLY_AKM_Data_OffsetPtInstrument +PLY_AKM_Track2_EscapeNote: equ PLY_AKM_Track2_Data + PLY_AKM_Data_OffsetEscapeNote +PLY_AKM_Track2_EscapeInstrument: equ PLY_AKM_Track2_Data + PLY_AKM_Data_OffsetEscapeInstrument +PLY_AKM_Track2_EscapeWait: equ PLY_AKM_Track2_Data + PLY_AKM_Data_OffsetEscapeWait + +;Data block for channel 3. + IFNDEF PLY_AKM_Rom +PLY_AKM_Track3_Data: +dkbs (void): + ds PLY_AKM_Track1_Data_Size, 0 +dkbe (void): +PLY_AKM_Track3_Data_End: + ELSE +PLY_AKM_Track3_Data: equ PLY_AKM_Track2_Data + PLY_AKM_Track1_Data_Size +PLY_AKM_Track3_Data_End: equ PLY_AKM_Track3_Data + PLY_AKM_Track1_Data_Size + +PLY_AKM_ROM_Buffer_End: equ PLY_AKM_Track3_Data_End +PLY_AKM_ROM_BufferSize: equ PLY_AKM_ROM_Buffer_End - PLY_AKM_ROM_Buffer + + expectedRomBufferSize = 199 ;Just to detect if the buffer grows. + IFNDEF PLY_AKM_MANAGE_SOUND_EFFECTS + assert PLY_AKM_ROM_BufferSize <= expectedRomBufferSize ;Decreases when using the Player Configuration. + ELSE + assert PLY_AKM_ROM_BufferSize <= (expectedRomBufferSize + 23) ;With sound effects, it takes a bit more memory. + ENDIF + ENDIF + + + + +PLY_AKM_Track3_WaitEmptyCell: equ PLY_AKM_Track3_Data + PLY_AKM_Data_OffsetWaitEmptyCell +PLY_AKM_Track3_PtTrack: equ PLY_AKM_Track3_Data + PLY_AKM_Data_OffsetPtTrack +PLY_AKM_Track3_PtInstrument: equ PLY_AKM_Track3_Data + PLY_AKM_Data_OffsetPtInstrument +PLY_AKM_Track3_EscapeNote: equ PLY_AKM_Track3_Data + PLY_AKM_Data_OffsetEscapeNote +PLY_AKM_Track3_EscapeInstrument: equ PLY_AKM_Track3_Data + PLY_AKM_Data_OffsetEscapeInstrument +PLY_AKM_Track3_EscapeWait: equ PLY_AKM_Track3_Data + PLY_AKM_Data_OffsetEscapeWait + + ;Makes sure the structure all have the same size! + ASSERT (PLY_AKM_Track1_Data_End - PLY_AKM_Track1_Data) == (PLY_AKM_Track2_Data_End - PLY_AKM_Track2_Data) + ASSERT (PLY_AKM_Track1_Data_End - PLY_AKM_Track1_Data) == (PLY_AKM_Track3_Data_End - PLY_AKM_Track3_Data) + ;No holes between the blocks, the init makes a LDIR to clear everything! + ASSERT PLY_AKM_Track1_Data_End == PLY_AKM_Track2_Data + ASSERT PLY_AKM_Track2_Data_End == PLY_AKM_Track3_Data + + + +;--------------------------------------------------------------------- +;Register block for all the channels. They are "polluted" with pointers to code because all this +;is actually a RET table! +;--------------------------------------------------------------------- +;DB register, DB value then DW code to jump to once the value is read. + IFNDEF PLY_AKM_Rom ;For ROM, a table is generated. +PLY_AKM_Registers_RetTable: +PLY_AKM_Track1_Registers: +dkbs (void): db 8 +PLY_AKM_Track1_Volume: db 0 +dkbe (void): +dkps (void): dw PLY_AKM_SendPsgRegister +dkpe (void): + +dkbs (void): db 0 +PLY_AKM_Track1_SoftwarePeriodLSB: db 0 +dkbe (void): +dkps (void): dw PLY_AKM_SendPsgRegister +dkpe (void): + +dkbs (void): db 1 +PLY_AKM_Track1_SoftwarePeriodMSB: db 0 +dkbe (void): +dkps (void): dw PLY_AKM_SendPsgRegister +dkpe (void): + +PLY_AKM_Track2_Registers: +dkbs (void): db 9 +PLY_AKM_Track2_Volume: db 0 +dkbe (void): +dkps (void): dw PLY_AKM_SendPsgRegister +dkpe (void): + +dkbs (void): db 2 +PLY_AKM_Track2_SoftwarePeriodLSB: db 0 +dkbe (void): +dkps (void): dw PLY_AKM_SendPsgRegister +dkpe (void): + +dkbs (void): db 3 +PLY_AKM_Track2_SoftwarePeriodMSB: db 0 +dkbe (void): +dkps (void): dw PLY_AKM_SendPsgRegister +dkpe (void): + + +PLY_AKM_Track3_Registers: +dkbs (void): db 10 +PLY_AKM_Track3_Volume: db 0 +dkbe (void): +dkps (void): dw PLY_AKM_SendPsgRegister +dkpe (void): + +dkbs (void): db 4 +PLY_AKM_Track3_SoftwarePeriodLSB: db 0 +dkbe (void): +dkps (void): dw PLY_AKM_SendPsgRegister +dkpe (void): + +dkbs (void): db 5 +PLY_AKM_Track3_SoftwarePeriodMSB: db 0 +dkbe (void): +dkps (void): dw PLY_AKM_SendPsgRegister +dkpe (void): + +;Generic registers. + IFDEF PLY_AKM_USE_NoiseRegister ;CONFIG SPECIFIC +dkbs (void): db 6 +PLY_AKM_NoiseRegister: db 0 +dkbe (void): +dkps (void): dw PLY_AKM_SendPsgRegister +dkpe (void): + ENDIF ;PLY_AKM_USE_NoiseRegister + +dkbs (void): db 7 +PLY_AKM_MixerRegister: db 0 +dkbe (void): + IFDEF PLY_CFG_UseHardwareSounds ;CONFIG SPECIFIC +dkps (void): dw PLY_AKM_SendPsgRegister +dkpe (void): + +dkbs (void): db 11 +PLY_AKM_Reg11: db 0 +dkbe (void): +dkps (void): dw PLY_AKM_SendPsgRegister +dkpe (void): + +dkbs (void): db 12 +PLY_AKM_Reg12: db 0 +dkbe (void): +dkps (void): dw PLY_AKM_SendPsgRegisterR13 + ;This one is a trick to send the register after R13 is managed. + dw PLY_AKM_SendPsgRegisterAfterPop +dkpe (void): + ENDIF ;PLY_CFG_UseHardwareSounds +dkps (void): dw PLY_AKM_SendPsgRegisterEnd +dkpe (void): + + ENDIF ;PLY_AKM_Rom + + +PLY_AKM_Registers_OffsetVolume: equ PLY_AKM_Track1_Volume - PLY_AKM_Track1_Registers +PLY_AKM_Registers_OffsetSoftwarePeriodLSB: equ PLY_AKM_Track1_SoftwarePeriodLSB - PLY_AKM_Track1_Registers +PLY_AKM_Registers_OffsetSoftwarePeriodMSB: equ PLY_AKM_Track1_SoftwarePeriodMSB - PLY_AKM_Track1_Registers + +;The period table for the first octave only. +PLY_AKM_PeriodTable: +dkws (void): + IFDEF PLY_AKM_HARDWARE_CPC + ;PSG running to 1000000 Hz. + dw 3822,3608,3405,3214,3034,2863,2703,2551,2408,2273,2145,2025 ; Octave 0. + ;dw 1911,1804,1703,1607,1517,1432,1351,1276,1204,1136,1073,1012 ;12 + ;dw 956, 902, 851, 804, 758, 716, 676, 638, 602, 568, 536, 506 ;24 + ;dw 478, 451, 426, 402, 379, 358, 338, 319, 301, 284, 268, 253 ;36 + ;dw 239, 225, 213, 201, 190, 179, 169, 159, 150, 142, 134, 127 ;48 + ;dw 119, 113, 106, 100, 95, 89, 84, 80, 75, 71, 67, 63 ;60 + ;dw 60, 56, 53, 50, 47, 45, 42, 40, 38, 36, 34, 32 ;72 + ;dw 30, 28, 27, 25, 24, 22, 21, 20, 19, 18, 17, 16 ;84 + ;dw 15, 14, 13, 13, 12, 11, 11, 10, 9, 9, 8, 8 ;96 + ;dw 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4 ;108 + ;dw 4, 4, 3, 3, 3, 3, 3, 2 ;,2, 2, 2, 2 ;120 -> 127 + ENDIF + + IFDEF PLY_AKM_HARDWARE_SPECTRUM_OR_MSX + ;PSG running to 1773400 Hz. + dw 6778, 6398, 6039, 5700, 5380, 5078, 4793, 4524, 4270, 4030, 3804, 3591 ; Octave 0. + ENDIF + + IFDEF PLY_AKM_HARDWARE_PENTAGON + ;PSG running to 1750000 Hz. + dw 6689, 6314, 5959, 5625, 5309, 5011, 4730, 4464, 4214, 3977, 3754, 3543 ; Octave 0. + ENDIF +dkwe (void): +PLY_AKM_End: + + + diff --git a/src/mplayer/akm/PlayerAkm_SoundEffects.asm b/src/mplayer/akm/PlayerAkm_SoundEffects.asm new file mode 100644 index 0000000..1e4c3b8 --- /dev/null +++ b/src/mplayer/akm/PlayerAkm_SoundEffects.asm @@ -0,0 +1,477 @@ +; Player of sound effects, for the AKM player. +; Note that this is the exact same code as for the Lightweight player, as the way to output to the PSG is the same. + + ;Is there a loaded Player Configuration source? If no, use a default configuration. + IFNDEF PLY_CFG_SFX_ConfigurationIsPresent + PLY_CFG_UseHardwareSounds = 1 + PLY_CFG_SFX_LoopTo = 1 + PLY_CFG_SFX_NoSoftNoHard = 1 + PLY_CFG_SFX_NoSoftNoHard_Noise = 1 + PLY_CFG_SFX_SoftOnly = 1 + PLY_CFG_SFX_SoftOnly_Noise = 1 + PLY_CFG_SFX_HardOnly = 1 + PLY_CFG_SFX_HardOnly_Noise = 1 + PLY_CFG_SFX_HardOnly_Retrig = 1 + PLY_CFG_SFX_SoftAndHard = 1 + PLY_CFG_SFX_SoftAndHard_Noise = 1 + PLY_CFG_SFX_SoftAndHard_Retrig = 1 + ENDIF + +; Agglomerates some Player Configuration flags. +; -------------------------------------------- +; Mixes the Hardware flags into one. + IFDEF PLY_CFG_SFX_HardOnly + PLY_AKM_SE_HardwareSounds = 1 + ENDIF + IFDEF PLY_CFG_SFX_SoftAndHard + PLY_AKM_SE_HardwareSounds = 1 + ENDIF +; Mixes the Hardware Noise flags into one. + IFDEF PLY_CFG_SFX_HardOnly_Noise + PLY_AKM_SE_HardwareNoise = 1 + ENDIF + IFDEF PLY_CFG_SFX_SoftAndHard_Noise + PLY_AKM_SE_HardwareNoise = 1 + ENDIF +; Mixes the Noise flags into one. + IFDEF PLY_AKM_SE_HardwareNoise + PLY_AKM_SE_Noise = 1 + ENDIF + IFDEF PLY_CFG_SFX_NoSoftNoHard_Noise + PLY_AKM_SE_Noise = 1 + ENDIF + IFDEF PLY_CFG_SFX_SoftOnly + PLY_AKM_SE_Noise = 1 + ENDIF +; Noise in Sound Effects? Then noise register code must be compiled. + IFDEF PLY_AKM_SE_Noise + PLY_AKM_USE_NoiseRegister = 1 + ENDIF +; Mixes the Software Volume flags into one. + IFDEF PLY_CFG_SFX_NoSoftNoHard + PLY_AKM_SE_VolumeSoft = 1 + PLY_AKM_SE_VolumeSoftOrHard = 1 + ENDIF + IFDEF PLY_CFG_SFX_SoftOnly + PLY_AKM_SE_VolumeSoft = 1 + PLY_AKM_SE_VolumeSoftOrHard = 1 + ENDIF +; Mixes the volume (soft/hard) into one. + IFDEF PLY_CFG_UseHardwareSounds + PLY_AKM_SE_VolumeSoftOrHard = 1 + ENDIF +; Mixes the retrig flags into one. + IFDEF PLY_CFG_SFX_HardOnly_Retrig + PLY_AKM_SE_Retrig = 1 + ENDIF + IFDEF PLY_CFG_SFX_SoftAndHard_Retrig + PLY_AKM_SE_Retrig = 1 + ENDIF + + +;Initializes the sound effects. It MUST be called at any times before a first sound effect is triggered. +;It doesn't matter whether the song is playing or not, or if it has been initialized or not. +;IN: HL = Address to the sound effects data. +PLY_AKM_InitSoundEffectsDisarkGenerateExternalLabel: +PLY_AKM_InitSoundEffects: + ld (PLY_AKM_PtSoundEffectTable + PLY_AKM_Offset1b),hl + ret + + +;Pl²ys a sound effect. If a previous one was already playing on the same channel, it is replaced. +;This does not actually plays the sound effect, but programs its playing. +;The music player, when called, will call the PLY_AKM_PlaySoundEffectsStream method below. +;IN: A = Sound effect number (>0!). +; C = The channel where to play the sound effect (0, 1, 2). +; B = Inverted volume (0 = full volume, 16 = no sound). Hardware sounds are also lowered. +PLY_AKM_PlaySoundEffectDisarkGenerateExternalLabel: +PLY_AKM_PlaySoundEffect: + ;Gets the address to the sound effect. + dec a ;The 0th is not encoded. + IFNDEF PLY_AKM_Rom +dknr3 (void): +PLY_AKM_PtSoundEffectTable: ld hl,0 + ELSE + ld hl,(PLY_AKM_PtSoundEffectTable) + ENDIF + ld e,a + ld d,0 + add hl,de + add hl,de + ld e,(hl) + inc hl + ld d,(hl) + ;Reads the header of the sound effect to get the speed. + ld a,(de) + inc de + ex af,af' + + ld a,b + + ;Finds the pointer to the sound effect of the desired channel. + ld hl,PLY_AKM_Channel1_SoundEffectData + ld b,0 + sla c + sla c + sla c + add hl,bc + ld (hl),e + inc hl + ld (hl),d + inc hl + + ;Now stores the inverted volume. + ld (hl),a + inc hl + + ;Resets the current speed, stores the instrument speed. + ld (hl),0 + inc hl + ex af,af' + ld (hl),a + + ret + +;Stops a sound effect. Nothing happens if there was no sound effect. +;IN: A = The channel where to stop the sound effect (0, 1, 2). +PLY_AKM_StopSoundEffectFromChannelDisarkGenerateExternalLabel: +PLY_AKM_StopSoundEffectFromChannel: + ;Puts 0 to the pointer of the sound effect. + add a,a + add a,a + add a,a + ld e,a + ld d,0 + ld hl,PLY_AKM_Channel1_SoundEffectData + add hl,de + ld (hl),d ;0 means "no sound". + inc hl + ld (hl),d + ret + +;Plays the sound effects, if any has been triggered by the user. +;This does not actually send registers to the PSG, it only overwrite the required values of the registers of the player. +;The sound effects initialization method must have been called before! +;As R7 is required, this must be called after the music has been played, but BEFORE the registers are sent to the PSG. +;IN: A = R7. +PLY_AKM_PlaySoundEffectsStream: + ;Shifts the R7 to the left twice, so that bit 2 and 5 only can be set for each track, below. + rla + rla + + ;Plays the sound effects on every track. + ld ix,PLY_AKM_Channel1_SoundEffectData + ld iy,PLY_AKM_Track1_Registers + ld c,a + call PLY_AKM_PSES_Play + ld ix,PLY_AKM_Channel2_SoundEffectData + ld iy,PLY_AKM_Track2_Registers + srl c ;Not RR, to make sure bit 6 is 0 (else, no more keyboard on CPC!). + ;Also, on MSX, bit 6 must be 0. + call PLY_AKM_PSES_Play + ld ix,PLY_AKM_Channel3_SoundEffectData + ld iy,PLY_AKM_Track3_Registers + IFDEF PLY_AKM_HARDWARE_MSX + scf ;On MSX, bit 7 must be 1. + rr c + ELSE + rr c ;On other platforms, we don't care about b7. + ENDIF + call PLY_AKM_PSES_Play + + ld a,c + ld (PLY_AKM_MixerRegister),a + ret + + +;Plays the sound stream from the given pointer to the sound effect. If 0, no sound is played. +;The given R7 is given shift twice to the left, so that this code MUST set/reset the bit 2 (sound), and set/reset bit 5 (noise). +;This code MUST overwrite these bits because sound effects have priority over the music. +;IN: IX = Points on the sound effect pointer. If the sound effect pointer is 0, nothing must be played. +; IY = Points at the beginning of the register structure related to the channel. +; C = R7, shifted twice to the left. +;OUT: The pointed pointer by IX may be modified as the sound advances. +; C = R7, MUST be modified if there is a sound effect. +PLY_AKM_PSES_Play: + ;Reads the pointer pointed by IX. + ld l,(ix + 0) + ld h,(ix + 1) + ld a,l + or h + ret z ;No sound to be played? Returns immediately. + + ;Reads the first byte. What type of sound is it? +PLY_AKM_PSES_ReadFirstByte: + ld a,(hl) + inc hl + ld b,a + rra + jr c,PLY_AKM_PSES_SoftwareOrSoftwareAndHardware + rra + IFDEF PLY_CFG_SFX_HardOnly ;CONFIG SPECIFIC + jr c,PLY_AKM_PSES_HardwareOnly + ENDIF ;PLY_CFG_SFX_HardOnly + + ;No software, no hardware, or end/loop. + ;------------------------------------------- + ;End or loop? + rra + IFDEF PLY_CFG_SFX_NoSoftNoHard ;CONFIG SPECIFIC. If not present, the jump is not needed, the method is just below. + jr c,PLY_AKM_PSES_S_EndOrLoop + + ;No software, no hardware. + ;------------------------------------------- + ;Gets the volume. + call PLY_AKM_PSES_ManageVolumeFromA_Filter4Bits + + ;Noise? + IFDEF PLY_CFG_SFX_NoSoftNoHard_Noise ;CONFIG SPECIFIC + rl b + call PLY_AKM_PSES_ReadNoiseIfNeededAndOpenOrCloseNoiseChannel + ELSE + set 5,c ;No noise in compilation, so stops the noise. + ENDIF ;PLY_CFG_SFX_NoSoftNoHard_Noise + + ;Cuts the sound. + set 2,c + + jr PLY_AKM_PSES_SavePointerAndExit + ENDIF ;PLY_CFG_SFX_NoSoftNoHard + + ;**Warning!** Do not put any instruction between EndOrLoop and NoSoftNoHard. + +PLY_AKM_PSES_S_EndOrLoop: + IFDEF PLY_CFG_SFX_LoopTo ;CONFIG SPECIFIC. If no "loop to", the sounds always end, no need to test. + ;Is it an end? + rra + jr c,PLY_AKM_PSES_S_Loop + ENDIF ;PLY_CFG_SFX_LoopTo + ;End of the sound. Marks the sound pointer with 0, meaning "no sound". + xor a + ld (ix + 0),a + ld (ix + 1),a + ret + IFDEF PLY_CFG_SFX_LoopTo ;CONFIG SPECIFIC. +PLY_AKM_PSES_S_Loop: + ;Loops. Reads the pointer and directly uses it. + ld a,(hl) + inc hl + ld h,(hl) + ld l,a + jr PLY_AKM_PSES_ReadFirstByte + ENDIF ;PLY_CFG_SFX_LoopTo + + +;Saves HL into IX, and exits. This must be called at the end of each Cell. +;If the speed has not been reached, it is not saved. +PLY_AKM_PSES_SavePointerAndExit: + ;Speed reached? + ld a,(ix + PLY_AKM_SoundEffectData_OffsetCurrentStep) + cp (ix + PLY_AKM_SoundEffectData_OffsetSpeed) + jr c,PLY_AKM_PSES_NotReached + ;The speed has been reached, so resets it and saves the pointer to the next cell to read. + ld (ix + PLY_AKM_SoundEffectData_OffsetCurrentStep),0 + ld (ix + 0),l + ld (ix + 1),h + ret +PLY_AKM_PSES_NotReached: + ;Speed not reached. Increases it, that's all. The same cell will be read next time. + inc (ix + PLY_AKM_SoundEffectData_OffsetCurrentStep) + ret + + IFDEF PLY_CFG_SFX_HardOnly ;CONFIG SPECIFIC + ;Hardware only. + ;------------------------------------------- +PLY_AKM_PSES_HardwareOnly: + ;Calls the shared code that manages everything. + call PLY_AKM_PSES_Shared_ReadRetrigHardwareEnvPeriodNoise + ;Cuts the sound. + set 2,c + + jr PLY_AKM_PSES_SavePointerAndExit + ENDIF ;PLY_CFG_SFX_HardOnly + + + +PLY_AKM_PSES_SoftwareOrSoftwareAndHardware: + ;Software only? + rra + IFDEF PLY_CFG_SFX_SoftAndHard ;CONFIG SPECIFIC + jr c,PLY_AKM_PSES_SoftwareAndHardware + ENDIF ;PLY_CFG_SFX_SoftAndHard + + ;Software. + ;------------------------------------------- + IFDEF PLY_CFG_SFX_SoftOnly ;CONFIG SPECIFIC + ;Volume. + call PLY_AKM_PSES_ManageVolumeFromA_Filter4Bits + + ;Noise? + IFDEF PLY_CFG_SFX_SoftOnly_Noise ;CONFIG SPECIFIC + rl b + call PLY_AKM_PSES_ReadNoiseIfNeededAndOpenOrCloseNoiseChannel + ELSE + set 5,c ;No noise in compilation, so stops the noise. + ENDIF ;PLY_CFG_SFX_SoftOnly_Noise + + ;Opens the "sound" channel. + res 2,c + + ;Reads the software period. + call PLY_AKM_PSES_ReadSoftwarePeriod + + jr PLY_AKM_PSES_SavePointerAndExit + ENDIF ;PLY_CFG_SFX_SoftOnly + + + ;Software and Hardware. + ;------------------------------------------- + IFDEF PLY_AKM_SE_HardwareSounds ;CONFIG SPECIFIC +PLY_AKM_PSES_SoftwareAndHardware: + ;Calls the shared code that manages everything. + call PLY_AKM_PSES_Shared_ReadRetrigHardwareEnvPeriodNoise + + ;Reads the software period. + call PLY_AKM_PSES_ReadSoftwarePeriod + + ;Opens the sound. + res 2,c + + jr PLY_AKM_PSES_SavePointerAndExit + ENDIF ;PLY_AKM_SE_HardwareSounds + + + IFDEF PLY_CFG_UseHardwareSounds ;CONFIG SPECIFIC + ;Shared code used by the "hardware only" and "software and hardware" part. + ;Reads the Retrig flag, the Hardware Envelope, the possible noise, the hardware period, + ;and sets the volume to 16. The R7 sound channel is NOT modified. +PLY_AKM_PSES_Shared_ReadRetrigHardwareEnvPeriodNoise: + ;Retrig? + rra + IFDEF PLY_AKM_SE_Retrig ;CONFIG SPECIFIC + jr nc,PLY_AKM_PSES_H_AfterRetrig + ld d,a + ld a,255 + ld (PLY_AKM_SetReg13Old + PLY_AKM_Offset1b),a + ld a,d +PLY_AKM_PSES_H_AfterRetrig: + ENDIF ;PLY_AKM_SE_Retrig + + ;The hardware envelope can be set (8-15). + and %111 + add a,8 + ld (PLY_AKM_SetReg13 + PLY_AKM_Offset1b),a + + ;Noise? + IFDEF PLY_AKM_SE_HardwareNoise ;CONFIG SPECIFIC. B not needed after, we can put it in the condition too. + rl b + call PLY_AKM_PSES_ReadNoiseIfNeededAndOpenOrCloseNoiseChannel + ELSE + set 5,c ;No noise in compilation, so stops the noise. + ENDIF ;PLY_AKM_SE_HardwareNoise + + ;Reads the hardware period. + call PLY_AKM_PSES_ReadHardwarePeriod + + ;Sets the volume to "hardware". It still may be decreased. + ld a,16 + jp PLY_AKM_PSES_ManageVolumeFromA_Hard + ENDIF ;PLY_CFG_UseHardwareSounds + + + IFDEF PLY_AKM_SE_Noise +;If asked to, reads the noise pointed by HL, increases HL, and opens the noise channel. If no noise, closes the noise channel. +;IN: Carry = true if noise to read. False if no noise, so the noise channel must be closed. +; HL = the data. +PLY_AKM_PSES_ReadNoiseIfNeededAndOpenOrCloseNoiseChannel: + jr c,PLY_AKM_PSES_ReadNoiseAndOpenNoiseChannel_OpenNoise + ;No noise, so closes the noise channel. + set 5,c + ret +PLY_AKM_PSES_ReadNoiseAndOpenNoiseChannel_OpenNoise: + ;Reads the noise. + ld a,(hl) + ld (PLY_AKM_NoiseRegister),a + inc hl + + ;Opens noise channel. + res 5,c + ret + ENDIF ;PLY_AKM_SE_Noise + + IFDEF PLY_CFG_UseHardwareSounds ;CONFIG SPECIFIC +;Reads the hardware period from HL and sets the R11/R12 registers. HL is incremented of 2. +PLY_AKM_PSES_ReadHardwarePeriod: + ld a,(hl) + ld (PLY_AKM_Reg11),a + inc hl + ld a,(hl) + ld (PLY_AKM_Reg12),a + inc hl + ret + ENDIF ;PLY_CFG_UseHardwareSounds + +;Reads the software period from HL and sets the period registers thanks to IY. HL is incremented of 2. +PLY_AKM_PSES_ReadSoftwarePeriod: + ld a,(hl) + ld (iy + PLY_AKM_Registers_OffsetSoftwarePeriodLSB),a + inc hl + ld a,(hl) + ld (iy + PLY_AKM_Registers_OffsetSoftwarePeriodMSB),a + inc hl + ret + + IFDEF PLY_AKM_SE_VolumeSoft ;CONFIG SPECIFIC +;Reads the volume in A, decreases it from the inverted volume of the channel, and sets the volume via IY. +;IN: A = volume, from 0 to 15 (no hardware envelope). +PLY_AKM_PSES_ManageVolumeFromA_Filter4Bits: + and %1111 + ENDIF ;PLY_AKM_SE_VolumeSoft + IFDEF PLY_AKM_SE_VolumeSoftOrHard ;CONFIG SPECIFIC +;After the filtering. Useful for hardware sound (volume has been forced to 16). +PLY_AKM_PSES_ManageVolumeFromA_Hard: + ;Decreases the volume, checks the limit. + sub (ix + PLY_AKM_SoundEffectData_OffsetInvertedVolume) + jr nc,PLY_AKM_PSES_MVFA_NoOverflow + xor a +PLY_AKM_PSES_MVFA_NoOverflow: + ld (iy + PLY_AKM_Registers_OffsetVolume),a + ret + ENDIF ;PLY_AKM_SE_VolumeSoftOrHard + + + ;The data for RAM player. For ROM player, it is declared in the main player. + IFNDEF PLY_AKM_Rom +;The data of the Channels MUST be consecutive. +PLY_AKM_Channel1_SoundEffectData: +dkws (void): + dw 0 ;Points to the sound effect for the track 1, or 0 if not playing. +PLY_AKM_Channel1_SoundEffectInvertedVolume: +dkwe (void): +dkbs (void): + db 0 ;Inverted volume. +PLY_AKM_Channel1_SoundEffectCurrentStep: + db 0 ;Current step (>=0). +PLY_AKM_Channel1_SoundEffectSpeed: + db 0 ;Speed (>=0). + ds 3,0 ;Padding. +dkbe (void): +PLY_AKM_Channel_SoundEffectDataSize: equ $ - PLY_AKM_Channel1_SoundEffectData + +dkbs (void): +PLY_AKM_Channel2_SoundEffectData: + ds PLY_AKM_Channel_SoundEffectDataSize, 0 +PLY_AKM_Channel3_SoundEffectData: + ds PLY_AKM_Channel_SoundEffectDataSize, 0 +dkbe (void): + + ;Checks that the pointers are consecutive. + assert (PLY_AKM_Channel1_SoundEffectData + PLY_AKM_Channel_SoundEffectDataSize) == PLY_AKM_Channel2_SoundEffectData + assert (PLY_AKM_Channel2_SoundEffectData + PLY_AKM_Channel_SoundEffectDataSize) == PLY_AKM_Channel3_SoundEffectData + + ENDIF ;PLY_AKM_Rom + +;Offset from the beginning of the data, to reach the inverted volume. +PLY_AKM_SoundEffectData_OffsetInvertedVolume: equ PLY_AKM_Channel1_SoundEffectInvertedVolume - PLY_AKM_Channel1_SoundEffectData +PLY_AKM_SoundEffectData_OffsetCurrentStep: equ PLY_AKM_Channel1_SoundEffectCurrentStep - PLY_AKM_Channel1_SoundEffectData +PLY_AKM_SoundEffectData_OffsetSpeed: equ PLY_AKM_Channel1_SoundEffectSpeed - PLY_AKM_Channel1_SoundEffectData
\ No newline at end of file diff --git a/src/mplayer/akm/README.md b/src/mplayer/akm/README.md new file mode 100644 index 0000000..4b4fde0 --- /dev/null +++ b/src/mplayer/akm/README.md @@ -0,0 +1,10 @@ +# Arkos 2 AKM Player + +Configured to use ROM and default options expecting a 222 bytes buffer starting +at 0xc000. + +## Local changes + + - A new function `PLY_AKM_IsDoundEffectOn` was added; this is used by the + priority based sound effects. + diff --git a/src/mplayer/akm/akm_ubox.asm b/src/mplayer/akm/akm_ubox.asm new file mode 100644 index 0000000..cbbf8af --- /dev/null +++ b/src/mplayer/akm/akm_ubox.asm @@ -0,0 +1,27 @@ + +; these are common +PLY_AKM_HARDWARE_MSX = 1 +PLY_AKM_MANAGE_SOUND_EFFECTS = 1 +PLY_AKM_Rom = 1 +PLY_AKM_ROM_Buffer = #c000 + +include "PlayerAkm.asm" + +; IN: L = channel +; OUT: L = 0 if is not on +PLY_AKM_IsSoundEffectOnDisarkGenerateExternalLabel: +PLY_AKM_IsSoundEffectOn: + ld a,l + add a,a + add a,a + add a,a + ld c,a + ld b,0 + ld hl,PLY_AKM_Channel1_SoundEffectData + add hl,bc + ld a,(hl) + inc hl + or (hl) + ld l,a + ret + |