;       AKY Multi-PSG music player - V1.1.
;       By Julien Névo a.k.a. Targhan/Arkos.
;       CPC PSG sending optimization trick by Madram/Overlanders.
;       2019-2025.
;
;       v1.3: Added PCW support / PCW 6-channel Turbosound support.
;       v1.2: Added digidrums and sample support.
;       v1.1: BREAKING CHANGE: Previously generated songs (such as done with AT2) are NOT compatible (hardware sounds will sound broken).
;             - Corrected a bug if using player configuration + hardware-only sound with noise (noise would disappear) (thanks Zik).
;             - Corrected another bug if using hardware-only sound with odd envelope (thanks Zik again).
;             - To remove hooks, define PLY_AKY_REMOVE_HOOKS (PLY_AKY_REMOVE_HOOKS = 1).
;
;       This compiles with RASM. Please check the compatibility page on Arkos Tracker website, there is a Source Converter (Disark) for ANY assembler!
;
;       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.
;
;       Multi-PSG
;       ----------------------
;       This player targets as many PSGs as you want! If you want to use a single PSG, it is better to use the PlayerAky.asm player, which is a bit faster/shorter.
;       However, this player allows you to use sound effects, which the single PSG source does not.
;
;       Sound effects
;       ----------------------
;       Sound effects are disabled by default. To enable them, two steps:
;       - Declare PLY_AKY_MANAGE_SOUND_EFFECTS to enable it:
;         PLY_AKY_MANAGE_SOUND_EFFECTS = 1
;       - Choose on what PSG the sound effects are played: (currently, only one PSG can have sound effects).
;         PLY_AKY_SFX_PSG_NUMBER = <psg number>    (>=1)
;         Example:
;         PLY_AKY_SFX_PSG_NUMBER = 1         for the first PSG to be the one where sound effects are played.
;
;       Check the sound effect tester for examples.
;       Note that the PRESENCE of this variable is tested, NOT its value.

;       Hardware target
;       ----------------------
;       This code targets specific hardware:
;
;       PlayCity (9 channels, Amstrad CPC)
;       TurboSound (6 channels, Spectrum)
;       SpecNext (9 channels, Spectrum)
;       FPGA Psg (6 channels, MSX)
;       Darky (6 channels, MSX)
;       All the 1-PSG usual hardware (once again, only use the current player if you need sound effects, else use "PlayerAky.asm" instead, it will be more efficient).
;       ... Or anything else if you fiddle with the constants (see below)!

;       Simply use one of the follow line (BEFORE this player):
;       PLY_AKY_HARDWARE_PLAYCITY = 1
;       PLY_AKY_HARDWARE_TURBOSOUND = 1
;       PLY_AKY_HARDWARE_SPECNEXT = 1
;       PLY_AKY_HARDWARE_FPGAPSG = 1
;       PLY_AKY_HARDWARE_DARKY = 1
;       PLY_AKY_HARDWARE_PCW_TURBOSOUND = 1
;       Or the one-PSG hardware:
;       PLY_AKY_HARDWARE_CPC = 1
;       PLY_AKY_HARDWARE_MSX = 1
;       PLY_AKY_HARDWARE_SPECTRUM = 1
;       PLY_AKY_HARDWARE_PENTAGON = 1
;       PLY_AKY_HARDWARE_PCW = 1

;       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_AKY_ROM = 1
;       PLY_AKY_ROM_Buffer = #4000 (or wherever). The buffer is PLY_AKY_ROM_BufferSize long (42 bytes, or 81 if using sound effects).
;       This makes the player a tiny bit slower.
;
;       Digidrums
;       ----------------------
;       Digidrums are supported on one channel:
;       PLY_AKY_USE_DIGIDRUMS = 1
;       PLY_AKY_DIGIDRUM_PSG_NUMBER = 1 (optional, 1 is default).
;       See the tester on how to initialize them. Samples and events must be exported from AT and included.
;
;       Samples
;       ----------------------
;       Samples are supported on all three channels, but one sample at a time.
;       PLY_AKY_USE_SAMPLE = 1
;       See the tester on how to initialize them. Samples and sample pattern must be exported from AT and included.
;
;       Optimizations
;       ----------------------
;       - Use the Player Configuration of Arkos Tracker 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.
;       - SIZE: Hooks for external calls (play/init) are present by default. Define PLY_AKY_REMOVE_HOOKS to remove them (PLY_AKY_REMOVE_HOOKS = 1).
;       - SIZE: If you don't play a song twice, all the code in PLY_AKY_Init can be removed, except the first lines that skip the header.
;       - SIZE: The header is only needed for players that want to load any song. Most of the time, you don't need it. Erase both the init code and the header bytes in the song.
;
;       -------------------------------------------------------

PLY_AKY_Start:

        ;A nice trick to manage the offset using the same instructions, according to the player (ROM or not).
        IFDEF PLY_AKY_ROM
PLY_AKY_Offset1b equ 0
        ELSE
PLY_AKY_Offset1b equ 1
        ENDIF

        IFNDEF PLY_AKY_ROM
PLY_AKY_OPCODE_OR_A equ #b7                        ;Opcode for "or a".
PLY_AKY_OPCODE_SCF equ #37                         ;Opcode for "scf".
        ELSE
        ;Another trick for the ROM player. The original opcodes are converted to number, which will be multiplied by 2, provoking a carry or not.
PLY_AKY_OPCODE_OR_A equ 0                          ;0 * 2 = 0, no carry.
PLY_AKY_OPCODE_SCF equ #ff                         ;255 * 2 = carry.
        ENDIF

        ;Simpler to declare this even if digidrums are not used.
        IFNDEF PLY_AKY_DIGIDRUM_PSG_NUMBER
                PLY_AKY_DIGIDRUM_PSG_NUMBER = 1               ;>=1. A default value.
        ENDIF

        IFDEF PLY_AKY_USE_DIGIDRUMS
                PLY_AKY_CAN_OVERRIDE_VOLUMES = 1

                IF PLY_AKY_DIGIDRUM_PSG_NUMBER <= 0
                        FAIL 'PLY_AKY_DIGIDRUM_PSG_NUMBER must be >=1!'
                ENDIF
        ENDIF

        IFDEF PLY_AKY_USE_SAMPLE
                PLY_AKY_CAN_OVERRIDE_VOLUMES = 1

                IFNDEF PLY_AKY_SAMPLE_PSG_NUMBER
                        PLY_AKY_SAMPLE_PSG_NUMBER = 1                 ;>=1. A default value.
                ENDIF

                IF PLY_AKY_SAMPLE_PSG_NUMBER <= 0
                        FAIL 'PLY_AKY_DIGIDRUM_PSG_NUMBER must be >=1!'
                ENDIF
        ENDIF

        ;Checks and sets up the basic hardware. Only one target must be selected.
        ;The "advanced" set up is at the end of the source, after the methods to send the registers to the PSGs are declared.
PLY_AKY_HardwareCounter = 0
        ;First, the 1-PSG hardware.
        IFDEF PLY_AKY_HARDWARE_CPC
                PLY_AKY_HardwareCounter = PLY_AKY_HardwareCounter + 1
                PLY_AKY_PsgCount = 1
                PLY_AKY_HARDWARE_PLAYCITY_OR_CPC = 1
        ENDIF
        IFDEF PLY_AKY_HARDWARE_MSX      ;Important to test it BEFORE the MSX related hardware, because the same constant is used!
                PLY_AKY_HardwareCounter = PLY_AKY_HardwareCounter + 1
                PLY_AKY_PsgCount = 1
                PLY_AKY_HARDWARE_MSX_STANDARD = 1
                PLY_AKY_HARDWARE_MSX_VANILLA = 1 ;A special variable is used to make the difference with multi-PSG hardware.
                PLY_AKY_HARDWARE_FAST_PSG_ACCESS = 1            ;Indicates out (xx),a is used.
        ENDIF

        IFDEF PLY_AKY_HARDWARE_SPECTRUM
                PLY_AKY_HardwareCounter = PLY_AKY_HardwareCounter + 1
                PLY_AKY_PsgCount = 1
                PLY_AKY_HARDWARE_SPECTRUM_OR_PENTAGON = 1
                PLY_AKY_HARDWARE_SPECTRUM_RELATED = 1
        ENDIF        
        IFDEF PLY_AKY_HARDWARE_PENTAGON
                PLY_AKY_HardwareCounter = PLY_AKY_HardwareCounter + 1
                PLY_AKY_PsgCount = 1
                PLY_AKY_HARDWARE_SPECTRUM_OR_PENTAGON = 1
                PLY_AKY_HARDWARE_SPECTRUM_RELATED = 1
        ENDIF
        IFDEF PLY_AKY_HARDWARE_PCW
                PLY_AKY_HardwareCounter = PLY_AKY_HardwareCounter + 1
                PLY_AKY_HARDWARE_FAST_PSG_ACCESS = 1
                PLY_AKY_PsgCount = 1
        ENDIF

        ;Then, the multi-PSG hardware.
        IFDEF PLY_AKY_HARDWARE_PLAYCITY
                PLY_AKY_HardwareCounter = PLY_AKY_HardwareCounter + 1
                PLY_AKY_PsgCount = 3
                PLY_AKY_HARDWARE_PLAYCITY_OR_CPC = 1
        ENDIF
        IFDEF PLY_AKY_HARDWARE_TURBOSOUND
                PLY_AKY_HardwareCounter = PLY_AKY_HardwareCounter + 1
                PLY_AKY_HARDWARE_TURBOSOUND_OR_SPECNEXT = 1
                PLY_AKY_HARDWARE_SPECTRUM_RELATED = 1
                PLY_AKY_PsgCount = 2
        ENDIF
        IFDEF PLY_AKY_HARDWARE_SPECNEXT
                PLY_AKY_HardwareCounter = PLY_AKY_HardwareCounter + 1
                PLY_AKY_HARDWARE_TURBOSOUND_OR_SPECNEXT = 1
                PLY_AKY_HARDWARE_SPECTRUM_RELATED = 1
                PLY_AKY_PsgCount = 3
        ENDIF
        IFDEF PLY_AKY_HARDWARE_FPGAPSG
                PLY_AKY_HardwareCounter = PLY_AKY_HardwareCounter + 1
                PLY_AKY_HARDWARE_MSX_STANDARD = 1
                PLY_AKY_HARDWARE_FAST_PSG_ACCESS = 1
                PLY_AKY_PsgCount = 2
        ENDIF
        IFDEF PLY_AKY_HARDWARE_DARKY
                PLY_AKY_HardwareCounter = PLY_AKY_HardwareCounter + 1
                PLY_AKY_HARDWARE_MSX_STANDARD = 1
                PLY_AKY_HARDWARE_FAST_PSG_ACCESS = 1
                PLY_AKY_PsgCount = 2
        ENDIF
        IFDEF PLY_AKY_HARDWARE_PCW_TURBOSOUND
                PLY_AKY_HardwareCounter = PLY_AKY_HardwareCounter + 1
                PLY_AKY_HARDWARE_FAST_PSG_ACCESS = 1
                PLY_AKY_PsgCount = 2
        ENDIF
        
        IF PLY_AKY_HardwareCounter == 0
                FAIL 'One hardware must be selected!'
        ENDIF
        IF PLY_AKY_HardwareCounter > 1
                FAIL 'Only one hardware must be selected!'
        ENDIF
        ;Makes sure the PSG Number for SFX is defined if there are sound effects.
        IFDEF PLY_AKY_MANAGE_SOUND_EFFECTS
                IFNDEF PLY_AKY_SFX_PSG_NUMBER
                        FAIL 'If sound effects, the PSG number where to play them must be declared!'
                ELSE
                        ;Also validates its value.
                        IF PLY_AKY_SFX_PSG_NUMBER < 1
                                FAIL 'The SFX PSG number must be >=1!'
                        ENDIF
                        IF PLY_AKY_SFX_PSG_NUMBER > PLY_AKY_PsgCount
                                FAIL 'The SFX PSG number is superior to the PSG count!'
                        ENDIF
                ENDIF
        ELSE
                ;If no sound effects, we make the SFX PSG number unreachable, which makes testing easier (no need to check its presence).
                PLY_AKY_SFX_PSG_NUMBER = -1
        ENDIF

        PLY_AKY_ChannelCount = PLY_AKY_PsgCount * 3

PLY_AKY_PLAYCITY_SELECTWRITE_PORT_LSB_RIGHT equ #84        ;The LSB of the PlayCity SELECT/WRITE port, for the right PSG.
PLY_AKY_PLAYCITY_SELECTWRITE_PORT_LSB_LEFT equ #88         ;The LSB of the PlayCity SELECT/WRITE port, for the left PSG.
PLY_AKY_PLAYCITY_WRITE_PORT_MSB equ #f8                    ;The MSB of the PlayCity WRITE port.
PLY_AKY_PLAYCITY_SELECT_PORT_MSB equ #f9                   ;The MSB of the PlayCity SELECT port.

