; ---------------------------------------------------------------------------
;*** Start of Arkos Tracker Player

;    Arkos Tracker Player V1.01 - CPC & MSX version.
;    21/09/09

;    Code By Targhan/Arkos.
;    PSG registers sendings based on Madram/Overlander's optimisation trick.
;    Restoring interruption status snippet by Grim/Arkos.

;    V1.01 additions
;    ---------------
;    - Small (but not useless!) optimisations by Grim/Arkos at the PLY_Track1_WaitCounter / PLY_Track2_WaitCounter / PLY_Track3_WaitCounter labels.
;    - Optimisation of the R13 management by Grim/Arkos.

;    This player can adapt to the following machines =
;    Amstrad CPC and MSX.
;    Output codes are specific, as well as the frequency tables.

;    This player modifies all these registers = HL, DE, BC, AF, HL', DE', BC', AF', IX, IY.
;    The Stack is used in conventionnal manners (Call, Ret, Push, Pop) so integration with any of your code should be seamless.
;    The player does NOT modifies the Interruption state, unless you use the PLY_SystemFriendly flag, which will cut the
;    interruptions at the beginning, and will restore them ONLY IF NEEDED.

;    Basically, there are three kind of players.

;    ASM
;    ---
;    Used in your Asm productions. You call the Player by yourself, you don't care if all the registers are modified.

;    Set PLY_SystemFriendly and PLY_UseFirmwareInterruptions to 0.

;    In Assembler =
;    ld de,MusicAddress
;    call Player / PLY_Init        to initialise the player with your song.
;    then
;    call Player + 3 / PLY_Play    whenever you want to play/continue the song.
;    call Player + 6 / PLY_Stop    to stop the song.

;    FADES IN/OUT
;    ------------
;    The player allows the volume to be modified. It provides the interface, but you'll have to set the volume by yourself.
;    Set PLY_UseFades to 1.
;    In Assembler =
;    ld e,Volume (0=full volume, 16 or more=no volume)
;    call PLY_SetFadeValue

;    SOUND EFFECTS
;    -------------
;    The player manages Sound Effects. They must be defined in another song, generated as a "SFX Music" in the Arkos Tracker.
;    Set the PLY_UseSoundEffects to 1.

;    In Assembler =
;    ld de,SFXMusicAddress
;    call PLY_SFX_Init        to initialise the SFX Song.

;    Then initialise and play the "music" song normally.

