;       Player of sound effects, for the AKM player.
;       Note that this is the exact same code as for the Lightweight player, as the way to output to the PSG is the same.

        ;Is there a loaded Player Configuration source? If no, use a default configuration.
        IFNDEF PLY_CFG_SFX_ConfigurationIsPresent
                PLY_CFG_UseHardwareSounds = 1
                PLY_CFG_SFX_LoopTo = 1
                PLY_CFG_SFX_NoSoftNoHard = 1
                PLY_CFG_SFX_NoSoftNoHard_Noise = 1
                PLY_CFG_SFX_SoftOnly = 1
                PLY_CFG_SFX_SoftOnly_Noise = 1
                PLY_CFG_SFX_HardOnly = 1
                PLY_CFG_SFX_HardOnly_Noise = 1
                PLY_CFG_SFX_HardOnly_Retrig = 1
                PLY_CFG_SFX_SoftAndHard = 1
                PLY_CFG_SFX_SoftAndHard_Noise = 1
                PLY_CFG_SFX_SoftAndHard_Retrig = 1
        ENDIF
        
;       Agglomerates some Player Configuration flags.
;       --------------------------------------------
;       Mixes the Hardware flags into one.
        IFDEF PLY_CFG_SFX_HardOnly
                PLY_AKM_SE_HardwareSounds = 1
        ENDIF
        IFDEF PLY_CFG_SFX_SoftAndHard
                PLY_AKM_SE_HardwareSounds = 1
        ENDIF
;       Mixes the Hardware Noise flags into one.
        IFDEF PLY_CFG_SFX_HardOnly_Noise
                PLY_AKM_SE_HardwareNoise = 1
        ENDIF
        IFDEF PLY_CFG_SFX_SoftAndHard_Noise
                PLY_AKM_SE_HardwareNoise = 1
        ENDIF
;       Mixes the Noise flags into one.
        IFDEF PLY_AKM_SE_HardwareNoise
                PLY_AKM_SE_Noise = 1
        ENDIF
        IFDEF PLY_CFG_SFX_NoSoftNoHard_Noise
                PLY_AKM_SE_Noise = 1
        ENDIF
        IFDEF PLY_CFG_SFX_SoftOnly
                PLY_AKM_SE_Noise = 1
        ENDIF
;       Noise in Sound Effects? Then noise register code must be compiled.
        IFDEF PLY_AKM_SE_Noise
                PLY_AKM_USE_NoiseRegister = 1
        ENDIF
;       Mixes the Software Volume flags into one.
        IFDEF PLY_CFG_SFX_NoSoftNoHard
                PLY_AKM_SE_VolumeSoft = 1
                PLY_AKM_SE_VolumeSoftOrHard = 1
        ENDIF
        IFDEF PLY_CFG_SFX_SoftOnly
                PLY_AKM_SE_VolumeSoft = 1
                PLY_AKM_SE_VolumeSoftOrHard = 1
        ENDIF
;       Mixes the volume (soft/hard) into one.
        IFDEF PLY_CFG_UseHardwareSounds
                PLY_AKM_SE_VolumeSoftOrHard = 1
        ENDIF
;       Mixes the retrig flags into one.
        IFDEF PLY_CFG_SFX_HardOnly_Retrig
                PLY_AKM_SE_Retrig = 1
        ENDIF
        IFDEF PLY_CFG_SFX_SoftAndHard_Retrig
                PLY_AKM_SE_Retrig = 1
        ENDIF


;Initializes the sound effects. It MUST be called at any times before a first sound effect is triggered.
;It doesn't matter whether the song is playing or not, or if it has been initialized or not.
;IN:    HL = Address to the sound effects data.
PLY_AKM_InitSoundEffectsDisarkGenerateExternalLabel:
PLY_AKM_InitSoundEffects:
        ld (PLY_AKM_PtSoundEffectTable + PLY_AKM_Offset1b),hl
        ret


;Pl²ys a sound effect. If a previous one was already playing on the same channel, it is replaced.
;This does not actually plays the sound effect, but programs its playing.
;The music player, when called, will call the PLY_AKM_PlaySoundEffectsStream method below.
;IN:    A = Sound effect number (>0!).
;       C = The channel where to play the sound effect (0, 1, 2).
;       B = Inverted volume (0 = full volume, 16 = no sound). Hardware sounds are also lowered.
PLY_AKM_PlaySoundEffectDisarkGenerateExternalLabel:
PLY_AKM_PlaySoundEffect:
        ;Gets the address to the sound effect.
        dec a                   ;The 0th is not encoded.
        IFNDEF PLY_AKM_Rom