;See https://www.specnext.com/turbo-sound-next/ for how this is encoded.
PLY_AKY_SPECNEXT_PSG1_REGISTER equ #ff                     ;Register to select to address the PSG1 later on, in the SpecNext.
PLY_AKY_SPECNEXT_PSG2_REGISTER equ #fe                     ;Register to select to address the PSG2 later on, in the SpecNext.
PLY_AKY_SPECNEXT_PSG3_REGISTER equ #fd                     ;Register to select to address the PSG3 later on, in the SpecNext.

PLY_AKY_TURBOSOUND_PSG1_REGISTER equ #ff                   ;Register to select to address the PSG1 later on, for the TurboSound.
PLY_AKY_TURBOSOUND_PSG2_REGISTER equ #fe                   ;Register to select to address the PSG2 later on, for the TurboSound.

PLY_AKY_PCW_TURBOSOUND_PSGNUMBER1_SELECTION_VALUE equ #ff  ;Value to select the PSG1 later on, for the PCW TurboSound.
PLY_AKY_PCW_TURBOSOUND_PSGNUMBER2_SELECTION_VALUE equ #fe  ;Value to select the PSG2 later on, for the PCW TurboSound.

        ;Is there a loaded Player Configuration source? If no, use a default configuration.
        IFNDEF PLY_CFG_ConfigurationIsPresent
                PLY_CFG_UseHardwareSounds = 1
                PLY_CFG_UseRetrig = 1
                PLY_CFG_NoSoftNoHard = 1
                PLY_CFG_NoSoftNoHard_Noise = 1
                PLY_CFG_SoftOnly = 1
                PLY_CFG_SoftOnly_Noise = 1
                PLY_CFG_SoftToHard = 1
                PLY_CFG_SoftToHard_Noise = 1
                PLY_CFG_SoftToHard_Retrig = 1
                PLY_CFG_HardOnly = 1
                PLY_CFG_HardOnly_Noise = 1
                PLY_CFG_HardOnly_Retrig = 1
                PLY_CFG_SoftAndHard = 1
                PLY_CFG_SoftAndHard_Noise = 1
                PLY_CFG_SoftAndHard_Retrig = 1
        ENDIF
        
        
        ;Agglomerates the hardware sound configuration flags, because they are treated the same in this player.
        ;-------------------------------------------------------
        IFDEF PLY_CFG_SoftToHard
                PLY_AKY_USE_SoftAndHard_Agglomerated = 1
        ENDIF
        IFDEF PLY_CFG_SoftAndHard
                PLY_AKY_USE_SoftAndHard_Agglomerated = 1
        ENDIF
        IFDEF PLY_CFG_HardToSoft
                PLY_AKY_USE_SoftAndHard_Agglomerated = 1
        ENDIF
        
        IFDEF PLY_CFG_SoftToHard_Noise
                PLY_AKY_USE_SoftAndHard_Noise_Agglomerated = 1
        ENDIF
        IFDEF PLY_CFG_SoftAndHard_Noise
                PLY_AKY_USE_SoftAndHard_Noise_Agglomerated = 1
        ENDIF
        IFDEF PLY_CFG_HardToSoft_Noise
                PLY_AKY_USE_SoftAndHard_Noise_Agglomerated = 1
        ENDIF
        IFDEF PLY_CFG_HardOnly_Noise
                PLY_AKY_USE_SoftAndHard_Noise_Agglomerated = 1
        ENDIF
        
        ;Any noise?
        IFDEF PLY_AKY_USE_SoftAndHard_Noise_Agglomerated
                PLY_AKY_USE_Noise = 1
        ENDIF
        IFDEF PLY_CFG_NoSoftNoHard_Noise
                PLY_AKY_USE_Noise = 1
        ENDIF
        IFDEF PLY_CFG_SoftOnly_Noise
                PLY_AKY_USE_Noise = 1
        ENDIF
        
 
        ;Disark macro: Word region Start.
        IFNDEF dkws
        disarkCounter = 0       ;Consider this macro is always declared when others are, so reset the counter for all of them.
        MACRO dkws
PLY_AKY_DisarkWordRegionStart_{disarkCounter}
        ENDM
        ENDIF
        ;Disark macro: Word region End.
        IFNDEF dkwe
        MACRO dkwe
PLY_AKY_DisarkWordRegionEnd_{disarkCounter}:
        disarkCounter = disarkCounter + 1
        ENDM
        ENDIF
        
        ;Disark macro: Pointer region Start.
        IFNDEF dkps
        MACRO dkps
PLY_AKY_DisarkPointerRegionStart_{disarkCounter}
        ENDM
        ENDIF
        ;Disark macro: Pointer region End.
        IFNDEF dkpe
        MACRO dkpe
PLY_AKY_DisarkPointerRegionEnd_{disarkCounter}:
        disarkCounter = disarkCounter + 1
        ENDM
        ENDIF
        
        ;Disark macro: Byte region Start.
        IFNDEF dkbs
        MACRO dkbs
PLY_AKY_DisarkByteRegionStart_{disarkCounter}
        ENDM
        ENDIF
        ;Disark macro: Byte region End.
        IFNDEF dkbe
        MACRO dkbe
PLY_AKY_DisarkByteRegionEnd_{disarkCounter}:
        disarkCounter = disarkCounter + 1
        ENDM
        ENDIF

        ;Disark macro: Force "No Reference Area" for 3 bytes (ld hl,xxxx).
        IFNDEF dknr3
        MACRO dknr3
PLY_AKY_DisarkForceNonReferenceDuring3_{disarkCounter}:
        disarkCounter = disarkCounter + 1
        ENDM
        ENDIF
        
        ;-------------------------------------------------------

        ;Hooks for external calls. Can be removed if not needed.
        IFNDEF PLY_AKY_REMOVE_HOOKS
                assert PLY_AKY_Start == $               ;Makes sure no byte has been added before the hooks.
                jp PLY_AKY_Init             ;Player + 0.
                jp PLY_AKY_Play             ;Player + 3.
        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_AKY_MANAGE_SOUND_EFFECTS
                include "PlayerAkyMultiPsg_SoundEffects.asm"
        ENDIF ;PLY_AKY_MANAGE_SOUND_EFFECTS
        ;[[INSERT_SOUND_EFFECT_SOURCE]]                 ;A tag for test units. Don't touch or you're dead.


;       Initializes the player.
;       HL = music address.
PLY_AKY_InitDisarkGenerateExternalLabel:
PLY_AKY_Init:
        ;Skips the header.
        inc hl                          ;Skips the format version.
        ld a,(hl)                       ;Channel count.
        inc hl
        
        ;Initializes the PlayCity with the frequencies of all the PSGs from the header.
        IFDEF PLY_AKY_HARDWARE_PLAYCITY
                ;Reads the first frequency, apply it to both PlayCity YMZ.
                ex de,hl
                call PLY_AKY_FindFrequencyAndSetYMZ
                ex de,hl
        ENDIF
        
        ;Skips the frequencies.
dknr3 (void): ld de,4 * PLY_AKY_PsgCount
        add hl,de
        ld (PLY_AKY_PtLinker + PLY_AKY_Offset1b),hl        ;HL now points on the Linker.

        ld a,PLY_AKY_OPCODE_OR_A
        REPEAT PLY_AKY_ChannelCount, channelNumber, 1
                ld (PLY_AKY_Channel{channelNumber}_RegisterBlockLineState_Opcode),a
        REND
dknr3 (void): ld hl,1
        ld (PLY_AKY_PatternFrameCounter + PLY_AKY_Offset1b),hl
        
        ;If sound effects, clears the SFX state.
        IFDEF PLY_AKY_MANAGE_SOUND_EFFECTS
dknr3 (void):   ld hl,0
                REPEAT 3, channelNumber, 1
                        ld (PLY_AKY_Channel{channelNumber}_SoundEffectData),hl
                REND
        ENDIF ;PLY_AKY_MANAGE_SOUND_EFFECTS
        
        ret


        ;--------------------------------------------------------------
        IFDEF PLY_AKY_HARDWARE_PLAYCITY         ;PlayCity-specific.
;Sets the YMZ frequency according to the one given. If not found, nothing is done (Atari ST is set by default, which is good, as it is the only way to select it).
;In all cases, the PlayCity is reset.
;IN:    DE = Points on the 32 bits little-endian PSG frequency that we want to set to the PlayCity.
;MOD:   DE is preserved.
PLY_AKY_FindFrequencyAndSetYMZ:
        ;Resets the PlayCity (= sets the frequencies to 2Mhz (Atari ST)).
dknr3 (void): ld bc,PLY_AKY_PLAYCITY_WRITE_PORT_MSB * 256 + #ff
        out (c),c

        ld a,PLY_AKY_PSGFrequenciesToYMZFlag_Count
        ex af,af'

        ;IX points on the frequency of the song.
        ld ixl,e
        ld ixh,d

        ;IY points on the frequency look-up table.
        ld iy,PLY_AKY_PSGFrequenciesToYMZFlag
PLY_AKY_FindFrequencyAndSetYMZ_Loop:
        ld a,(ix + 0)
        cp (iy + 0)
        jr nz,PLY_AKY_FindFrequencyAndSetYMZ_Next
        ld a,(ix + 1)
        cp (iy + 1)
        jr nz,PLY_AKY_FindFrequencyAndSetYMZ_Next
        ld a,(ix + 2)
        cp (iy + 2)
        jr nz,PLY_AKY_FindFrequencyAndSetYMZ_Next
        ld a,(ix + 3)
        cp (iy + 3)
        jr nz,PLY_AKY_FindFrequencyAndSetYMZ_Next
        ;Match!
        ld a,(iy + 4)           ;Gets the frequency index.
dknr3 (void): ld bc,PLY_AKY_PLAYCITY_WRITE_PORT_MSB * 256 + #80   ;#f880
        ld h,#7f
        out (c),h                                       ;Clock generator.
        out (c),a                                       ;Sends frequency index.

        ret

PLY_AKY_FindFrequencyAndSetYMZ_Next
        ex af,af'
        dec a
        ret z                                           ;No match! Returns (default to Atari ST).

        ex af,af'

        ;Next frequency!
dknr3 (void): ld bc,5
        add iy,bc
        jr PLY_AKY_FindFrequencyAndSetYMZ_Loop

;The frequencies (hz) to YMZ flag (index).
PLY_AKY_PSGFrequenciesToYMZFlag:
dkbs (void):
        db #40, #42, #f, 0,             1               ;1000000        CPC
        db #60, #e3, #16, #0,           2               ;1500000
        db #70, #7b, #19, #0,           3               ;1670000
        db #f0, #b3, #1a, #0,           4               ;1750000        ZX
        db #40, #77, #1b, #0,           5               ;1800000        ~MSX
        db #70, #ec, #1b, #0,           6               ;1830000
        db #a0, #61, #1c, #0,           7               ;1860000
        db #c0, #af, #1c, #0,           8               ;1880000
        db #d0, #d6, #1c, #0,           9               ;1890000
        db #e0, #fd, #1c, #0,           10              ;1900000
        db #f0, #24, #1d, #0,           11              ;1910000
        db #00, #4c, #1d, #0,           12              ;1920000
        db #00, #4c, #1d, #0,           13              ;1920000        ;No change
        db #10, #73, #1d, #0,           14              ;1930000
        db #10, #73, #1d, #0,           15              ;1930000        ;No change
        db #70, #5d, #1e, #0,           0               ;1990000        ;~ST
dkbe (void):
PLY_AKY_PSGFrequenciesToYMZFlag_End:

PLY_AKY_PSGFrequenciesToYMZFlag_Count equ (PLY_AKY_PSGFrequenciesToYMZFlag_End - PLY_AKY_PSGFrequenciesToYMZFlag) / 5

        ENDIF ;PLY_AKY_HARDWARE_PLAYCITY
        ;-------------------------------------------------------------------------------