;    To play a sound effect =
;    A = No Channel (0,1,2)
;    L = SFX Number (>0)
;    H = Volume (0...F)
;    E = Note (0...143)
;    D = Speed (0 = As original, 1...255 = new Speed (1 is the fastest))
;    BC = Inverted Pitch (-#FFFF -> FFFF). 0 is no pitch. The higher the pitch, the lower the sound.
;    call PLY_SFX_Play
;    To stop a sound effect =
;    ld e,No Channel (0,1,2)
;    call PLY_SFX_Stop
;    To stop the sound effects on all the channels =
;    call PLY_SFX_StopAll

;    For more information, check the manual.

;    Any question, complaint, a need to reward? Write to contact@julien-nevo.com
; ---------------------------------------------------------------------------
    LIST
PLY_UseSoundEffects EQU 0       ; Set to 1 if you want to use Sound Effects in your player. Both CPU and memory consuming.

PLY_UseFades        EQU 1       ; Set to 1 to allow fades in/out. A little CPU and memory consuming.
                                ; PLY_SetFadeValue becomes available.

PLY_RetrigValue     EQU $FE     ; Value used to trigger the Retrig of Register 13. 
                                ; $FE corresponds to CP xx. Do not change it!

PLY_Digidrum        DEFB 0      ; Read here to know if a Digidrum has been played (0=no).

; ---------------------------------------------------------------------------
PLY_Play
    XOR  A
    LD   (PLY_Digidrum),A       ; Reset the Digidrum flag.

; Manage Speed. If Speed counter is over, we have to read the Pattern further.
PLY_SpeedCpt
    LD   A,1
    DEC  A
    JP   NZ,PLY_SpeedEnd

; Moving forward in the Pattern. Test if it is not over.
PLY_HeightCpt
    LD   A,1
    DEC  A
    JR   NZ,PLY_HeightEnd

; Pattern Over. We have to read the Linker.

; Get the Transpositions, if they have changed, or detect the Song Ending!
PLY_Linker_PT
    LD   HL,0
    LD   A,(HL)
    INC  HL
    RRA
    JR   NC,PLY_SongNotOver
    
    ; Song over! We read the address of the Loop point.
    LD   A,(HL)
    INC  HL
    LD   H,(HL)
    LD   L,A
    LD   A,(HL)                 ; We know the Song won't restart now, so we can skip the first bit.
    INC  HL
    RRA
PLY_SongNotOver
    RRA
    JR   NC,PLY_NoNewTransposition1
    LD   DE,PLY_Transposition1 + 1
    LDI
PLY_NoNewTransposition1
    RRA
    JR   NC,PLY_NoNewTransposition2
    LD   DE,PLY_Transposition2 + 1
    LDI
PLY_NoNewTransposition2
    RRA
    JR   NC,PLY_NoNewTransposition3
    LD   DE,PLY_Transposition3 + 1
    LDI
PLY_NoNewTransposition3

    ; Get the Tracks addresses.
    LD   DE,PLY_Track1_PT + 1
    LDI
    LDI
    LD   DE,PLY_Track2_PT + 1
    LDI
    LDI
    LD   DE,PLY_Track3_PT + 1
    LDI
    LDI

    ; Get the Special Track address, if it has changed.
    RRA
    JR   NC,PLY_NoNewHeight
    LD   DE,PLY_Height + 1
    LDI
PLY_NoNewHeight

    RRA
    JR   NC,PLY_NoNewSpecialTrack
PLY_NewSpecialTrack
    LD   E,(HL)
    INC  HL
    LD   D,(HL)
    INC  HL
    LD   (PLY_SaveSpecialTrack + 1),DE

PLY_NoNewSpecialTrack
    LD   (PLY_Linker_PT + 1),HL
PLY_SaveSpecialTrack
    LD   HL,0
    LD   (PLY_SpecialTrack_PT + 1),HL

    ; Reset the SpecialTrack/Tracks line counter.
    ; We can't rely on the song data, because the Pattern Height is not related to the Tracks Height.
    LD   A,1
    LD   (PLY_SpecialTrack_WaitCounter + 1),A
    LD   (PLY_Track1_WaitCounter + 1),A
    LD   (PLY_Track2_WaitCounter + 1),A
    LD   (PLY_Track3_WaitCounter + 1),A

PLY_Height
    LD   A,1
PLY_HeightEnd
    LD   (PLY_HeightCpt + 1),A

;-------------------------------
; Read the Special Track/Tracks.
;-------------------------------

; Read the Special Track.
PLY_SpecialTrack_WaitCounter 
    LD   A,1
    DEC  A
    JR   NZ,PLY_SpecialTrack_Wait

PLY_SpecialTrack_PT 
    LD   HL,0
    LD   A,(HL)
    INC  HL
    SRL  A                      ; Data (1) or Wait (0)?
    JR   NC,PLY_SpecialTrack_NewWait    ; If Wait, A contains the Wait value.
    ; Data. Effect Type?
    SRL  A                              ; Speed (0) or Digidrum (1)?
    ; First, we don't test the Effect Type, but only the Escape Code (=0)
    JR   NZ,PLY_SpecialTrack_NoEscapeCode
    LD   A,(HL)
    INC  HL
    
PLY_SpecialTrack_NoEscapeCode
    ; Now, we test the Effect type, since the Carry didn't change.
    JR   NC,PLY_SpecialTrack_Speed
    LD   (PLY_Digidrum),A
    JR   PLY_PT_SpecialTrack_EndData

PLY_SpecialTrack_Speed
    LD   (PLY_Speed + 1),A
PLY_PT_SpecialTrack_EndData
    LD   A,1
PLY_SpecialTrack_NewWait
    LD   (PLY_SpecialTrack_PT + 1),HL
PLY_SpecialTrack_Wait
    LD   (PLY_SpecialTrack_WaitCounter + 1),A

;------------------
; Read the Track 1.
;------------------

; Store the parameters, because the player below is called every frame, but the Read Track isn't.
PLY_Track1_WaitCounter 
    LD   A,1
    DEC  A
    JR   NZ,PLY_Track1_NewInstrument_SetWait

PLY_Track1_PT 
    LD   HL,0
    CALL PLY_ReadTrack
    LD   (PLY_Track1_PT + 1),HL
    JR   C,PLY_Track1_NewInstrument_SetWait

    ; No Wait command. Can be a Note and/or Effects.
    LD   A,D                    ; Make a copy of the flags+Volume in A, not to temper with the original.

    RRA                         ; Volume? If bit 4 was 1, then volume exists on b3-b0
    JR   NC,PLY_Track1_SameVolume
    AND  %1111
    LD   (PLY_Track1_Volume),A
PLY_Track1_SameVolume

    RL   D                      ; New Pitch?
    JR   NC,PLY_Track1_NoNewPitch
    LD   (PLY_Track1_PitchAdd + 1),IX
PLY_Track1_NoNewPitch

    RL   D                      ; Note? If no Note, we don't have to test if a new Instrument is here.
    JR   NC,PLY_Track1_NoNoteGiven
    LD   A,E
PLY_Transposition1 
    ADD  A,0                    ; Transpose Note according to the Transposition in the Linker.
    LD   (PLY_Track1_Note),A

    LD   HL,0                   ; Reset the TrackPitch.
    LD   (PLY_Track1_Pitch + 1),HL

    RL   D                      ; New Instrument?
    JR   C,PLY_Track1_NewInstrument
PLY_Track1_SavePTInstrument 
    LD   HL,0                   ; Same Instrument. We recover its address to restart it.
    LD   A,(PLY_Track1_InstrumentSpeed + 1)     ; Reset the Instrument Speed Counter. Never seemed useful...
    LD   (PLY_Track1_InstrumentSpeedCpt + 1),A
    JR   PLY_Track1_InstrumentResetPT

PLY_Track1_NewInstrument        ; New Instrument. We have to get its new address, and Speed.
    LD   L,B                    ; H is already set to 0 before.
    ADD  HL,HL
PLY_Track1_InstrumentsTablePT 
    LD   BC,0
    ADD  HL,BC
    LD   A,(HL)                 ; Get Instrument address.
    INC  HL
    LD   H,(HL)
    LD   L,A
    LD   A,(HL)                 ; Get Instrument speed.
    INC  HL
    LD   (PLY_Track1_InstrumentSpeed + 1),A
    LD   (PLY_Track1_InstrumentSpeedCpt + 1),A
    LD   A,(HL)
    OR   A                      ; Get IsRetrig?. Code it only if different to 0, else next Instruments are going to overwrite it.
    JR   Z,.skip_retrig
.sm_retrig
    LD   (PLY_PSGReg13_Code_ext.PLY_PSGReg13_Retrig + 1),A
.skip_retrig

    INC  HL

    LD   (PLY_Track1_SavePTInstrument + 1),HL   ; When using the Instrument again, no need to give the Speed, it is skipped.

PLY_Track1_InstrumentResetPT
    LD   (PLY_Track1_Instrument + 1),HL

PLY_Track1_NoNoteGiven

    LD   A,1
PLY_Track1_NewInstrument_SetWait
    LD   (PLY_Track1_WaitCounter + 1),A

;------------------
; Read the Track 2.
;------------------

; Store the parameters, because the player below is called every frame, but the Read Track isn't.
PLY_Track2_WaitCounter 
    LD   A,1
    DEC  A
    JR   NZ,PLY_Track2_NewInstrument_SetWait

PLY_Track2_PT 
    LD   HL,0
    CALL PLY_ReadTrack
    LD   (PLY_Track2_PT + 1),HL
    JR   C,PLY_Track2_NewInstrument_SetWait

    ; No Wait command. Can be a Note and/or Effects.
    LD   A,D                    ; Make a copy of the flags+Volume in A, not to temper with the original.

    RRA                         ; Volume? If bit 4 was 1, then volume exists on b3-b0
    JR   NC,PLY_Track2_SameVolume
    AND  %1111
    LD   (PLY_Track2_Volume),A
PLY_Track2_SameVolume

    RL   D                      ; New Pitch?
    JR   NC,PLY_Track2_NoNewPitch
    LD   (PLY_Track2_PitchAdd + 1),IX
PLY_Track2_NoNewPitch

    RL   D                      ; Note? If no Note, we don't have to test if a new Instrument is here.
    JR   NC,PLY_Track2_NoNoteGiven
    LD   A,E
PLY_Transposition2 
    ADD  A,0                    ; Transpose Note according to the Transposition in the Linker.
    LD   (PLY_Track2_Note),A

    LD   HL,0                   ; Reset the TrackPitch.
    LD   (PLY_Track2_Pitch + 1),HL

    RL   D                      ; New Instrument?
    JR   C,PLY_Track2_NewInstrument
PLY_Track2_SavePTInstrument 
    LD   HL,0                   ; Same Instrument. We recover its address to restart it.
    LD   A,(PLY_Track2_InstrumentSpeed + 1) ; Reset the Instrument Speed Counter. Never seemed useful...
    LD   (PLY_Track2_InstrumentSpeedCpt + 1),A
    JR   PLY_Track2_InstrumentResetPT

PLY_Track2_NewInstrument        ; New Instrument. We have to get its new address, and Speed.
    LD   L,B                    ; H is already set to 0 before.
    ADD  HL,HL
PLY_Track2_InstrumentsTablePT 
    LD   BC,0
    ADD  HL,BC
    LD   A,(HL)                 ; Get Instrument address.
    INC  HL
    LD   H,(HL)
    LD   L,A
    LD   A,(HL)                 ; Get Instrument speed.
    INC  HL
    LD   (PLY_Track2_InstrumentSpeed + 1),A
    LD   (PLY_Track2_InstrumentSpeedCpt + 1),A
    LD   A,(HL)
    OR   A                      ; Get IsRetrig?. Code it only if different to 0, else next Instruments are going to overwrite it.
    JR   Z,.skip_retrig
.sm_retrig
    LD   (PLY_PSGReg13_Code_ext.PLY_PSGReg13_Retrig + 1),A
.skip_retrig
    INC  HL

    LD   (PLY_Track2_SavePTInstrument + 1),HL   ; When using the Instrument again, no need to give the Speed, it is skipped.

PLY_Track2_InstrumentResetPT
    LD   (PLY_Track2_Instrument + 1),HL

PLY_Track2_NoNoteGiven

    LD   A,1
PLY_Track2_NewInstrument_SetWait
    LD   (PLY_Track2_WaitCounter + 1),A

;------------------
; Read the Track 3.
;------------------

; Store the parameters, because the player below is called every frame, but the Read Track isn't.
PLY_Track3_WaitCounter 
    LD   A,1
    DEC  A
    JR   NZ,PLY_Track3_NewInstrument_SetWait

PLY_Track3_PT 
    LD   HL,0
    CALL PLY_ReadTrack
    LD   (PLY_Track3_PT + 1),HL
    JR   C,PLY_Track3_NewInstrument_SetWait

    ; No Wait command. Can be a Note and/or Effects.
    LD   A,D                    ; Make a copy of the flags+Volume in A, not to temper with the original.

    RRA                         ; Volume? If bit 4 was 1, then volume exists on b3-b0
    JR   NC,PLY_Track3_SameVolume
    AND  %1111
    LD   (PLY_Track3_Volume),A
PLY_Track3_SameVolume

    RL   D                      ; New Pitch?
    JR   NC,PLY_Track3_NoNewPitch
    LD   (PLY_Track3_PitchAdd + 1),IX
PLY_Track3_NoNewPitch

    RL   D                      ; Note? If no Note, we don't have to test if a new Instrument is here.
    JR   NC,PLY_Track3_NoNoteGiven
    LD   A,E
PLY_Transposition3 
    ADD  A,0                    ; Transpose Note according to the Transposition in the Linker.
    LD   (PLY_Track3_Note),A

    LD   HL,0                   ; Reset the TrackPitch.
    LD   (PLY_Track3_Pitch + 1),HL

    RL   D                      ; New Instrument?
    JR   C,PLY_Track3_NewInstrument
PLY_Track3_SavePTInstrument 
    LD   HL,0                   ; Same Instrument. We recover its address to restart it.
    LD   A,(PLY_Track3_InstrumentSpeed + 1) ; Reset the Instrument Speed Counter. Never seemed useful...
    LD   (PLY_Track3_InstrumentSpeedCpt + 1),A
    JR   PLY_Track3_InstrumentResetPT

PLY_Track3_NewInstrument        ; New Instrument. We have to get its new address, and Speed.
    LD   L,B                    ; H is already set to 0 before.
    ADD  HL,HL
PLY_Track3_InstrumentsTablePT 
    LD   BC,0
    ADD  HL,BC
    LD   A,(HL)                 ; Get Instrument address.
    INC  HL
    LD   H,(HL)
    LD   L,A
    LD   A,(HL)                 ; Get Instrument speed.
    INC  HL
    LD   (PLY_Track3_InstrumentSpeed + 1),A
    LD   (PLY_Track3_InstrumentSpeedCpt + 1),A
    LD   A,(HL)
    OR   A                      ; Get IsRetrig?. Code it only if different to 0, else next Instruments are going to overwrite it.
    JR   Z,.skip_retrig
.sm_retrig
    LD   (PLY_PSGReg13_Code_ext.PLY_PSGReg13_Retrig + 1),A
.skip_retrig
    INC  HL

    LD   (PLY_Track3_SavePTInstrument + 1),HL   ; When using the Instrument again, no need to give the Speed, it is skipped.

PLY_Track3_InstrumentResetPT
    LD   (PLY_Track3_Instrument + 1),HL

PLY_Track3_NoNoteGiven

    LD   A,1
PLY_Track3_NewInstrument_SetWait
    LD   (PLY_Track3_WaitCounter + 1),A

PLY_Speed
    LD   A,1
PLY_SpeedEnd
    LD   (PLY_SpeedCpt + 1),A

;--------------------------
; Play the Sound on Track 3
;--------------------------
; Plays the sound on each frame, but only save the forwarded Instrument pointer when Instrument Speed is reached.
; This is needed because TrackPitch is involved in the Software Frequency/Hardware Frequency calculation, and is calculated every frame.

    LD   IY,PLY_PSGRegistersArray + 4
PLY_Track3_Pitch 
    LD   HL,0
PLY_Track3_PitchAdd 
    LD   DE,0
    ADD  HL,DE
    LD   (PLY_Track3_Pitch + 1),HL
    SRA  H                      ; Shift the Pitch to slow its speed.
    RR   L
    SRA  H
    RR   L
    EX   DE,HL
    EXX

PLY_Track3_Volume   EQU $+2
PLY_Track3_Note     EQU $+1
    LD   DE,0                   ; D=Inverted Volume E=Note
PLY_Track3_Instrument 
    LD   HL,0
    CALL PLY_PlaySound

PLY_Track3_InstrumentSpeedCpt 
    LD   A,1
    DEC  A
    JR   NZ,PLY_Track3_PlayNoForward
    LD   (PLY_Track3_Instrument + 1),HL
PLY_Track3_InstrumentSpeed 
    LD   A,6
PLY_Track3_PlayNoForward
    LD   (PLY_Track3_InstrumentSpeedCpt + 1),A

;-----------------------------------------------------------------------------------------
; Play Sound Effects on Track 3 (only assembled used if PLY_UseSoundEffects is set to one)
;-----------------------------------------------------------------------------------------
    if PLY_UseSoundEffects

PLY_SFX_Track3_Pitch 
    LD   DE,0
    EXX
PLY_SFX_Track3_Volume   EQU $+2
PLY_SFX_Track3_Note     EQU $+1
    LD   DE,0                   ; D=Inverted Volume E=Note
PLY_SFX_Track3_Instrument 
    LD   HL,0                   ; If 0, no sound effect.
    LD   A,L
    OR   H
    JR   Z,PLY_SFX_Track3_End
    LD   A,1
    LD   (PLY_PS_EndSound_SFX + 1),A
    CALL PLY_PlaySound
    XOR  A
    LD   (PLY_PS_EndSound_SFX + 1),A
    LD   A,L                    ; If the new address is 0, the instrument is over. Speed is set in the process, we don't care.
    OR   H
    JR   Z,PLY_SFX_Track3_Instrument_SetAddress

PLY_SFX_Track3_InstrumentSpeedCpt 
    LD   A,1
    DEC  A
    JR   NZ,PLY_SFX_Track3_PlayNoForward
PLY_SFX_Track3_Instrument_SetAddress
    LD   (PLY_SFX_Track3_Instrument + 1),HL
PLY_SFX_Track3_InstrumentSpeed 
    LD   A,6
PLY_SFX_Track3_PlayNoForward
    LD   (PLY_SFX_Track3_InstrumentSpeedCpt + 1),A

PLY_SFX_Track3_End

    endif
;------------------------------------------

    LD   A,IXL                  ; Save the Register 7 of the Track 3.
    EX   AF,AF'


;--------------------------
; Play the Sound on Track 2
;--------------------------
    LD   IY,PLY_PSGRegistersArray + 2
PLY_Track2_Pitch 
    LD   HL,0
PLY_Track2_PitchAdd 
    LD   DE,0
    ADD  HL,DE
    LD   (PLY_Track2_Pitch + 1),HL
    SRA  H                      ; Shift the Pitch to slow its speed.
    RR   L
    SRA  H
    RR   L
    EX   DE,HL
    EXX

PLY_Track2_Volume   EQU $+2
PLY_Track2_Note     EQU $+1
    LD   DE,0                   ; D=Inverted Volume E=Note
PLY_Track2_Instrument 
    LD   HL,0
    CALL PLY_PlaySound

PLY_Track2_InstrumentSpeedCpt 
    LD   A,1
    DEC  A
    JR   NZ,PLY_Track2_PlayNoForward
    LD   (PLY_Track2_Instrument + 1),HL
PLY_Track2_InstrumentSpeed 
    LD   A,6
PLY_Track2_PlayNoForward
    LD   (PLY_Track2_InstrumentSpeedCpt + 1),A


;-----------------------------------------------------------------------------------------
; Play Sound Effects on Track 2 (only assembled used if PLY_UseSoundEffects is set to one)
;-----------------------------------------------------------------------------------------
    if PLY_UseSoundEffects

PLY_SFX_Track2_Pitch 
    LD   DE,0
    EXX
PLY_SFX_Track2_Volume   EQU $+2
PLY_SFX_Track2_Note     EQU $+1
    LD   DE,0                   ; D=Inverted Volume E=Note
PLY_SFX_Track2_Instrument 
    LD   HL,0                   ; If 0, no sound effect.
    LD   A,L
    OR   H
    JR   Z,PLY_SFX_Track2_End
    LD   A,1
    LD   (PLY_PS_EndSound_SFX + 1),A
    CALL PLY_PlaySound
    XOR  A
    LD   (PLY_PS_EndSound_SFX + 1),A
    LD   A,L                    ; If the new address is 0, the instrument is over. Speed is set in the process, we don't care.
    OR   H
    JR   Z,PLY_SFX_Track2_Instrument_SetAddress

PLY_SFX_Track2_InstrumentSpeedCpt 
    LD   A,1
    DEC  A
    JR   NZ,PLY_SFX_Track2_PlayNoForward
PLY_SFX_Track2_Instrument_SetAddress
    LD   (PLY_SFX_Track2_Instrument + 1),HL
PLY_SFX_Track2_InstrumentSpeed 
    LD   A,6
PLY_SFX_Track2_PlayNoForward
    LD   (PLY_SFX_Track2_InstrumentSpeedCpt + 1),A

PLY_SFX_Track2_End
    endif
;------------------------------------------

    EX   AF,AF'
    ADD  A,A                    ; Mix Reg7 from Track2 with Track3, making room first.
    OR   IXL
    RLA
    EX   AF,AF'

;--------------------------
; Play the Sound on Track 1
;--------------------------

    LD   IY,PLY_PSGRegistersArray
PLY_Track1_Pitch
    LD   HL,0
PLY_Track1_PitchAdd 
    LD   DE,0
    ADD  HL,DE
    LD   (PLY_Track1_Pitch + 1),HL
    SRA  H                      ; Shift the Pitch to slow its speed.
    RR   L
    SRA  H
    RR   L
    EX   DE,HL
    EXX

PLY_Track1_Volume   EQU $+2
PLY_Track1_Note     EQU $+1
    LD   DE,0                   ; D=Inverted Volume E=Note
PLY_Track1_Instrument
    LD   HL,0
    CALL PLY_PlaySound

PLY_Track1_InstrumentSpeedCpt 
    LD   A,1
    DEC  A
    JR   NZ,PLY_Track1_PlayNoForward
    LD   (PLY_Track1_Instrument + 1),HL
PLY_Track1_InstrumentSpeed 
    LD   A,6
PLY_Track1_PlayNoForward
    LD   (PLY_Track1_InstrumentSpeedCpt + 1),A

;-----------------------------------------------------------------------------------------
; Play Sound Effects on Track 1 (only assembled used if PLY_UseSoundEffects is set to one)
;-----------------------------------------------------------------------------------------
    if PLY_UseSoundEffects

PLY_SFX_Track1_Pitch 
    LD   DE,0
    EXX
PLY_SFX_Track1_Volume   EQU $+2
PLY_SFX_Track1_Note     EQU $+1
    LD   DE,0                   ; D=Inverted Volume E=Note
PLY_SFX_Track1_Instrument 
    LD   HL,0                   ; If 0, no sound effect.
    LD   A,L
    OR   H
    JR   Z,PLY_SFX_Track1_End
    LD   A,1
    LD   (PLY_PS_EndSound_SFX + 1),A
    CALL PLY_PlaySound
    XOR  A
    LD   (PLY_PS_EndSound_SFX + 1),A
    LD   A,L                    ; If the new address is 0, the instrument is over. Speed is set in the process, we don't care.
    OR   H
    JR   Z,PLY_SFX_Track1_Instrument_SetAddress

PLY_SFX_Track1_InstrumentSpeedCpt
    LD   A,1
    DEC  A
    JR   NZ,PLY_SFX_Track1_PlayNoForward
PLY_SFX_Track1_Instrument_SetAddress
    LD   (PLY_SFX_Track1_Instrument + 1),HL
PLY_SFX_Track1_InstrumentSpeed
    LD   A,6
PLY_SFX_Track1_PlayNoForward
    LD   (PLY_SFX_Track1_InstrumentSpeedCpt + 1),A

PLY_SFX_Track1_End
    endif
;-----------------------------------

    EX   AF,AF'
    OR   IXL                    ; Mix Reg7 from Track3 with Track2+1.

; Send the registers to PSG. Various codes according to the machine used.
PLY_SendRegisters
; A=Register 7

; ---------------------------------------------------------------------------
EXT_PSG_SELECT      EQU $F984   ; Select external AY/YM register
EXT_PSG_WRITE       EQU $F884   ; Write value in selected register in external AY/YM 

; Send registers to external AY
use_external_ay
    LD   HL,PLY_PSGRegistersArray
    LD   E,A                    ; Save value for PSG R7
    LD   BC,EXT_PSG_SELECT
; Register 0
    XOR  A
    OUT  (C),A                  ; Register
    OUTI                        ; Value
    INC  B
    
; Register 1
    INC  A
    OUT  (C),A
    OUTI
    INC  B

; Register 2
    INC  A
    OUT  (C),A
    OUTI
    INC  B

; Register 3
    INC  A
    OUT  (C),A
    OUTI
    INC  B

; Register 4
    INC  A
    OUT  (C),A
    OUTI
    INC  B

; Register 5
    INC  A
    OUT  (C),A
    OUTI
    INC  B

; Register 6
    INC  A
    OUT  (C),A
    OUTI
    INC  B

; Register 7
    INC  A
    OUT  (C),A
    DEC  B
    OUT  (C),E                  ; Use the stored Register 7
    INC  B

; Register 8
    IF   PLY_UseFades
        ; With Fade
        LD   D,8
        OUT  (C),D
        LD   A,(HL)
        INC  HL                 ; Skip volume byte
PLY_Channel1_FadeValue_ext
        SUB  0                  ; Set a value from 0 (full volume) to 16 or more (volume to 0).
        JR   NC,.apply_fade_channel
        XOR  A
.apply_fade_channel
        DEC  B
        OUT  (C),A
        INC  B
        INC  HL                 ; Skip unused byte.
    ELSE
        ; No Fade
        INC  A
        OUT  (C),A
        OUTI
        INC  HL                 ; Skip unused byte (*)
        INC  B
    ENDIF

; Register 9
    IF   PLY_UseFades
        ; With Fade
        INC  D
        OUT  (C),D
        LD   A,(HL)
        INC  HL                 ; Skip volume byte
PLY_Channel2_FadeValue_ext
        SUB  0                  ; Set a value from 0 (full volume) to 16 or more (volume to 0).
        JR   NC,.apply_fade_channel
        XOR  A
.apply_fade_channel
        DEC  B
        OUT  (C),A
        INC  B
        INC  HL                 ; Skip unused byte.
    ELSE
        ; No Fade
        INC  A
        OUT  (C),A
        OUTI
        INC  HL                 ; Skip unused byte
        INC  B
    ENDIF
    
; Register 10
    IF   PLY_UseFades
        ; With Fade
        INC  D
        OUT  (C),D
        LD   A,(HL)
        INC  HL                 ; Skip volume byte
PLY_Channel3_FadeValue_ext
        SUB  0                  ; Set a value from 0 (full volume) to 16 or more (volume to 0).
        JR   NC,.apply_fade_channel
        XOR  A
.apply_fade_channel
        DEC  B
        OUT  (C),A
        INC  B
        INC  HL                 ; Skip unused byte.
        LD   A,D                ; Update AY register counter
    ELSE
        ; No Fade
        INC  A
        OUT  (C),A
        OUTI
        INC  HL                 ; Skip unused byte
        INC  B
    ENDIF

; Register 11
    INC  A
    OUT  (C),A
    OUTI
    INC  B

; Register 12
    INC  A
    OUT  (C),A
    OUTI
    INC  B

; Register 13
PLY_PSGReg13_Code_ext
    INC  A
    OUT  (C),A
    LD   A,(HL)
.PLY_PSGReg13_Retrig
    CP   255                    ; If IsRetrig?, force the R13 to be triggered.
    RET  Z
    OUTI                        ; = DEC B | OUT (C),A
    LD   (.PLY_PSGReg13_Retrig + 1),A
    RET

; ---------------------------------------------------------------------------
use_internal_ay
    LD   DE,$C080
    LD   B,$F6
    OUT  (C),D                  ; $F6C0
    EXX
    LD   HL,PLY_PSGRegistersArray
    LD   E,$F6
    LD   BC,$F401

; Register 0
    DEFB $ED,$71                ; $F400+Register
    LD   B,E
    DEFB $ED,$71                ; $F600
    DEC  B
    OUTI                        ; $F400+Value
    EXX
    OUT  (C),E                  ; $F680
    OUT  (C),D                  ; $F6C0
    EXX

; Register 1
    OUT  (C),C
    LD   B,E
    DEFB $ED,$71
    DEC  B
    OUTI
    EXX
    OUT  (C),E
    OUT  (C),D
    EXX
    INC  C

; Register 2
    OUT  (C),C
    LD   B,E
    DEFB $ED,$71
    DEC  B
    OUTI
    EXX
    OUT  (C),E
    OUT  (C),D
    EXX
    INC  C

; Register 3
    OUT  (C),C
    LD   B,E
    DEFB $ED,$71
    DEC  B
    OUTI
    EXX
    OUT  (C),E
    OUT  (C),D
    EXX
    INC  C

; Register 4
    OUT  (C),C
    LD   B,E
    DEFB $ED,$71
    DEC  B
    OUTI
    EXX
    OUT  (C),E
    OUT  (C),D
    EXX
    INC  C

; Register 5
    OUT  (C),C
    LD   B,E
    DEFB $ED,$71
    DEC  B
    OUTI
    EXX
    OUT  (C),E
    OUT  (C),D
    EXX
    INC  C

; Register 6
    OUT  (C),C
    LD   B,E
    DEFB $ED,$71
    DEC  B
    OUTI
    EXX
    OUT  (C),E
    OUT  (C),D
    EXX
    INC  C

; Register 7
    OUT  (C),C
    LD   B,E
    DEFB $ED,$71
    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
    OUT  (C),C
    LD   B,E
    DEFB $ED,$71
    DEC  B
    if PLY_UseFades
    DEC  B
    LD   A,(HL)
PLY_Channel1_FadeValue_int
    SUB  0                      ; Set a value from 0 (full volume) to 16 or more (volume to 0).
    JR   NC,$+6
    DEFB $ED,$71
    JR   $+4
    OUT  (C),A
    INC  HL

    else
    
    OUTI
    endif
    EXX
    OUT  (C),E
    OUT  (C),D
    EXX
    INC  C
    INC  HL                     ; Skip unused byte.

; Register 9
    OUT  (C),C
    LD   B,E
    DEFB $ED,$71
    DEC  B
    if PLY_UseFades             ; If PLY_UseFades is set to 1, we manage the volume fade.
    DEC  B
    LD   A,(HL)
PLY_Channel2_FadeValue_int 
    SUB  0                      ; Set a value from 0 (full volume) to 16 or more (volume to 0).
    JR   NC,$+6
    DEFB $ED,$71
    JR   $+4
    OUT  (C),A
    INC  HL

    else
    
    OUTI
    endif
    EXX
    OUT  (C),E
    OUT  (C),D
    EXX
    INC  C
    INC  HL                     ; Skip unused byte.

; Register 10
    OUT  (C),C
    LD   B,E
    DEFB $ED,$71
    DEC  B
    if PLY_UseFades
    DEC  B
    LD   A,(HL)
PLY_Channel3_FadeValue_int
    SUB  0                      ; Set a value from 0 (full volume) to 16 or more (volume to 0).
    JR   NC,$+6
    DEFB $ED,$71
    JR   $+4
    OUT  (C),A
    INC  HL

    else
    
    OUTI
    endif
    EXX
    OUT  (C),E
    OUT  (C),D
    EXX
    INC  C

; Register 11
    OUT  (C),C
    LD   B,E
    DEFB $ED,$71
    DEC  B
    OUTI
    EXX
    OUT  (C),E
    OUT  (C),D
    EXX
    INC  C

; Register 12
    OUT  (C),C
    LD   B,E
    DEFB $ED,$71
    DEC  B
    OUTI
    EXX
    OUT  (C),E
    OUT  (C),D
    EXX
    INC  C

; Register 13
PLY_PSGReg13_Code_int
    LD   A,(HL)
.PLY_PSGReg13_Retrig
    CP   255                    ; If IsRetrig?, force the R13 to be triggered.
    RET  Z
    LD   (.PLY_PSGReg13_Retrig + 1),A
    OUT  (C),C
    LD   B,E
    DEFB $ED,$71
    DEC  B
    OUTI
    EXX
    OUT  (C),E
    OUT  (C),D
    RET
; ---------------------------------------------------------------------------

; There are two holes in the list, because the Volume registers are set relatively to the Frequency of the same Channel (+7, always).
; Also, the Reg7 is passed as a register, so is not kept in the memory.
PLY_PSGRegistersArray
PLY_PSGReg0 DEFB 0
PLY_PSGReg1 DEFB 0
PLY_PSGReg2 DEFB 0
PLY_PSGReg3 DEFB 0
PLY_PSGReg4 DEFB 0
PLY_PSGReg5 DEFB 0
PLY_PSGReg6 DEFB 0
PLY_PSGReg8 DEFB 0              ; +7
        DEFB 0
PLY_PSGReg9 DEFB 0              ; +9
        DEFB 0
PLY_PSGReg10 DEFB 0             ; +11
PLY_PSGReg11 DEFB 0
PLY_PSGReg12 DEFB 0
PLY_PSGReg13 DEFB 0
PLY_PSGRegistersArray_End

; ---------------------------------------------------------------------------
; Plays a sound stream.
; HL=Pointer on Instrument Data
; IY=Pointer on Register code (volume, frequency).
; E=Note
; D=Inverted Volume
; DE'=TrackPitch

; RET=
; HL=New Instrument pointer.
; IXL=Reg7 mask (x00x)

; Also used inside =
; B,C=read byte/second byte.
; IXH=Save original Note (only used for Independant mode).
; ---------------------------------------------------------------------------
PLY_PlaySound
    LD   B,(HL)
    INC  HL
    RR   B
    JP   C,PLY_PS_Hard

;---------------
; Software Sound
;---------------
    ; Second Byte needed?
    RR   B
    JR   C,PLY_PS_S_SecondByteNeeded

    ; No second byte needed. We need to check if Volume is null or not.
    LD   A,B
    AND  %1111
    JR   NZ,PLY_PS_S_SoundOn

    ; Null Volume. It means no Sound. We stop the Sound, the Noise, and it's over.
    LD   (IY + 7),A             ; We have to make the volume to 0, because if a bass Hard was activated before, we have to stop it.
    LD   IXL,%1001

    RET

; ---------------------------------------------------------------------------
PLY_PS_S_SoundOn
    ; Volume is here, no Second Byte needed. It means we have a simple Software sound (Sound = On, Noise = Off)
    ; We have to test Arpeggio and Pitch, however.
    LD   IXL,%1000

    SUB  D                      ; Code Volume.
    JR   NC,.skip_xor_a
    XOR  A
.skip_xor_a
    LD   (IY + 7),A

    RR   B                      ; Needed for the subroutine to get the good flags.
    CALL PLY_PS_CalculateFrequency
    LD   (IY + 0),L             ; Code Frequency.
    LD   (IY + 1),H
    EXX

    RET

; ---------------------------------------------------------------------------
PLY_PS_S_SecondByteNeeded
    LD   IXL,%1000              ; By defaut, No Noise, Sound.

    ; Second Byte needed.
    LD   C,(HL)
    INC  HL

    ; Noise?
    LD   A,C
    AND  %11111
    JR   Z,PLY_PS_S_SBN_NoNoise
    LD   (PLY_PSGReg6),A
    LD   IXL,%0000              ; Open Noise Channel.
PLY_PS_S_SBN_NoNoise

    ; Here we have either Volume and/or Sound. So first we need to read the Volume.
    LD   A,B
    AND  %1111
    SUB  D                      ; Code Volume.
    JR   NC,.skip_xor_a2
    XOR  A
.skip_xor_a2
    LD   (IY + 7),A

    ; Sound?
    BIT  5,C
    JR   NZ,PLY_PS_S_SBN_Sound
    ; No Sound. Stop here.
    INC  IXL                    ; Set Sound bit to stop the Sound.
    RET

; ---------------------------------------------------------------------------
PLY_PS_S_SBN_Sound
    ; Manual Frequency?
    RR   B                      ; Needed for the subroutine to get the good flags.
    BIT  6,C
    CALL PLY_PS_CalculateFrequency_TestManualFrequency
    LD   (IY + 0),L             ; Code Frequency.
    LD   (IY + 1),H
    EXX

    RET

; ---------------------------------------------------------------------------

;-----------
; Hard Sound
;-----------
PLY_PS_Hard
    ; We don't set the Volume to 16 now because we may have reached the end of the sound!

    RR   B                      ; Test Retrig here, it is common to every Hard sounds.
    JR   NC,PLY_PS_Hard_NoRetrig
    LD   A,(PLY_Track1_InstrumentSpeedCpt + 1)  ; Retrig only if it is the first step in this line of Instrument!
    LD   C,A
    LD   A,(PLY_Track1_InstrumentSpeed + 1)
    CP   C
    JR   NZ,PLY_PS_Hard_NoRetrig
    LD   A,PLY_RetrigValue
.sm_retrig
    LD   (PLY_PSGReg13_Code_ext.PLY_PSGReg13_Retrig + 1),A
PLY_PS_Hard_NoRetrig

    ; Independant/Loop or Software/Hardware Dependent?
    BIT  1,B                    ; We don't shift the bits, so that we can use the same code (Frequency calculation) several times.
    JP   NZ,PLY_PS_Hard_LoopOrIndependent

    ; Hardware Sound.
    LD   (IY + 7),16            ; Set Volume
    LD   IXL,%1000              ; Sound is always On here (only Independence mode can switch it off).

    ; This code is common to both Software and Hardware Dependent.
    LD   C,(HL)                 ; Get Second Byte.
    INC  HL
    LD   A,C                    ; Get the Hardware Envelope waveform.
    AND  %1111                  ; We don't care about the bit 7-4, but we have to clear them, else the waveform might be reset.
    LD   (PLY_PSGReg13),A

    BIT  0,B
    JR   Z,PLY_PS_HardwareDependent

;-------------------
; Software Dependent
;-------------------

    ; Calculate the Software frequency
    BIT  4-2,B                  ; Manual Frequency? -2 Because the byte has been shifted previously.
    CALL PLY_PS_CalculateFrequency_TestManualFrequency
    LD   (IY + 0),L             ; Code Software Frequency.
    LD   (IY + 1),H
    EXX

    ; Shift the Frequency.
    LD   A,C
    RRA
    RRA                         ; Shift=Shift*4. The shift is inverted in memory (7 - Editor Shift).
    AND  %11100
    LD   (PLY_PS_SD_Shift + 1),A
    LD   A,B                    ; Used to get the HardwarePitch flag within the second registers set.
    EXX

PLY_PS_SD_Shift
    JR   .smjr_PLY_PS_SD_Shift
.smjr_PLY_PS_SD_Shift
    SRL  H
    RR   L
    SRL  H
    RR   L
    SRL  H
    RR   L
    SRL  H
    RR   L
    SRL  H
    RR   L
    SRL  H
    RR   L
    SRL  H
    RR   L
    JR   NC,.skip_inc_hl
    INC  HL
.skip_inc_hl

    ; Hardware Pitch?
    BIT  7-2,A
    JR   Z,PLY_PS_SD_NoHardwarePitch
    EXX                         ; Get Pitch and add it to the just calculated Hardware Frequency.
    LD   A,(HL)
    INC  HL
    EXX
    ADD  A,L                    ; Slow. Can be optimised? Probably never used anyway.....
    LD   L,A
    EXX
    LD   A,(HL)
    INC  HL
    EXX
    ADC  A,H
    LD   H,A
PLY_PS_SD_NoHardwarePitch
    LD   (PLY_PSGReg11),HL
    EXX

    ; This code is also used by Hardware Dependent.
PLY_PS_SD_Noise
    ; Noise?
    BIT  7,C
    RET  Z
    LD   A,(HL)
    INC  HL
    LD   (PLY_PSGReg6),A
    LD   IXL,%0000
    RET

; ---------------------------------------------------------------------------

;-------------------
; Hardware Dependent
;-------------------
PLY_PS_HardwareDependent
    ; Calculate the Hardware frequency
    BIT  4-2,B                  ; Manual Hardware Frequency? -2 Because the byte has been shifted previously.
    CALL PLY_PS_CalculateFrequency_TestManualFrequency
    LD   (PLY_PSGReg11),HL      ; Code Hardware Frequency.
    EXX

    ; Shift the Hardware Frequency.
    LD   A,C
    RRA
    RRA                         ; Shift=Shift*4. The shift is inverted in memory (7 - Editor Shift).
    AND  %11100
    LD   (PLY_PS_HD_Shift + 1),A
    LD   A,B                    ; Used to get the Software flag within the second registers set.
    EXX

PLY_PS_HD_Shift
    JR   .smjr_PLY_PS_HD_Shift
.smjr_PLY_PS_HD_Shift
    SLA  L
    RL   H
    SLA  L
    RL   H
    SLA  L
    RL   H
    SLA  L
    RL   H
    SLA  L
    RL   H
    SLA  L
    RL   H
    SLA  L
    RL   H

    ; Software Pitch?
    BIT  7-2,A
    JR   Z,PLY_PS_HD_NoSoftwarePitch
    EXX                         ; Get Pitch and add it to the just calculated Software Frequency.
    LD   A,(HL)
    INC  HL
    EXX
    ADD  A,L
    LD   L,A                    ; Slow. Can be optimised? Probably never used anyway.....
    EXX
    LD   A,(HL)
    INC  HL
    EXX
    ADC  A,H
    LD   H,A
PLY_PS_HD_NoSoftwarePitch
    LD   (IY + 0),L             ; Code Frequency.
    LD   (IY + 1),H
    EXX

    ; Go to manage Noise, common to Software Dependent.
    JR   PLY_PS_SD_Noise

; ---------------------------------------------------------------------------
PLY_PS_Hard_LoopOrIndependent
    BIT  0,B                    ; We mustn't shift it to get the result in the Carry, as it would be mess the structure
    JR   Z,PLY_PS_Independent   ; of the flags, making it uncompatible with the common code.

    ; The sound has ended.
    ; If Sound Effects activated, we mark the "end of sound" by returning a 0 as an address.
    if PLY_UseSoundEffects
PLY_PS_EndSound_SFX
    LD   A,0                    ; Is the sound played is a SFX (1) or a normal sound (0)?
    OR   A
    JR   Z,PLY_PS_EndSound_NotASFX

;    LD   HL,0
;    RET

	;******* FOR FANO
	; We have an ending sound effect.
	; The only way to know if it should stop or loop is to compare its 'jump' position
	; with the "empty" instrument.
	; We return 0 if the sound should stop.
	LD   A,(HL)
	INC  HL
	LD   H,(HL)
	LD   L,A

	LD   (PLY_PS_Save_HL + 1),HL
	PUSH DE

Empty_Instrument_Data_Address
    LD   DE,0
	OR   A
	SBC  HL,DE
	POP  DE
	RET  Z	                    ; The sound should stop!

	; The sound should loop. We return its loop point address.
PLY_PS_Save_HL 
    LD   HL,0
	JP   PLY_PlaySound
	;****************

PLY_PS_EndSound_NotASFX
    endif

    ; The sound has ended. Read the new pointer and restart instrument.

    LD   A,(HL)
    INC  HL
    LD   H,(HL)
    LD   L,A
    JP   PLY_PlaySound

; ---------------------------------------------------------------------------

;------------
; Independent
;------------
PLY_PS_Independent
    LD   (IY + 7),16            ; Set Volume

    ; Sound?
    BIT  7-2,B                  ; -2 Because the byte has been shifted previously.
    JR   NZ,PLY_PS_I_SoundOn
    ; No Sound! It means we don't care about the software frequency (manual frequency, arpeggio, pitch).
    LD   IXL,%1001
    JR   PLY_PS_I_SkipSoftwareFrequencyCalculation
PLY_PS_I_SoundOn
    LD   IXL,%1000              ; Sound is on.
    LD   IXH,E                  ; Save the original note for the Hardware frequency, because a Software Arpeggio will modify it.

    ; Calculate the Software frequency
    BIT  4-2,B                  ; Manual Frequency? -2 Because the byte has been shifted previously.
    CALL PLY_PS_CalculateFrequency_TestManualFrequency
    LD   (IY + 0),L             ; Code Software Frequency.
    LD   (IY + 1),H
    EXX

    LD   E,IXH
PLY_PS_I_SkipSoftwareFrequencyCalculation

    LD   B,(HL)                 ; Get Second Byte.
    INC  HL
    LD   A,B                    ; Get the Hardware Envelope waveform.
    AND  %1111                  ; We don't care about the bit 7-4, but we have to clear them, else the waveform might be reset.
    LD   (PLY_PSGReg13),A

    ; Calculate the Hardware frequency
    RR   B                      ; Must shift it to match the expected data of the subroutine.
    RR   B
    BIT  4-2,B                  ; Manual Hardware Frequency? -2 Because the byte has been shifted previously.
    CALL PLY_PS_CalculateFrequency_TestManualFrequency
    LD   (PLY_PSGReg11),HL      ; Code Hardware Frequency.
    EXX

    ; Noise? We can't use the previous common code, because the setting of the Noise is different, since Independent can have no Sound.
    BIT  7-2,B
    RET  Z
    LD   A,(HL)
    INC  HL
    LD   (PLY_PSGReg6),A
    LD   A,IXL                  ; Set the Noise bit.
    RES  3,A
    LD   IXL,A
    RET

; ---------------------------------------------------------------------------
; Subroutine that =
; If Manual Frequency? (Flag Z off), read frequency (Word) and adds the TrackPitch (DE').
; Else, Auto Frequency.
;     if Arpeggio? = 1 (bit 3 from B), read it (Byte).
;     if Pitch? = 1 (bit 4 from B), read it (Word).
;     Calculate the frequency according to the Note (E) + Arpeggio + TrackPitch (DE').

; HL = Pointer on Instrument data.
; DE'= TrackPitch.

; RET=
; HL = Pointer on Instrument moved forward.
; HL'= Frequency
;     RETURN IN AUXILIARY REGISTERS
; ---------------------------------------------------------------------------
PLY_PS_CalculateFrequency_TestManualFrequency
    JR   Z,PLY_PS_CalculateFrequency
    ; Manual Frequency. We read it, no need to read Pitch and Arpeggio.
    ; However, we add TrackPitch to the read Frequency, and that's all.
    LD   A,(HL)
    INC  HL
    EXX
    ADD  A,E                    ; Add TrackPitch LSB.
    LD   L,A
    EXX
    LD   A,(HL)
    INC  HL
    EXX
    ADC  A,D                    ; Add TrackPitch HSB.
    LD   H,A
    RET

; ---------------------------------------------------------------------------
PLY_PS_CalculateFrequency
    ; Pitch?
    BIT  5-1,B
    JR   Z,PLY_PS_S_SoundOn_NoPitch
    LD   A,(HL)
    INC  HL
    EXX
    ADD  A,E                    ; If Pitch found, add it directly to the TrackPitch.
    LD   E,A
    EXX
    LD   A,(HL)
    INC  HL
    EXX
    ADC  A,D
    LD   D,A
    EXX
PLY_PS_S_SoundOn_NoPitch

    ; Arpeggio?
    LD   A,E
    BIT  4-1,B
    JR   Z,PLY_PS_S_SoundOn_ArpeggioEnd
    ADD  A,(HL)                 ; Add Arpeggio to Note.
    INC  HL
    CP   144
    JR   C,PLY_PS_S_SoundOn_ArpeggioEnd
    LD   A,143
PLY_PS_S_SoundOn_ArpeggioEnd

    ; Frequency calculation.
    EXX

    LD   L,A
    LD   H,0
    ADD  HL,HL
    
    LD   BC,PLY_FrequencyTable
    ADD  HL,BC

    LD   A,(HL)
    INC  HL
    LD   H,(HL)
    LD   L,A
    ADD  HL,DE                  ; Add TrackPitch + InstrumentPitch (if any).

    RET

; ---------------------------------------------------------------------------
; Read one Track.
; HL=Track Pointer.

; Ret =
; HL=New Track Pointer.
; Carry = 1 = Wait A lines. Carry=0=Line not empty.
; A=Wait (0(=256)-127), if Carry.
; D=Parameters + Volume.
; E=Note
; B=Instrument. 0=RST
; IX=PitchAdd. Only used if Pitch? = 1.
; ---------------------------------------------------------------------------
PLY_ReadTrack
    LD   A,(HL)
    INC  HL
    SRL  A                      ; Full Optimisation? If yes = Note only, no Pitch, no Volume, Same Instrument.
    JR   C,PLY_ReadTrack_FullOptimisation
    SUB  32                     ; 0-31 = Wait.
    JR   C,PLY_ReadTrack_Wait
    JR   Z,PLY_ReadTrack_NoOptimisation_EscapeCode
    DEC  A                      ; 0 (32-32) = Escape Code for more Notes (parameters will be read)
    
    ; Note. Parameters are present. But the note is only present if Note? flag is 1.
    LD   E,A                    ; Save Note.

    ; Read Parameters
PLY_ReadTrack_ReadParameters
    LD   A,(HL)
    LD   D,A                    ; Save Parameters.
    INC  HL

    RLA                         ; Pitch?
    JR   NC,PLY_ReadTrack_Pitch_End
    LD   B,(HL)                 ; Get PitchAdd
    LD   IXL,B
    INC  HL
    LD   B,(HL)
    LD   IXH,B
    INC  HL
PLY_ReadTrack_Pitch_End

    RLA                         ; Skip IsNote? flag.
    RLA                         ; New Instrument?
    RET  NC
    LD   B,(HL)
    INC  HL
    OR   A                      ; Remove Carry, as the player interpret it as a Wait command.
    RET

; ---------------------------------------------------------------------------
; Escape code, read the Note and returns to read the Parameters.
; ---------------------------------------------------------------------------
PLY_ReadTrack_NoOptimisation_EscapeCode
    LD   E,(HL)
    INC  HL
    JR   PLY_ReadTrack_ReadParameters

; ---------------------------------------------------------------------------
PLY_ReadTrack_FullOptimisation
    ; Note only, no Pitch, no Volume, Same Instrument.
    LD   D,%01000000            ; Note only.
    SUB  1
    LD   E,A
    RET  NC
    LD   E,(HL)                 ; Escape Code found (0). Read Note.
    INC  HL
    OR   A
    RET

; ---------------------------------------------------------------------------
PLY_ReadTrack_Wait
    ADD  A,32
    RET

; ---------------------------------------------------------------------------
PLY_FrequencyTable
    dw 3822,3608,3405,3214,3034,2863,2703,2551,2408,2273,2145,2025 ; Oct -3
    dw 1911,1804,1703,1607,1517,1432,1351,1276,1204,1136,1073,1012 ; Oct -2 (0 in CPC32)
    dw 956,902,851,804,758,716,676,638,602,568,536,506             ; Oct -1 (0 in CPC16, ST32)
    dw 478,451,426,402,379,358,338,319,301,284,268,253             ; Oct  0 (0 in ST16, Arc32)
    dw 239,225,213,201,190,179,169,159,150,142,134,127             ; Oct  1 (0 in Arc16)
    dw 119,113,106,100,95,89,84,80,75,71,67,63                     ; Oct  2
    dw 60,56,53,50,47,45,42,40,38,36,34,32                         ; Oct  3
    dw 30,28,27,25,24,22,21,20,19,18,17,16                         ; Oct  4
    dw 15,14,13,13,12,11,11,10,9,9,8,8                             ; Oct  5 (Medium useful)
    dw 7,7,7,6,6,6,5,5,5,4,4,4                                     ; Oct  6 (Worst)
    dw 4,4,3,3,3,3,3,2,2,2,2,2                                     ; Oct  7 (Even worst)
    dw 2,2,2,2,1,1,1,1,1,1,1,1                                     ; Oct  8 (Not useful)

; ---------------------------------------------------------------------------
; Rutina de inicialización de la canción
; Entradas:
;    DE = Music
; ---------------------------------------------------------------------------
PLY_Init
    LD   HL,9                   ; Skip Header, SampleChannel, YM Clock (DB*3), and Replay Frequency.
    ADD  HL,DE

    LD   DE,PLY_Speed + 1
    LDI                         ; Copy Speed.
    LD   C,(HL)                 ; Get Instruments chunk size.
    INC  HL
    LD   B,(HL)
    INC  HL
    LD   (PLY_Track1_InstrumentsTablePT + 1),HL
    LD   (PLY_Track2_InstrumentsTablePT + 1),HL
    LD   (PLY_Track3_InstrumentsTablePT + 1),HL

    ADD  HL,BC                  ; Skip Instruments to go to the Linker address.
    ; Get the pre-Linker information of the first pattern.
    LD   DE,PLY_Height + 1
    LDI
    LD   DE,PLY_Transposition1 + 1
    LDI
    LD   DE,PLY_Transposition2 + 1
    LDI
    LD   DE,PLY_Transposition3 + 1
    LDI
    LD   DE,PLY_SaveSpecialTrack + 1
    LDI
    LDI
    LD   (PLY_Linker_PT + 1),HL ; Get the Linker address.

    LD   A,1
    LD   (PLY_SpeedCpt + 1),A
    LD   (PLY_HeightCpt + 1),A

    LD   A,$FF
    LD   (PLY_PSGReg13),A
    
    ; Set the Instruments pointers to Instrument 0 data (Header has to be skipped).
    LD   HL,(PLY_Track1_InstrumentsTablePT + 1)
    LD   E,(HL)
    INC  HL
    LD   D,(HL)
    EX   DE,HL
    INC  HL                     ; Skip Instrument 0 Header.
    INC  HL
    LD   (PLY_Track1_Instrument + 1),HL
    LD   (PLY_Track2_Instrument + 1),HL
    LD   (PLY_Track3_Instrument + 1),HL
    RET

; ---------------------------------------------------------------------------
; Stop the music, cut the channels.
; ---------------------------------------------------------------------------
PLY_Stop
    LD   HL,PLY_PSGReg8
    LD   BC,$0300               ; BUG: No silenciaba los 3 canales
.loop_PLY_Stop
    LD   (HL),C
    INC  HL
    INC  HL                     ; EXTRA INC HL to fix it
    DJNZ PLY_Stop
    LD   A,%00111111
    JP   PLY_SendRegisters

    if PLY_UseSoundEffects
; ---------------------------------------------------------------------------
; Initialize the Sound Effects.
; DE = SFX Music.
; ---------------------------------------------------------------------------
PLY_SFX_Init
    ; Find the Instrument Table.
    LD   HL,12
    ADD  HL,DE
    LD   (PLY_SFX_Play_InstrumentTable + 1),HL

	;******* FOR FANO
	LD   E,(HL)
	INC  HL
	LD   D,(HL)
	EX   DE,HL

	INC  HL					    ; Skip Instrument 0 Header.
	INC  HL

	LD   (Empty_Instrument_Data_Address + 1),HL
	;****************
    
; Clear the three channels of any sound effect.
PLY_SFX_StopAll
    LD   HL,0
    LD   (PLY_SFX_Track1_Instrument + 1),HL
    LD   (PLY_SFX_Track2_Instrument + 1),HL
    LD   (PLY_SFX_Track3_Instrument + 1),HL
    RET

PLY_SFX_OffsetPitch         EQU 0
PLY_SFX_OffsetVolume        EQU PLY_SFX_Track1_Volume - PLY_SFX_Track1_Pitch
PLY_SFX_OffsetNote          EQU PLY_SFX_Track1_Note - PLY_SFX_Track1_Pitch
PLY_SFX_OffsetInstrument    EQU PLY_SFX_Track1_Instrument - PLY_SFX_Track1_Pitch
PLY_SFX_OffsetSpeed         EQU PLY_SFX_Track1_InstrumentSpeed - PLY_SFX_Track1_Pitch
PLY_SFX_OffsetSpeedCpt      EQU PLY_SFX_Track1_InstrumentSpeedCpt - PLY_SFX_Track1_Pitch

; ---------------------------------------------------------------------------
; Plays a Sound Effects along with the music.
; Entradas:
;    A = No Channel (0,1,2)
;    L = SFX Number (>0)
;    H = Volume (0...F)
;    E = Note (0...143)
;    D = Speed (0 = As original, 1...255 = new Speed (1 is fastest))
;   BC = Inverted Pitch (-#FFFF -> FFFF). 0 is no pitch. The higher the pitch, the lower the sound.
; ---------------------------------------------------------------------------
PLY_SFX_Play
    LD   IX,PLY_SFX_Track1_Pitch
    OR   A
    JR   Z,PLY_SFX_Play_Selected
    LD   IX,PLY_SFX_Track2_Pitch
    DEC  A
    JR   Z,PLY_SFX_Play_Selected
    LD   IX,PLY_SFX_Track3_Pitch
    
PLY_SFX_Play_Selected
    LD   (IX + PLY_SFX_OffsetPitch + 1),C   ; Set Pitch
    LD   (IX + PLY_SFX_OffsetPitch + 2),B
    LD   A,E                    ; Set Note
    LD   (IX + PLY_SFX_OffsetNote),A
    LD   A,15                   ; Set Volume
    SUB  H
    LD   (IX + PLY_SFX_OffsetVolume),A
    LD   H,0                    ; Set Instrument Address
    ADD  HL,HL
PLY_SFX_Play_InstrumentTable
    LD   BC,0
    ADD  HL,BC
    LD   A,(HL)
    INC  HL
    LD   H,(HL)
    LD   L,A
    LD   A,D                    ; Read Speed or use the user's one?
    OR   A
    JR   NZ,PLY_SFX_Play_UserSpeed
    LD   A,(HL)                 ; Get Speed
PLY_SFX_Play_UserSpeed
    LD   (IX + PLY_SFX_OffsetSpeed + 1),A
    LD   (IX + PLY_SFX_OffsetSpeedCpt + 1),A
    INC  HL                     ; Skip Retrig
    INC  HL
    LD   (IX + PLY_SFX_OffsetInstrument + 1),L
    LD   (IX + PLY_SFX_OffsetInstrument + 2),H

    RET

; ---------------------------------------------------------------------------
; Stops a sound effect on the selected channel                    *** SyX ***
; A = No Channel (0,1,2)
; ---------------------------------------------------------------------------
PLY_SFX_Stop
    LD   HL,PLY_SFX_Track1_Instrument + 1
    OR   A
    JR   Z,PLY_SFX_Stop_ChannelFound
    LD   HL,PLY_SFX_Track2_Instrument + 1
    DEC  A
    JR   Z,PLY_SFX_Stop_ChannelFound
    LD   HL,PLY_SFX_Track3_Instrument + 1
    DEC  A

PLY_SFX_Stop_ChannelFound
    LD   (HL),A
    INC  HL
    LD   (HL),A
    RET

    endif

    if PLY_UseFades
; ---------------------------------------------------------------------------
; Sets the Fade value.
; A = Fade value (0 = full volume, 16 or more = no volume).
; ---------------------------------------------------------------------------
PLY_SetFadeValue
.sm_fadevalue_01
    LD   (PLY_Channel1_FadeValue_ext + 1),A
.sm_fadevalue_02
    LD   (PLY_Channel2_FadeValue_ext + 1),A
.sm_fadevalue_03
    LD   (PLY_Channel3_FadeValue_ext + 1),A
	RET

    endif

; ---------------------------------------------------------------------------
; Choose AY used for the music, the other is used for SFX
; A = 0 -> External AY | Rest -> Internal AY
; ---------------------------------------------------------------------------
PLY_SelectMusicAY
    OR   A
    JR   NZ,.set_internal_ay
.set_external_ay
    ; Patch Send Registers to AY
    LD   A,$21                  ; LD HL,$xxxx
    LD   HL,PLY_PSGRegistersArray
    LD   (PLY_SendRegisters),A
    LD   (PLY_SendRegisters + 1),HL
    
    ; Patch PSG R13 retrig
    LD   HL,PLY_PSGReg13_Code_ext.PLY_PSGReg13_Retrig + 1
    LD   (PLY_Track1_InstrumentsTablePT.sm_retrig + 1),HL
    LD   (PLY_Track2_InstrumentsTablePT.sm_retrig + 1),HL
    LD   (PLY_Track3_InstrumentsTablePT.sm_retrig + 1),HL
    LD   (PLY_PS_Hard.sm_retrig + 1), HL
    
    ; Patch Fade
    if PLY_UseFades
    LD   HL,PLY_Channel1_FadeValue_ext + 1
    LD   (PLY_SetFadeValue.sm_fadevalue_01 + 1),HL
    LD   HL,PLY_Channel2_FadeValue_ext + 1
    LD   (PLY_SetFadeValue.sm_fadevalue_02 + 1),HL
    LD   HL,PLY_Channel3_FadeValue_ext + 1
    LD   (PLY_SetFadeValue.sm_fadevalue_03 + 1),HL
    endif
    RET

.set_internal_ay
    ; Patch Send Registers to AY
    LD   A,$C3                  ; JP $xxxx
    LD   HL,use_internal_ay
    LD   (PLY_SendRegisters),A
    LD   (PLY_SendRegisters + 1),HL
    
    ; Patch PSG R13 Retrig
    LD   HL,PLY_PSGReg13_Code_int.PLY_PSGReg13_Retrig + 1
    LD   (PLY_Track1_InstrumentsTablePT.sm_retrig + 1),HL
    LD   (PLY_Track2_InstrumentsTablePT.sm_retrig + 1),HL
    LD   (PLY_Track3_InstrumentsTablePT.sm_retrig + 1),HL
    LD   (PLY_PS_Hard.sm_retrig + 1), HL

    if PLY_UseFades
    LD   HL,PLY_Channel1_FadeValue_int + 1
    LD   (PLY_SetFadeValue.sm_fadevalue_01 + 1),HL
    LD   HL,PLY_Channel2_FadeValue_int + 1
    LD   (PLY_SetFadeValue.sm_fadevalue_02 + 1),HL
    LD   HL,PLY_Channel3_FadeValue_int + 1
    LD   (PLY_SetFadeValue.sm_fadevalue_03 + 1),HL
    endif

    RET
; ---------------------------------------------------------------------------
;*** End of Arkos Tracker Player