dknr3 (void):
PLY_AKM_PtSoundEffectTable: ld hl,0
        ELSE
        ld hl,(PLY_AKM_PtSoundEffectTable)
        ENDIF
        ld e,a
        ld d,0
        add hl,de
        add hl,de
        ld e,(hl)
        inc hl
        ld d,(hl)
        ;Reads the header of the sound effect to get the speed.
        ld a,(de)
        inc de
        ex af,af'

        ld a,b

        ;Finds the pointer to the sound effect of the desired channel.
        ld hl,PLY_AKM_Channel1_SoundEffectData
        ld b,0
        sla c
        sla c
        sla c
        add hl,bc
        ld (hl),e
        inc hl
        ld (hl),d
        inc hl        
        
        ;Now stores the inverted volume.
        ld (hl),a
        inc hl
        
        ;Resets the current speed, stores the instrument speed.
        ld (hl),0
        inc hl
        ex af,af'
        ld (hl),a
        
        ret

;Stops a sound effect. Nothing happens if there was no sound effect.
;IN:    A = The channel where to stop the sound effect (0, 1, 2).
PLY_AKM_StopSoundEffectFromChannelDisarkGenerateExternalLabel:
PLY_AKM_StopSoundEffectFromChannel:
        ;Puts 0 to the pointer of the sound effect.
        add a,a
        add a,a
        add a,a
        ld e,a
        ld d,0
        ld hl,PLY_AKM_Channel1_SoundEffectData
        add hl,de
        ld (hl),d               ;0 means "no sound".
        inc hl
        ld (hl),d
        ret

;Plays the sound effects, if any has been triggered by the user.
;This does not actually send registers to the PSG, it only overwrite the required values of the registers of the player.
;The sound effects initialization method must have been called before!
;As R7 is required, this must be called after the music has been played, but BEFORE the registers are sent to the PSG.
;IN:    A = R7.
PLY_AKM_PlaySoundEffectsStream:
        ;Shifts the R7 to the left twice, so that bit 2 and 5 only can be set for each track, below.
        rla
        rla

        ;Plays the sound effects on every track.
        ld ix,PLY_AKM_Channel1_SoundEffectData
        ld iy,PLY_AKM_Track1_Registers
        ld c,a
        call PLY_AKM_PSES_Play
        ld ix,PLY_AKM_Channel2_SoundEffectData
        ld iy,PLY_AKM_Track2_Registers
        srl c                                   ;Not RR, to make sure bit 6 is 0 (else, no more keyboard on CPC!).
                                                ;Also, on MSX, bit 6 must be 0.
        call PLY_AKM_PSES_Play
        ld ix,PLY_AKM_Channel3_SoundEffectData
        ld iy,PLY_AKM_Track3_Registers
        IFDEF PLY_AKM_HARDWARE_MSX
                scf             ;On MSX, bit 7 must be 1.
                rr c
        ELSE
                rr c            ;On other platforms, we don't care about b7.
        ENDIF
        call PLY_AKM_PSES_Play

        ld a,c
        ld (PLY_AKM_MixerRegister),a
        ret


;Plays the sound stream from the given pointer to the sound effect. If 0, no sound is played.
;The given R7 is given shift twice to the left, so that this code MUST set/reset the bit 2 (sound), and set/reset bit 5 (noise).
;This code MUST overwrite these bits because sound effects have priority over the music.
;IN:    IX = Points on the sound effect pointer. If the sound effect pointer is 0, nothing must be played.
;       IY = Points at the beginning of the register structure related to the channel.
;       C = R7, shifted twice to the left.
;OUT:   The pointed pointer by IX may be modified as the sound advances.
;       C = R7, MUST be modified if there is a sound effect.
PLY_AKM_PSES_Play:
        ;Reads the pointer pointed by IX.
        ld l,(ix + 0)
        ld h,(ix + 1)
        ld a,l
        or h
        ret z           ;No sound to be played? Returns immediately.

        ;Reads the first byte. What type of sound is it?