;       Plays the music. It must have been initialized before.
;       The interruption SHOULD be disabled (DI), as the stack is heavily used.
PLY_AKY_PlayDisarkGenerateExternalLabel:
PLY_AKY_Play:
        ld (PLY_AKY_SaveSp + PLY_AKY_Offset1b),sp

        ;Special case for Darky. Must be done once before messing with the PSGs.
        IFDEF PLY_AKY_HARDWARE_DARKY
                ld a,#aa
                out (#40),a
        ENDIF


;Linker.
;----------------------------------------
        IFNDEF PLY_AKY_ROM
dknr3 (void):
PLY_AKY_PatternFrameCounter: ld hl,1                ;How many frames left before reading the next Pattern.
        ELSE
        ld hl,(PLY_AKY_PatternFrameCounter)
        ENDIF
        dec hl
        ld a,l
        or h
        jr z,PLY_AKY_PatternFrameCounter_Over
        ld (PLY_AKY_PatternFrameCounter + PLY_AKY_Offset1b),hl
        ;The pattern is not over.
        IFNDEF PLY_AKY_ROM
        jr PLY_AKY_Channel1_WaitBeforeNextRegisterBlock
        ELSE
        jr PLY_AKY_Channel1_WaitBeforeNextRegisterBlock_Start
        ENDIF

PLY_AKY_PatternFrameCounter_Over:

;The pattern is over. Reads the next one.
        IFNDEF PLY_AKY_ROM
dknr3 (void):
PLY_AKY_PtLinker: ld sp,0                                   ;Points on the Pattern of the linker.
        ELSE
        ld sp,(PLY_AKY_PtLinker)                            ;Points on the Pattern of the linker.
        ENDIF
        pop hl                                          ;Gets the duration of the Pattern, or 0 if end of the song.
        ld a,l
        or h
        jr nz,PLY_AKY_LinkerNotEndSong
        ;End of the song. Where to loop?
        pop hl
        ;We directly point on the frame counter of the pattern to loop to.
        ld sp,hl
        ;Gets the duration again. No need to check the end of the song,
        ;we know it contains at least one pattern.
        pop hl
PLY_AKY_LinkerNotEndSong:
        ld (PLY_AKY_PatternFrameCounter + PLY_AKY_Offset1b),hl

        REPEAT PLY_AKY_ChannelCount, channelNumber, 1
                pop hl
                ld (PLY_AKY_Channel{channelNumber}_PtTrack + PLY_AKY_Offset1b),hl
        REND
        ld (PLY_AKY_PtLinker + PLY_AKY_Offset1b),sp

        ;Resets the RegisterBlocks of the channel >1. The first one is skipped so there is no need to do so.
        ld a,1
        REPEAT PLY_AKY_ChannelCount - 1, channelNumber, 1
                ld (PLY_AKY_Channel{channelNumber+1}_WaitBeforeNextRegisterBlock + PLY_AKY_Offset1b),a
        REND
        jr PLY_AKY_Channel1_WaitBeforeNextRegisterBlock_Over

;Reading the Tracks.
;----------------------------------------

        REPEAT PLY_AKY_ChannelCount, channelNumber, 1                     ; ------------------------------------ REPEAT

        IFNDEF PLY_AKY_ROM
PLY_AKY_Channel{channelNumber}_WaitBeforeNextRegisterBlock: ld a,1        ;Frames to wait before reading the next RegisterBlock. 0 = finished.
        ELSE
PLY_AKY_Channel{channelNumber}_WaitBeforeNextRegisterBlock_Start:
        ld a,(PLY_AKY_Channel{channelNumber}_WaitBeforeNextRegisterBlock)
        ENDIF
        dec a
        jr nz,PLY_AKY_Channel{channelNumber}_RegisterBlock_Process
PLY_AKY_Channel{channelNumber}_WaitBeforeNextRegisterBlock_Over:
        ;This RegisterBlock is finished. Reads the next one from the Track.
        ;Obviously, starts at the initial state.
        ld a,PLY_AKY_OPCODE_OR_A
        ld (PLY_AKY_Channel{channelNumber}_RegisterBlockLineState_Opcode),a
        IFNDEF PLY_AKY_ROM
dknr3 (void):
PLY_AKY_Channel{channelNumber}_PtTrack: ld sp,0                   ;Points on the Track.
        ELSE
        ld sp,(PLY_AKY_Channel{channelNumber}_PtTrack)
        ENDIF
        dec sp                                  ;Only one byte is read. Compensate.
        pop af                                  ;Gets the duration.
        pop hl                                  ;Reads the RegisterBlock address.

        ld (PLY_AKY_Channel{channelNumber}_PtTrack + PLY_AKY_Offset1b),sp
        ld (PLY_AKY_Channel{channelNumber}_PtRegisterBlock + PLY_AKY_Offset1b),hl

        ;A is the duration of the block.
PLY_AKY_Channel{channelNumber}_RegisterBlock_Process:
        ;Processes the RegisterBlock, whether it is the current one or a new one.
        ld (PLY_AKY_Channel{channelNumber}_WaitBeforeNextRegisterBlock + PLY_AKY_Offset1b),a
        
        REND                                                            ; ------------------------------------ REPEAT END













;Reading the RegisterBlock, for each channel of each PSG.
;-----------------------------------------------------------------

;This macro takes care of reading the RegisterBlock for one channel.        
        MACRO PLY_AKY_ReadRegisterBlockMacro, channelNumber          ;-------------------------------------------------

        ld ix,PLY_AKY_Channel{channelNumber}_RegisterBase

        IFNDEF PLY_AKY_ROM
dknr3 (void):
PLY_AKY_Channel{channelNumber}_PtRegisterBlock: ld hl,0                   ;Points on the data of the RegisterBlock to read.
        ELSE
        ld hl,(PLY_AKY_Channel{channelNumber}_PtRegisterBlock)
        ENDIF

        IFNDEF PLY_AKY_ROM
PLY_AKY_Channel{channelNumber}_RegisterBlockLineState_Opcode: or a        ;"or a" if initial state, "scf" (#37) if non-initial state.
        ELSE
        ld a,(PLY_AKY_Channel{channelNumber}_RegisterBlockLineState_Opcode)
        add a,a                                             ;Carry is set according to the opcode.
        ENDIF
        jp PLY_AKY_ReadRegisterBlock
PLY_AKY_Channel{channelNumber}_RegisterBlock_Return:
        ld a,PLY_AKY_OPCODE_SCF
        ld (PLY_AKY_Channel{channelNumber}_RegisterBlockLineState_Opcode),a
        ld (PLY_AKY_Channel{channelNumber}_PtRegisterBlock + PLY_AKY_Offset1b),hl        ;This is new pointer on the RegisterBlock.
        
        ENDM                                                    ;--------------------------------------------------

        currentChannel = 1

        ld sp,PLY_AKY_RetTable_ReadRegisterBlock

        REPEAT PLY_AKY_PsgCount, psgNumber, 1                           ;-------------------------------------- LOOP FOR EACH PSG.

        ld iy,PLY_AKY_Psg{psgNumber}HardwareRegisterArray

        ;Channel 1
        ;---------
        ;In B, R7 with default values: fully sound-open but noise-close.
        ;R7 has been shift twice to the left, it will be shifted back as the channels are treated.
        ld b,%11100000

        PLY_AKY_ReadRegisterBlockMacro {currentChannel}
        currentChannel = currentChannel + 1
        
        ;Channel 2
        ;---------
        ;Shifts the R7 for the next channels.
        srl b           ;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.
        
        PLY_AKY_ReadRegisterBlockMacro {currentChannel}
        currentChannel = currentChannel + 1
        
        ;Channel 3
        ;---------
        ;Shifts the R7 for the next channels.
        IFDEF PLY_AKY_HARDWARE_MSX_STANDARD
                scf             ;On MSX, bit 7 must be 1.
                rr b
        ELSE
                rr b            ;Safe to use RR, we don't care if b7 of R7 is 0 or 1.
        ENDIF

        PLY_AKY_ReadRegisterBlockMacro {currentChannel}
        currentChannel = currentChannel + 1

        ;If digidrums, calls the "manage drums" method to set the R7 bit or not, and to set-up the sample pointer.
        IFDEF PLY_AKY_USE_DIGIDRUMS
                IFDEF PLY_AKY_USE_SAMPLE
                        FAIL 'Use either sample or digidrums, but not both!'
                ENDIF
                IF psgNumber == PLY_AKY_DIGIDRUM_PSG_NUMBER
                        jp PLY_AKY_ManageDigidrums
PLY_AKY_ManageDigidrums_Return
                ENDIF
        ENDIF
        ;If sample, calls the "manage sample" method to set the R7 bit or not, and to set-up the sample pointer.
        IFDEF PLY_AKY_USE_SAMPLE
                IFDEF PLY_AKY_USE_DIGIDRUMS
                        FAIL 'Use either sample or digidrums, but not both!'
                ENDIF
                IF psgNumber == PLY_AKY_SAMPLE_PSG_NUMBER
                        jp PLY_AKY_ManageSample
PLY_AKY_ManageSample_Return
                ENDIF
        ENDIF

        ;Sound effects?
        if PLY_AKY_SFX_PSG_NUMBER == psgNumber          ;If no SFX, the psg number is inaccessible, so nothing to worry about.
                ;If THIS PSG is the SFX PSG, we duplicate the data into a special buffer, which can be modified by the sound effects.
                ;This is necessary because AKY only updates the changing registers, so working on the primary buffers would create
                ;problems in sounds: the music wouldn't overwrite the SFX once they are finished, the next frames will be corrupted.
                ld a,b  ;Saves R7.
                ld hl,PLY_AKY_Psg{psgNumber}SoftwareRegisterArray
                ld de,PLY_AKY_SfxPsgRegisterArray
                ld bc,PLY_AKY_PsgRegisterArray_Size
                ldir

                ;Plays the sound effects.
                ;IN : A = R7
                ;OUT: A = R7, possibly modified.
                ;SP must be saved.
                ld (PLY_AKY_SaveSpTemp + PLY_AKY_Offset1b),sp
                ld sp,(PLY_AKY_SaveSp + PLY_AKY_Offset1b)       ;Gets a "normal" stack we can use.
                call PLY_AKY_PlaySoundEffectsStream
                ld b,a
                ;Restores the stack.
                IFNDEF PLY_AKY_ROM
dknr3 (void):
PLY_AKY_SaveSpTemp: ld sp,0
                ELSE
                ld sp,(PLY_AKY_SaveSpTemp)
                ENDIF
        endif

        ;Before calling the PSG register sending code, a bit of set-up must be done according to the hardware!
        
        IFDEF PLY_AKY_HARDWARE_CPC             ;Nothing to do for CPC, the code doesn't need any parameter.
                PLY_AKY_PSG_SENDING_SETUP = 1
        ENDIF
        
        IFDEF PLY_AKY_HARDWARE_SPECTRUM_OR_PENTAGON
                ;Sfx PSG or not? If yes, uses a special buffer.
                if PLY_AKY_SFX_PSG_NUMBER != psgNumber
                        ld hl,PLY_AKY_Psg{psgNumber}SoftwareRegisterArray
                else
                        ld hl,PLY_AKY_SfxPsgRegisterArray       ;SFX PSG. Uses a specific register array.
                endif

                PLY_AKY_PSG_SENDING_SETUP = 1
        ENDIF
        
        IFDEF PLY_AKY_HARDWARE_PLAYCITY
                ;Nothing to do for PlayCity PSG 2 (CPC PSG).
                if psgNumber != 2
                        ;PSG 1 or 3.
                        ;Sfx PSG or not? If yes, uses a special buffer.
                        if PLY_AKY_SFX_PSG_NUMBER != psgNumber
                                ld hl,PLY_AKY_Psg{psgNumber}SoftwareRegisterArray
                        else
                                ld hl,PLY_AKY_SfxPsgRegisterArray       ;SFX PSG. Uses a specific register array.
                        endif
                endif
                ;Selects the PlayCity PSG.
                if psgNumber == 1
                        ld c,PLY_AKY_PLAYCITY_SELECTWRITE_PORT_LSB_LEFT
                endif
                if psgNumber == 3
                        ld c,PLY_AKY_PLAYCITY_SELECTWRITE_PORT_LSB_RIGHT
                endif

                PLY_AKY_PSG_SENDING_SETUP = 1
        ENDIF
        
        IFDEF PLY_AKY_HARDWARE_SPECNEXT
                ;Sfx PSG or not? If yes, uses a special buffer.
                if PLY_AKY_SFX_PSG_NUMBER != psgNumber
                        ld hl,PLY_AKY_Psg{psgNumber}SoftwareRegisterArray
                else
                        ld hl,PLY_AKY_SfxPsgRegisterArray       ;SFX PSG. Uses a specific register array.
                endif
                ld d,PLY_AKY_SPECNEXT_PSG{psgNumber}_REGISTER

                PLY_AKY_PSG_SENDING_SETUP = 1
        ENDIF

        IFDEF PLY_AKY_HARDWARE_TURBOSOUND
                ;Sfx PSG or not? If yes, uses a special buffer.
                if PLY_AKY_SFX_PSG_NUMBER != psgNumber
                        ld hl,PLY_AKY_Psg{psgNumber}SoftwareRegisterArray
                else
                        ld hl,PLY_AKY_SfxPsgRegisterArray       ;SFX PSG. Uses a specific register array.
                endif
                ld d,PLY_AKY_TURBOSOUND_PSG{psgNumber}_REGISTER

                PLY_AKY_PSG_SENDING_SETUP = 1
        ENDIF
        
        ;Vanilla MSX, FPGA PSG or Darky.
        IFDEF PLY_AKY_HARDWARE_MSX_STANDARD
                ;Sfx PSG or not? If yes, uses a special buffer.
                if PLY_AKY_SFX_PSG_NUMBER != psgNumber
                        ld hl,PLY_AKY_Psg{psgNumber}SoftwareRegisterArray
                else
                        ld hl,PLY_AKY_SfxPsgRegisterArray       ;SFX PSG. Uses a specific register array.
                endif

                PLY_AKY_PSG_SENDING_SETUP = 1
        ENDIF

        IFDEF PLY_AKY_HARDWARE_PCW
                ;Sfx PSG or not? If yes, uses a special buffer.
                if PLY_AKY_SFX_PSG_NUMBER != psgNumber
                        ld hl,PLY_AKY_Psg{psgNumber}SoftwareRegisterArray
                else
                        ld hl,PLY_AKY_SfxPsgRegisterArray       ;SFX PSG. Uses a specific register array.
                endif

                PLY_AKY_PSG_SENDING_SETUP = 1
        ENDIF

        IFDEF PLY_AKY_HARDWARE_PCW_TURBOSOUND
                ;Sfx PSG or not? If yes, uses a special buffer.
                IF PLY_AKY_SFX_PSG_NUMBER != psgNumber
                        ld hl,PLY_AKY_Psg{psgNumber}SoftwareRegisterArray
                ELSE
                        ld hl,PLY_AKY_SfxPsgRegisterArray       ;SFX PSG. Uses a specific register array.
                ENDIF

                IF psgNumber == 1
                        ld a,PLY_AKY_PCW_TURBOSOUND_PSGNUMBER1_SELECTION_VALUE
                ELSEIF psgNumber == 2
                        ld a,PLY_AKY_PCW_TURBOSOUND_PSGNUMBER2_SELECTION_VALUE
                ELSE
                        FAIL("Illegal PSG number. Only 1 or 2 is acceptable.")
                ENDIF
                out (#aa),a      ;Register.

                PLY_AKY_PSG_SENDING_SETUP = 1
        ENDIF
        
        ;Security.
        IFNDEF PLY_AKY_PSG_SENDING_SETUP
                FAIL "You didn't set up the PSG sending!"       ;See above for how it is done.
        ENDIF


        ;Register 7 is B.
        ret                     ;Calls the PSG register sending, according to the RET table.
PLY_AKY_Psg{psgNumber}AfterSendingPsgRegisters:

        ;One last step if SFX, the PSG sending register code has probably modified the Retrig byte.
        ;This must be put back from the SFX byte array to the PSG byte array.
        if PLY_AKY_SFX_PSG_NUMBER == psgNumber          ;If no SFX, the psg number is inaccessible, so nothing to worry about.
                ld a,(PLY_AKY_SfxPsgRegisterArray + PLY_AKY_Psg1SoftwareRegisterArray_Size + PLY_AKY_PsgRegister_OffsetRetrig)  ;Skips the software array, the offset is related to the hardware array.
                ld (PLY_AKY_Psg{psgNumber}SoftwareRegisterArray + PLY_AKY_Psg1SoftwareRegisterArray_Size + PLY_AKY_PsgRegister_OffsetRetrig),a
        endif

        ;If digidrums, plays a frame of sample, if any.
        IFDEF PLY_AKY_USE_DIGIDRUMS
                IF psgNumber == PLY_AKY_DIGIDRUM_PSG_NUMBER
                        jp PLY_AKY_PlayDigidrums
PLY_AKY_PlayDigidrums_Return
                ENDIF
        ENDIF

        ;If sample, plays a frame of sample, if any.
        IFDEF PLY_AKY_USE_SAMPLE
                IF psgNumber == PLY_AKY_SAMPLE_PSG_NUMBER
                        jp PLY_AKY_PlaySample
PLY_AKY_PlaySample_Return
                ENDIF
        ENDIF

        REND                                                         ;-------------------------------------- LOOP FOR EACH PSG.



        IFNDEF PLY_AKY_ROM
dknr3 (void):
PLY_AKY_SaveSp: ld sp,0
        ELSE
        ld sp,(PLY_AKY_SaveSp)
        ENDIF
        ret









;Generic code interpreting the RegisterBlock
;IN:    HL = First byte.
;       Carry = 0 = initial state, 1 = non-initial state.
;----------------------------------------------------------------

PLY_AKY_ReadRegisterBlock:
        ;Gets the first byte of the line. What type? Jump to the matching code.
        ld a,(hl)
        inc hl
        jp c,PLY_AKY_RRB_NonInitialState
        ;Initial state.
        rra
        jr c,PLY_AKY_RRB_IS_SoftwareOnlyOrSoftwareAndHardware
        rra
                        IFDEF PLY_CFG_HardOnly  ;CONFIG SPECIFIC
        jr c,PLY_AKY_RRB_IS_HardwareOnly
                        ENDIF ;PLY_CFG_HardOnly
        ;jr PLY_AKY_RRB_IS_NoSoftwareNoHardware

;Generic code interpreting the RegisterBlock - Initial state.
;----------------------------------------------------------------
;IN:    HL = Points after the first byte.
;       A = First byte, twice shifted to the right (type removed).
;       B = Register 7. All sounds are open (0) by default, all noises closed (1). The code must put ONLY bit 2 and 5 for sound and noise respectively. NOT any other bits!
;       C = May be used as a temp.
;       DE = free to use.
;       IX = Points on the software registers array.
;       IY = Points on the hardware registers array.

;       A' = free to use (not used).
;       DE' = f4f6
;       BC' = f680
;       L' = Volume register.
;       H' = LSB frequency register.

;OUT:   HL MUST points after the structure.
;       B = updated (ONLY bit 2 and 5).
;       L' = Volume register increased of 1 (*** IMPORTANT! The code MUST increase it, even if not using it! ***)
;       H' = LSB frequency register, increased of 2 (see above).
;       DE' = unmodified (f4f6)
;       BC' = unmodified (f680)

PLY_AKY_RRB_NoiseChannelBit equ 5          ;Bit to modify to set/reset the noise channel.
PLY_AKY_RRB_SoundChannelBit equ 2          ;Bit to modify to set/reset the sound channel.

                        IFDEF PLY_CFG_NoSoftNoHard        ;CONFIG SPECIFIC
PLY_AKY_RRB_IS_NoSoftwareNoHardware:
        ;No software no hardware.
        rra                     ;Noise?
                                IFDEF PLY_CFG_NoSoftNoHard_Noise        ;CONFIG SPECIFIC
        jr nc,PLY_AKY_RRB_NIS_NoSoftwareNoHardware_ReadVolume
        ;There is a noise. Reads it.
        ld c,(hl)
        inc hl
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterNoise),c

        ;Opens the noise channel.
        res PLY_AKY_RRB_NoiseChannelBit, b
PLY_AKY_RRB_NIS_NoSoftwareNoHardware_ReadVolume:
                                ENDIF ;PLY_CFG_NoSoftNoHard_Noise
        ;The volume is now in b0-b3.
        ;and %1111      ;No need, the bit 7 is 0.
        ld (ix + PLY_AKY_PsgRegister_OffsetRegisterVolume),a

        ;Closes the sound channel.
        set PLY_AKY_RRB_SoundChannelBit, b
        ret
                        ENDIF ;PLY_CFG_NoSoftNoHard


;---------------------
                        IFDEF PLY_CFG_HardOnly  ;CONFIG SPECIFIC
PLY_AKY_RRB_IS_HardwareOnly:
        ;Retrig?
        rra
                                IFDEF PLY_CFG_HardOnly_Retrig   ;CONFIG SPECIFIC
        jr nc,PLY_AKY_RRB_IS_HO_NoRetrig
        ld (iy + PLY_AKY_PsgRegister_OffsetRetrig),255
PLY_AKY_RRB_IS_HO_NoRetrig:
                                ENDIF ;PLY_CFG_HardOnly_Retrig
        ;Noise?
        rra
                                IFDEF PLY_CFG_HardOnly_Noise   ;CONFIG SPECIFIC
        jr nc,PLY_AKY_RRB_IS_HO_NoNoise
        ;Reads the noise.
        ld c,(hl)
        inc hl
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterNoise),c

        ;Opens the noise channel.
        res PLY_AKY_RRB_NoiseChannelBit, b
PLY_AKY_RRB_IS_HO_NoNoise:
                                ENDIF ;PLY_CFG_HardOnly_Noise

        ;The envelope.
        and %1111
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterHardwareEnvelope),a

        ;Copies the hardware period.
        ld a,(hl)
        inc hl
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterHardwarePeriodLSB),a
        ld a,(hl)
        inc hl
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterHardwarePeriodMSB),a

        ;Closes the sound channel.
        set PLY_AKY_RRB_SoundChannelBit, b

        ;Sends the hardware volume.
        ld (ix + PLY_AKY_PsgRegister_OffsetRegisterVolume),16
        ret
                        ENDIF ;PLY_CFG_HardOnly

