;
; AKY music player
;
; Port by George Nakos (GGN of pick-your-favorite-group - KUA software productions/D-Bug/Paradize/Reboot/Bello games)
; (yes, crews are becoming pointless :))
;
; Based on the soruces of "Stabilized AKY music player - V1.0."
;       By Julien Névo a.k.a. Targhan/Arkos.
;       February 2018.
;
; This source was written for the rmac assembler (http://virtualjaguar.kicks-ass.net/builds/)
; It should be fairly easy to adapt to other assemblers.
;
; Note that the source makes use of macros, so take a look at their definitions (after these messages end) before reading the code


PLY_AKYst_RRB_NoiseChannelBit equ 5                             ;Bit to modify to set/reset the noise channel.
PLY_AKYst_RRB_SoundChannelBit equ 2                             ;Bit to modify to set/reset the sound channel.


PLY_AKYst_OPCODE_SZF equ $7200                                  ;Opcode for "moveq #0,d0".
PLY_AKYst_OPCODE_CZF  equ $72ff                                 ;Opcode for "moveq #-1,d0".

PLY_AKYst_Start:
        ;Hooks for external calls. Can be removed if not needed.
        bra.s PLY_AKYst_Init                                    ;Player + 0.
        bra.s PLY_AKYst_Play                                    ;Player + 2.
    


;       Initializes the player.
;       a0.l=music address
PLY_AKYst_Init:

        ;Skips the header.
        addq.l #1,a0                                            ;Skips the format version.
        move.b (a0)+,d1                                         ;Channel count.
PLY_AKYst_Init_SkipHeaderLoop:                                  ;There is always at least one PSG to skip.
        addq.l #4,a0
        subq.b #3,d1                                            ;A PSG is three channels.
        beq.s PLY_AKYst_Init_SkipHeaderEnd
        bcc.s PLY_AKYst_Init_SkipHeaderLoop                     ;Security in case of the PSG channel is not a multiple of 3.
PLY_AKYst_Init_SkipHeaderEnd:
        move.l a0,PLY_AKYst_PtLinker                           ;a0 now points on the Linker.

        move.w #PLY_AKYst_OPCODE_SZF,d0
        move.w d0,PLY_AKYst_Channel1_RegisterBlockLineState_Opcode
        move.w d0,PLY_AKYst_Channel2_RegisterBlockLineState_Opcode
        move.w d0,PLY_AKYst_Channel3_RegisterBlockLineState_Opcode
        move.w #1,PLY_AKYst_PatternFrameCounter

        rts

;       Plays the music. It must have been initialized before.
;       The interruption SHOULD be disabled (DI), as the stack is heavily used.
;       a0.l=start of tune

PLY_AKYst_Play:

        lea $ffff8800.w,a2                                      ;cache YM registers
        lea $ffff8802.w,a3


;Linker.
;----------------------------------------
* SMC - DO NOT OPTIMISE!
PLY_AKYst_PatternFrameCounter equ * + 2
        move.w #1,d1                                            ;How many frames left before reading the next Pattern.
        subq.w #1,d1

        beq.s PLY_AKYst_PatternFrameCounter_Over
        move.w d1,PLY_AKYst_PatternFrameCounter
        bra.s PLY_AKYst_PatternFrameManagement_End

PLY_AKYst_PatternFrameCounter_Over:

;The pattern is over. Reads the next one.
* SMC - DO NOT OPTIMISE!
PLY_AKYst_PtLinker = * + 2
        lea 0.l,a6                                              ;Points on the Pattern of the linker.
        move.w (a6)+,d1                                         ;Gets the duration of the Pattern, or 0 if end of the song.
        bne.s PLY_AKYst_LinkerNotEndSong
        ;End of the song. Where to loop?
        move.w (a6)+,d1
        ;We directly point on the frame counter of the pattern to loop to.
        lea (a0,d1.w),a6
        ;Gets the duration again. No need to check the end of the song,
        ;we know it contains at least one pattern.
        move.w (a6)+,d1
PLY_AKYst_LinkerNotEndSong:
        move.w d1,PLY_AKYst_PatternFrameCounter

        move.w (a6)+,PLY_AKYst_Channel1_PtTrack
        move.w (a6)+,PLY_AKYst_Channel2_PtTrack
        move.w (a6)+,PLY_AKYst_Channel3_PtTrack
        move.l a6,PLY_AKYst_PtLinker

        ;Resets the RegisterBlocks of the channels.
        moveq #1,d1
        move.b d1,PLY_AKYst_Channel1_WaitBeforeNextRegisterBlock
        move.b d1,PLY_AKYst_Channel2_WaitBeforeNextRegisterBlock
        move.b d1,PLY_AKYst_Channel3_WaitBeforeNextRegisterBlock
PLY_AKYst_PatternFrameManagement_End:

;Reading the Track - channel 1.
;----------------------------------------
* SMC - DO NOT OPTIMISE!
PLY_AKYst_Channel1_WaitBeforeNextRegisterBlock = * + 3
        move.b #1,d1                                            ;Frames to wait before reading the next RegisterBlock. 0 = finished.
        subq.b #1,d1
        beq.s PLY_AKYst_Channel1_RegisterBlock_Finished
        bra.s PLY_AKYst_Channel1_RegisterBlock_Process
PLY_AKYst_Channel1_RegisterBlock_Finished:
        ;This RegisterBlock is finished. Reads the next one from the Track.
        ;Obviously, starts at the initial state.
        move.w #PLY_AKYst_OPCODE_SZF,PLY_AKYst_Channel1_RegisterBlockLineState_Opcode
* SMC - DO NOT OPTIMISE!
PLY_AKYst_Channel1_PtTrack = * + 2
        lea 0(a0),a6                                            ;Points on the Track.
        move.b (a6),d1                                          ;Gets the duration.
        move.w 2(a6),a1                                         ;Reads the RegisterBlock address.
        lea (a0,a1.w),a1
        move.l a1,PLY_AKYst_Channel1_PtRegisterBlock
        addq.w #4,a6

        sub.l a0,a6                                             ;TODO can we do without this?

        move.w a6,PLY_AKYst_Channel1_PtTrack
        ;d1 is the duration of the block.
PLY_AKYst_Channel1_RegisterBlock_Process:
        ;Processes the RegisterBlock, whether it is the current one or a new one.
        move.b d1,PLY_AKYst_Channel1_WaitBeforeNextRegisterBlock



;Reading the Track - channel 2.
;----------------------------------------
* SMC - DO NOT OPTIMISE!
PLY_AKYst_Channel2_WaitBeforeNextRegisterBlock = * + 3
        move.b #1,d1    ;Frames to wait before reading the next RegisterBlock. 0 = finished.
        subq.b #1,d1       
        beq.s PLY_AKYst_Channel2_RegisterBlock_Finished
        bra.s PLY_AKYst_Channel2_RegisterBlock_Process
PLY_AKYst_Channel2_RegisterBlock_Finished:
        ;This RegisterBlock is finished. Reads the next one from the Track.
        ;Obviously, starts at the initial state.
        move.w #PLY_AKYst_OPCODE_SZF,PLY_AKYst_Channel2_RegisterBlockLineState_Opcode
* SMC - DO NOT OPTIMISE!
PLY_AKYst_Channel2_PtTrack = * + 2
        lea 0(a0),a6                                            ;Points on the Track.
        move.b (a6),d1                                          ;Gets the duration (b1-7). b0 = silence block?
        move.w 2(a6),a1
        lea (a0,a1.w),a1
        move.l a1,PLY_AKYst_Channel2_PtRegisterBlock
        addq.w #4,a6

        sub.l a0,a6                                             ;TODO can we do without this?

        move.w a6,PLY_AKYst_Channel2_PtTrack
        ;d1 is the duration of the block.
PLY_AKYst_Channel2_RegisterBlock_Process:
        ;Processes the RegisterBlock, whether it is the current one or a new one.
        move.b d1,PLY_AKYst_Channel2_WaitBeforeNextRegisterBlock




;Reading the Track - channel 3.
;----------------------------------------
;PLY_AKYst_Channel3_WaitBeforeNextRegisterBlock: ld a,1         ;Frames to wait before reading the next RegisterBlock. 0 = finished.
* SMC - DO NOT OPTIMISE!
PLY_AKYst_Channel3_WaitBeforeNextRegisterBlock = * + 3
        move.b #1,d1    ;Frames to wait before reading the next RegisterBlock. 0 = finished.
        subq.b #1,d1
        beq.s PLY_AKYst_Channel3_RegisterBlock_Finished
        bra.s PLY_AKYst_Channel3_RegisterBlock_Process
PLY_AKYst_Channel3_RegisterBlock_Finished:
        ;This RegisterBlock is finished. Reads the next one from the Track.
        ;Obviously, starts at the initial state.
        move.w #PLY_AKYst_OPCODE_SZF,PLY_AKYst_Channel3_RegisterBlockLineState_Opcode
* SMC - DO NOT OPTIMISE!
PLY_AKYst_Channel3_PtTrack equ * + 2
        lea 0(a0),a6                                            ;Points on the Track.

        move.b (a6),d1                                          ;Gets the duration (b1-7). b0 = silence block?
        move.w 2(a6),a1
        lea (a0,a1.w),a1
        move.l a1,PLY_AKYst_Channel3_PtRegisterBlock
        addq.w #4,a6

        sub.l a0,a6                                             ;TODO can we do without this?

        move.w a6,PLY_AKYst_Channel3_PtTrack
PLY_AKYst_Channel3_RegisterBlock_Process:
        ;Processes the RegisterBlock, whether it is the current one or a new one.
        move.b d1,PLY_AKYst_Channel3_WaitBeforeNextRegisterBlock











;Reading the RegisterBlock.
;----------------------------------------

;Reading the RegisterBlock - Channel 1
;----------------------------------------

        move.w #((0 * 256) + 8),d7                              ;d7 high byte = first frequency register, d7 low byte = first volume register.
        move.w #$f690,d4                                        ;$90 used for both $80 for the PSG, and volume 16!
        
        ;In d3, 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.
        move.w #%11100000,d3


* SMC - DO NOT OPTIMISE!
PLY_AKYst_Channel1_PtRegisterBlock = * + 2
        lea 0.l,a1                                              ;Points on the data of the RegisterBlock to read.
* SMC - DO NOT OPTIMISE!
PLY_AKYst_Channel1_RegisterBlockLineState_Opcode: moveq #0,d1   ;if initial state, "moveq #0,d1" / "moveq #-1,d1" if non-initial state.
        bsr PLY_AKYst_ReadRegisterBlock
PLY_AKYst_Channel1_RegisterBlock_Return:
        move.w #PLY_AKYst_OPCODE_CZF,PLY_AKYst_Channel1_RegisterBlockLineState_Opcode
        move.l a1,PLY_AKYst_Channel1_PtRegisterBlock           ;This is new pointer on the RegisterBlock.


;Reading the RegisterBlock - Channel 2
;----------------------------------------

        ;Shifts the R7 for the next channels.
        lsr.b #1,d3
        

* SMC - DO NOT OPTIMISE!
PLY_AKYst_Channel2_PtRegisterBlock equ * + 2
        lea 0.l,a1                                              ;Points on the data of the RegisterBlock to read.
* SMC - DO NOT OPTIMISE!
PLY_AKYst_Channel2_RegisterBlockLineState_Opcode: moveq #0,d1   ;if initial state, "moveq #0,d1" / "moveq #-1,d1" if non-initial state.
        bsr PLY_AKYst_ReadRegisterBlock
PLY_AKYst_Channel2_RegisterBlock_Return:
        move.w #PLY_AKYst_OPCODE_CZF,PLY_AKYst_Channel2_RegisterBlockLineState_Opcode
        move.l a1,PLY_AKYst_Channel2_PtRegisterBlock           ;This is new pointer on the RegisterBlock.


;Reading the RegisterBlock - Channel 3
;----------------------------------------

        ;Shifts the R7 for the next channels.
        lsr.b #1,d3

* SMC - DO NOT OPTIMISE!
PLY_AKYst_Channel3_PtRegisterBlock equ * + 2
        lea 0.l,a1                                              ;Points on the data of the RegisterBlock to read.
* SMC - DO NOT OPTIMISE!
PLY_AKYst_Channel3_RegisterBlockLineState_Opcode: moveq #0,d1   ;if initial state, "moveq #0,d1" / "moveq #-1,d1" if non-initial state.
        bsr PLY_AKYst_ReadRegisterBlock
PLY_AKYst_Channel3_RegisterBlock_Return:
        move.w #PLY_AKYst_OPCODE_CZF,PLY_AKYst_Channel3_RegisterBlockLineState_Opcode
        move.l a1,PLY_AKYst_Channel3_PtRegisterBlock           ;This is new pointer on the RegisterBlock.

        ;Register 7 to d1.
        move.w d3,d1

;Almost all the channel specific registers have been sent. Now sends the remaining registers (6, 7, 11, 12, 13).

;Register 7. Note that managing register 7 before 6/11/12 is done on purpose (the 6/11/12 registers are filled using OUTI).

        move.b #7,(a2)
        move.b d1,(a3)

;Register 6
        move.b #6,(a2)
        move.b PLY_AKYst_PsgRegister6(pc),(a3)

;Register 11
        move.b #11,(a2)
        move.b PLY_AKYst_PsgRegister11(pc),(a3)

;Register 12
        move.b #12,(a2)
        move.b PLY_AKYst_PsgRegister12(pc),(a3)


;Register 13
PLY_AKYst_PsgRegister13_Code:
        move.b PLY_AKYst_PsgRegister13(pc),d1
* SMC - DO NOT OPTIMISE!
PLY_AKYst_PsgRegister13_Retrig equ * + 3
        cmp.b #255,d1                                           ;If IsRetrig?, force the R13 to be triggered.
        bne.s PLY_AKYst_PsgRegister13_Change
        bra.s PLY_AKYst_PsgRegister13_End
PLY_AKYst_PsgRegister13_Change:
        move.b d1,PLY_AKYst_PsgRegister13_Retrig

        move.b #13,(a2)
        move.b d1,(a3)

PLY_AKYst_PsgRegister13_End:



PLY_AKYst_Exit:
        rts





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

PLY_AKYst_ReadRegisterBlock:
        ;Gets the first byte of the line. What type? Jump to the matching code thanks to the zero flag.
PLY_AKYst_RRB_BranchOnNonInitailState:
        bne PLY_AKYst_RRB_NonInitialState

        ; Code from the bcs and above copied here so nothing will screw with the zero flag
        move.b (a1)+,d1
        
        move.b d1,d2
        and.b #%00000011,d2
        add.b d2,d2
        lsr.b #2,d1
        ext.w d2
        lea PLY_AKYst_IS_JPTable(pc),a5
        add.w d2,a5
        move.w (a5),a5
        jmp PLY_AKYst_IS_JPTable(pc,a5.w)
PLY_AKYst_IS_JPTable:
        dc.w PLY_AKYst_RRB_IS_NoSoftwareNoHardware-PLY_AKYst_IS_JPTable
        dc.w PLY_AKYst_RRB_IS_SoftwareOnly        -PLY_AKYst_IS_JPTable
        dc.w PLY_AKYst_RRB_IS_HardwareOnly        -PLY_AKYst_IS_JPTable
        dc.w PLY_AKYst_RRB_IS_SoftwareAndHardware -PLY_AKYst_IS_JPTable

;Generic code interpreting the RegisterBlock - Initial state.
;----------------------------------------------------------------
;IN:    a1 = Points after the first byte.
;       A = First byte, twice shifted to the right (type removed).
;       d3 = 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!
;       A' = free to use (not used).
;       d4 = f680
;       d7 (low byte) = Volume register.
;       d7 (high byte) = LSB frequency register.

;OUT:   a1 MUST point after the structure.
;       d3 = updated (ONLY bit 2 and 5).
;       d7 (low byte) = Volume register increased of 1 (*** IMPORTANT! The code MUST increase it, even if not using it! ***)
;       d7 (high byte) = LSB frequency register, increased of 2 (see above).


PLY_AKYst_RRB_IS_NoSoftwareNoHardware:

        ;No software no hardware.
        lsr.b #1,d1             ;Noise?
        bcs.s PLY_AKYst_RRB_NIS_NoSoftwareNoHardware_ReadNoise
        bra.s PLY_AKYst_RRB_NIS_NoSoftwareNoHardware_ReadNoise_End
PLY_AKYst_RRB_NIS_NoSoftwareNoHardware_ReadNoise:
        ;There is a noise. Reads it.
        move.b (a1)+,PLY_AKYst_PsgRegister6

        ;Opens the noise channel.
        bclr #PLY_AKYst_RRB_NoiseChannelBit,d4
PLY_AKYst_RRB_NIS_NoSoftwareNoHardware_ReadNoise_End:
        
PLY_AKYst_RRB_NIS_NoSoftwareNoHardware_ReadVolume:
        ;The volume is now in b0-b3.

        move.b d7,(a2)
        move.b d1,(a3)

        add.w #(2<<8)+1,d7                                      ;Increases the volume register (low byte) and frequency register (high byte).
        ;Closes the sound channel.
        bset #PLY_AKYst_RRB_SoundChannelBit, d3
        rts


;---------------------
PLY_AKYst_RRB_IS_HardwareOnly:

        ;Retrig?
        lsr.b #1,d1
        bcs.s PLY_AKYst_RRB_IS_HO_Retrig
        bra.s PLY_AKYst_RRB_IS_HO_AfterRetrig
PLY_AKYst_RRB_IS_HO_Retrig:
        bset #7,d1                                              ;A value to make sure the retrig is performed, yet A can still be use.
        move.b d1,PLY_AKYst_PsgRegister13_Retrig
PLY_AKYst_RRB_IS_HO_AfterRetrig:

        ;Noise?
        lsr.b #1,d1
        bcs.s PLY_AKYst_RRB_IS_HO_Noise 
        bra.s PLY_AKYst_RRB_IS_HO_AfterNoise
PLY_AKYst_RRB_IS_HO_Noise:                                      ;Reads the noise.
        move.b (a1)+,PLY_AKYst_PsgRegister6
 
        ;Opens the noise channel.
        bclr #PLY_AKYst_RRB_NoiseChannelBit,d3
PLY_AKYst_RRB_IS_HO_AfterNoise:
        ;The envelope.
        and.b #%1111,d1
        move.b d1,PLY_AKYst_PsgRegister13

        ;Copies the hardware period.
        move.b (a1)+,PLY_AKYst_PsgRegister11
        move.b (a1)+,PLY_AKYst_PsgRegister11+1

        ;Closes the sound channel.
        bset #PLY_AKYst_RRB_SoundChannelBit,d3

        move.b d7,(a2)
        move.b d4,(a3)                                     ;(volume to 16).

        add.w #$201,d7                                          ;Increases the volume register (low byte), and frequency register (high byte - mandatory!).
        rts

;---------------------
PLY_AKYst_RRB_IS_SoftwareOnly:

        ;Software only. Structure: 0vvvvntt.
        ;Noise?
        lsr.b #1,d1
        bcs.s PLY_AKYst_RRB_IS_SoftwareOnly_Noise
        bra.s PLY_AKYst_RRB_IS_SoftwareOnly_AfterNoise
PLY_AKYst_RRB_IS_SoftwareOnly_Noise:
        ;Noise. Reads it.
        move.b (a1)+,PLY_AKYst_PsgRegister6
        ;Opens the noise channel.
        bclr #PLY_AKYst_RRB_NoiseChannelBit,d3
PLY_AKYst_RRB_IS_SoftwareOnly_AfterNoise:

        ;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!
        move.b d7,(a2)
        move.b d1,(a3)

        addq.w #1,d7                                            ;Increases the volume register.

        ;Reads the software period.
        move.w d7,(a2)
        move.b (a1)+,(a3)

        add.w #1<<8,d7                                          ;Increases the frequency register.

        move.w d7,(a2)
        move.b (a1)+,(a3)

        add.w #1<<8,d7                                          ;Increases the frequency register.

        rts




;---------------------
PLY_AKYst_RRB_IS_SoftwareAndHardware:
        
        ;Retrig?
        lsr.b #1,d1
        bcs.s PLY_AKYst_RRB_IS_SAH_Retrig
        bra.s PLY_AKYst_RRB_IS_SAH_AfterRetrig
PLY_AKYst_RRB_IS_SAH_Retrig:
        bset #7,d1                                              ;A value to make sure the retrig is performed, yet d1 can still be use.
        move.b d1,PLY_AKYst_PsgRegister13_Retrig
PLY_AKYst_RRB_IS_SAH_AfterRetrig:

        ;Noise?
        lsr.b #1,d1
        bcs.s PLY_AKYst_RRB_IS_SAH_Noise
        bra.s PLY_AKYst_RRB_IS_SAH_AfterNoise
PLY_AKYst_RRB_IS_SAH_Noise:
        ;Reads the noise.
        move.b (a1)+,PLY_AKYst_PsgRegister6
        ;Opens the noise channel.
        bclr #PLY_AKYst_RRB_NoiseChannelBit,d3
PLY_AKYst_RRB_IS_SAH_AfterNoise:

        ;The envelope.
        and.b #%1111,d1
        move.b d1,PLY_AKYst_PsgRegister13

        ;Reads the software period.
        move.w d7,(a2)
        move.b (a1)+,(a3)
       
        add.w #1<<8,d7                                          ;Increases the frequency register.

        move.w d7,(a2)
        move.b (a1)+,(a3)

        add.w #1<<8,d7                                          ;Increases the frequency register.

        move.b d7,(a2)
        move.b d4,(a3)                                     ;(volume to 16).

        addq.w #1,d7                                            ;Increases the volume register.

        ;Copies the hardware period.
        move.b (a1)+,PLY_AKYst_PsgRegister11
        move.b (a1)+,PLY_AKYst_PsgRegister11+1
        rts





;Generic code interpreting the RegisterBlock - Non initial state. See comment about the Initial state for the registers ins/outs.
;----------------------------------------------------------------
PLY_AKYst_RRB_NonInitialState:

        ; Code from the start of PLY_AKYst_ReadRegisterBlock copied here so nothing will screw with the zero flag        
        move.b (a1)+,d1

        move.b d1,d2
        and.b #%00001111,d2                                      ;Keeps 4 bits to be able to detect the loop. (%1000)
        add.b d2,d2

        lsr.b #2,d1
        ext.w d2
        lea PLY_AKYst_NIS_JPTable(pc),a5
        add.w PLY_AKYst_NIS_JPTable(pc,d2.w),a5
        jmp (a5)
PLY_AKYst_NIS_JPTable:

        dc.w PLY_AKYst_RRB_NIS_NoSoftwareNoHardware-PLY_AKYst_NIS_JPTable          ;%0000
        dc.w PLY_AKYst_RRB_NIS_SoftwareOnly        -PLY_AKYst_NIS_JPTable          ;%0001
        dc.w PLY_AKYst_RRB_NIS_HardwareOnly        -PLY_AKYst_NIS_JPTable          ;%0010
        dc.w PLY_AKYst_RRB_NIS_SoftwareAndHardware -PLY_AKYst_NIS_JPTable          ;%0011

        dc.w PLY_AKYst_RRB_NIS_NoSoftwareNoHardware-PLY_AKYst_NIS_JPTable          ;%0100
        dc.w PLY_AKYst_RRB_NIS_SoftwareOnly        -PLY_AKYst_NIS_JPTable          ;%0101
        dc.w PLY_AKYst_RRB_NIS_HardwareOnly        -PLY_AKYst_NIS_JPTable          ;%0110
        dc.w PLY_AKYst_RRB_NIS_SoftwareAndHardware -PLY_AKYst_NIS_JPTable          ;%0111
        
        dc.w PLY_AKYst_RRB_NIS_ManageLoop          -PLY_AKYst_NIS_JPTable          ;%1000. Loop!
        dc.w PLY_AKYst_RRB_NIS_SoftwareOnly        -PLY_AKYst_NIS_JPTable          ;%1001
        dc.w PLY_AKYst_RRB_NIS_HardwareOnly        -PLY_AKYst_NIS_JPTable          ;%1010
        dc.w PLY_AKYst_RRB_NIS_SoftwareAndHardware -PLY_AKYst_NIS_JPTable          ;%1011
        
        dc.w PLY_AKYst_RRB_NIS_NoSoftwareNoHardware-PLY_AKYst_NIS_JPTable          ;%1100
        dc.w PLY_AKYst_RRB_NIS_SoftwareOnly        -PLY_AKYst_NIS_JPTable          ;%1101
        dc.w PLY_AKYst_RRB_NIS_HardwareOnly        -PLY_AKYst_NIS_JPTable          ;%1110
        dc.w PLY_AKYst_RRB_NIS_SoftwareAndHardware -PLY_AKYst_NIS_JPTable          ;%1111
        

PLY_AKYst_RRB_NIS_ManageLoop:
        ;Loops. Reads the next pointer to this RegisterBlock.
;Check if address is odd, and make it even if so
;Auto-even address. Not the best thing we could do performance wise but it'll do for now
        move.l a1,d1
        addq.l #1,d1
        bclr #0,d1
        move.l d1,a1
        move.w (a1),a1
        lea (a0,a1.w),a1

        ;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.
        move.b (a1)+,d1
        
        ;Reads the next NIS state. We know there won't be any loop.
        move.b d1,d2                                            ;d1 must be saved!
        and.b #%00000011,d2
        add.b d2,d2

        lsr.b #2,d1
        ext.w d2
        lea PLY_AKYst_NIS_JPTable_NoLoop(pc),a5
        add.w d2,a5
        move.w (a5),a5
        jmp PLY_AKYst_NIS_JPTable_NoLoop(pc,a5.w)




        ;This table jumps at each state, but AFTER the loop compensation.
PLY_AKYst_NIS_JPTable_NoLoop:
        dc.w PLY_AKYst_RRB_NIS_NoSoftwareNoHardware_Loop-PLY_AKYst_NIS_JPTable_NoLoop     ;%00
        dc.w PLY_AKYst_RRB_NIS_SoftwareOnly_Loop        -PLY_AKYst_NIS_JPTable_NoLoop     ;%01
        dc.w PLY_AKYst_RRB_NIS_HardwareOnly_Loop        -PLY_AKYst_NIS_JPTable_NoLoop     ;%10
        dc.w PLY_AKYst_RRB_NIS_SoftwareAndHardware_Loop -PLY_AKYst_NIS_JPTable_NoLoop     ;%11
        
        


PLY_AKYst_RRB_NIS_NoSoftwareNoHardware:
PLY_AKYst_RRB_NIS_NoSoftwareNoHardware_Loop:
        ;No software, no hardware.
        ;NO NEED to test the loop! It has been tested before. We can optimize from the original code.
        move.b d1,d2                                            ;Used below.

        ;Closes the sound channel.
        bset #PLY_AKYst_RRB_SoundChannelBit,d3

        ;Volume? bit 2 - 2.
        lsr.b #1,d1
        bcs.s PLY_AKYst_RRB_NIS_Volume
        bra.s PLY_AKYst_RRB_NIS_AfterVolume
PLY_AKYst_RRB_NIS_Volume:
        and.b #%1111,d1
        move.b d7,(a2)
        move.b d1,(a3)

PLY_AKYst_RRB_NIS_AfterVolume:

        add.w #$201,d7                                          ;Next volume register (low byte) and frequency registers (high byte)

        ;Noise? Was on bit 7, but there has been two shifts. We can't use d1, it may have been modified by the volume AND.
        btst #7 - 2,d2
        bne.s PLY_AKYst_RRB_NIS_Noise
        rts
PLY_AKYst_RRB_NIS_Noise:
        ;Noise.
        move.b (a1)+,PLY_AKYst_PsgRegister6
        ;Opens the noise channel.
        bclr #PLY_AKYst_RRB_NoiseChannelBit,d3
        rts






;---------------------
PLY_AKYst_RRB_NIS_SoftwareOnly:
PLY_AKYst_RRB_NIS_SoftwareOnly_Loop:
        
        ;Software only. Structure: mspnoise lsp v  v  v  v  (0  1).
        move.b d1,d2
        ;Gets the volume (already shifted).
        and.b #%1111,d1
        move.b d7,(a2)
        move.b d1,(a3)

        addq.w #1,d7                                            ;Increases the volume register.

        ;LSP? (Least Significant byte of Period). Was bit 6, but now shifted.
        btst #6 - 2,d2
        bne.s PLY_AKYst_RRB_NIS_SoftwareOnly_LSP
        bra.s PLY_AKYst_RRB_NIS_SoftwareOnly_AfterLSP
PLY_AKYst_RRB_NIS_SoftwareOnly_LSP:
        move.w d7,(a2)
        move.b (a1)+,(a3)
                                                                ;d7 high byte not incremented on purpose.
PLY_AKYst_RRB_NIS_SoftwareOnly_AfterLSP:

        ;MSP AND/OR (Noise and/or new Noise)? (Most Significant byte of Period).
        btst #7 - 2,d2
        bne.s PLY_AKYst_RRB_NIS_SoftwareOnly_MSPAndMaybeNoise
        add.w #2<<8,d7
        rts
        
PLY_AKYst_RRB_NIS_SoftwareOnly_MSPAndMaybeNoise:
        ;MSP and noise?, in the next byte. nipppp (n = newNoise? i = isNoise? p = MSB period).
        move.b (a1)+,d1                                         ;Useless bits at the end, not a problem.
                                                                ;Sends the MSB software frequency.
        add.w #1<<8,d7                                          ;Was not increased before.

        move.w d7,(a2)
        move.b d1,(a3)

        add.w #1<<8,d7                                          ;Increases the frequency register.
        
        rol.b #1,d1                                             ;Carry is isNoise?
        bcs.s PLY_AKYst_RRB_NIS_SoftwareOnly_NoisePresent
        rts
PLY_AKYst_RRB_NIS_SoftwareOnly_NoisePresent:
        ;Opens the noise channel.
        bclr #PLY_AKYst_RRB_NoiseChannelBit,d3
       
        ;Is there a new noise value? If yes, gets the noise.
        rol.b #1,d1
        bcs.s PLY_AKYst_RRB_NIS_SoftwareOnly_Noise
        rts
PLY_AKYst_RRB_NIS_SoftwareOnly_Noise:
        ;Gets the noise.
        move.b (a1)+,PLY_AKYst_PsgRegister6
        rts


;---------------------
PLY_AKYst_RRB_NIS_HardwareOnly:

PLY_AKYst_RRB_NIS_HardwareOnly_Loop:

        ;Gets the envelope (initially on b2-b4, but currently on b0-b2). It is on 3 bits, must be encoded on 4. Bit 0 must be 0.
        rol.b #1,d1
        move.b d1,d2
        and.b #%1110,d1
        move.b d1,PLY_AKYst_PsgRegister13

        ;Closes the sound channel.
        bset #PLY_AKYst_RRB_SoundChannelBit,d3

        ;Hardware volume.
        move.b d7,(a2)
        move.b d4,(a3)                                     ;(16 = hardware volume).

        add.w #$201,d7                                          ;Increases the volume register (low byte), frequency register (high byte)

        move.b d2,d1

        ;LSB for hardware period? Currently on b6.
        rol.b #2,d1
        bcs.s PLY_AKYst_RRB_NIS_HardwareOnly_LSB
        bra.s PLY_AKYst_RRB_NIS_HardwareOnly_AfterLSB
PLY_AKYst_RRB_NIS_HardwareOnly_LSB:
        move.b (a1)+,PLY_AKYst_PsgRegister11
PLY_AKYst_RRB_NIS_HardwareOnly_AfterLSB:

        ;MSB for hardware period?
        rol.b #1,d1
        bcs.s PLY_AKYst_RRB_NIS_HardwareOnly_MSB
        bra.s PLY_AKYst_RRB_NIS_HardwareOnly_AfterMSB
PLY_AKYst_RRB_NIS_HardwareOnly_MSB:
        move.b (a1)+,PLY_AKYst_PsgRegister12
PLY_AKYst_RRB_NIS_HardwareOnly_AfterMSB:
        
        ;Noise or retrig?
        rol.b #1,d1
        bcs.s PLY_AKYst_RRB_NIS_Hardware_Shared_NoiseOrRetrig_AndStop
        rts


;---------------------
PLY_AKYst_RRB_NIS_SoftwareAndHardware:

PLY_AKYst_RRB_NIS_SoftwareAndHardware_Loop:

        ;Hardware volume.
                                                                ;Sends the volume.
        move.b d7,(a2)
        move.b d4,(a3)                                     ;(16 = hardware volume).

        addq.w #1,d7                                            ;Increases the volume register.

        ;LSB of hardware period?
        ror.b #1,d1
        bcs.s PLY_AKYst_RRB_NIS_SAHH_LSBH
        bra.s PLY_AKYst_RRB_NIS_SAHH_AfterLSBH
PLY_AKYst_RRB_NIS_SAHH_LSBH:
        move.b (a1)+,PLY_AKYst_PsgRegister11
PLY_AKYst_RRB_NIS_SAHH_AfterLSBH:

        ;MSB of hardware period?
        ror.b #1,d1
        bcs.s PLY_AKYst_RRB_NIS_SAHH_MSBH
        bra.s PLY_AKYst_RRB_NIS_SAHH_AfterMSBH
PLY_AKYst_RRB_NIS_SAHH_MSBH:
        move.b (a1)+,PLY_AKYst_PsgRegister12
PLY_AKYst_RRB_NIS_SAHH_AfterMSBH:
        
        ;LSB of software period?
        ror.b #1,d1
        bcs.s PLY_AKYst_RRB_NIS_SAHH_LSBS
        bra.s PLY_AKYst_RRB_NIS_SAHH_AfterLSBS
PLY_AKYst_RRB_NIS_SAHH_LSBS:
        move.w d7,(a2)
        move.b (a1)+,(a3)
                                                                ;d7 high byte not increased on purpose.
PLY_AKYst_RRB_NIS_SAHH_AfterLSBS:
       
        ;MSB of software period?
        ror.b #1,d1
        bcs.s PLY_AKYst_RRB_NIS_SAHH_MSBS
        bra.s PLY_AKYst_RRB_NIS_SAHH_AfterMSBS
PLY_AKYst_RRB_NIS_SAHH_MSBS:
                                                                ;Sends the MSB software frequency.
        add.w #1<<8,d7

        move.w d7,(a2)
        move.b (a1)+,(a3)

        sub.w #1<<8,d7                                          ;Yup. Will be compensated below.
PLY_AKYst_RRB_NIS_SAHH_AfterMSBS:
        add.w #2<<8,d7

        ;New hardware envelope?
        ror.b #1,d1
        bcs.s PLY_AKYst_RRB_NIS_SAHH_Envelope
        bra.s PLY_AKYst_RRB_NIS_SAHH_AfterEnvelope
PLY_AKYst_RRB_NIS_SAHH_Envelope:
        move.b (a1)+,PLY_AKYst_PsgRegister13
PLY_AKYst_RRB_NIS_SAHH_AfterEnvelope:

        ;Retrig and/or noise?
        ror.b #1,d1
        bcs.s PLY_AKYst_RRB_NIS_Hardware_Shared_NoiseOrRetrig_AndStop
        rts

        ;This code is shared with the HardwareOnly. It reads the Noise/Retrig byte, interprets it and exits.
        ;------------------------------------------
PLY_AKYst_RRB_NIS_Hardware_Shared_NoiseOrRetrig_AndStop:
        ;Noise or retrig. Reads the next byte.
        move.b (a1)+,d1

        ;Retrig?
        ror.b #1,d1
        bcs.s PLY_AKYst_RRB_NIS_S_NOR_Retrig
        bra.s PLY_AKYst_RRB_NIS_S_NOR_AfterRetrig
PLY_AKYst_RRB_NIS_S_NOR_Retrig:
        bset #7,d1                                              ;A value to make sure the retrig is performed, yet d1 can still be use.
        move.b d1,PLY_AKYst_PsgRegister13_Retrig
PLY_AKYst_RRB_NIS_S_NOR_AfterRetrig:

        ;Noise? If no, nothing more to do.
        ror.b #1,d1
        bcs.s PLY_AKYst_RRB_NIS_S_NOR_Noise
        rts
PLY_AKYst_RRB_NIS_S_NOR_Noise:
        
        ;Noise. Opens the noise channel.
        bclr #PLY_AKYst_RRB_NoiseChannelBit,d3
        ;Is there a new noise value? If yes, gets the noise.
        ror.b #1,d1
        bcs.s PLY_AKYst_RRB_NIS_S_NOR_SetNoise
        rts
PLY_AKYst_RRB_NIS_S_NOR_SetNoise:
        ;Sets the noise.
        move.b d1,PLY_AKYst_PsgRegister6
        rts

;Some stored PSG registers.
PLY_AKYst_PsgRegister6: dc.b 0
PLY_AKYst_PsgRegister11: dc.b 0
PLY_AKYst_PsgRegister12: dc.b 0
PLY_AKYst_PsgRegister13: dc.b 0