PLY_AKM_PSES_ReadFirstByte:
        ld a,(hl)
        inc hl
        ld b,a
        rra
        jr c,PLY_AKM_PSES_SoftwareOrSoftwareAndHardware
        rra
                        IFDEF PLY_CFG_SFX_HardOnly              ;CONFIG SPECIFIC
        jr c,PLY_AKM_PSES_HardwareOnly
                        ENDIF ;PLY_CFG_SFX_HardOnly

        ;No software, no hardware, or end/loop.
        ;-------------------------------------------
        ;End or loop?
        rra
                        IFDEF PLY_CFG_SFX_NoSoftNoHard         ;CONFIG SPECIFIC. If not present, the jump is not needed, the method is just below.
        jr c,PLY_AKM_PSES_S_EndOrLoop
                        
        ;No software, no hardware.
        ;-------------------------------------------
        ;Gets the volume.
        call PLY_AKM_PSES_ManageVolumeFromA_Filter4Bits

        ;Noise?
                                IFDEF PLY_CFG_SFX_NoSoftNoHard_Noise                ;CONFIG SPECIFIC
        rl b
        call PLY_AKM_PSES_ReadNoiseIfNeededAndOpenOrCloseNoiseChannel
                                ELSE
        set 5,c                                                 ;No noise in compilation, so stops the noise.
                                ENDIF ;PLY_CFG_SFX_NoSoftNoHard_Noise

        ;Cuts the sound.
        set 2,c
        
        jr PLY_AKM_PSES_SavePointerAndExit
                        ENDIF ;PLY_CFG_SFX_NoSoftNoHard

        ;**Warning!** Do not put any instruction between EndOrLoop and NoSoftNoHard.

PLY_AKM_PSES_S_EndOrLoop:
                        IFDEF PLY_CFG_SFX_LoopTo                ;CONFIG SPECIFIC. If no "loop to", the sounds always end, no need to test.
        ;Is it an end?
        rra
        jr c,PLY_AKM_PSES_S_Loop
                        ENDIF ;PLY_CFG_SFX_LoopTo
        ;End of the sound. Marks the sound pointer with 0, meaning "no sound".
        xor a
        ld (ix + 0),a
        ld (ix + 1),a
        ret
                        IFDEF PLY_CFG_SFX_LoopTo                ;CONFIG SPECIFIC.
PLY_AKM_PSES_S_Loop:
        ;Loops. Reads the pointer and directly uses it.
        ld a,(hl)
        inc hl
        ld h,(hl)
        ld l,a
        jr PLY_AKM_PSES_ReadFirstByte
                        ENDIF ;PLY_CFG_SFX_LoopTo


;Saves HL into IX, and exits. This must be called at the end of each Cell.
;If the speed has not been reached, it is not saved.
PLY_AKM_PSES_SavePointerAndExit:
        ;Speed reached?
        ld a,(ix + PLY_AKM_SoundEffectData_OffsetCurrentStep)
        cp (ix + PLY_AKM_SoundEffectData_OffsetSpeed)
        jr c,PLY_AKM_PSES_NotReached
        ;The speed has been reached, so resets it and saves the pointer to the next cell to read.
        ld (ix + PLY_AKM_SoundEffectData_OffsetCurrentStep),0
        ld (ix + 0),l
        ld (ix + 1),h
        ret
PLY_AKM_PSES_NotReached:
        ;Speed not reached. Increases it, that's all. The same cell will be read next time.
        inc (ix + PLY_AKM_SoundEffectData_OffsetCurrentStep)
        ret

                        IFDEF PLY_CFG_SFX_HardOnly         ;CONFIG SPECIFIC
        ;Hardware only.
        ;-------------------------------------------
PLY_AKM_PSES_HardwareOnly:
        ;Calls the shared code that manages everything.
        call PLY_AKM_PSES_Shared_ReadRetrigHardwareEnvPeriodNoise
        ;Cuts the sound.
        set 2,c

        jr PLY_AKM_PSES_SavePointerAndExit
                        ENDIF ;PLY_CFG_SFX_HardOnly