;---------------------
PLY_AKY_RRB_IS_SoftwareOnlyOrSoftwareAndHardware:
        ;Another decision to make about the sound type.
        rra
                        IFDEF PLY_AKY_USE_SoftAndHard_Agglomerated      ;CONFIG SPECIFIC
        jr c,PLY_AKY_RRB_IS_SoftwareAndHardware
                        ENDIF ;PLY_AKY_USE_SoftAndHard_Agglomerated

        ;Software only. Structure: 0vvvvntt.
        ;Noise?
        rra
                        IFDEF PLY_CFG_SoftOnly_Noise    ;CONFIG SPECIFIC
        jr nc,PLY_AKY_RRB_IS_SoftwareOnly_NoNoise
        ;Noise. Reads it.
        ld c,(hl)
        inc hl
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterNoise),c
        ;Opens the noise channel.
        res PLY_AKY_RRB_NoiseChannelBit, b
PLY_AKY_RRB_IS_SoftwareOnly_NoNoise:
                        ENDIF ;PLY_CFG_SoftOnly_Noise
        ;Reads the volume (now b0-b3).
        ;Note: we do NOT peform a "and %1111" because we know the bit 7 of the original byte is 0, so the bit 4 is currently 0. Else the hardware volume would be on!
        ld (ix + PLY_AKY_PsgRegister_OffsetRegisterVolume),a

        ;Reads the software period.
        ld a,(hl)
        inc hl
        ld (ix + PLY_AKY_PsgRegister_OffsetRegisterSoftwarePeriodLSB),a
        ld a,(hl)
        inc hl
        ld (ix + PLY_AKY_PsgRegister_OffsetRegisterSoftwarePeriodMSB),a
        ret





;---------------------
                        IFDEF PLY_AKY_USE_SoftAndHard_Agglomerated      ;CONFIG SPECIFIC
PLY_AKY_RRB_IS_SoftwareAndHardware:
        ;Retrig?
        rra
                                IFDEF PLY_CFG_UseRetrig      ;CONFIG SPECIFIC
        jr nc,PLY_AKY_RRB_IS_SAH_NoRetrig
        ld (iy + PLY_AKY_PsgRegister_OffsetRetrig),255
PLY_AKY_RRB_IS_SAH_NoRetrig:
                                ENDIF ;PLY_CFG_UseRetrig

        ;Noise?
        rra
                                IFDEF PLY_AKY_USE_SoftAndHard_Noise_Agglomerated
        jr nc,PLY_AKY_RRB_IS_SAH_NoNoise
        ;Reads the noise.
        ld c,(hl)
        inc hl
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterNoise),c

        ;Opens the noise channel.
        res PLY_AKY_RRB_NoiseChannelBit, b
PLY_AKY_RRB_IS_SAH_NoNoise:
                                ENDIF ;PLY_AKY_USE_SoftAndHard_Noise_Agglomerated

        ;The envelope.
        and %1111
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterHardwareEnvelope),a

        ;Reads the software period.
        ld a,(hl)
        inc hl
        ld (ix + PLY_AKY_PsgRegister_OffsetRegisterSoftwarePeriodLSB),a
        ld a,(hl)
        inc hl
        ld (ix + PLY_AKY_PsgRegister_OffsetRegisterSoftwarePeriodMSB),a

        ;Sends the hardware volume.
        ld (ix + PLY_AKY_PsgRegister_OffsetRegisterVolume),16

        ;Copies the hardware period.
        ld a,(hl)
        inc hl
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterHardwarePeriodLSB),a
        ld a,(hl)
        inc hl
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterHardwarePeriodMSB),a
        ret
                        ENDIF ;PLY_AKY_USE_SoftAndHard_Agglomerated







        ;Manages the loop. This code is put here so that no jump needs to be coded when its job is done.
PLY_AKY_RRB_NIS_NoSoftwareNoHardware_Loop
        ;Loops. Reads the next pointer to this RegisterBlock.
        ld a,(hl)
        inc hl
        ld h,(hl)
        ld l,a

        ;Makes another iteration to read the new data.
        ;Since we KNOW it is not an initial state (because no jump goes to an initial state), we can directly go to the right branching.
        ;Reads the first byte.
        ld a,(hl)
        inc hl
        ;jr PLY_AKY_RRB_NonInitialState

