;       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:



