From 2fbdf974338bde8576efdae40a819a76b2391033 Mon Sep 17 00:00:00 2001 From: "Juan J. Martinez" Date: Sun, 5 Nov 2023 11:22:55 +0000 Subject: Initial import of the open source release --- lib/plw/player/Makefile | 11 + lib/plw/player/PlayerLightWeight.asm | 1694 +++++++++++++++++++++ lib/plw/player/PlayerLightWeight_SoundEffects.asm | 458 ++++++ lib/plw/player/player.asm | 43 + 4 files changed, 2206 insertions(+) create mode 100644 lib/plw/player/Makefile create mode 100644 lib/plw/player/PlayerLightWeight.asm create mode 100644 lib/plw/player/PlayerLightWeight_SoundEffects.asm create mode 100644 lib/plw/player/player.asm (limited to 'lib/plw/player') diff --git a/lib/plw/player/Makefile b/lib/plw/player/Makefile new file mode 100644 index 0000000..53e0b23 --- /dev/null +++ b/lib/plw/player/Makefile @@ -0,0 +1,11 @@ +SOURCES=$(wildcard *.asm) + +all: plw_player.h + +plw_player.h: $(SOURCES) + rasm player.asm -ob plw_player.bin + bin2h.py plw_player.bin plw_player > plw_player.h + +.PHONY: clean all +clean: + rm -f *.bin plw_player.h diff --git a/lib/plw/player/PlayerLightWeight.asm b/lib/plw/player/PlayerLightWeight.asm new file mode 100644 index 0000000..5da830c --- /dev/null +++ b/lib/plw/player/PlayerLightWeight.asm @@ -0,0 +1,1694 @@ +; Arkos Tracker 2 Lightweight player (format V1 (used by AT2 since alpha4)). + +; ** This player has been superseded by the AKM format, more compact but also more powerful. Please use it instead. ** + +; 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 generic player, but much simpler and using only the most used features, so that the music and players are both +; lightweight. The player supports sound effects. + +; 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 may reach 35 scanlines on a CPC, plus some few more if you are using sound effects. + +; 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 harware: +; --------------- +; 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_LW_HARDWARE_CPC = 1 +; PLY_LW_HARDWARE_MSX = 1 +; PLY_LW_HARDWARE_SPECTRUM = 1 +; PLY_LW_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_LW_MANAGE_SOUND_EFFECTS to enable it: +; PLY_LW_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 +; ---------------------- +; No ROM player is available for this player. I suggest you try the AKM player, which is more powerful and more compact (albeit a bit slower). +; +; ------------------------------------------------------- + +PLY_LW_Start: + + ;Checks the hardware. Only one must be selected. +PLY_LW_HardwareCounter = 0 + IFDEF PLY_LW_HARDWARE_CPC + PLY_LW_HardwareCounter = PLY_LW_HardwareCounter + 1 + ENDIF + IFDEF PLY_LW_HARDWARE_MSX + PLY_LW_HardwareCounter = PLY_LW_HardwareCounter + 1 + PLY_LW_HARDWARE_SPECTRUM_OR_MSX = 1 + ENDIF + IFDEF PLY_LW_HARDWARE_SPECTRUM + PLY_LW_HardwareCounter = PLY_LW_HardwareCounter + 1 + PLY_LW_HARDWARE_SPECTRUM_OR_PENTAGON = 1 + PLY_LW_HARDWARE_SPECTRUM_OR_MSX = 1 + ENDIF + IFDEF PLY_LW_HARDWARE_PENTAGON + PLY_LW_HardwareCounter = PLY_LW_HardwareCounter + 1 + PLY_LW_HARDWARE_SPECTRUM_OR_PENTAGON = 1 + ENDIF + IF PLY_LW_HARDWARECounter > 1 + FAIL 'Only one hardware must be selected!' + ENDIF + ;By default, selects the Amstrad CPC. + IF PLY_LW_HARDWARECounter == 0 + PLY_LW_HARDWARE_CPC = 1 + ENDIF + +;PLY_LW_USE_HOOKS: equ 1 ;Use hooks for external calls? 0 if the Init/Play methods are directly called, will save a few bytes. +;PLY_LW_STOP_SOUNDS: equ 1 ;1 to have the "stop sounds" code. Set it to 0 if you never plan on stopping your music. + + ;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_SoftwareArpeggio = 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_PitchTable = 1 + PLY_CFG_UseEffect_PitchUp = 1 + PLY_CFG_UseEffect_PitchDown = 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_LW_PitchInInstrument = 1 + ENDIF + IFDEF PLY_CFG_SoftToHard_SoftwarePitch + PLY_LW_PitchInInstrument = 1 + PLY_LW_PitchInHardwareInstrument = 1 + ENDIF + IFDEF PLY_CFG_SoftAndHard_SoftwarePitch + PLY_LW_PitchInInstrument = 1 + PLY_LW_PitchInHardwareInstrument = 1 + ENDIF + ;A flag for Arpeggios in Instrument, both in software and hardware. + IFDEF PLY_CFG_SoftOnly_SoftwareArpeggio + PLY_LW_ArpeggioInSoftwareOrHardwareInstrument = 1 + ENDIF + IFDEF PLY_CFG_SoftToHard_SoftwareArpeggio + PLY_LW_ArpeggioInSoftwareOrHardwareInstrument = 1 + PLY_LW_ArpeggioInHardwareInstrument = 1 + ENDIF + IFDEF PLY_CFG_SoftAndHard_SoftwareArpeggio + PLY_LW_ArpeggioInSoftwareOrHardwareInstrument = 1 + PLY_LW_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_LW_USE_Noise = 1 + ENDIF + IFDEF PLY_CFG_SoftOnly_Noise + PLY_LW_USE_Noise = 1 + ENDIF + ;The noise is managed? Then the noise register access must be compiled. + IFDEF PLY_LW_USE_Noise + PLY_LW_USE_NoiseRegister = 1 + ENDIF + + ;Mixing Pitch up/down effects. + IFDEF PLY_CFG_UseEffect_PitchUp + PLY_LW_USE_EffectPitchUpDown = 1 + ENDIF + IFDEF PLY_CFG_UseEffect_PitchDown + PLY_LW_USE_EffectPitchUpDown = 1 + ENDIF + ;Volume and Pitch up/down dual effects (if one exists, the other one too). + IFDEF PLY_CFG_UseEffect_SetVolume + PLY_LW_USE_Volume_And_PitchUpDown_Effects = 1 + PLY_LW_USE_EffectPitchUpDown = 1 + ENDIF + IFDEF PLY_LW_USE_EffectPitchUpDown + PLY_LW_USE_Volume_And_PitchUpDown_Effects = 1 + PLY_CFG_UseEffect_SetVolume = 1 + ENDIF + ;Volume and Arpeggio Table dual effect (if one exists, the other one too). + IFDEF PLY_CFG_UseEffect_SetVolume + PLY_LW_USE_Volume_And_ArpeggioTable_Effects = 1 + PLY_CFG_UseEffect_ArpeggioTable = 1 + ENDIF + IFDEF PLY_CFG_UseEffect_ArpeggioTable + PLY_LW_USE_Volume_And_ArpeggioTable_Effects = 1 + PLY_CFG_UseEffect_SetVolume = 1 + ENDIF + ;Reset and Arpeggio Table dual effect (if one exists, the other one too). + IFDEF PLY_CFG_UseEffect_Reset + PLY_LW_USE_Reset_And_ArpeggioTable_Effects = 1 + PLY_CFG_UseEffect_ArpeggioTable = 1 + ENDIF + IFDEF PLY_CFG_UseEffect_ArpeggioTable + PLY_LW_USE_Reset_And_ArpeggioTable_Effects = 1 + PLY_CFG_UseEffect_Reset = 1 + ENDIF + + + ;Disark macro: Word region Start. + disarkCounter = 0 + IFNDEF dkws + MACRO dkws +PLY_LW_DisarkWordRegionStart_{disarkCounter} + ENDM + ENDIF + ;Disark macro: Word region End. + IFNDEF dkwe + MACRO dkwe +PLY_LW_DisarkWordRegionEnd_{disarkCounter}: + disarkCounter = disarkCounter + 1 + ENDM + ENDIF + + ;Disark macro: Pointer region Start. + disarkCounter = 0 + IFNDEF dkps + MACRO dkps +PLY_LW_DisarkPointerRegionStart_{disarkCounter} + ENDM + ENDIF + ;Disark macro: Pointer region End. + IFNDEF dkpe + MACRO dkpe +PLY_LW_DisarkPointerRegionEnd_{disarkCounter}: + disarkCounter = disarkCounter + 1 + ENDM + ENDIF + + ;Disark macro: Byte region Start. + disarkCounter = 0 + IFNDEF dkbs + MACRO dkbs +PLY_LW_DisarkByteRegionStart_{disarkCounter} + ENDM + ENDIF + ;Disark macro: Byte region End. + IFNDEF dkbe + MACRO dkbe +PLY_LW_DisarkByteRegionEnd_{disarkCounter}: + disarkCounter = disarkCounter + 1 + ENDM + ENDIF + + ;Disark macro: Force "No Reference Area" for 3 bytes (ld hl,xxxx). + IFNDEF dknr3 + MACRO dknr3 +PLY_LW_DisarkForceNonReferenceDuring3_{disarkCounter}: + disarkCounter = disarkCounter + 1 + ENDM + ENDIF + + + + ;Hooks for external calls. Can be removed if not needed. + if PLY_LW_USE_HOOKS + assert PLY_LW_Start == $ ;Makes sure no extra byte were inserted before the hooks. + jp PLY_LW_Init ;Player + 0. + jp PLY_LW_Play ;Player + 3. + if PLY_LW_STOP_SOUNDS + jp PLY_LW_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_LW_MANAGE_SOUND_EFFECTS + include "PlayerLightWeight_SoundEffects.asm" + ENDIF + ;[[INSERT_SOUND_EFFECT_SOURCE]] ;A tag for test units. Don't touch or you're dead. + + +;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_LW_InitDisarkGenerateExternalLabel: +PLY_LW_Init: + ;Reads the Song data. + ;Skips the tag and format number. +dknr3: ld de,5 + add hl,de + + ;Reads the pointers to the various index tables. + ld de,PLY_LW_PtInstruments + 1 + ldi + ldi + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC + IFDEF PLY_CFG_UseEffect_ArpeggioTable ;CONFIG SPECIFIC + ld de,PLY_LW_PtArpeggios + 1 + ldi + ldi + ELSE + inc hl + inc hl + ENDIF ;PLY_CFG_UseEffect_ArpeggioTable + IFDEF PLY_CFG_UseEffect_PitchTable ;CONFIG SPECIFIC + ld de,PLY_LW_PtPitches + 1 + ldi + ldi + ELSE + inc hl + inc hl + ENDIF ;PLY_CFG_UseEffect_PitchTable + ELSE +dknr3: 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. + 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 Subsong. + ld a,(de) ;Gets the speed. + inc de + ld (PLY_LW_Linker + 1),de + ld (PLY_LW_Speed + 1),a + ;Forces a new line. + dec a + ld (PLY_LW_TickCounter + 1),a + + ;Can be removed if there is no need to reset the song. + xor a + ld (PLY_LW_PatternRemainingHeight + 1),a + + ;A big LDIR to erase all the data blocks. Optimization: can be removed if there is no need to reset the song. + ld hl,PLY_LW_Track1_Data + ld de,PLY_LW_Track1_Data + 1 +dknr3: ld bc,PLY_LW_Track3_Data_End - PLY_LW_Track3_Data - 1 + ld (hl),0 + ldir + + ;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_LW_PtInstruments + 1) + ld e,(hl) + inc hl + ld d,(hl) + inc de ;Skips the header. + ld (PLY_LW_Track1_PtInstrument),de + ld (PLY_LW_Track2_PtInstrument),de + ld (PLY_LW_Track3_PtInstrument),de + ret + + +;Cuts the channels, stopping all sounds. + if PLY_LW_STOP_SOUNDS +PLY_LW_StopDisarkGenerateExternalLabel: +PLY_LW_Stop: + ld (PLY_LW_SaveSP + 1),sp + + xor a + ld (PLY_LW_Track1_Volume),a + ld (PLY_LW_Track2_Volume),a + ld (PLY_LW_Track3_Volume),a + IFDEF PLY_LW_HARDWARE_MSX + ld a,%10111111 ;Bit 7/6 must be 10 on MSX! + ELSE + ld a,%00111111 ;On CPC, bit 6 must be 0! Other platforms don't care. + ENDIF + ld (PLY_LW_MixerRegister),a + jp PLY_LW_SendPsg + endif ;PLY_LW_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_LW_PlayDisarkGenerateExternalLabel: +PLY_LW_Play: + ld (PLY_LW_SaveSP + 1),sp + + ;Reads a new line? +PLY_LW_TickCounter: ld a,0 + inc a +PLY_LW_Speed: cp 1 ;Speed (>0). + jp nz,PLY_LW_TickCounterManaged + + ;A new line must be read. But have we reached the end of the Pattern? +PLY_LW_PatternRemainingHeight: ld a,0 ;Height. If 0, end of the pattern. + sub 1 + jr c,PLY_LW_Linker + ;Pattern not ended. No need to read the Linker. + ld (PLY_LW_PatternRemainingHeight + 1),a + jr PLY_LW_ReadLine + + ;New pattern. Reads the Linker. +dknr3: +PLY_LW_Linker: ld hl,0 +PLY_LW_LinkerPostPt: + ;Resets the possible empty cell counter of each Track. + xor a + ld (PLY_LW_Track1_WaitEmptyCell),a + ld (PLY_LW_Track2_WaitEmptyCell),a + ld (PLY_LW_Track3_WaitEmptyCell),a + + ;Reads the state byte of the pattern. + ld a,(hl) + inc hl + rra + jr c,PLY_LW_LinkerNotEndOfSongOk + ;End of song. + ld a,(hl) ;Reads where to loop in the Linker. + inc hl + ld h,(hl) + ld l,a + jr PLY_LW_LinkerPostPt + +PLY_LW_LinkerNotEndOfSongOk: + rra + ld b,a + IFDEF PLY_CFG_UseSpeedTracks ;CONFIG SPECIFIC + ;New speed? + jr nc,PLY_LW_LinkerAfterSpeed + ld a,(hl) + inc hl + ld (PLY_LW_Speed + 1),a +PLY_LW_LinkerAfterSpeed: + ENDIF ;PLY_CFG_UseSpeedTracks + + ;New height? + rr b + jr nc,PLY_LW_LinkerUsePreviousHeight + ld a,(hl) + inc hl + ld (PLY_LW_LinkerPreviousRemainingHeight + 1),a + jr PLY_LW_LinkerSetRemainingHeight + ;The same height is used. It was stored before. +PLY_LW_LinkerUsePreviousHeight: +PLY_LW_LinkerPreviousRemainingHeight: ld a,0 +PLY_LW_LinkerSetRemainingHeight: + ld (PLY_LW_PatternRemainingHeight + 1),a + + ;New transpositions? + rr b + IFDEF PLY_CFG_UseTranspositions ;CONFIG SPECIFIC + jr nc,PLY_LW_LinkerAfterNewTranspositions + ;New transpositions. + ld de,PLY_LW_Track1_Transposition + ldi + ld de,PLY_LW_Track2_Transposition + ldi + ld de,PLY_LW_Track3_Transposition + ldi +PLY_LW_LinkerAfterNewTranspositions: + ENDIF ;PLY_CFG_UseTranspositions + + ;Reads the 3 track pointers. + ld de,PLY_LW_Track1_PtTrack + ldi + ldi + ld de,PLY_LW_Track2_PtTrack + ldi + ldi + ld de,PLY_LW_Track3_PtTrack + ldi + ldi + ld (PLY_LW_Linker + 1),hl + +;Reads the Tracks. +;--------------------------------- +PLY_LW_ReadLine: +dknr3: +PLY_LW_PtInstruments: ld de,0 + exx + ld ix,PLY_LW_Track1_Data + call PLY_LW_ReadTrack + ld ix,PLY_LW_Track2_Data + call PLY_LW_ReadTrack + ld ix,PLY_LW_Track3_Data + call PLY_LW_ReadTrack + + xor a +PLY_LW_TickCounterManaged: + ld (PLY_LW_TickCounter + 1),a + + + +;Plays the sound stream. +;--------------------------------- + ld de,PLY_LW_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_LW_Track1_Data + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC + call PLY_LW_ManageEffects + ENDIF ;PLY_CFG_UseEffects + ld iy,PLY_LW_Track1_Registers + call PLY_LW_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_LW_Track2_Data + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC + call PLY_LW_ManageEffects + ENDIF ;PLY_CFG_UseEffects + ld iy,PLY_LW_Track2_Registers + call PLY_LW_PlaySoundStream + + IFDEF PLY_LW_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_LW_Track3_Data + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC + call PLY_LW_ManageEffects + ENDIF ;PLY_CFG_UseEffects + ld iy,PLY_LW_Track3_Registers + call PLY_LW_PlaySoundStream + + ld a,c + +;Plays the sound effects, if desired. +;------------------------------------------- + IFDEF PLY_LW_MANAGE_SOUND_EFFECTS + call PLY_LW_PlaySoundEffectsStream + ELSE + ld (PLY_LW_MixerRegister),a + ENDIF ;PLY_LW_MANAGE_SOUND_EFFECTS + + + +;Sends the values to the PSG. +;--------------------------------- +PLY_LW_SendPsg: + ld sp,PLY_LW_Registers_RetTable + + IFDEF PLY_LW_HARDWARE_CPC +dknr3: ld bc,#f680 + ld a,#c0 +dknr3: 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_LW_HARDWARE_SPECTRUM_OR_PENTAGON +dknr3: ld de,#bfff +dknr3: ld bc,#fffd + ENDIF + +PLY_LW_SendPsgRegister: + pop hl ;H = value, L = register. +PLY_LW_SendPsgRegisterAfterPop: + + IFDEF PLY_LW_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_LW_HARDWARE_SPECTRUM_OR_PENTAGON + out (c),l ;#fffd + register. + ld b,d + out (c),h ;#bffd + value + ld b,e + ENDIF + + IFDEF PLY_LW_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_LW_SendPsgRegisterR13: + + ;Should the R13 be played? Yes only if different. No "force retrig" is managed by this player. +PLY_LW_SetReg13: ld a,0 +PLY_LW_SetReg13Old: cp 0 + jr z,PLY_LW_SendPsgRegisterEnd + ;Different. R13 must be played. Updates the old R13 value. + ld (PLY_LW_SetReg13Old + 1),a + + ld h,a + ld l,13 + IFDEF PLY_LW_HARDWARE_CPC + ld a,#c0 + ENDIF + ret ;Sends the 13th registers. + ENDIF ;PLY_CFG_UseHardwareSounds + +PLY_LW_SendPsgRegisterEnd: + +dknr3: +PLY_LW_SaveSP: ld sp,0 + ret + + + + + + + + + + +;Reads a Track. +;IN: IX = Data block of the Track. +; DE'= Instrument table. Do not modify! +PLY_LW_ReadTrack: + ;Are there any empty lines to wait? + ld a,(ix + PLY_LW_Data_OffsetWaitEmptyCell) + sub 1 + jr c,PLY_LW_RT_NoEmptyCell + ;Wait! + ld (ix + PLY_LW_Data_OffsetWaitEmptyCell),a + ret + +PLY_LW_RT_NoEmptyCell: + ;Reads the Track pointer. + ld l,(ix + PLY_LW_Data_OffsetPtTrack + 0) + ld h,(ix + PLY_LW_Data_OffsetPtTrack + 1) + ld a,(hl) + inc hl + ld b,a + and %111111 ;Keeps only the note. + sub 60 + jr c,PLY_LW_RT_NoteMaybeEffect + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC + jr z,PLY_LW_RT_ReadEffect ;No note, but effect. + ENDIF ;PLY_CFG_UseEffects + dec a + jr z,PLY_LW_RT_WaitLong + dec a + jr z,PLY_LW_RT_WaitShort + ;63: Escape code for a note, because octave <2 or >5. + ;Reads the note. + ld a,(hl) + inc hl + ;The rest is exactly as the "note maybe effect", as B contains the flag to know about the possible + ;New Instrument and/or Effect?. + jr PLY_LW_RT_NMB_AfterOctaveCompensation + +PLY_LW_RT_NoteMaybeEffect: + ;A is the note from octave 2, and 60 to compensate the sub above. + ;Then adds the transposition. + add a,12 * 2 + 60 +PLY_LW_RT_NMB_AfterOctaveCompensation: + IFDEF PLY_CFG_UseTranspositions ;CONFIG SPECIFIC + add a,(ix + PLY_LW_Data_OffsetTransposition) + ENDIF ;PLY_CFG_UseTranspositions + ld (ix + PLY_LW_Data_OffsetBaseNote),a + + ;New Instrument? + rl b + jr c,PLY_LW_RT_NME_NewInstrument + ;Same Instrument. Retrieves the address previously stored. + ld a,(ix + PLY_LW_Data_OffsetPtBaseInstrument + 0) + ld (ix + PLY_LW_Data_OffsetPtInstrument + 0),a + ld a,(ix + PLY_LW_Data_OffsetPtBaseInstrument + 1) + ld (ix + PLY_LW_Data_OffsetPtInstrument + 1),a + jr PLY_LW_RT_NME_AfterInstrument + +PLY_LW_RT_NME_NewInstrument: + ;New Instrument, reads it. + ld a,(hl) + inc hl + exx + ;Gets the address of the Instrument. + ld l,a ;No need to *2, it is already encoded like that. + ld h,0 + add hl,de ;Adds to the Instrument Table. + ld c,(hl) + inc hl + ld b,(hl) + ;Reads the header of the Instrument. + ld a,(bc) ;Speed. + ld (ix + PLY_LW_Data_OffsetInstrumentSpeed),a + inc bc + ;Stores the pointer on the data of the Instrument. + ld (ix + PLY_LW_Data_OffsetPtInstrument + 0),c + ld (ix + PLY_LW_Data_OffsetPtInstrument + 1),b + ld (ix + PLY_LW_Data_OffsetPtBaseInstrument + 0),c ;Useful to store the base Instrument address to retrieve it when + ld (ix + PLY_LW_Data_OffsetPtBaseInstrument + 1),b ;there is a new instrument, without providing its number (optimization). + exx +PLY_LW_RT_NME_AfterInstrument: + ;Resets the step on the Instrument. + ld (ix + PLY_LW_Data_OffsetInstrumentCurrentStep),0 + + ;Resets the Track pitch. + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC + xor a + IFDEF PLY_LW_USE_EffectPitchUpDown ;CONFIG SPECIFIC + ld (ix + PLY_LW_Data_OffsetIsPitchUpDownUsed),a + ld (ix + PLY_LW_Data_OffsetTrackPitchInteger + 0),a + ld (ix + PLY_LW_Data_OffsetTrackPitchInteger + 1),a + ENDIF ;PLY_LW_USE_EffectPitchUpDown + ;ld (ix + PLY_LW_Data_OffsetTrackPitchDecimal),a ;Shouldn't be needed, the difference shouldn't be noticeable. + ;Resets the offset on Arpeggio and Pitch tables. + IFDEF PLY_CFG_UseEffect_ArpeggioTable ;CONFIG SPECIFIC + ld (ix + PLY_LW_Data_OffsetPtArpeggioOffset),a + ENDIF ;PLY_CFG_UseEffect_ArpeggioTable + IFDEF PLY_CFG_UseEffect_PitchTable ;CONFIG SPECIFIC + ld (ix + PLY_LW_Data_OffsetPtPitchOffset),a + ENDIF ;PLY_CFG_UseEffect_PitchTable + + ENDIF ;PLY_CFG_UseEffects + + ;Any effect? If no, stop. + rl b + jr nc,PLY_LW_RT_CellRead + ;Effect present. + ;jr PLY_LW_RT_ReadEffect + + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC +PLY_LW_RT_ReadEffect: + ;Reads effect number and possible data. + ld a,(hl) + inc hl + ld b,a + exx + rra + rra + rra + rra + and %1110 + ld iy,PLY_LW_EffectTable + ld c,a + ld b,0 + add iy,bc + exx + jp (iy) + ENDIF ;PLY_CFG_UseEffects + +PLY_LW_RT_WaitLong: + ;A 8-bit byte is encoded just after. + ld a,(hl) + inc hl + ld (ix + PLY_LW_Data_OffsetWaitEmptyCell),a + jr PLY_LW_RT_CellRead +PLY_LW_RT_WaitShort: + ;Only a 2-bit value is encoded. + ld a,b + rla ;Transfers the bit 7/6 to 1/0. + rla + rla + and %11 + ;inc a + ld (ix + PLY_LW_Data_OffsetWaitEmptyCell),a + ;jr PLY_LW_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_LW_RT_CellRead: + ld (ix + PLY_LW_Data_OffsetPtTrack + 0),l + ld (ix + PLY_LW_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! + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC +PLY_LW_ManageEffects: + IFDEF PLY_LW_USE_EffectPitchUpDown ;CONFIG SPECIFIC + ;Pitch up/down used? + ld a,(ix + PLY_LW_Data_OffsetIsPitchUpDownUsed) + or a + jr z,PLY_LW_ME_PitchUpDownFinished + + ;Adds the LSB of integer part and decimal part, using one 16 bits operation. + ld l,(ix + PLY_LW_Data_OffsetTrackPitchDecimal) + ld h,(ix + PLY_LW_Data_OffsetTrackPitchInteger + 0) + + ld e,(ix + PLY_LW_Data_OffsetTrackPitchSpeed + 0) + ld d,(ix + PLY_LW_Data_OffsetTrackPitchSpeed + 1) + + ld a,(ix + PLY_LW_Data_OffsetTrackPitchInteger + 1) + + ;Negative pitch? + bit 7,d + jr nz,PLY_LW_ME_PitchUpDown_NegativeSpeed + +PLY_LW_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_LW_ME_PitchUpDown_Save +PLY_LW_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_LW_ME_PitchUpDown_Save: + ld (ix + PLY_LW_Data_OffsetTrackPitchInteger + 1),a + + ld (ix + PLY_LW_Data_OffsetTrackPitchDecimal),l + ld (ix + PLY_LW_Data_OffsetTrackPitchInteger + 0),h + +PLY_LW_ME_PitchUpDownFinished: + ENDIF ;PLY_LW_USE_EffectPitchUpDown + + + ;Manages the Arpeggio Table effect, if any. + IFDEF PLY_CFG_UseEffect_ArpeggioTable ;CONFIG SPECIFIC + ld a,(ix + PLY_LW_Data_OffsetIsArpeggioTableUsed) + or a + jr z,PLY_LW_ME_ArpeggioTableFinished + ;Reads the Arpeggio Table. Adds the Arpeggio base address to an offset. + ld e,(ix + PLY_LW_Data_OffsetPtArpeggioTable + 0) + ld d,(ix + PLY_LW_Data_OffsetPtArpeggioTable + 1) + ld l,(ix + PLY_LW_Data_OffsetPtArpeggioOffset) +PLY_LW_ME_ArpeggioTableReadAgain: ld h,0 + add hl,de + ld a,(hl) + ;End of the Arpeggio? + sra a + jr nc,PLY_LW_ME_ArpeggioTableEndNotReached + ;End of the Arpeggio. The loop offset is now in A. + ld l,a ;And read the next value! + ld (ix + PLY_LW_Data_OffsetPtArpeggioOffset),a + jr PLY_LW_ME_ArpeggioTableReadAgain + +PLY_LW_ME_ArpeggioTableEndNotReached: + ;Not the end. A = arpeggio note. + ld (ix + PLY_LW_Data_OffsetCurrentArpeggioValue),a + ;Increases the offset for next time. + inc (ix + PLY_LW_Data_OffsetPtArpeggioOffset) +PLY_LW_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_LW_Data_OffsetIsPitchTableUsed) + or a + ret z + ;Reads the Pitch Table. Adds the Pitch base address to an offset. + ld e,(ix + PLY_LW_Data_OffsetPtPitchTable + 0) + ld d,(ix + PLY_LW_Data_OffsetPtPitchTable + 1) + ld l,(ix + PLY_LW_Data_OffsetPtPitchOffset) +PLY_LW_ME_PitchTableReadAgain: ld h,0 + add hl,de + ld a,(hl) + ;End of the Pitch? + sra a + jr nc,PLY_LW_ME_PitchTableEndNotReached + ;End of the Pitch. The loop offset is now in A. + ld l,a ;And read the next value! + ld (ix + PLY_LW_Data_OffsetPtPitchOffset),a + jr PLY_LW_ME_PitchTableReadAgain + +PLY_LW_ME_PitchTableEndNotReached: + ;Not the end. A = pitch note. It is converted to 16 bits. + ld h,0 + or a + jp p,PLY_LW_ME_PitchTableEndNotReached_Positive + dec h +PLY_LW_ME_PitchTableEndNotReached_Positive: + ld (ix + PLY_LW_Data_OffsetCurrentPitchTableValue + 0),a + ld (ix + PLY_LW_Data_OffsetCurrentPitchTableValue + 1),h + ;Increases the offset for next time. + inc (ix + PLY_LW_Data_OffsetPtPitchOffset) + 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_LW_PlaySoundStream: + ;Gets the pointer on the Instrument, from its base address and the offset. + ld l,(ix + PLY_LW_Data_OffsetPtInstrument + 0) + ld h,(ix + PLY_LW_Data_OffsetPtInstrument + 1) + + ;Reads the first byte of the cell of the Instrument. What type? +PLY_LW_PSS_ReadFirstByte: + ld a,(hl) + ld b,a + inc hl + rra + jr c,PLY_LW_PSS_SoftOrSoftAndHard + + ;NoSoftNoHard or SoftwareToHardware + rra + IFDEF PLY_CFG_UseHardwareSounds ;CONFIG SPECIFIC + jr c,PLY_LW_PSS_SoftwareToHardware + ENDIF ;PLY_CFG_UseHardwareSounds + + ;No software no hardware, or end of sound (loop)! + ;End of sound? + rra + jr nc,PLY_LW_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_LW_Data_OffsetPtInstrument + 0),l + ld (ix + PLY_LW_Data_OffsetPtInstrument + 1),h + jr PLY_LW_PSS_ReadFirstByte + +PLY_LW_PSS_NSNH_NotEndOfSound: + ;No software, no hardware. + ;------------------------- + ;Stops the sound. + set 2,c + + ;Volume. A now contains the volume on b0-3. + call PLY_LW_PSS_Shared_AdjustVolume + ld (iy + PLY_LW_Registers_OffsetVolume),a + + ;Read noise? + rl b + IFDEF PLY_CFG_NoSoftNoHard_Noise ;CONFIG SPECIFIC + call c,PLY_LW_PSS_ReadNoise + ENDIF ;PLY_CFG_NoSoftNoHard_Noise + jr PLY_LW_PSS_Shared_StoreInstrumentPointer + + ;Software sound, or Software and Hardware? +PLY_LW_PSS_SoftOrSoftAndHard: + rra + IFDEF PLY_CFG_UseHardwareSounds ;CONFIG SPECIFIC + jr c,PLY_LW_PSS_SoftAndHard + ENDIF ;PLY_CFG_UseHardwareSounds + + ;Software sound. + ;----------------- + ;A is the volume. Already shifted twice, so it can be used directly. + call PLY_LW_PSS_Shared_AdjustVolume + ld (iy + PLY_LW_Registers_OffsetVolume),a + + ;Arp and/or noise? + ld d,0 ;Default arpeggio. + rl b + jr nc,PLY_LW_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_LW_USE_Noise ;CONFIG SPECIFIC + call c,PLY_LW_PSS_ReadNoise + ENDIF ;PLY_LW_USE_Noise +PLY_LW_PSS_S_AfterArpAndOrNoise: + + ld a,d ;Gets the instrument arpeggio, if any. + call PLY_LW_CalculatePeriodForBaseNote + + ;Read pitch? + rl b + IFDEF PLY_CFG_SoftOnly_SoftwarePitch ;CONFIG SPECIFIC + call c,PLY_LW_ReadPitchAndAddToPeriod + ENDIF ;PLY_CFG_SoftOnly_SoftwarePitch + + ;Stores the new period of this channel. + exx + ld (iy + PLY_LW_Registers_OffsetSoftwarePeriodLSB),l + ld (iy + PLY_LW_Registers_OffsetSoftwarePeriodMSB),h + exx + + ;The code below is mutualized! + ;Stores the new instrument pointer, if Speed allows it. + ;-------------------------------------------------- +PLY_LW_PSS_Shared_StoreInstrumentPointer: + ;Checks the Instrument speed, and only stores the Instrument new pointer if the speed is reached. + ld a,(ix + PLY_LW_Data_OffsetInstrumentCurrentStep) + cp (ix + PLY_LW_Data_OffsetInstrumentSpeed) + jr z,PLY_LW_PSS_S_SpeedReached + ;Increases the current step. + inc (ix + PLY_LW_Data_OffsetInstrumentCurrentStep) + ret +PLY_LW_PSS_S_SpeedReached: + ;Stores the Instrument new pointer, resets the speed counter. + ld (ix + PLY_LW_Data_OffsetPtInstrument + 0),l + ld (ix + PLY_LW_Data_OffsetPtInstrument + 1),h + ld (ix + PLY_LW_Data_OffsetInstrumentCurrentStep),0 + ret + + + IFDEF PLY_CFG_UseHardwareSounds ;CONFIG SPECIFIC + + ;Software and Hardware. + ;---------------------------- +PLY_LW_PSS_SoftAndHard: + ;Reads the envelope bit, the possible pitch, and sets the software period accordingly. + call PLY_LW_PSS_Shared_ReadEnvBitPitchArp_SoftPeriod_HardVol_HardEnv + ;Reads the hardware period. + ld a,(hl) + ld (PLY_LW_Reg11),a + inc hl + ld a,(hl) + ld (PLY_LW_Reg12),a + inc hl + + jr PLY_LW_PSS_Shared_StoreInstrumentPointer + + + ;Software to Hardware. + ;------------------------- +PLY_LW_PSS_SoftwareToHardware: + call PLY_LW_PSS_Shared_ReadEnvBitPitchArp_SoftPeriod_HardVol_HardEnv + + ;Now we can calculate the hardware period thanks to the ratio. + ld a,b + rra + rra + and %11100 + ld (PLY_LW_PSS_STH_Jump + 1),a + exx +PLY_LW_PSS_STH_Jump: jr $ + 2 ;Automodified by the line above to jump to the right place. + srl h + rr l + srl h + rr l + srl h + rr l + srl h + rr l + srl h + rr l + srl h + rr l + srl h + rr l + jr nc,PLY_LW_PSS_STH_RatioEnd + inc hl +PLY_LW_PSS_STH_RatioEnd: + ld a,l + ld (PLY_LW_Reg11),a + ld a,h + ld (PLY_LW_Reg12),a + exx + + jr PLY_LW_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_LW_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_LW_SetReg13 + 1),a + + ;Volume to 16 to trigger the hardware envelope. + ld (iy + PLY_LW_Registers_OffsetVolume),16 + + ;Arpeggio? + xor a ;Default arpeggio. + IFDEF PLY_LW_ArpeggioInHardwareInstrument ;CONFIG SPECIFIC + bit 7,b ;Not shifted yet. + jr z,PLY_LW_PSS_Shared_REnvBAP_AfterArpeggio + ;Reads the Arpeggio. + ld a,(hl) + inc hl +PLY_LW_PSS_Shared_REnvBAP_AfterArpeggio: + ENDIF ;PLY_LW_ArpeggioInHardwareInstrument + ;Calculates the software period. + call PLY_LW_CalculatePeriodForBaseNote + + ;Pitch? + IFDEF PLY_LW_PitchInHardwareInstrument ;CONFIG SPECIFIC + bit 2,b ;Not shifted yet. + call nz,PLY_LW_ReadPitchAndAddToPeriod + ENDIF ;PLY_LW_PitchInHardwareInstrument + + ;Stores the new period of this channel. + exx + ld (iy + PLY_LW_Registers_OffsetSoftwarePeriodLSB),l + ld (iy + PLY_LW_Registers_OffsetSoftwarePeriodMSB),h + exx + ret + + ENDIF ;PLY_CFG_UseHardwareSounds + +;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_LW_PSS_Shared_AdjustVolume: + and %1111 + sub (ix + PLY_LW_Data_OffsetTrackInvertedVolume) + ret nc + xor a + ret + +;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_LW_USE_Noise ;CONFIG SPECIFIC +PLY_LW_PSS_ReadNoise: + ld a,(hl) + inc hl + ld (PLY_LW_NoiseRegister),a + res 5,c ;Opens the noise channel. + ret + ENDIF ;PLY_LW_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_LW_CalculatePeriodForBaseNote: + ;Gets the period from the current note. + exx + ld h,0 + add a,(ix + PLY_LW_Data_OffsetBaseNote) ;Adds the instrument Arp to the base note (including the transposition). + IFDEF PLY_CFG_UseEffect_ArpeggioTable ;CONFIG SPECIFIC + add (ix + PLY_LW_Data_OffsetCurrentArpeggioValue) ;Adds the Arpeggio Table effect. + ENDIF ;PLY_CFG_UseEffect_ArpeggioTable + ld l,a + sla l ;Note encoded on 7 bits, so should be fine. + add hl,de + ld a,(hl) + inc hl + ld h,(hl) + ld l,a ;HL' = period. + + ;Adds the Pitch Table value, if used. + IFDEF PLY_CFG_UseEffect_PitchTable ;CONFIG SPECIFIC + ld a,(ix + PLY_LW_Data_OffsetIsPitchTableUsed) + or a + jr z,PLY_LW_CalculatePeriodForBaseNote_NoPitchTable + ld c,(ix + PLY_LW_Data_OffsetCurrentPitchTableValue + 0) + ld b,(ix + PLY_LW_Data_OffsetCurrentPitchTableValue + 1) + add hl,bc +PLY_LW_CalculatePeriodForBaseNote_NoPitchTable: + ENDIF ;PLY_CFG_UseEffect_PitchTable + ;Adds the Track Pitch. + IFDEF PLY_LW_USE_EffectPitchUpDown + ld c,(ix + PLY_LW_Data_OffsetTrackPitchInteger + 0) + ld b,(ix + PLY_LW_Data_OffsetTrackPitchInteger + 1) + add hl,bc + ENDIF ;PLY_LW_USE_EffectPitchUpDown + exx + ret + + IFDEF PLY_LW_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_LW_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_LW_PitchInInstrument + + + + + + + + + + + + + +;--------------------------------------------------------------------- +;Effect management. +;--------------------------------------------------------------------- + + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC + +;IN: HL = points after the first byte. +; B = data of the first byte on bits 0-4 (will probably needed to be ANDed, as bits 5-7 are undetermined). +; DE'= Instrument Table (not useful here). Do not modify! +; IX = data block of the Track. +;OUT: HL = points after the data of the effect (maybe nothing to do). +; Each effect must jump to PLY_LW_RT_CellRead. + +;Clears all the effects (volume, pitch table, arpeggio table). + IFDEF PLY_CFG_UseEffect_Reset ;CONFIG SPECIFIC. +PLY_LW_EffectReset: + ;Inverted volume. + call PLY_LW_ReadInvertedVolumeFromB + + xor a + ;The inverted volume is managed above, so don't change it. + IFDEF PLY_LW_USE_EffectPitchUpDown ;CONFIG SPECIFIC + ld (ix + PLY_LW_Data_OffsetIsPitchUpDownUsed),a + ENDIF ;PLY_LW_USE_EffectPitchUpDown + IFDEF PLY_CFG_UseEffect_ArpeggioTable ;CONFIG SPECIFIC + ld (ix + PLY_LW_Data_OffsetIsArpeggioTableUsed),a + ld (ix + PLY_LW_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_LW_Data_OffsetIsPitchTableUsed),a + ENDIF ;PLY_CFG_UseEffect_PitchTable + jp PLY_LW_RT_CellRead + ENDIF ;PLY_CFG_UseEffect_Reset + +;Changes the volume. Possibly changes the Track pitch. + IFDEF PLY_LW_USE_Volume_And_PitchUpDown_Effects ;CONFIG SPECIFIC. +PLY_LW_EffectVolumeAndPitchUpDown: + ;Stores the new inverted volume. + call PLY_LW_ReadInvertedVolumeFromB + + ;Pitch? Warning, the code below is shared with the PitchUp/Down effect. + bit 4,b + jp z,PLY_LW_RT_CellRead + ;Pitch present. Reads and stores its 16 bits value (integer/decimal). +PLY_LW_EffectPitchUpDown_Activated: + ;Code shared with the effect above. + ;Activates the effect. + ld (ix + PLY_LW_Data_OffsetIsPitchUpDownUsed),255 + ld a,(hl) + inc hl + ld (ix + PLY_LW_Data_OffsetTrackPitchSpeed + 0),a + ld a,(hl) + inc hl + ld (ix + PLY_LW_Data_OffsetTrackPitchSpeed + 1),a + + jp PLY_LW_RT_CellRead + ENDIF ;PLY_LW_USE_Volume_And_PitchUpDown_Effects + + +;Effect table. Each entry jumps to an effect management code. +;Put after the code above so that the JR are within bound. +PLY_LW_EffectTable: + IFDEF PLY_LW_EffectReset ;CONFIG SPECIFIC + jr PLY_LW_EffectReset ;000 + ELSE + jr $ + ENDIF + + IFDEF PLY_CFG_UseEffect_ArpeggioTable ;CONFIG SPECIFIC + jr PLY_LW_EffectArpeggioTable ;001 + ELSE + jr $ + ENDIF + + IFDEF PLY_CFG_UseEffect_PitchTable ;CONFIG SPECIFIC + jr PLY_LW_EffectPitchTable ;010 + ELSE + jr $ + ENDIF + + IFDEF PLY_LW_USE_EffectPitchUpDown ;CONFIG SPECIFIC + jr PLY_LW_EffectPitchUpDown ;011 + ELSE + jr $ + ENDIF + + IFDEF PLY_LW_USE_Volume_And_PitchUpDown_Effects ;CONFIG SPECIFIC + jr PLY_LW_EffectVolumeAndPitchUpDown ;100 + ELSE + jr $ + ENDIF + + IFDEF PLY_LW_USE_Volume_And_ArpeggioTable_Effects ;CONFIG SPECIFIC + jr PLY_LW_EffectVolumeArpeggioTable ;101 + ELSE + jr $ + ENDIF + + IFDEF PLY_LW_USE_Reset_And_ArpeggioTable_Effects ;CONFIG SPECIFIC + jr PLY_LW_EffectResetArpeggioTable ;110 + ELSE + jr $ + ENDIF + + ;111 Unused. + + + +;Pitch up/down effect, activation or stop. + IFDEF PLY_LW_USE_EffectPitchUpDown ;CONFIG SPECIFIC +PLY_LW_EffectPitchUpDown: + rr b ;Pitch present or pitch stop? + jr c,PLY_LW_EffectPitchUpDown_Activated + ;Pitch stop. + ld (ix + PLY_LW_Data_OffsetIsPitchUpDownUsed),0 + jp PLY_LW_RT_CellRead + ENDIF ;PLY_LW_USE_EffectPitchUpDown + +;Arpeggio table effect, activation or stop. + IFDEF PLY_CFG_UseEffect_ArpeggioTable ;CONFIG SPECIFIC +PLY_LW_EffectArpeggioTable: + ld a,b + and %11111 +PLY_LW_EffectArpeggioTable_AfterMask: + ld (ix + PLY_LW_Data_OffsetIsArpeggioTableUsed),a ;Sets to 0 if the Arpeggio is stopped, or any other value if it starts. + jr z,PLY_LW_EffectArpeggioTable_Stop + + ;Gets the Arpeggio address. + add a,a + exx + ld l,a + ld h,0 +dknr3: +PLY_LW_PtArpeggios: ld bc,0 + add hl,bc + ld a,(hl) + inc hl + ld (ix + PLY_LW_Data_OffsetPtArpeggioTable + 0),a + ld a,(hl) + ld (ix + PLY_LW_Data_OffsetPtArpeggioTable + 1),a + exx + + ;Resets the offset of the Arpeggio, to force a restart. + xor a + ld (ix + PLY_LW_Data_OffsetPtArpeggioOffset),a + jp PLY_LW_RT_CellRead +PLY_LW_EffectArpeggioTable_Stop: + ;Contrary to the Pitch, the Arpeggio must also be set to 0 when stopped. + ld (ix + PLY_LW_Data_OffsetCurrentArpeggioValue),a + jp PLY_LW_RT_CellRead + ENDIF ;PLY_CFG_UseEffect_ArpeggioTable + +;Pitch table effect, activation or stop. +;This is exactly the same code as for the Arpeggio, but I can't find a way to share it... + IFDEF PLY_CFG_UseEffect_PitchTable ;CONFIG SPECIFIC +PLY_LW_EffectPitchTable: + ld a,b + and %11111 +PLY_LW_EffectPitchTable_AfterMask: + ld (ix + PLY_LW_Data_OffsetIsPitchTableUsed),a ;Sets to 0 if the Pitch is stopped, or any other value if it starts. + jp z,PLY_LW_RT_CellRead + + ;Gets the Pitch address. + add a,a + exx + ld l,a + ld h,0 +dknr3: +PLY_LW_PtPitches: ld bc,0 + add hl,bc + ld a,(hl) + inc hl + ld (ix + PLY_LW_Data_OffsetPtPitchTable + 0),a + ld a,(hl) + inc hl + ld (ix + PLY_LW_Data_OffsetPtPitchTable + 1),a + exx + + ;Resets the offset of the Pitch, to force a restart. + xor a + ld (ix + PLY_LW_Data_OffsetPtPitchOffset),a + + jp PLY_LW_RT_CellRead + ENDIF ;PLY_CFG_UseEffect_PitchTable + + + +;Volume, and Arpeggio Table, activation or stop. + IFDEF PLY_LW_USE_Volume_And_ArpeggioTable_Effects ;CONFIG SPECIFIC +PLY_LW_EffectVolumeArpeggioTable: + ;Stores the new inverted volume. + call PLY_LW_ReadInvertedVolumeFromB + + ;Manages the Arpeggio, encoded just after. + ld a,(hl) + inc hl + or a ;Required, else a volume of 0 will disturb the flag test after the jump! + jr PLY_LW_EffectArpeggioTable_AfterMask + ENDIF ;PLY_LW_USE_Volume_And_ArpeggioTable_Effects + +;Reset, and Arpeggio Table (activation only). + IFDEF PLY_LW_USE_Reset_And_ArpeggioTable_Effects ;CONFIG SPECIFIC +PLY_LW_EffectResetArpeggioTable: + ;Resets effects and read volume. + ;A bit of loss of CPU because we're going to set the Arpeggio just after, AND the effect pointer is stored! + ;Oh well, less memory taken this way. + call PLY_LW_EffectReset + + ;Reads the Arpeggio. + ld a,(hl) + inc hl + or a ;Required, else a volume of 0 will disturb the flag test after the jump! + jp PLY_LW_EffectArpeggioTable_AfterMask ;No need to use the mask, the value is clean. + ENDIF ;PLY_LW_USE_Reset_And_ArpeggioTable_Effects + + +;Reads the inverted volume from B, stored it after masking the bits in A. +PLY_LW_ReadInvertedVolumeFromB: + ld a,b + and %1111 + ld (ix + PLY_LW_Data_OffsetTrackInvertedVolume),a + ret + + ENDIF ;PLY_CFG_UseEffects + + + + + + + + +;--------------------------------------------------------------------- +;Data blocks for the three channels. Make sure NOTHING is added between, as the init clears everything! +;--------------------------------------------------------------------- + +;Data block for channel 1. +PLY_LW_Track1_Data: +dkbs: +PLY_LW_Track1_WaitEmptyCell: db 0 ;How many empty cells have to be waited. 0 = none. + IFDEF PLY_CFG_UseTranspositions ;CONFIG SPECIFIC +PLY_LW_Track1_Transposition: db 0 + ENDIF ;PLY_CFG_UseTranspositions +PLY_LW_Track1_BaseNote: db 0 ;Base note, such as the note played. The transposition IS included. +PLY_LW_Track1_InstrumentCurrentStep: db 0 ;The current step on the Instrument (>=0, till it reaches the Speed). +PLY_LW_Track1_InstrumentSpeed: db 0 ;The Instrument speed (>=0). +PLY_LW_Track1_TrackInvertedVolume: db 0 +dkbe: +dkws: +PLY_LW_Track1_PtTrack: dw 0 ;Points on the next Cell of the Track to read. Evolves. +PLY_LW_Track1_PtInstrument: dw 0 ;Points on the Instrument, evolves. +PLY_LW_Track1_PtBaseInstrument: dw 0 ;Points on the base of the Instrument, does not evolve. +dkwe: + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC +dkbs: +PLY_LW_Track1_IsPitchUpDownUsed: db 0 ;>0 if a Pitch Up/Down is currently in use. +PLY_LW_Track1_TrackPitchDecimal: db 0 ;The decimal part of the Track pitch. Evolves as the pitch goes up/down. +dkbe: +dkws: +PLY_LW_Track1_TrackPitchSpeed: dw 0 ;The integer and decimal part of the Track pitch speed. Is added to the Track Pitch every frame. +PLY_LW_Track1_TrackPitchInteger: dw 0 ;The integer part of the Track pitch. Evolves as the pitch goes up/down. +dkwe: +dkbs: +PLY_LW_Track1_IsArpeggioTableUsed: db 0 ;>0 if an Arpeggio Table is currently in use. +PLY_LW_Track1_PtArpeggioOffset: db 0 ;Increases over the Arpeggio. +PLY_LW_Track1_CurrentArpeggioValue: db 0 ;Value from the Arpeggio to add to the base note. Read even if the Arpeggio effect is deactivated. +dkbe: +dkws: +PLY_LW_Track1_PtArpeggioTable: dw 0 ;Point on the base of the Arpeggio table, does not evolve. +dkwe: +dkbs: +PLY_LW_Track1_IsPitchTableUsed: db 0 ;>0 if a Pitch Table is currently in use. +PLY_LW_Track1_PtPitchOffset: db 0 ;Increases over the Pitch. +dkbe: +dkws: +PLY_LW_Track1_CurrentPitchTableValue: dw 0 ;16 bit value from the Pitch to add to the base note. Not read if the Pitch effect is deactivated. +PLY_LW_Track1_PtPitchTable: dw 0 ;Points on the base of the Pitch table, does not evolve. +dkwe: + ENDIF ;PLY_CFG_UseEffects +PLY_LW_Track1_Data_End: + +PLY_LW_Track1_Data_Size: equ PLY_LW_Track1_Data_End - PLY_LW_Track1_Data + +PLY_LW_Data_OffsetWaitEmptyCell: equ PLY_LW_Track1_WaitEmptyCell - PLY_LW_Track1_Data + IFDEF PLY_CFG_UseTranspositions ;CONFIG SPECIFIC +PLY_LW_Data_OffsetTransposition: equ PLY_LW_Track1_Transposition - PLY_LW_Track1_Data + ENDIF ;PLY_CFG_UseTranspositions + +PLY_LW_Data_OffsetPtTrack: equ PLY_LW_Track1_PtTrack - PLY_LW_Track1_Data +PLY_LW_Data_OffsetBaseNote: equ PLY_LW_Track1_BaseNote - PLY_LW_Track1_Data +PLY_LW_Data_OffsetPtInstrument: equ PLY_LW_Track1_PtInstrument - PLY_LW_Track1_Data +PLY_LW_Data_OffsetPtBaseInstrument: equ PLY_LW_Track1_PtBaseInstrument - PLY_LW_Track1_Data +PLY_LW_Data_OffsetInstrumentCurrentStep: equ PLY_LW_Track1_InstrumentCurrentStep - PLY_LW_Track1_Data +PLY_LW_Data_OffsetInstrumentSpeed: equ PLY_LW_Track1_InstrumentSpeed - PLY_LW_Track1_Data +PLY_LW_Data_OffsetTrackInvertedVolume: equ PLY_LW_Track1_TrackInvertedVolume - PLY_LW_Track1_Data + IFDEF PLY_CFG_UseEffects ;CONFIG SPECIFIC + IFDEF PLY_LW_USE_EffectPitchUpDown ;CONFIG SPECIFIC +PLY_LW_Data_OffsetIsPitchUpDownUsed: equ PLY_LW_Track1_IsPitchUpDownUsed - PLY_LW_Track1_Data +PLY_LW_Data_OffsetTrackPitchInteger: equ PLY_LW_Track1_TrackPitchInteger - PLY_LW_Track1_Data +PLY_LW_Data_OffsetTrackPitchDecimal: equ PLY_LW_Track1_TrackPitchDecimal - PLY_LW_Track1_Data +PLY_LW_Data_OffsetTrackPitchSpeed: equ PLY_LW_Track1_TrackPitchSpeed - PLY_LW_Track1_Data + ENDIF ;PLY_LW_USE_EffectPitchUpDown + IFDEF PLY_CFG_UseEffect_ArpeggioTable ;CONFIG SPECIFIC +PLY_LW_Data_OffsetIsArpeggioTableUsed: equ PLY_LW_Track1_IsArpeggioTableUsed - PLY_LW_Track1_Data +PLY_LW_Data_OffsetPtArpeggioTable: equ PLY_LW_Track1_PtArpeggioTable - PLY_LW_Track1_Data +PLY_LW_Data_OffsetPtArpeggioOffset: equ PLY_LW_Track1_PtArpeggioOffset - PLY_LW_Track1_Data +PLY_LW_Data_OffsetCurrentArpeggioValue: equ PLY_LW_Track1_CurrentArpeggioValue - PLY_LW_Track1_Data + ENDIF ;PLY_CFG_UseEffect_ArpeggioTable + IFDEF PLY_CFG_UseEffect_PitchTable ;CONFIG SPECIFIC +PLY_LW_Data_OffsetIsPitchTableUsed: equ PLY_LW_Track1_IsPitchTableUsed - PLY_LW_Track1_Data +PLY_LW_Data_OffsetPtPitchTable: equ PLY_LW_Track1_PtPitchTable - PLY_LW_Track1_Data +PLY_LW_Data_OffsetCurrentPitchTableValue: equ PLY_LW_Track1_CurrentPitchTableValue - PLY_LW_Track1_Data +PLY_LW_Data_OffsetPtPitchOffset: equ PLY_LW_Track1_PtPitchOffset - PLY_LW_Track1_Data + ENDIF ;PLY_CFG_UseEffect_PitchTable + ENDIF ;PLY_CFG_UseEffects + +;Data block for channel 2. +PLY_LW_Track2_Data: +dkbs: + ds PLY_LW_Track1_Data_Size, 0 +dkbe: +PLY_LW_Track2_Data_End: +PLY_LW_Track2_WaitEmptyCell: equ PLY_LW_Track2_Data + PLY_LW_Data_OffsetWaitEmptyCell + IFDEF PLY_CFG_UseTranspositions ;CONFIG SPECIFIC +PLY_LW_Track2_Transposition: equ PLY_LW_Track2_Data + PLY_LW_Data_OffsetTransposition + ENDIF ;PLY_CFG_UseTranspositions + +PLY_LW_Track2_PtTrack: equ PLY_LW_Track2_Data + PLY_LW_Data_OffsetPtTrack +PLY_LW_Track2_PtInstrument: equ PLY_LW_Track2_Data + PLY_LW_Data_OffsetPtInstrument + +;Data block for channel 3. +PLY_LW_Track3_Data: +dkbs: + ds PLY_LW_Track1_Data_Size, 0 +dkbe: +PLY_LW_Track3_Data_End: +PLY_LW_Track3_WaitEmptyCell: equ PLY_LW_Track3_Data + PLY_LW_Data_OffsetWaitEmptyCell + IFDEF PLY_CFG_UseTranspositions ;CONFIG SPECIFIC +PLY_LW_Track3_Transposition: equ PLY_LW_Track3_Data + PLY_LW_Data_OffsetTransposition + ENDIF ;PLY_CFG_UseTranspositions +PLY_LW_Track3_PtTrack: equ PLY_LW_Track3_Data + PLY_LW_Data_OffsetPtTrack +PLY_LW_Track3_PtInstrument: equ PLY_LW_Track3_Data + PLY_LW_Data_OffsetPtInstrument + + ;Makes sure the structure all have the same size! + ASSERT (PLY_LW_Track1_Data_End - PLY_LW_Track1_Data) == (PLY_LW_Track2_Data_End - PLY_LW_Track2_Data) + ASSERT (PLY_LW_Track1_Data_End - PLY_LW_Track1_Data) == (PLY_LW_Track3_Data_End - PLY_LW_Track3_Data) + ;No holes between the blocks, the init makes a LDIR to clear everything! + ASSERT PLY_LW_Track1_Data_End == PLY_LW_Track2_Data + ASSERT PLY_LW_Track2_Data_End == PLY_LW_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. +PLY_LW_Registers_RetTable: +PLY_LW_Track1_Registers: +dkbs: db 8 +PLY_LW_Track1_Volume: db 0 +dkbe: +dkps: dw PLY_LW_SendPsgRegister +dkpe: + +dkbs: db 0 +PLY_LW_Track1_SoftwarePeriodLSB: db 0 +dkbe: +dkps: dw PLY_LW_SendPsgRegister +dkpe: + +dkbs: db 1 +PLY_LW_Track1_SoftwarePeriodMSB: db 0 +dkbe: +dkps: dw PLY_LW_SendPsgRegister +dkpe: + +PLY_LW_Track2_Registers: +dkbs: db 9 +PLY_LW_Track2_Volume: db 0 +dkbe: +dkps: dw PLY_LW_SendPsgRegister +dkpe: + +dkbs: db 2 +PLY_LW_Track2_SoftwarePeriodLSB: db 0 +dkbe: +dkps: dw PLY_LW_SendPsgRegister +dkpe: + +dkbs: db 3 +PLY_LW_Track2_SoftwarePeriodMSB: db 0 +dkbe: +dkps: dw PLY_LW_SendPsgRegister +dkpe: + + +PLY_LW_Track3_Registers: +dkbs: db 10 +PLY_LW_Track3_Volume: db 0 +dkbe: +dkps: dw PLY_LW_SendPsgRegister +dkpe: + +dkbs: db 4 +PLY_LW_Track3_SoftwarePeriodLSB: db 0 +dkbe: +dkps: dw PLY_LW_SendPsgRegister +dkpe: + +dkbs: db 5 +PLY_LW_Track3_SoftwarePeriodMSB: db 0 +dkbe: +dkps: dw PLY_LW_SendPsgRegister +dkpe: + +;Generic registers. + IFDEF PLY_LW_USE_NoiseRegister ;CONFIG SPECIFIC +dkbs: db 6 +PLY_LW_NoiseRegister: db 0 +dkbe: +dkps: dw PLY_LW_SendPsgRegister +dkpe: + ENDIF ;PLY_LW_USE_NoiseRegister + +dkbs: db 7 +PLY_LW_MixerRegister: db 0 +dkbe: + IFDEF PLY_CFG_UseHardwareSounds ;CONFIG SPECIFIC +dkps: dw PLY_LW_SendPsgRegister +dkpe: + + +dkbs: db 11 +PLY_LW_Reg11: db 0 +dkbe: +dkps: dw PLY_LW_SendPsgRegister +dkpe: + +dkbs: db 12 +PLY_LW_Reg12: db 0 +dkbe: +dkps: + dw PLY_LW_SendPsgRegisterR13 + ;This one is a trick to send the register after R13 is managed. + dw PLY_LW_SendPsgRegisterAfterPop + + dw PLY_LW_SendPsgRegisterEnd +dkpe: + ELSE +dkps: + dw PLY_LW_SendPsgRegisterEnd +dkpe: + ENDIF ;PLY_CFG_UseHardwareSounds + + + +PLY_LW_Registers_OffsetVolume: equ PLY_LW_Track1_Volume - PLY_LW_Track1_Registers +PLY_LW_Registers_OffsetSoftwarePeriodLSB: equ PLY_LW_Track1_SoftwarePeriodLSB - PLY_LW_Track1_Registers +PLY_LW_Registers_OffsetSoftwarePeriodMSB: equ PLY_LW_Track1_SoftwarePeriodMSB - PLY_LW_Track1_Registers + +;The period table for each note (from 0 to 127 included). +PLY_LW_PeriodTable: +dkws + IFDEF PLY_LW_HARDWARE_CPC + ;PSG running to 1000000 Hz. + dw 3822,3608,3405,3214,3034,2863,2703,2551,2408,2273,2145,2025 ;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_LW_HARDWARE_SPECTRUM_OR_MSX + ;PSG running to 1773400 Hz. + dw 6778, 6398, 6039, 5700, 5380, 5078, 4793, 4524, 4270, 4030, 3804, 3591 ; Octave 0 + dw 3389, 3199, 3019, 2850, 2690, 2539, 2397, 2262, 2135, 2015, 1902, 1795 ; Octave 1 + dw 1695, 1599, 1510, 1425, 1345, 1270, 1198, 1131, 1068, 1008, 951, 898 ; Octave 2 + dw 847, 800, 755, 712, 673, 635, 599, 566, 534, 504, 476, 449 ; Octave 3 + dw 424, 400, 377, 356, 336, 317, 300, 283, 267, 252, 238, 224 ; Octave 4 + dw 212, 200, 189, 178, 168, 159, 150, 141, 133, 126, 119, 112 ; Octave 5 + dw 106, 100, 94, 89, 84, 79, 75, 71, 67, 63, 59, 56 ; Octave 6 + dw 53, 50, 47, 45, 42, 40, 37, 35, 33, 31, 30, 28 ; Octave 7 + dw 26, 25, 24, 22, 21, 20, 19, 18, 17, 16, 15, 14 ; Octave 8 + dw 13, 12, 12, 11, 11, 10, 9, 9, 8, 8, 7, 7 ; Octave 9 + dw 7, 6, 6, 6, 5, 5, 5, 4 ; Octave 10 + ENDIF + + IFDEF PLY_LW_HARDWARE_PENTAGON + ;PSG running to 1750000 Hz. + dw 6689, 6314, 5959, 5625, 5309, 5011, 4730, 4464, 4214, 3977, 3754, 3543 ; Octave 0 + dw 3344, 3157, 2980, 2812, 2655, 2506, 2365, 2232, 2107, 1989, 1877, 1772 ; Octave 1 + dw 1672, 1578, 1490, 1406, 1327, 1253, 1182, 1116, 1053, 994, 939, 886 ; Octave 2 + dw 836, 789, 745, 703, 664, 626, 591, 558, 527, 497, 469, 443 ; Octave 3 + dw 418, 395, 372, 352, 332, 313, 296, 279, 263, 249, 235, 221 ; Octave 4 + dw 209, 197, 186, 176, 166, 157, 148, 140, 132, 124, 117, 111 ; Octave 5 + dw 105, 99, 93, 88, 83, 78, 74, 70, 66, 62, 59, 55 ; Octave 6 + dw 52, 49, 47, 44, 41, 39, 37, 35, 33, 31, 29, 28 ; Octave 7 + dw 26, 25, 23, 22, 21, 20, 18, 17, 16, 16, 15, 14 ; Octave 8 + dw 13, 12, 12, 11, 10, 10, 9, 9, 8, 8, 7, 7 ; Octave 9 + dw 7, 6, 6, 5, 5, 5, 5, 4 ; Octave 10 + ENDIF +dkwe +PLY_LW_End: + + + diff --git a/lib/plw/player/PlayerLightWeight_SoundEffects.asm b/lib/plw/player/PlayerLightWeight_SoundEffects.asm new file mode 100644 index 0000000..cd15cb8 --- /dev/null +++ b/lib/plw/player/PlayerLightWeight_SoundEffects.asm @@ -0,0 +1,458 @@ +; Player of sound effects, for the Lightweight player. + + ;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_LW_SE_HardwareSounds = 1 + ENDIF + IFDEF PLY_CFG_SFX_SoftAndHard + PLY_LW_SE_HardwareSounds = 1 + ENDIF +; Mixes the Hardware Noise flags into one. + IFDEF PLY_CFG_SFX_HardOnly_Noise + PLY_LW_SE_HardwareNoise = 1 + ENDIF + IFDEF PLY_CFG_SFX_SoftAndHard_Noise + PLY_LW_SE_HardwareNoise = 1 + ENDIF +; Mixes the Noise flags into one. + IFDEF PLY_LW_SE_HardwareNoise + PLY_LW_SE_Noise = 1 + ENDIF + IFDEF PLY_CFG_SFX_NoSoftNoHard_Noise + PLY_LW_SE_Noise = 1 + ENDIF + IFDEF PLY_CFG_SFX_SoftOnly + PLY_LW_SE_Noise = 1 + ENDIF +; Noise in Sound Effects? Then noise register code must be compiled. + IFDEF PLY_LW_SE_Noise + PLY_LW_USE_NoiseRegister = 1 + ENDIF +; Mixes the Software Volume flags into one. + IFDEF PLY_CFG_SFX_NoSoftNoHard + PLY_LW_SE_VolumeSoft = 1 + PLY_LW_SE_VolumeSoftOrHard = 1 + ENDIF + IFDEF PLY_CFG_SFX_SoftOnly + PLY_LW_SE_VolumeSoft = 1 + PLY_LW_SE_VolumeSoftOrHard = 1 + ENDIF +; Mixes the volume (soft/hard) into one. + IFDEF PLY_CFG_UseHardwareSounds + PLY_LW_SE_VolumeSoftOrHard = 1 + ENDIF +; Mixes the retrig flags into one. + IFDEF PLY_CFG_SFX_HardOnly_Retrig + PLY_LW_SE_Retrig = 1 + ENDIF + IFDEF PLY_CFG_SFX_SoftAndHard_Retrig + PLY_LW_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_LW_InitSoundEffectsDisarkGenerateExternalLabel: +PLY_LW_InitSoundEffects: + ld (PLY_LW_SE_PtSoundEffectTable + 1),hl + ret + + +;Plays 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_LW_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_LW_PlaySoundEffectDisarkGenerateExternalLabel: +PLY_LW_PlaySoundEffect: + ;Gets the address to the sound effect. + dec a ;The 0th is not encoded. +dknr3: +PLY_LW_SE_PtSoundEffectTable: ld hl,0 + 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 + + push af + ;ex af,af' + + ld a,b + + ;Finds the pointer to the sound effect of the desired channel. + ld hl,PLY_LW_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' + pop 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_LW_StopSoundEffectFromChannelDisarkGenerateExternalLabel: +PLY_LW_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_LW_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_LW_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_LW_Channel1_SoundEffectData + ld iy,PLY_LW_Track1_Registers + ld c,a + call PLY_LW_PSES_Play + ld ix,PLY_LW_Channel2_SoundEffectData + ld iy,PLY_LW_Track2_Registers + 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. + call PLY_LW_PSES_Play + ld ix,PLY_LW_Channel3_SoundEffectData + ld iy,PLY_LW_Track3_Registers + IFDEF PLY_LW_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_LW_PSES_Play + + ld a,c + ld (PLY_LW_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 maybe 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_LW_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_LW_PSES_ReadFirstByte: + ld a,(hl) + inc hl + ld b,a + rra + jr c,PLY_LW_PSES_SoftwareOrSoftwareAndHardware + rra + IFDEF PLY_CFG_SFX_HardOnly ;CONFIG SPECIFIC + jr c,PLY_LW_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_LW_PSES_S_EndOrLoop + + ;No software, no hardware. + ;------------------------------------------- + ;Gets the volume. + call PLY_LW_PSES_ManageVolumeFromA_Filter4Bits + + ;Noise? + IFDEF PLY_CFG_SFX_NoSoftNoHard_Noise ;CONFIG SPECIFIC + rl b + call c,PLY_LW_PSES_ReadNoiseAndOpenNoiseChannel + ENDIF ;PLY_CFG_SFX_NoSoftNoHard_Noise + + jr PLY_LW_PSES_SavePointerAndExit + ENDIF ;PLY_CFG_SFX_NoSoftNoHard + + ;**Warning!** Do not put any instruction between EndOrLoop and NoSoftNoHard. + +PLY_LW_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_LW_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_LW_PSES_S_Loop: + ;Loops. Reads the pointer and directly uses it. + ld a,(hl) + inc hl + ld h,(hl) + ld l,a + jr PLY_LW_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_LW_PSES_SavePointerAndExit: + ;Speed reached? + ld a,(ix + PLY_LW_SoundEffectData_OffsetCurrentStep) + cp (ix + PLY_LW_SoundEffectData_OffsetSpeed) + jr c,PLY_LW_PSES_NotReached + ;The speed has been reached, so resets it and saves the pointer to the next cell to read. + ld (ix + PLY_LW_SoundEffectData_OffsetCurrentStep),0 + ld (ix + 0),l + ld (ix + 1),h + ret +PLY_LW_PSES_NotReached: + ;Speed not reached. Increases it, that's all. The same cell will be read next time. + inc (ix + PLY_LW_SoundEffectData_OffsetCurrentStep) + ret + + IFDEF PLY_CFG_SFX_HardOnly ;CONFIG SPECIFIC + ;Hardware only. + ;------------------------------------------- +PLY_LW_PSES_HardwareOnly: + ;Calls the shared code that manages everything. + call PLY_LW_PSES_Shared_ReadRetrigHardwareEnvPeriodNoise + ;Cuts the sound. + set 2,c + + jr PLY_LW_PSES_SavePointerAndExit + ENDIF ;PLY_CFG_SFX_HardOnly + + + +PLY_LW_PSES_SoftwareOrSoftwareAndHardware: + ;Software only? + rra + IFDEF PLY_CFG_SFX_SoftAndHard ;CONFIG SPECIFIC + jr c,PLY_LW_PSES_SoftwareAndHardware + ENDIF ;PLY_CFG_SFX_SoftAndHard + + ;Software. + ;------------------------------------------- + IFDEF PLY_CFG_SFX_SoftOnly ;CONFIG SPECIFIC + ;Volume. + call PLY_LW_PSES_ManageVolumeFromA_Filter4Bits + + ;Noise? + rl b + IFDEF PLY_CFG_SFX_SoftOnly_Noise ;CONFIG SPECIFIC + call c,PLY_LW_PSES_ReadNoiseAndOpenNoiseChannel + ENDIF ;PLY_CFG_SFX_SoftOnly_Noise + + ;Opens the "sound" channel. + res 2,c + + ;Reads the software period. + call PLY_LW_PSES_ReadSoftwarePeriod + + jr PLY_LW_PSES_SavePointerAndExit + ENDIF ;PLY_CFG_SFX_SoftOnly + + + ;Software and Hardware. + ;------------------------------------------- + IFDEF PLY_LW_SE_HardwareSounds ;CONFIG SPECIFIC +PLY_LW_PSES_SoftwareAndHardware: + ;Calls the shared code that manages everything. + call PLY_LW_PSES_Shared_ReadRetrigHardwareEnvPeriodNoise + + ;Reads the software period. + call PLY_LW_PSES_ReadSoftwarePeriod + + ;Opens the sound. + res 2,c + + jr PLY_LW_PSES_SavePointerAndExit + ENDIF ;PLY_LW_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_LW_PSES_Shared_ReadRetrigHardwareEnvPeriodNoise: + ;Retrig? + rra + IFDEF PLY_LW_SE_Retrig ;CONFIG SPECIFIC + jr nc,PLY_LW_PSES_H_AfterRetrig + ld d,a + ld a,255 + ld (PLY_LW_SetReg13Old + 1),a + ld a,d +PLY_LW_PSES_H_AfterRetrig: + ENDIF ;PLY_LW_SE_Retrig + + ;The hardware envelope can be set (8-15). + and %111 + add a,8 + ld (PLY_LW_SetReg13 + 1),a + + ;Noise? + IFDEF PLY_LW_SE_HardwareNoise ;CONFIG SPECIFIC. B not needed after, we can put it in the condition too. + rl b + call c,PLY_LW_PSES_ReadNoiseAndOpenNoiseChannel + ENDIF ;PLY_LW_SE_HardwareNoise + + ;Reads the hardware period. + call PLY_LW_PSES_ReadHardwarePeriod + + ;Sets the volume to "hardware". It still may be decreased. + ld a,16 + jp PLY_LW_PSES_ManageVolumeFromA_Hard + ENDIF ;PLY_CFG_UseHardwareSounds + + + IFDEF PLY_LW_SE_Noise +;Reads the noise pointed by HL, increases HL, and opens the noise channel. +PLY_LW_PSES_ReadNoiseAndOpenNoiseChannel: + ;Reads the noise. + ld a,(hl) + ld (PLY_LW_NoiseRegister),a + inc hl + + ;Opens noise channel. + res 5,c + ret + ENDIF ;PLY_LW_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_LW_PSES_ReadHardwarePeriod: + ld a,(hl) + ld (PLY_LW_Reg11),a + inc hl + ld a,(hl) + ld (PLY_LW_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_LW_PSES_ReadSoftwarePeriod: + ld a,(hl) + ld (iy + PLY_LW_Registers_OffsetSoftwarePeriodLSB),a + inc hl + ld a,(hl) + ld (iy + PLY_LW_Registers_OffsetSoftwarePeriodMSB),a + inc hl + ret + + IFDEF PLY_LW_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_LW_PSES_ManageVolumeFromA_Filter4Bits: + and %1111 + ENDIF ;PLY_LW_SE_VolumeSoft + IFDEF PLY_LW_SE_VolumeSoftOrHard ;CONFIG SPECIFIC +;After the filtering. Useful for hardware sound (volume has been forced to 16). +PLY_LW_PSES_ManageVolumeFromA_Hard: + ;Decreases the volume, checks the limit. + sub (ix + PLY_LW_SoundEffectData_OffsetInvertedVolume) + jr nc,PLY_LW_PSES_MVFA_NoOverflow + xor a +PLY_LW_PSES_MVFA_NoOverflow: + ld (iy + PLY_LW_Registers_OffsetVolume),a + ret + ENDIF ;PLY_LW_SE_VolumeSoftOrHard + + +;The data of the Channels MUST be consecutive. +PLY_LW_Channel1_SoundEffectData: +dkws + dw 0 ;Points to the sound effect for the track 1, or 0 if not playing. +PLY_LW_Channel1_SoundEffectInvertedVolume: +dkwe +dkbs + db 0 ;Inverted volume. +PLY_LW_Channel1_SoundEffectCurrentStep: + db 0 ;Current step (>=0). +PLY_LW_Channel1_SoundEffectSpeed: + db 0 ;Speed (>=0). + ds 3,0 ;Padding. +dkbe +PLY_LW_Channel_SoundEffectDataSize: equ $ - PLY_LW_Channel1_SoundEffectData + +dkbs +PLY_LW_Channel2_SoundEffectData: + ds PLY_LW_Channel_SoundEffectDataSize, 0 +PLY_LW_Channel3_SoundEffectData: + ds PLY_LW_Channel_SoundEffectDataSize, 0 +dkbe + +;Offset from the beginning of the data, to reach the inverted volume. +PLY_LW_SoundEffectData_OffsetInvertedVolume: equ PLY_LW_Channel1_SoundEffectInvertedVolume - PLY_LW_Channel1_SoundEffectData +PLY_LW_SoundEffectData_OffsetCurrentStep: equ PLY_LW_Channel1_SoundEffectCurrentStep - PLY_LW_Channel1_SoundEffectData +PLY_LW_SoundEffectData_OffsetSpeed: equ PLY_LW_Channel1_SoundEffectSpeed - PLY_LW_Channel1_SoundEffectData + + ;Checks that the pointers are consecutive. + assert (PLY_LW_Channel1_SoundEffectData + PLY_LW_Channel_SoundEffectDataSize) == PLY_LW_Channel2_SoundEffectData + assert (PLY_LW_Channel2_SoundEffectData + PLY_LW_Channel_SoundEffectDataSize) == PLY_LW_Channel3_SoundEffectData + diff --git a/lib/plw/player/player.asm b/lib/plw/player/player.asm new file mode 100644 index 0000000..3c2d137 --- /dev/null +++ b/lib/plw/player/player.asm @@ -0,0 +1,43 @@ +org #0b06 + +PLY_LW_HARDWARE_CPC = 1 +PLY_LW_MANAGE_SOUND_EFFECTS = 1 +PLY_LW_USE_HOOKS = 0 +PLY_LW_STOP_SOUNDS = 1 + +PLY_CFG_ConfigurationIsPresent = 1 +PLY_CFG_SFX_ConfigurationIsPresent = 1 +PLY_CFG_SFX_SoftOnly = 1 +PLY_CFG_SFX_SoftOnly_Noise = 1 +PLY_CFG_NoSoftNoHard = 1 +PLY_CFG_SoftOnly = 1 +PLY_CFG_SoftOnly_Noise = 1 +PLY_CFG_SoftOnly_SoftwarePitch = 1 + + ; my hook table + + jp PLY_LW_Init + jp PLY_LW_Play + jp PLY_LW_Stop + jp PLY_LW_InitSoundEffects + jp PLY_LW_PlaySoundEffect + jp PLW_LW_IsSoundEffectOn + +include "PlayerLightWeight.asm" + +; IN: L = channel +; OUT: L = 0 if is not on +PLW_LW_IsSoundEffectOn: + ld a,l + add a,a + add a,a + add a,a + ld c,a + ld b,0 + ld hl,PLY_LW_Channel1_SoundEffectData + add hl,bc + ld a,(hl) + inc hl + or (hl) + ld l,a + ret -- cgit v1.2.3