;Generic code interpreting the RegisterBlock - Non initial state. See comment about the Initial state for the registers ins/outs.
;----------------------------------------------------------------
PLY_AKY_RRB_NonInitialState:
        rra
        jr c,PLY_AKY_RRB_NIS_SoftwareOnlyOrSoftwareAndHardware
        rra
                        IFDEF PLY_CFG_HardOnly  ;CONFIG SPECIFIC
        jp c,PLY_AKY_RRB_NIS_HardwareOnly
                        ENDIF ;PLY_CFG_HardOnly

        ;No software, no hardware, OR loop.

        ld e,a
        and %11         ;Bit 3:loop?/volume bit 0, bit 2: volume?
        cp %10          ;If no volume, yet the volume is >0, it means loop.
        jr z,PLY_AKY_RRB_NIS_NoSoftwareNoHardware_Loop

        ;No loop: so "no software no hardware".
                        IFDEF PLY_CFG_NoSoftNoHard        ;CONFIG SPECIFIC

        ;Closes the sound channel.
        set PLY_AKY_RRB_SoundChannelBit, b

        ;Volume? bit 2 - 2.
        ld a,e
        rra
        jr nc,PLY_AKY_RRB_NIS_NoVolume
        and %1111
        ;Sends the volume.
        ld (ix + PLY_AKY_PsgRegister_OffsetRegisterVolume),a
PLY_AKY_RRB_NIS_NoVolume:

        ;Noise? Was on bit 7, but there has been two shifts. We can't use A, it may have been modified by the volume AND.
                                IFDEF PLY_CFG_NoSoftNoHard_Noise        ;CONFIG SPECIFIC
        bit 7 - 2, e
        ret z
        ;Noise.
        ld c,(hl)
        inc hl
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterNoise),c
        ;Opens the noise channel.
        res PLY_AKY_RRB_NoiseChannelBit, b
                                ENDIF ;PLY_CFG_NoSoftNoHard_Noise
        ret
                        ENDIF ;PLY_CFG_NoSoftNoHard






PLY_AKY_RRB_NIS_SoftwareOnlyOrSoftwareAndHardware:
        ;Another decision to make about the sound type.
        rra
                        IFDEF PLY_AKY_USE_SoftAndHard_Agglomerated      ;CONFIG SPECIFIC
        jp c,PLY_AKY_RRB_NIS_SoftwareAndHardware
                        ENDIF
                        

;---------------------
                        IFDEF PLY_CFG_SoftOnly  ;CONFIG SPECIFIC
        ;Software only. Structure: mspnoise lsp v  v  v  v  (0  1).
        ld e,a
        ;Gets the volume (already shifted).
        and %1111
        ld (ix + PLY_AKY_PsgRegister_OffsetRegisterVolume),a

        ;LSP? (Least Significant byte of Period). Was bit 6, but now shifted.
        bit 6 - 2, e
        jr z,PLY_AKY_RRB_NIS_SoftwareOnly_NoLSP
        ld a,(hl)
        inc hl
        ld (ix + PLY_AKY_PsgRegister_OffsetRegisterSoftwarePeriodLSB),a
PLY_AKY_RRB_NIS_SoftwareOnly_NoLSP:
                                
        ;MSP AND/OR (Noise and/or new Noise)? (Most Significant byte of Period).
        bit 7 - 2, e
        ret z

        ;MSP and noise?, in the next byte. in--pppp (i = isNoise? n = newNoise? p = MSB period).
        ld a,(hl)       ;Useless bits at the end, not a problem.
        inc hl
        ld (ix + PLY_AKY_PsgRegister_OffsetRegisterSoftwarePeriodMSB),a

                                IFDEF PLY_CFG_SoftOnly_Noise  ;CONFIG SPECIFIC. Can NOT be put before, because MSP must be tested.
        rla     ;Carry is isNoise?
        ret nc

        ;Opens the noise channel.
        res PLY_AKY_RRB_NoiseChannelBit, b

        ;Is there a new noise value? If yes, gets the noise.
        rla
        ret nc
        ;Gets the noise.
        ld c,(hl)
        inc hl
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterNoise),c
                                ENDIF ;PLY_CFG_SoftOnly_Noise
        ret
                        ENDIF ;PLY_CFG_SoftOnly


;---------------------
                        IFDEF PLY_CFG_HardOnly  ;CONFIG SPECIFIC
PLY_AKY_RRB_NIS_HardwareOnly
        ;Gets the envelope (initially on b2-b4, but currently on b0-b2). It is on 3 bits, must be encoded on 4. Bit 3 must be 1.
        ld e,a
        and %111
        or %1000                ;To get envelope from 8 to 15.
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterHardwareEnvelope),a

        ;Closes the sound channel.
        set PLY_AKY_RRB_SoundChannelBit, b

        ;Hardware volume.
        ld (ix + PLY_AKY_PsgRegister_OffsetRegisterVolume),16

        ld a,e

        ;LSB for hardware period? Currently on b5.
        rla
        rla
        rla
        jr nc,PLY_AKY_RRB_NIS_HardwareOnly_NoLSB
        ld c,(hl)
        inc hl
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterHardwarePeriodLSB),c
PLY_AKY_RRB_NIS_HardwareOnly_NoLSB:

        ;MSB for hardware period?
        rla
        jr nc,PLY_AKY_RRB_NIS_HardwareOnly_NoMSB
        ld c,(hl)
        inc hl
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterHardwarePeriodMSB),c
PLY_AKY_RRB_NIS_HardwareOnly_NoMSB:

        ;Noise or retrig?
        rla
        jr c,PLY_AKY_RRB_NIS_Hardware_Shared_NoiseOrRetrig_AndStop          ;The retrig/noise code is shared.

        ret
                        ENDIF ;PLY_CFG_HardOnly


;---------------------
                        IFDEF PLY_AKY_USE_SoftAndHard_Agglomerated      ;CONFIG SPECIFIC
PLY_AKY_RRB_NIS_SoftwareAndHardware:
        ;Hardware volume.
        ld (ix + PLY_AKY_PsgRegister_OffsetRegisterVolume),16

        ;LSB of hardware period?
        rra
        jr nc,PLY_AKY_RRB_NIS_SAHH_AfterLSBH
        ld c,(hl)
        inc hl
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterHardwarePeriodLSB),c
PLY_AKY_RRB_NIS_SAHH_AfterLSBH:
        ;MSB of hardware period?
        rra
        jr nc,PLY_AKY_RRB_NIS_SAHH_AfterMSBH
        ld c,(hl)
        inc hl
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterHardwarePeriodMSB),c
PLY_AKY_RRB_NIS_SAHH_AfterMSBH:

        ;LSB of software period?
        rra
        jr nc,PLY_AKY_RRB_NIS_SAHH_AfterLSBS
        ld c,(hl)
        inc hl
        ld (ix + PLY_AKY_PsgRegister_OffsetRegisterSoftwarePeriodLSB),c
PLY_AKY_RRB_NIS_SAHH_AfterLSBS:

        ;MSB of software period?
        rra
        jr nc,PLY_AKY_RRB_NIS_SAHH_AfterMSBS
        ld c,(hl)
        inc hl
        ld (ix + PLY_AKY_PsgRegister_OffsetRegisterSoftwarePeriodMSB),c
PLY_AKY_RRB_NIS_SAHH_AfterMSBS:

        ;New hardware envelope?
        rra
        jr nc,PLY_AKY_RRB_NIS_SAHH_AfterEnvelope
        ld c,(hl)
        inc hl
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterHardwareEnvelope),c
PLY_AKY_RRB_NIS_SAHH_AfterEnvelope:

        ;Retrig and/or noise?
        rra
        ret nc
                        ENDIF ;PLY_AKY_USE_SoftAndHard_Agglomerated

                        IFDEF PLY_CFG_UseHardwareSounds         ;CONFIG SPECIFIC
        ;This code is shared with the HardwareOnly. It reads the Noise/Retrig byte, interprets it and exits.
        ;------------------------------------------
PLY_AKY_RRB_NIS_Hardware_Shared_NoiseOrRetrig_AndStop:
        ;Noise or retrig. Reads the next byte.
        ld a,(hl)
        inc hl

        ;Retrig?
        rra
                                IFDEF PLY_CFG_UseRetrig         ;CONFIG SPECIFIC
        jr nc,PLY_AKY_RRB_NIS_S_NOR_NoRetrig
        ld (iy + PLY_AKY_PsgRegister_OffsetRetrig),255
PLY_AKY_RRB_NIS_S_NOR_NoRetrig:
                                ENDIF ;PLY_CFG_UseRetrig

                                IFDEF PLY_AKY_USE_SoftAndHard_Noise_Agglomerated        ;CONFIG SPECIFIC
        ;Noise? If no, nothing more to do.
        rra
        ret nc
        ;Noise. Opens the noise channel.
        res PLY_AKY_RRB_NoiseChannelBit, b
        ;Is there a new noise value? If yes, gets the noise.
        rra
        ret nc
        ;Sets the noise.
        ld (iy + PLY_AKY_PsgRegister_OffsetRegisterNoise),a
                                ENDIF ;PLY_AKY_USE_SoftAndHard_Noise_Agglomerated
        ret
                        ENDIF ;PLY_CFG_UseHardwareSounds
        
        
        
        
        
        
        
        
;--------------------------------------------------------------------------
;Codes to send values to the PSG registers. Highly target dependent.
;IN:    B = register 7.
;       SP= points on where to return. Do not modify!
;--------------------------------------------------------------------------

        IFDEF PLY_AKY_HARDWARE_PLAYCITY_OR_CPC
        
;Targets only the PSG2 (CPC Playcity) or PSG1 (CPC) (no need to do more for now).
PLY_AKY_SendPsgRegisters_CPC
        ld a,b

dknr3 (void): ld de,#c080
        ld b,#f6
        out (c),d       ;#f6c0
        exx
        IFDEF PLY_AKY_HARDWARE_PLAYCITY
                IF PLY_AKY_SFX_PSG_NUMBER != 2
                        ld hl,PLY_AKY_Psg2SoftwareRegisterArray         ;Targets only the PSG2 if PlayCity (no need to be more flexible).
                ELSE
                        ld hl,PLY_AKY_SfxPsgRegisterArray       ;SFX PSG. Uses a specific register array.
                ENDIF
        ENDIF
        IFDEF PLY_AKY_HARDWARE_CPC
                IF PLY_AKY_SFX_PSG_NUMBER != 1
                        ld hl,PLY_AKY_Psg1SoftwareRegisterArray         ;Targets only the PSG1 if CPC (no need to be more flexible).
                ELSE
                        ld hl,PLY_AKY_SfxPsgRegisterArray       ;SFX PSG. Uses a specific register array.
                ENDIF        
        ENDIF
        ld e,#f6
dknr3 (void): ld bc,#f401

;Register 0
        out (c),0       ;#f400+Register
        ld b,e
        out (c),0       ;#f600
        dec b
        outi            ;#f400+value
        exx
        out (c),e       ;#f680
        out (c),d       ;#f6c0
        exx

;Register 1
        out (c),c
        ld b,e
        out (c),0
        dec b
        outi
        exx
        out (c),e
        out (c),d
        exx
        inc c

;Register 2
        out (c),c
        ld b,e
        out (c),0
        dec b
        outi
        exx
        out (c),e
        out (c),d
        exx
        inc c

;Register 3
        out (c),c
        ld b,e
        out (c),0
        dec b
        outi
        exx
        out (c),e
        out (c),d
        exx
        inc c

;Register 4
        out (c),c
        ld b,e
        out (c),0
        dec b
        outi
        exx
        out (c),e
        out (c),d
        exx
        inc c

;Register 5
        out (c),c
        ld b,e
        out (c),0
        dec b
        outi
        exx
        out (c),e
        out (c),d
        exx
        inc c
        inc c                           ;R6 is encoded later.

;Register 7
        out (c),c
        ld b,e
        out (c),0
        dec b
        dec b
        out (c),a                       ;Read A register instead of the list.
        exx
        out (c),e
        out (c),d
        exx
        inc c

;Register 8
        IFDEF PLY_AKY_CAN_OVERRIDE_VOLUMES
            IFNDEF PLY_AKY_ROM
PLY_AKY_RegisterToSkip ld a,0           ;Now that A has been used just above, we can overwrite it.
            ELSE
            ld a,(PLY_AKY_RegisterToSkip)
            ENDIF
                ;While playing a digidrum, the player must not use its own value, else the sound cracks.
                cp 8
                jr nz,PLY_AKY_Register8
                inc hl
                jr PLY_AKY_Register8End
        ENDIF
PLY_AKY_Register8
        out (c),c
        ld b,e
        out (c),0
        dec b
        outi
        exx
        out (c),e
        out (c),d
        exx
PLY_AKY_Register8End
        inc c
        inc hl                          ;Skip padding byte.

;Register 9
        IFDEF PLY_AKY_CAN_OVERRIDE_VOLUMES
                cp 9
                jr nz,PLY_AKY_Register9
                inc hl
                jr PLY_AKY_Register9End
        ENDIF
PLY_AKY_Register9
        out (c),c
        ld b,e
        out (c),0
        dec b
        outi
        exx
        out (c),e
        out (c),d
        exx
PLY_AKY_Register9End
        inc c
        inc hl                          ;Skip padding byte.