PLY_AKM_PSES_SoftwareOrSoftwareAndHardware:
        ;Software only?
        rra
                        IFDEF PLY_CFG_SFX_SoftAndHard         ;CONFIG SPECIFIC
        jr c,PLY_AKM_PSES_SoftwareAndHardware
                        ENDIF ;PLY_CFG_SFX_SoftAndHard

        ;Software.
        ;-------------------------------------------
                        IFDEF PLY_CFG_SFX_SoftOnly          ;CONFIG SPECIFIC
        ;Volume.
        call PLY_AKM_PSES_ManageVolumeFromA_Filter4Bits

        ;Noise?
                                IFDEF PLY_CFG_SFX_SoftOnly_Noise                ;CONFIG SPECIFIC
        rl b
        call PLY_AKM_PSES_ReadNoiseIfNeededAndOpenOrCloseNoiseChannel
                                ELSE
        set 5,c                                                 ;No noise in compilation, so stops the noise.
                                ENDIF ;PLY_CFG_SFX_SoftOnly_Noise

        ;Opens the "sound" channel.
        res 2,c

        ;Reads the software period.
        call PLY_AKM_PSES_ReadSoftwarePeriod

        jr PLY_AKM_PSES_SavePointerAndExit
                        ENDIF ;PLY_CFG_SFX_SoftOnly


        ;Software and Hardware.
        ;-------------------------------------------
                        IFDEF PLY_AKM_SE_HardwareSounds         ;CONFIG SPECIFIC
PLY_AKM_PSES_SoftwareAndHardware:
        ;Calls the shared code that manages everything.
        call PLY_AKM_PSES_Shared_ReadRetrigHardwareEnvPeriodNoise

        ;Reads the software period.
        call PLY_AKM_PSES_ReadSoftwarePeriod

        ;Opens the sound.
        res 2,c

        jr PLY_AKM_PSES_SavePointerAndExit
                        ENDIF ;PLY_AKM_SE_HardwareSounds


                        IFDEF PLY_CFG_UseHardwareSounds         ;CONFIG SPECIFIC
        ;Shared code used by the "hardware only" and "software and hardware" part.
        ;Reads the Retrig flag, the Hardware Envelope, the possible noise, the hardware period,
        ;and sets the volume to 16. The R7 sound channel is NOT modified.
PLY_AKM_PSES_Shared_ReadRetrigHardwareEnvPeriodNoise:
        ;Retrig?
        rra
                                IFDEF PLY_AKM_SE_Retrig                  ;CONFIG SPECIFIC
        jr nc,PLY_AKM_PSES_H_AfterRetrig
        ld d,a
        ld a,255
        ld (PLY_AKM_SetReg13Old + PLY_AKM_Offset1b),a
        ld a,d
PLY_AKM_PSES_H_AfterRetrig:
                                ENDIF ;PLY_AKM_SE_Retrig

        ;The hardware envelope can be set (8-15).
        and %111
        add a,8
        ld (PLY_AKM_SetReg13 + PLY_AKM_Offset1b),a

        ;Noise?
                                IFDEF PLY_AKM_SE_HardwareNoise           ;CONFIG SPECIFIC. B not needed after, we can put it in the condition too.
        rl b
        call PLY_AKM_PSES_ReadNoiseIfNeededAndOpenOrCloseNoiseChannel
                                ELSE
        set 5,c                                                 ;No noise in compilation, so stops the noise.
                                ENDIF ;PLY_AKM_SE_HardwareNoise

        ;Reads the hardware period.
        call PLY_AKM_PSES_ReadHardwarePeriod

        ;Sets the volume to "hardware". It still may be decreased.
        ld a,16
        jp PLY_AKM_PSES_ManageVolumeFromA_Hard
                        ENDIF ;PLY_CFG_UseHardwareSounds


                        IFDEF PLY_AKM_SE_Noise
;If asked to, reads the noise pointed by HL, increases HL, and opens the noise channel. If no noise, closes the noise channel.
;IN:    Carry = true if noise to read. False if no noise, so the noise channel must be closed.
;       HL = the data.
PLY_AKM_PSES_ReadNoiseIfNeededAndOpenOrCloseNoiseChannel:
        jr c,PLY_AKM_PSES_ReadNoiseAndOpenNoiseChannel_OpenNoise
        ;No noise, so closes the noise channel.
        set 5,c
        ret
PLY_AKM_PSES_ReadNoiseAndOpenNoiseChannel_OpenNoise:
        ;Reads the noise.
        ld a,(hl)
        ld (PLY_AKM_NoiseRegister),a
        inc hl

        ;Opens noise channel.
        res 5,c
        ret
                        ENDIF ;PLY_AKM_SE_Noise

                        IFDEF PLY_CFG_UseHardwareSounds         ;CONFIG SPECIFIC