;Register 10
        IFDEF PLY_AKY_CAN_OVERRIDE_VOLUMES
                cp 10
                jr nz,PLY_AKY_Register10
                inc hl
                jr PLY_AKY_Register10End
        ENDIF
PLY_AKY_Register10
        out (c),c
        ld b,e
        out (c),0
        dec b
        outi
        exx
        out (c),e
        out (c),d
        exx
PLY_AKY_Register10End
        inc c

                        IFDEF PLY_CFG_UseHardwareSounds         ;CONFIG SPECIFIC
;Register 11
        out (c),c
        ld b,e
        out (c),0
        dec b
        outi
        exx
        out (c),e
        out (c),d
        exx
        inc c

;Register 12
        out (c),c
        ld b,e
        out (c),0
        dec b
        outi
        exx
        out (c),e
        out (c),d
        exx
        inc c

;Register 13
PLY_AKY_PsgRegister13_Code
        ld a,(hl)
        inc hl                          ;Goes to the "retrig" value, just after R13.
        cp (hl)                         ;If IsRetrig?, force the R13 to be triggered.
                IFDEF PLY_AKY_USE_Noise         ;CONFIG SPECIFIC
        jr z,PLY_AKY_PsgRegister13_End
                ELSE
        ret z
                ENDIF ;PLY_AKY_USE_Noise
        ld (hl),a                       ;The "retrig" value becomes the R13, so that it is not played again, unless the R13 value is modified.

        out (c),c
        ld b,e
        out (c),0
        dec b
        dec b
        out (c),a
        exx
        out (c),e
        out (c),d
        exx
PLY_AKY_PsgRegister13_End:
                ELSE
                        ;No hardware. But maybe noise?
                        IFDEF PLY_AKY_USE_Noise         ;CONFIG SPECIFIC
                inc hl          ;Skips R11/R12/R13 data.
                inc hl
                inc hl
                        ENDIF ;PLY_AKY_USE_Noise
                ENDIF ;PLY_CFG_UseHardwareSounds
                        
                IFDEF PLY_AKY_USE_Noise         ;CONFIG SPECIFIC
        inc hl

;Register 6
        ld c,6
        out (c),c
        ld b,e
        out (c),0
        dec b
        outi
        exx
        out (c),e
        out (c),d
                ENDIF ;PLY_AKY_USE_Noise
        ret
        
        ENDIF ;PLY_AKY_HARDWARE_PLAYCITY_OR_CPC


        IFDEF PLY_AKY_HARDWARE_PLAYCITY
        
;Sends the registers to the PlayCity.
;IN:    B = Register7 value.
;       HL = Register list, starting at register 0.
;       C = SELECT/WRITE LSB port of the PlayCity (#84 for right channels, #88 for left channels).
PLY_AKY_SendPsgRegisters_PlayCity:
        ld a,b

        ;Sends the register 7 first, to be able to use A later.
dknr3 (void): ld de,7 * 256 + PLY_AKY_PLAYCITY_SELECT_PORT_MSB
        ld b,e
        out (c),d               ;#f984/88 to select a register (7 here).
        dec b
        out (c),a

        ;Register 0.
        xor a                   ;A = register. We could use out (c),0, but we would need to set a to 1 after. The code is cleared this way.
        ld b,e
        out (c),a               ;#f984/88 to select a register.
        outi                    ;#f884/88 to select a value. Thanks to OUTI, no need to decrease B!

        ;Register 1.
        inc a
        ld b,e
        out (c),a
        outi

        ;Register 2.
        inc a
        ld b,e
        out (c),a
        outi

        ;Register 3.
        inc a
        ld b,e
        out (c),a
        outi

        ;Register 4.
        inc a
        ld b,e
        out (c),a
        outi

        ;Register 5.
        inc a
        ld b,e
        out (c),a
        outi

        ;Register 8.
        ld a,8
        ld b,e
        out (c),a
        outi
        inc hl                          ;Skips padding byte.

        ;Register 9.
        inc a
        ld b,e
        out (c),a
        outi
        inc hl                          ;Skips padding byte.

        ;Register 10.
        inc a
        ld b,e
        out (c),a
        outi

                        IFDEF PLY_CFG_UseHardwareSounds         ;CONFIG SPECIFIC
        ;Register 11.
        inc a
        ld b,e
        out (c),a
        outi

        ;Register 12.
        inc a
        ld b,e
        out (c),a
        outi

        ;Register 13.
        inc a
        ld b,e
        out (c),a       ;Selects the register even if it won't be used. Simpler this way.

        ld a,(hl)
        inc hl
        cp (hl)                         ;If IsRetrig?, force the R13 to be triggered.
                        IFDEF PLY_AKY_USE_Noise         ;CONFIG SPECIFIC
        jr z,PLY_AKY_PsgPlayCityRegister13_End
                        ELSE
        ret z
                        ENDIF ;PLY_AKY_USE_Noise
        ld (hl),a                       ;The R13 becomes the "retrig" value, so that it is not played again, unless Retrig value is modified.
        dec b
        out (c),a
PLY_AKY_PsgPlayCityRegister13_End:
                        ELSE
                              ;No hardware but maybe noise.
                              IFDEF PLY_AKY_USE_Noise         ;CONFIG SPECIFIC
                                inc hl          ;Skips R11/R12/R13 data.
                                inc hl
                                inc hl
                              ENDIF
                        ENDIF ;PLY_CFG_UseHardwareSounds

                        IFDEF PLY_AKY_USE_Noise         ;CONFIG SPECIFIC
        inc hl                          ;Go past the IsRetrig?.
        ;Register 6.
        ld a,6
        ld b,e
        out (c),a
        outi
                        ENDIF ;PLY_AKY_USE_Noise
        ret


        ENDIF ;PLY_AKY_HARDWARE_PLAYCITY
        
        
        
        ;Used by Vanilla Spectrum, or SpecNext or Turbosound.
        IFDEF PLY_AKY_HARDWARE_SPECTRUM_RELATED

;Sends the registers to one of the PSG, for the Vanilla Spectrum, SpecNext or TurboSound.
;IN:    B = Register7 value.
;       HL = Register list, starting at register 0.
;       D = If special hardware, register to reach the PSG to use, and the possible stereo bits. Else, ignore.
PLY_AKY_SendPsgRegisters_SpectrumRelated:
        ld a,b
        ex af,af'               ;A' is now R7.

dknr3 (void): ld bc,#fffd                             ;PSG ports.
        
        ;Special out for specific hardware.
        IFDEF PLY_AKY_HARDWARE_TURBOSOUND_OR_SPECNEXT
                ;Selects the PSG + stereo bits.
                out (c),d       ;#fffd + register.
        ENDIF

dknr3 (void): ld de,#c0ff     ;#bfff + value. Use #c0 because OUTI will decrease B first.
        
        ;Register 0.
        xor a
        out (c),a
        ld b,d
        outi
        ld b,e
        
        ;Register 1.
        inc a
        out (c),a
        ld b,d
        outi
        ld b,e
        
        ;Register 2.
        inc a
        out (c),a
        ld b,d
        outi
        ld b,e
        
        ;Register 3.
        inc a
        out (c),a
        ld b,d
        outi
        ld b,e
        
        ;Register 4.
        inc a
        out (c),a
        ld b,d
        outi
        ld b,e
        
        ;Register 5.
        inc a
        out (c),a
        ld b,d
        outi
        ld b,e
        
        ;Register 7.
        inc a                           ;Skips R6.
        inc a
        out (c),a
        ld b,d
        dec b                           ;#c0 to #bf.
        ex af,af'                       ;Restores R7.
        out (c),a
        ex af,af'
        ld b,e
        
        ;Register 8.
        IFDEF PLY_AKY_CAN_OVERRIDE_VOLUMES
            IFNDEF PLY_AKY_ROM
PLY_AKY_RegisterToSkip ld a,0
            ELSE
            ld a,(PLY_AKY_RegisterToSkip)
            ENDIF
                ;While playing a digidrum, the player must not use its own value, else the sound cracks.
                cp 8
                jr nz,PLY_AKY_Register8
                inc hl
                jr PLY_AKY_Register8End
PLY_AKY_Register8 ld a,8
        ELSE
                inc a
        ENDIF
        out (c),a
        ld b,d
        outi
        ld b,e
PLY_AKY_Register8End
        inc hl                          ;Skips padding byte.
        
        ;Register 9.
        IFDEF PLY_AKY_CAN_OVERRIDE_VOLUMES
                ;While playing a digidrum, the player must not use its own value, else the sound cracks.
                ld a,(PLY_AKY_RegisterToSkip + PLY_AKY_Offset1b)
                cp 9
                jr nz,PLY_AKY_Register9
                inc hl
                jr PLY_AKY_Register9End
PLY_AKY_Register9 ld a,9
        ELSE
                inc a
        ENDIF
        out (c),a
        ld b,d
        outi
        ld b,e
PLY_AKY_Register9End
        inc hl                          ;Skips padding byte.
        
        ;Register 10.
        IFDEF PLY_AKY_CAN_OVERRIDE_VOLUMES
                ;While playing a digidrum, the player must not use its own value, else the sound cracks.
                ld a,(PLY_AKY_RegisterToSkip + PLY_AKY_Offset1b)
                cp 10
                jr z,PLY_AKY_Register10End
        ld a,10
        ELSE
                inc a
        ENDIF
        out (c),a
        ld b,d
        outi
        ld b,e
PLY_AKY_Register10End
        
                        IFDEF PLY_CFG_UseHardwareSounds         ;CONFIG SPECIFIC
        ;Register 11.
        inc a
        out (c),a
        ld b,d
        outi
        ld b,e
        
        ;Register 12.
        inc a
        out (c),a
        ld b,d
        outi
        ld b,e
        
        ;Register 13.
        inc a
        out (c),a       ;Selects the register even if it won't be used. Simpler this way.

        ld a,(hl)
        inc hl
        cp (hl)                         ;If IsRetrig?, force the R13 to be triggered.
                        IFDEF PLY_AKY_USE_Noise         ;CONFIG SPECIFIC
        jr z,PLY_AKY_PsgSpectrumRelatedRegister13_End
                        ELSE
        ret z
                        ENDIF ;PLY_AKY_USE_Noise
        ld (hl),a                       ;The R13 becomes the "retrig" value, so that it is not played again, unless Retrig value is modified.
        ld b,d
        dec b                           ;#c0 to #bf.
        out (c),a
        ld b,e
PLY_AKY_PsgSpectrumRelatedRegister13_End
                        ELSE
                              ;No hardware but maybe noise.
                              IFDEF PLY_AKY_USE_Noise         ;CONFIG SPECIFIC
                                inc hl          ;Skips R11/R12/R13 data.
                                inc hl
                                inc hl
                              ENDIF
                        ENDIF ;PLY_CFG_UseHardwareSounds
                        
                        IFDEF PLY_AKY_USE_Noise         ;CONFIG SPECIFIC
        inc hl                          ;Go past the IsRetrig?.

        ;Register 6.
        ld a,6
        out (c),a
        ld b,d
        outi
                        ENDIF ;PLY_AKY_USE_Noise
        ret

   
        ENDIF ;PLY_AKY_HARDWARE_SPECTRUM_RELATED
   
   
   
   
   
        IFDEF PLY_AKY_HARDWARE_FAST_PSG_ACCESS

        ;This generates a method which targets specific MSX port for the register and value. These are also put in the method name.
        ;The PSG Number is useful to know if the digidrum labels must be generated.
        MACRO PLY_AKY_GenerateSendPsgRegistersFast hexPortRegister,hexPortValue,psgNumber

;Sends the registers to one of the PSG, for lucky hardware with simple and fast OUTs (register/value).
;IN:    B = Register7 value.
;       HL = Register list, starting at register 0.
PLY_AKY_SendPsgRegisters_FastPsgAccess_{hexPortRegister}_{hexPortValue}
        ;Register 7.
        ld a,7
        out (#{hexPortRegister}),a      ;Register.
        ld a,b
        out (#{hexPortValue}),a         ;Value.
        
        ;Register 0.
        xor a
        out (#{hexPortRegister}),a
        ld a,(hl)
        inc hl
        out (#{hexPortValue}),a
        
        ;Register 1.
        ld a,1
        out (#{hexPortRegister}),a
        ld a,(hl)
        inc hl
        out (#{hexPortValue}),a
        
        ;Register 2.
        ld a,2
        out (#{hexPortRegister}),a
        ld a,(hl)
        inc hl
        out (#{hexPortValue}),a
        
        ;Register 3.
        ld a,3
        out (#{hexPortRegister}),a
        ld a,(hl)
        inc hl
        out (#{hexPortValue}),a
        
        ;Register 4.
        ld a,4
        out (#{hexPortRegister}),a
        ld a,(hl)
        inc hl
        out (#{hexPortValue}),a
        
        ;Register 5.
        ld a,5
        out (#{hexPortRegister}),a
        ld a,(hl)
        inc hl
        out (#{hexPortValue}),a
        
        ;Register 8 (R6 is sent the last one, R7 has already been sent).
        IFDEF PLY_AKY_CAN_OVERRIDE_VOLUMES
                IF {psgNumber} == PLY_AKY_DIGIDRUM_PSG_NUMBER
                        IFNDEF PLY_AKY_ROM
        PLY_AKY_RegisterToSkip ld a,0
                        ELSE
                        ld a,(PLY_AKY_RegisterToSkip)
                        ENDIF
                        ;While playing a digidrum, the player must not use its own value, else the sound cracks.
                        cp 8
                        jr z,PLY_AKY_Register8End
                ENDIF
        ENDIF
        ld a,8
        out (#{hexPortRegister}),a
        ld a,(hl)
        out (#{hexPortValue}),a
        IFDEF PLY_AKY_CAN_OVERRIDE_VOLUMES
                IF {psgNumber} == PLY_AKY_DIGIDRUM_PSG_NUMBER
PLY_AKY_Register8End
                ENDIF
        ENDIF
        inc hl
        inc hl                          ;Skips padding byte.

        ;Register 9.
        IFDEF PLY_AKY_CAN_OVERRIDE_VOLUMES
                ;While playing a digidrum, the player must not use its own value, else the sound cracks.
                ld a,(PLY_AKY_RegisterToSkip + PLY_AKY_Offset1b)
                cp 9
                jr z,PLY_AKY_Register9End
        ENDIF
        ld a,9
        out (#{hexPortRegister}),a
        ld a,(hl)
        out (#{hexPortValue}),a
        IF {psgNumber} == PLY_AKY_DIGIDRUM_PSG_NUMBER
PLY_AKY_Register9End
        ENDIF
        inc hl
        inc hl                          ;Skips padding byte.

        ;Register 10.
        IFDEF PLY_AKY_CAN_OVERRIDE_VOLUMES
                ;While playing a digidrum, the player must not use its own value, else the sound cracks.
                ld a,(PLY_AKY_RegisterToSkip + PLY_AKY_Offset1b)
                cp 10
                jr z,PLY_AKY_Register10End
        ENDIF
        ld a,10
        out (#{hexPortRegister}),a
        ld a,(hl)
        out (#{hexPortValue}),a
        IF {psgNumber} == PLY_AKY_DIGIDRUM_PSG_NUMBER
PLY_AKY_Register10End
        ENDIF
        inc hl

                        IFDEF PLY_CFG_UseHardwareSounds         ;CONFIG SPECIFIC
        ;Register 11.
        ld a,11
        out (#{hexPortRegister}),a
        ld a,(hl)
        inc hl
        out (#{hexPortValue}),a
        
        ;Register 12.
        ld a,12
        out (#{hexPortRegister}),a
        ld a,(hl)
        inc hl
        out (#{hexPortValue}),a
        
        ;Register 13.
        ld a,13
        out (#{hexPortRegister}),a       ;Selects the register even if it won't be used. Simpler this way.
        
        ld a,(hl)
        inc hl
        cp (hl)                         ;If IsRetrig?, force the R13 to be triggered.
                        IFDEF PLY_AKY_USE_Noise         ;CONFIG SPECIFIC
        jr z,PLY_AKY_SendPsgRegisters_FastPsgAccess_{hexPortRegister}_{hexPortValue}_Register13_End
                        ELSE
        ret z
                        ENDIF ;PLY_AKY_USE_Noise
        ld (hl),a                       ;The R13 becomes the "retrig" value, so that it is not played again, unless Retrig value is modified.
        out (#{hexPortValue}),a

PLY_AKY_SendPsgRegisters_FastPsgAccess_{hexPortRegister}_{hexPortValue}_Register13_End
                        ELSE
                              ;No hardware but maybe noise.
                              IFDEF PLY_AKY_USE_Noise         ;CONFIG SPECIFIC
                                inc hl          ;Skips R11/R12/R13 data.
                                inc hl
                                inc hl
                              ENDIF
                        ENDIF ;PLY_CFG_UseHardwareSounds

                        IFDEF PLY_AKY_USE_Noise         ;CONFIG SPECIFIC
        inc hl                          ;Go past the IsRetrig?.
        
        ;Register 6.
        ld a,6
        out (#{hexPortRegister}),a
        ld a,(hl)
        out (#{hexPortValue}),a
                        ENDIF ;PLY_AKY_USE_Noise
        ret
        
        ENDM
        
        ;Generates the two "send PSG methods" according to the hardware (one for each PSG).
        IFDEF PLY_AKY_HARDWARE_FPGAPSG
                PLY_AKY_GenerateSendPsgRegistersFast a0,a1,1
                PLY_AKY_GenerateSendPsgRegistersFast 10,11,2
        ENDIF
        
        IFDEF PLY_AKY_HARDWARE_DARKY
                PLY_AKY_GenerateSendPsgRegistersFast 44,45,1
                PLY_AKY_GenerateSendPsgRegistersFast 4c,4d,2
        ENDIF
        
        ;The vanilla MSX has only one PSG.
        IFDEF PLY_AKY_HARDWARE_MSX_VANILLA
                PLY_AKY_GenerateSendPsgRegistersFast a0,a1,1
        ENDIF

        IFDEF PLY_AKY_HARDWARE_PCW
                PLY_AKY_GenerateSendPsgRegistersFast aa,ab,1
        ENDIF

        IFDEF PLY_AKY_HARDWARE_PCW_TURBOSOUND
                ;There are two PSGs, but the same code is used.
                ;However, this prevents the digidrums from working well as the PSG number is the same
                ;for both PSG, so the digidrums would be played on both PSGs.
                ;This would need patching, but I won't do that unless asked to
                ;(the patch could be only adding the PSG number in the name of the generated code, pretty easy).
                PLY_AKY_GenerateSendPsgRegistersFast aa,ab,1
        ENDIF


        ENDIF ;PLY_AKY_HARDWARE_FAST_PSG_ACCESS
   
   
   
   
        
        
;------------------------------------------------------
        
        ;Declares the PSG code to use for each PSG of each target.
        IFDEF PLY_AKY_HARDWARE_PLAYCITY
                ;Psg 1 is direct to CPC PSG.
                PLY_AKY_PSG1PLAYCODE = PLY_AKY_SendPsgRegisters_PlayCity
                PLY_AKY_PSG2PLAYCODE = PLY_AKY_SendPsgRegisters_CPC
                PLY_AKY_PSG3PLAYCODE = PLY_AKY_SendPsgRegisters_PlayCity
        ENDIF
        IFDEF PLY_AKY_HARDWARE_TURBOSOUND
                PLY_AKY_PSG1PLAYCODE = PLY_AKY_SendPsgRegisters_SpectrumRelated
                PLY_AKY_PSG2PLAYCODE = PLY_AKY_SendPsgRegisters_SpectrumRelated
        ENDIF
        IFDEF PLY_AKY_HARDWARE_SPECNEXT
                PLY_AKY_PSG1PLAYCODE = PLY_AKY_SendPsgRegisters_SpectrumRelated
                PLY_AKY_PSG2PLAYCODE = PLY_AKY_SendPsgRegisters_SpectrumRelated
                PLY_AKY_PSG3PLAYCODE = PLY_AKY_SendPsgRegisters_SpectrumRelated                
        ENDIF
        IFDEF PLY_AKY_HARDWARE_FPGAPSG
                PLY_AKY_PSG1PLAYCODE = PLY_AKY_SendPsgRegisters_FastPsgAccess_a0_a1
                PLY_AKY_PSG2PLAYCODE = PLY_AKY_SendPsgRegisters_FastPsgAccess_10_11

                IF PLY_AKY_DIGIDRUM_PSG_NUMBER == 1
                        PLY_AKY_FAST_PSG_SELECT_REGISTER = #a0
                        PLY_AKY_FAST_PSG_SELECT_VALUE = #a1
                ELSEIF PLY_AKY_DIGIDRUM_PSG_NUMBER == 2
                        PLY_AKY_FAST_PSG_SELECT_REGISTER = #10
                        PLY_AKY_FAST_PSG_SELECT_VALUE = #11
                ELSE
                        FAIL 'Invalid PLY_AKY_DIGIDRUM_PSG_NUMBER!'
                ENDIF
        ENDIF
        IFDEF PLY_AKY_HARDWARE_DARKY
                PLY_AKY_PSG1PLAYCODE = PLY_AKY_SendPsgRegisters_FastPsgAccess_44_45
                PLY_AKY_PSG2PLAYCODE = PLY_AKY_SendPsgRegisters_FastPsgAccess_4c_4d

                IF PLY_AKY_DIGIDRUM_PSG_NUMBER == 1
                        PLY_AKY_FAST_PSG_SELECT_REGISTER = #44
                        PLY_AKY_FAST_PSG_SELECT_VALUE = #45
                ENDIF
                IF PLY_AKY_DIGIDRUM_PSG_NUMBER == 2
                        PLY_AKY_FAST_PSG_SELECT_REGISTER = #4c
                        PLY_AKY_FAST_PSG_SELECT_VALUE = #4d
                ENDIF
        ENDIF
        
        IFDEF PLY_AKY_HARDWARE_CPC
                PLY_AKY_PSG1PLAYCODE = PLY_AKY_SendPsgRegisters_CPC

                IF PLY_AKY_DIGIDRUM_PSG_NUMBER != 1
                        FAIL 'Invalid PLY_AKY_DIGIDRUM_PSG_NUMBER!'
                ENDIF
        ENDIF
        IFDEF PLY_AKY_HARDWARE_MSX_VANILLA
                PLY_AKY_PSG1PLAYCODE = PLY_AKY_SendPsgRegisters_FastPsgAccess_a0_a1

                PLY_AKY_FAST_PSG_SELECT_REGISTER = #a0
                PLY_AKY_FAST_PSG_SELECT_VALUE = #a1

                IF PLY_AKY_DIGIDRUM_PSG_NUMBER != 1
                        FAIL 'Invalid PLY_AKY_DIGIDRUM_PSG_NUMBER!'
                ENDIF
        ENDIF
        IFDEF PLY_AKY_HARDWARE_PCW
                PLY_AKY_PSG1PLAYCODE = PLY_AKY_SendPsgRegisters_FastPsgAccess_aa_ab

                PLY_AKY_FAST_PSG_SELECT_REGISTER = #aa
                PLY_AKY_FAST_PSG_SELECT_VALUE = #ab

                IF PLY_AKY_DIGIDRUM_PSG_NUMBER != 1
                        FAIL 'Invalid PLY_AKY_DIGIDRUM_PSG_NUMBER!'
                ENDIF
        ENDIF
        IFDEF PLY_AKY_HARDWARE_PCW_TURBOSOUND
                PLY_AKY_PSG1PLAYCODE = PLY_AKY_SendPsgRegisters_FastPsgAccess_aa_ab
                PLY_AKY_PSG2PLAYCODE = PLY_AKY_SendPsgRegisters_FastPsgAccess_aa_ab

                IFDEF PLY_AKY_USE_DIGIDRUMS
                        FAIL 'Digidrums are not available right now.' ;See above (PSG generation) about the reason.
                ENDIF
        ENDIF
        IFDEF PLY_AKY_HARDWARE_SPECTRUM_OR_PENTAGON
                PLY_AKY_PSG1PLAYCODE = PLY_AKY_SendPsgRegisters_SpectrumRelated
        ENDIF


;RET table for the Read RegisterBlock code to know where to return, and then points
;to the PSG sending code.
PLY_AKY_RetTable_ReadRegisterBlock:
dkps (void):
        channelNumber = 1
        REPEAT PLY_AKY_PsgCount, psgNumber, 1
                REPEAT 3
                        dw PLY_AKY_Channel{channelNumber}_RegisterBlock_Return
                        channelNumber = channelNumber + 1
                REND
                ;Plugs the Psg play code for this PSG. It has been set by the hardware declaration at the beginning.
                dw PLY_AKY_Psg{psgNumber}PlayCode
                dw PLY_AKY_Psg{psgNumber}AfterSendingPsgRegisters
        REND
dkpe (void):        



        IFNDEF PLY_AKY_ROM
dkbs (void):
;The PSG registers. Note than the Register 7 (mixer) is not present, because it is passed inside a register.
PLY_AKY_Psg1SoftwareRegisterArray:
PLY_AKY_Psg1Register0:      db 0    ;Register 0.
PLY_AKY_Psg1Register1:      db 0    ;Register 1.
PLY_AKY_Psg1Register2:      db 0    ;Register 2.
PLY_AKY_Psg1Register3:      db 0    ;Register 3.
PLY_AKY_Psg1Register4:      db 0    ;Register 4.
PLY_AKY_Psg1Register5:      db 0    ;Register 5.
;No Reg6 (noise)!
;No Reg7 (mix)!
PLY_AKY_Psg1Register8:      db 0    ;Register 8.
        db 0                    ;A byte to skip, needed to allow index registers when filling the register with a generic code.
PLY_AKY_Psg1Register9:      db 0    ;Register 9.
        db 0                    ;A byte to skip, same as above.
PLY_AKY_Psg1Register10:     db 0    ;Register 10.
PLY_AKY_Psg1SoftwareRegisterArray_End:
;The hardware register array MUST be stuck to the software register array.
PLY_AKY_Psg1HardwareRegisterArray
PLY_AKY_Psg1Register11:     db 0    ;Register 11.
PLY_AKY_Psg1Register12:     db 0    ;Register 12.
PLY_AKY_Psg1Register13:     db 0    ;Register 13.
PLY_AKY_Psg1Retrig:         db 0    ;Retrig value, must be just after Register13.
PLY_AKY_Psg1Noise:          db 0    ;Noise.
PLY_AKY_Psg1HardwareRegisterArray_End:
dkbe (void):
        ASSERT PLY_AKY_Psg1HardwareRegisterArray == PLY_AKY_Psg1SoftwareRegisterArray_End

        ELSE
        ;Rom buffer.
        ;Bytes first.
PLY_AKY_ROM_BufferSize = 0
PLY_AKY_Psg1Register0                   equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
PLY_AKY_Psg1SoftwareRegisterArray       equ PLY_AKY_Psg1Register0
PLY_AKY_Psg1Register1                   equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
PLY_AKY_Psg1Register2                   equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
PLY_AKY_Psg1Register3                   equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
PLY_AKY_Psg1Register4                   equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
PLY_AKY_Psg1Register5                   equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
PLY_AKY_Psg1Register8                   equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
        PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1     ;A byte to skip, see above as to why.
PLY_AKY_Psg1Register9                   equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
        PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1     ;A byte to skip, see above as to why.
PLY_AKY_Psg1Register10                  equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
PLY_AKY_Psg1Register11                  equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
PLY_AKY_Psg1SoftwareRegisterArray_End   equ PLY_AKY_Psg1Register11
PLY_AKY_Psg1HardwareRegisterArray       equ PLY_AKY_Psg1SoftwareRegisterArray_End
PLY_AKY_Psg1Register12                  equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
PLY_AKY_Psg1Register13                  equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
PLY_AKY_Psg1Retrig                      equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
PLY_AKY_Psg1Noise                       equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
        ;Words.
PLY_AKY_PtLinker                        equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
PLY_AKY_Psg1HardwareRegisterArray_End   equ PLY_AKY_PtLinker
PLY_AKY_SaveSp                          equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
PLY_AKY_SaveSpTemp                      equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
PLY_AKY_PatternFrameCounter             equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
        REPEAT PLY_AKY_ChannelCount, ChannelNumber, 1
PLY_AKY_Channel{ChannelNumber}_PtTrack equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
        REND
        REPEAT PLY_AKY_ChannelCount, ChannelNumber, 1
PLY_AKY_Channel{ChannelNumber}_PtRegisterBlock equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
        REND
        ;More data...
        REPEAT PLY_AKY_ChannelCount, ChannelNumber, 1
PLY_AKY_Channel{ChannelNumber}_WaitBeforeNextRegisterBlock      equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
        REND
        REPEAT PLY_AKY_ChannelCount, ChannelNumber, 1
PLY_AKY_Channel{ChannelNumber}_RegisterBlockLineState_Opcode    equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
        REND
        
        ;The buffers for sound effects (if any), for each channel. They are treated apart, because they must be consecutive.
        IFDEF PLY_AKY_MANAGE_SOUND_EFFECTS
PLY_AKY_PtSoundEffectTable                                      equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
                REPEAT 3, channelNumber, 1
PLY_AKY_Channel{channelNumber}_SoundEffectData                  equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
PLY_AKY_Channel{channelNumber}_SoundEffectInvertedVolume        equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
PLY_AKY_Channel{channelNumber}_SoundEffectCurrentStep           equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
PLY_AKY_Channel{channelNumber}_SoundEffectSpeed                 equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
        if channelNumber != 3
               PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 3 ;Padding of 3, but only necessary for channel 1 and 2.
        endif
                REND
        ENDIF

        IFDEF PLY_AKY_USE_DIGIDRUMS
PLY_AKY_RegisterToSkip                                          equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
PLY_AKY_SampleVolumeRegister                                    equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
PLY_AKY_PT_SampleTable                                          equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
PLY_AKY_ReadEventsWait                                          equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
PLY_AKY_PT_Events                                               equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
PLY_AKY_PT_Sample                                               equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
PLY_AKY_PT_SampleEnd                                            equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
PLY_AKY_R7CloseChannelOpcode    set 1,b                 ;Modified.
        jp PLY_AKY_R7CloseChannelOpcode_Return
        ENDIF

        IFDEF PLY_AKY_USE_SAMPLE
PLY_AKY_RegisterToSkip                                          equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
PLY_AKY_SPL_SampleVolumeRegister                                equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 1
PLY_AKY_SPL_ManageSampleWait                                    equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
PLY_AKY_SPL_PT_Pattern                                          equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
PLY_AKY_SPL_PT_SampleTable                                      equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
PLY_AKY_SPL_Sample                                              equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
PLY_AKY_SPL_SampleEnd                                           equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
PLY_AKY_SPL_SampleLoop                                          equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
PLY_AKY_SPL_StepIntegerAndDecimal                               equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + 2
PLY_AKY_SPL_R7CloseChannelOpcode    set 1,b                 ;Modified.
        jp PLY_AKY_SPL_R7CloseChannelOpcode_Return
        ENDIF



        ENDIF ;PLY_AKY_ROM


PLY_AKY_Psg1SoftwareRegisterArray_Size equ PLY_AKY_Psg1SoftwareRegisterArray_End - PLY_AKY_Psg1SoftwareRegisterArray
PLY_AKY_Psg1HardwareRegisterArray_Size equ PLY_AKY_Psg1HardwareRegisterArray_End - PLY_AKY_Psg1HardwareRegisterArray
PLY_AKY_PsgRegisterArray_Size = PLY_AKY_Psg1SoftwareRegisterArray_Size + PLY_AKY_Psg1HardwareRegisterArray_Size

;Offsets to reach the registers in a generic way with an offset, according to the channel.
PLY_AKY_PsgRegister_OffsetRegisterVolume equ PLY_AKY_Psg1Register8 - PLY_AKY_Psg1SoftwareRegisterArray
PLY_AKY_PsgRegister_OffsetRegisterSoftwarePeriodLSB equ PLY_AKY_Psg1Register0 - PLY_AKY_Psg1SoftwareRegisterArray
PLY_AKY_PsgRegister_OffsetRegisterSoftwarePeriodMSB equ PLY_AKY_Psg1Register1 - PLY_AKY_Psg1SoftwareRegisterArray

PLY_AKY_PsgRegister_OffsetRegisterHardwarePeriodLSB equ PLY_AKY_Psg1Register11 - PLY_AKY_Psg1HardwareRegisterArray
PLY_AKY_PsgRegister_OffsetRegisterHardwarePeriodMSB equ PLY_AKY_Psg1Register12 - PLY_AKY_Psg1HardwareRegisterArray
PLY_AKY_PsgRegister_OffsetRegisterHardwareEnvelope equ PLY_AKY_Psg1Register13 - PLY_AKY_Psg1HardwareRegisterArray
PLY_AKY_PsgRegister_OffsetRetrig equ PLY_AKY_Psg1Retrig - PLY_AKY_Psg1HardwareRegisterArray
PLY_AKY_PsgRegister_OffsetRegisterNoise equ PLY_AKY_Psg1Noise - PLY_AKY_Psg1HardwareRegisterArray

;The registers array for the **other** PSGs, and also the SFX buffer if needed.
dkbs (void):
        psgNumber = 2
        WHILE psgNumber <= PLY_AKY_PsgCount
        
                IFNDEF PLY_AKY_ROM        
PLY_AKY_Psg{psgNumber}SoftwareRegisterArray:
        ds PLY_AKY_Psg1SoftwareRegisterArray_Size, 0            ;The same size as for PSG 1.
PLY_AKY_Psg{psgNumber}HardwareRegisterArray:
        ds PLY_AKY_Psg1HardwareRegisterArray_Size, 0            ;The same size as for PSG 1.
                ELSE
                ;Rom
PLY_AKY_Psg{psgNumber}SoftwareRegisterArray equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + PLY_AKY_Psg1SoftwareRegisterArray_Size
PLY_AKY_Psg{psgNumber}HardwareRegisterArray equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + PLY_AKY_Psg1HardwareRegisterArray_Size
                ENDIF ;PLY_AKY_ROM
        psgNumber = psgNumber + 1
        WEND
        
        ;Declares a SFX register array, if SFXs are used.
        IFDEF PLY_AKY_MANAGE_SOUND_EFFECTS
                IFNDEF PLY_AKY_ROM
PLY_AKY_SfxPsgRegisterArray:
        ds PLY_AKY_Psg1SoftwareRegisterArray_Size, 0            ;The same size as for PSG 1.
        ds PLY_AKY_Psg1HardwareRegisterArray_Size, 0
                ELSE
PLY_AKY_SfxPsgRegisterArray equ PLY_AKY_ROM_Buffer + PLY_AKY_ROM_BufferSize : PLY_AKY_ROM_BufferSize = PLY_AKY_ROM_BufferSize + PLY_AKY_Psg1SoftwareRegisterArray_Size + PLY_AKY_Psg1HardwareRegisterArray_Size
                ENDIF
                                
        ;Declares the SFX address to the SFX buffer.
PLY_AKY_SfxReg8 equ PLY_AKY_SfxPsgRegisterArray + PLY_AKY_PsgRegister_OffsetRegisterVolume + 0
PLY_AKY_SfxReg9 equ PLY_AKY_SfxPsgRegisterArray + PLY_AKY_PsgRegister_OffsetRegisterVolume + 2         ;One byte in between is padding!
PLY_AKY_SfxReg10 equ PLY_AKY_SfxPsgRegisterArray + PLY_AKY_PsgRegister_OffsetRegisterVolume + 4        ;One byte in between is padding!
PLY_AKY_SfxReg01 equ PLY_AKY_SfxPsgRegisterArray + PLY_AKY_PsgRegister_OffsetRegisterSoftwarePeriodLSB + 0
        assert PLY_AKY_PsgRegister_OffsetRegisterSoftwarePeriodLSB == (PLY_AKY_PsgRegister_OffsetRegisterSoftwarePeriodMSB - 1) ;Makes sure the two regs are consecutive!
PLY_AKY_SfxReg23 equ PLY_AKY_SfxPsgRegisterArray + PLY_AKY_PsgRegister_OffsetRegisterSoftwarePeriodLSB + 2
PLY_AKY_SfxReg45 equ PLY_AKY_SfxPsgRegisterArray + PLY_AKY_PsgRegister_OffsetRegisterSoftwarePeriodLSB + 4
        ;Hardware part. A "hardware part offset" must be added!
PLY_AKY_SfxReg6 equ PLY_AKY_SfxPsgRegisterArray + PLY_AKY_Psg1SoftwareRegisterArray_Size + PLY_AKY_PsgRegister_OffsetRegisterNoise
PLY_AKY_SfxHardwarePeriod equ PLY_AKY_SfxPsgRegisterArray + PLY_AKY_Psg1SoftwareRegisterArray_Size + PLY_AKY_PsgRegister_OffsetRegisterHardwarePeriodLSB
        assert PLY_AKY_PsgRegister_OffsetRegisterHardwarePeriodLSB == (PLY_AKY_PsgRegister_OffsetRegisterHardwarePeriodMSB - 1) ;Makes sure the two regs are consecutive!
PLY_AKY_SfxReg13 equ PLY_AKY_SfxPsgRegisterArray + PLY_AKY_Psg1SoftwareRegisterArray_Size + PLY_AKY_PsgRegister_OffsetRegisterHardwareEnvelope
PLY_AKY_SfxRetrig equ PLY_AKY_SfxPsgRegisterArray + PLY_AKY_Psg1SoftwareRegisterArray_Size + PLY_AKY_PsgRegister_OffsetRetrig

        ENDIF
dkbe (void):

;Declares the base pointer on the PSG structure for each channel.
        channelNumber = 1
        WHILE channelNumber <= PLY_AKY_ChannelCount
                psgNumber = floor(((channelNumber - 1) / 3) + 1)
                channelOffset = ((channelNumber - 1) % 3) * 2          ; 0, 2, 4 for channel 1, 2, 3 respectively.

PLY_AKY_Channel{channelNumber}_RegisterBase equ PLY_AKY_Psg{psgNumber}SoftwareRegisterArray + channelOffset

        channelNumber = channelNumber + 1
        WEND

        IFDEF PLY_AKY_USE_DIGIDRUMS
                include "PlayerAkyMultiPsg_Digidrums.asm"
        ENDIF

        IFDEF PLY_AKY_USE_SAMPLE
                include "PlayerAkyMultiPsg_Sample.asm"
        ENDIF