;Reads the hardware period from HL and sets the R11/R12 registers. HL is incremented of 2.
PLY_AKM_PSES_ReadHardwarePeriod:
        ld a,(hl)
        ld (PLY_AKM_Reg11),a
        inc hl
        ld a,(hl)
        ld (PLY_AKM_Reg12),a
        inc hl
        ret
                        ENDIF ;PLY_CFG_UseHardwareSounds

;Reads the software period from HL and sets the period registers thanks to IY. HL is incremented of 2.
PLY_AKM_PSES_ReadSoftwarePeriod:
        ld a,(hl)
        ld (iy + PLY_AKM_Registers_OffsetSoftwarePeriodLSB),a
        inc hl
        ld a,(hl)
        ld (iy + PLY_AKM_Registers_OffsetSoftwarePeriodMSB),a
        inc hl
        ret

                        IFDEF PLY_AKM_SE_VolumeSoft      ;CONFIG SPECIFIC
;Reads the volume in A, decreases it from the inverted volume of the channel, and sets the volume via IY.
;IN:    A = volume, from 0 to 15 (no hardware envelope).
PLY_AKM_PSES_ManageVolumeFromA_Filter4Bits:
        and %1111
                        ENDIF ;PLY_AKM_SE_VolumeSoft
                        IFDEF PLY_AKM_SE_VolumeSoftOrHard        ;CONFIG SPECIFIC
;After the filtering. Useful for hardware sound (volume has been forced to 16).
PLY_AKM_PSES_ManageVolumeFromA_Hard:
        ;Decreases the volume, checks the limit.
        sub (ix + PLY_AKM_SoundEffectData_OffsetInvertedVolume)
        jr nc,PLY_AKM_PSES_MVFA_NoOverflow
        xor a
PLY_AKM_PSES_MVFA_NoOverflow:
        ld (iy + PLY_AKM_Registers_OffsetVolume),a
        ret
                        ENDIF ;PLY_AKM_SE_VolumeSoftOrHard


        ;The data for RAM player. For ROM player, it is declared in the main player.
        IFNDEF PLY_AKM_Rom
;The data of the Channels MUST be consecutive.
PLY_AKM_Channel1_SoundEffectData:
dkws (void):
        dw 0                                            ;Points to the sound effect for the track 1, or 0 if not playing.
PLY_AKM_Channel1_SoundEffectInvertedVolume:
dkwe (void):
dkbs (void):
        db 0                                            ;Inverted volume.
PLY_AKM_Channel1_SoundEffectCurrentStep:
        db 0                                            ;Current step (>=0).
PLY_AKM_Channel1_SoundEffectSpeed:
        db 0                                            ;Speed (>=0).
        ds 3,0                                          ;Padding.
dkbe (void):
PLY_AKM_Channel_SoundEffectDataSize: equ $ - PLY_AKM_Channel1_SoundEffectData
    
dkbs (void):    
PLY_AKM_Channel2_SoundEffectData:
        ds PLY_AKM_Channel_SoundEffectDataSize, 0
PLY_AKM_Channel3_SoundEffectData:
        ds PLY_AKM_Channel_SoundEffectDataSize, 0
dkbe (void):

        ;Checks that the pointers are consecutive.
        assert (PLY_AKM_Channel1_SoundEffectData + PLY_AKM_Channel_SoundEffectDataSize) == PLY_AKM_Channel2_SoundEffectData
        assert (PLY_AKM_Channel2_SoundEffectData + PLY_AKM_Channel_SoundEffectDataSize) == PLY_AKM_Channel3_SoundEffectData

        ENDIF ;PLY_AKM_Rom
        
;Offset from the beginning of the data, to reach the inverted volume.
PLY_AKM_SoundEffectData_OffsetInvertedVolume: equ PLY_AKM_Channel1_SoundEffectInvertedVolume - PLY_AKM_Channel1_SoundEffectData
PLY_AKM_SoundEffectData_OffsetCurrentStep: equ PLY_AKM_Channel1_SoundEffectCurrentStep - PLY_AKM_Channel1_SoundEffectData
PLY_AKM_SoundEffectData_OffsetSpeed: equ PLY_AKM_Channel1_SoundEffectSpeed - PLY_AKM_Channel1_SoundEffectData