; variable rate sample player Z80 driver
; it can play a sample (8 bit signed) from 8 Khz up to 32 Khz rate
; Stéphane Dallongeville @2011
;
; Z80 clock is 3.547 Mhz on PAL and 3.575 on NTSC
;
; we always take pessimist case
;
; 32000 Hz : 110 cycles per sample
; 22050 Hz : 161 cycles per sample
; 16000 Hz : 221 cycles per sample
; 13400 Hz : 264 cycles per sample
; 11025 Hz : 321 cycles per sample
;  8000 Hz : 443 cycles per sample
;
; so we have to do 110 cycles per sample output which consist of :
; - read 2 samples from ROM and copy it to a temp buffer in RAM (~48 cycles)
; - output the obtained sample to the DAC (30 cycles)
; - handle wait for sample rate (18 cycles at least)
; - handle loop (14 cycles)
;
; register usage :
; SP  = sample source (in ROM)
; HL  = mix buffer
; B   = rate
; HL' = YMPORT0
; BC' = read buffer
; D'  = $80 (used for sample unsign)


; ###########################      define      ##############################

            INCLUDE "z80_def.i80"   ; basic definitions


; ###########################       var        ##############################

WAVADR      EQU     PARAMS+$00      ; WAV address (bit 8 --> bit 23)
WAVLEN      EQU     PARAMS+$02      ; WAV lenght (bit 8 --> bit 23)
RATE        EQU     PARAMS+$04      ; rate (0=32000, 1=22050, 2=16000, 3=13400, 4=11025, 5=8000)
PAN         EQU     PARAMS+$06      ; panning

IWAVADR     EQU     PARAMS+$10      ; internal WAV address (bit 8 --> bit 23)
IWAVLEN     EQU     PARAMS+$12      ; internal WAV lenght (bit 8 --> bit 23)

NULLWAVADR  EQU     PARAMS+$20      ; silent WAV adr
NULLWAVLEN  EQU     PARAMS+$22      ; silent WAV len

WRITEBUF    EQU     PARAMS+$24
READBUF     EQU     PARAMS+$26

WAVBUFFER0  EQU     $1E00           ; WAV buffer 0
WAVBUFFER1  EQU     $1F00           ; WAV buffer 1


; ###########################      macro       ##############################

            INCLUDE "z80_mac.i80"  ; basic macros


; doWaitForRate
; -------------
; B    -> rate
; A   <-  ?
;
; wait the necessary number of cycle depending the sample rate
; = 18 --> 351 cycles

            macro doWaitForRate

            LD      A, B                    ;                   ' 4     |
            OR      A                       ;                   ' 4     | 18
            JP      Z, .end_wait            ;                   ' 10    |

            wait30                          ;                   ' 30    |
            LD      A, B                    ;                   ' 4     | 51 (69)
            CP      A, 1                    ;                   ' 7     |
            JP      Z, .end_wait            ;                   ' 10    |

            wait39                          ;                   ' 39    |
            LD      A, B                    ;                   ' 4     | 60 (129)
            CP      A, 2                    ;                   ' 7     |
            JP      Z, .end_wait            ;                   ' 10    |

            wait22                          ;                   ' 22    |
            LD      A, B                    ;                   ' 4     | 43 (172)
            CP      A, 3                    ;                   ' 7     |
            JP      Z, .end_wait            ;                   ' 10    |

            wait36                          ;                   ' 36    |
            LD      A, B                    ;                   ' 4     | 57 (229)
            CP      A, 4                    ;                   ' 7     |
            JP      Z, .end_wait            ;                   ' 10    |

            wait122                         ;                   ' 122 (351)

.end_wait

            endm                            ;                   ' 18 --> 351


; copyChannelParams
; -----------------
; copy channel parameters to internal structure
; = 64 cycles

            macro copyChannelParams

            LD      HL, (WAVADR)            ;                   ' 16
            LD      (IWAVADR), HL           ;                   ' 16
            LD      HL, (WAVLEN)            ;                   ' 16
            LD      (IWAVLEN), HL           ;                   ' 16

            endm                            ;                   ' 64

; setNullWav
; ----------
; set null sample play
; = 64 cycles

            macro setNullWav

            LD      HL, (NULLWAVADR)        ;                   ' 16
            LD      (IWAVADR), HL           ;                   ' 16
            LD      HL, (NULLWAVLEN)        ;                   ' 16
            LD      (IWAVLEN), HL           ;                   ' 16

            endm                            ;                   ' 64


; clearPlayAndLoopStatus
; ----------------------
; clear play and loop status flag
; = 50 cycles

            macro clearPlayAndLoopStatus

            clearPlayStatus 0               ; clear play status ' 25
            clearLoopStatus 0               ; clear loop status ' 25

            endm                            ;                   ' 50


; stopChannel
; -----------
; set null sample play
; = 114 cycles

            macro stopChannel

            setNullWav                      ;                   ' 64
            clearPlayAndLoopStatus          ;                   ' 50

            endm                            ;                   ' 114


; handlePlayCommandWhilePlay4
; ---------------------------
; HL  <-  ?
; DE  <-  ?
;
; handle play command while play 4 samples
; = 440 cycles

            macro handlePlayCommandWhilePlay4

            sampleOutputAndWait     ; (0*110)+0 --> perfect     ' 48 (48)

            getPlayCommand 0            ; play command ?        ' 22    | 32 (80)
            JP      NZ, .do_command     ;                       ' 10    |

            wait30                  ; synchro                   ' 30 (110)

            sampleOutputAndWait     ; (1*110)+0 --> perfect     ' 48 (158)

            wait62                  ; synchro                   ' 62 (220)

            sampleOutputAndWait     ; (2*110)+0 --> perfect     ' 48 (268)

            wait62                  ; synchro                   ' 62 (330)

            sampleOutputAndWait     ; (3*110)+0 --> perfect     ' 48 (378)

            wait52                  ;                           ' 52    | 62 (440)
            JP      .end            ;                           ' 10    |

.do_command
            clearPlayCommand 0      ; clear play command        ' 25    | 29 (109)
            wait4                   ; synchro                   ' 4     |

            sampleOutputAndWait     ; (1*110)-1 --> bit soon    ' 48 (157)

            copyChannelParams       ; copy params               ' 64 (221)

            sampleOutputAndWait     ; (2*110)+1 --> bit late    ' 48 (269)

            setPlayStatus 0         ; set play status           ' 25    |
            LD      A, (RATE)       ; get rate value            ' 13    |
            LD      B, A            ; store in B                ' 4     | 59 (328)
            LD      A, (PAN)        ; set pan value             ' 13    |
            LD      C, A            ; store in C                ' 4     |

            sampleOutputAndWait     ; (3*110)-2 --> soon        ' 48 (376)

            LD      HL, YMPORT2     ; prepare panning           ' 10    |
            LD      (HL), $B6       ;                           ' 10    |
            INC     L               ;                           ' 4     |
            wait11                  ;                           ' 11    |
            LD      (HL), C         ; set pan value             ' 7     | 64 (440)
            DEC     L               ;                           ' 4     |
            DEC     L               ;                           ' 4     |
            DEC     L               ;                           ' 4     |
            LD      (HL), $2A       ; prepare DAC out           ' 10    |

.end
            endm                    ;                           ' 440


; prepareChannelWhilePlay4
; ------------------------
; SP  <-  point to the sample source
; HL  <-  point to write buffer
; A   <-  ?
; E   <-  ?
;
; set bank and prepare registers to play channel
; = 402 cycles

            macro prepareChannelWhilePlay4

            sampleOutputAndWait     ; (0*110)+0 --> perfect     ' 48 (48)

            LD      HL, IWAVADR     ; point to sample adr       ' 10    |
            LD      E, (HL)         ; E = bit 8-15              ' 7     |
            LD      A, E            ; A = bit 8-15              ' 4     |
            INC     L               ;                           ' 4     | 40 (88)
            RLA                     ; C flag = bit 15           ' 4     |
            LD      A, (HL)         ; A = bit 16-23             ' 7     |
            RLA                     ; A = bit 15-22             ' 4     |

            LD      HL, BANKREG     ; HL = BANKREG              ' 10    |
            EX      AF, AF'         ; save A                    ' 4     | 22 (110)
            wait8                   ; synchro                   ' 8     |

            sampleOutputAndWait     ; (1*110)+0 --> perfect     ' 48 (158)

            EX      AF, AF'         ; restore A                 ' 4     |
            LD      (HL), A         ; #1 (bit 15)               ' 7     |
            RRA                     ;                           ' 4     |
            LD      (HL), A         ; #2 (bit 16)               ' 7     |
            RRA                     ;                           ' 4     |
            LD      (HL), A         ; #3 (bit 17)               ' 7     | 63 (221)
            RRA                     ;                           ' 4     |
            LD      (HL), A         ; #4 (bit 18)               ' 7     |
            RRA                     ;                           ' 4     |
            LD      (HL), A         ; #5 (bit 19)               ' 7     |
            EX      AF, AF'         ; save A                    ' 4     |
            wait4                   ; synchro                   ' 4     |

            sampleOutputAndWait     ; (2*110)+1 --> bit late    ' 48 (269)

            EX      AF, AF'         ; restore A                 ' 4     |
            RRA                     ;                           ' 4     |
            LD      (HL), A         ; #6 (bit 20)               ' 7     |
            RRA                     ;                           ' 4     | 40 (309)
            LD      (HL), A         ; #7 (bit 21)               ' 7     |
            LD      (HL), L         ; #8 (bit 22 = 0)           ' 7     |
            LD      (HL), L         ; #9 (bit 23 = 0)           ' 7     |

            LD      A, E            ; A = bit 8-15              ' 4     |
            OR      $80             ; A = $80 | bit 8-14        ' 7     |
            LD      H, A            ;                           ' 4     | 23 (332)
            XOR     A               ;                           ' 4     |
            LD      L, A            ; L = $00                   ' 4     |

            sampleOutputAndWait     ; (3*110)+2 --> late        ' 48 (380)

            LD      SP, HL          ; SP = sample src           ' 6     | 22 (402)
            LD      HL, (WRITEBUF)  ;                           ' 16    |

            endm                    ;                           ' 402


; updateChannelDataWhilePlay2
; ---------------------------
; HL  <-  ?
;
; update sample address and sample lenght
; = 180 cycles

            macro updateChannelDataWhilePlay2

            sampleOutputAndWait     ; (0*110)+0 --> perfect     ' 48 (48)

            LD      HL, (IWAVADR)   ; increment address         ' 16    |
            INC     HL              ;                           ' 6     | 38 (86)
            LD      (IWAVADR), HL   ;                           ' 16    |

            LD      HL, (IWAVLEN)   ; decrement lenght          ' 16    | 24 (110)
            wait8                   ; synchro                   ' 8     |

            sampleOutputAndWait     ; (1*110)+0 --> perfect     ' 48 (158)

            DEC     HL              ;                           ' 6     | 22
            LD      (IWAVLEN), HL   ;                           ' 16    | (180)

            endm                    ; 180

; checkChannelEndWhilePlay4
; -------------------------
; HL  <-  ?
; A   <-  ?
;
; check is sample is done, restart it if needed
; = 430 cycles

            macro checkChannelEndWhilePlay4

            sampleOutputAndWait     ; (0*110)+0 --> perfect     ' 48 (48)

            LD      HL, (IWAVLEN)   ; get sample lenght         ' 16    |
            LD      A, H            ;                           ' 4     | 34 (82)
            OR      L               ;                           ' 4     |
            JP      Z, .done        ; sample done ?             ' 10    |

            wait28                  ; synchro                   ' 28 (110)

            sampleOutputAndWait     ; (1*110)+0 --> perfect     ' 48 (158)

            wait62                  ; synchro                   ' 62 (220)

            sampleOutputAndWait     ; (2*110)+0 --> perfect     ' 48 (268)

            wait62                  ; synchro                   ' 62 (330)

            sampleOutputAndWait     ; (3*110)+0 --> perfect     ' 48 (378)

            wait42                  ;                           ' 42    | 52 (430)
            JP      .end            ;                           ' 10    |

.done
            wait28                  ; synchro                   ' 28 (110)

            sampleOutputAndWait     ; (1*110)+0 --> perfect     ' 48 (158)

            getLoopStatus 0         ; loop play ?               ' 22    | 32 (190)
            JP      Z, .stop        ;                           ' 10    |

            wait30                  ; synchro                   ' 30 (220)

            sampleOutputAndWait     ; (2*110)+0 --> perfect     ' 48 (268)

            copyChannelParams       ; recopy channel param      ' 64 (332)

            sampleOutputAndWait     ; (3*110)+2 --> late        ' 48 (380)

            wait40                  ;                           ' 40    | 50 (430)
            JP      .end            ;                           ' 10    |

.stop
            wait30                  ; synchro                   ' 30 (220)

            sampleOutputAndWait     ; (2*110)+0 --> perfect     ' 48 (268)

            setNullWav              ;                           ' 64 (332)

            sampleOutputAndWait     ; (3*110)+2 --> late        ' 48 (380)

            clearPlayAndLoopStatus  ;                           ' 50 (430)

.end
            endm                    ;                           ' 430


; swapBufferWhilePlay2
; --------------------
; HL  <-  point to new write buffer
; DE  <-  point to new read buffer
;
; switch read and write buffer
; = 174 cycles

            macro swapBufferWhilePlay2

            sampleOutputAndWait     ; (0*110)+0 --> perfect     ' 48 (48)

            LD      HL, (WRITEBUF)  ;                           ' 16    |
            LD      DE, (READBUF)   ;                           ' 20    | 62 (110)
            LD      (WRITEBUF), DE  ;                           ' 20    |
            wait6                   ; (can use it here)         ' 6     |

            sampleOutputAndWait     ; (1*110)+0 --> perfect     ' 48 (158)

            LD      (READBUF), HL   ;                           ' 16 (174)

            endm                    ;                           ' 174


; sampleOutput
; ------------
; HL'  -> YMPORT1
; BC' <-> point to play output buffer
; D'   -> $80
;
; output a sample to the DAC
; = 30 cycles

            macro sampleOutput

            EXX                     ;                           ' 4
            LD      A, (BC)         ; read sample from buffer   ' 7
            ADD     D               ; unsign                    ' 4
            INC     C               ; increment with looping    ' 4
            LD      (HL), A         ; play sample               ' 7
            EXX                     ;                           ' 4

            endm                    ;                           ' 30


; sampleOutputAndWait
; -------------------
; HL'  -> YMPORT1
; BC' <-> point to play output buffer
; D'   -> $80
; B    -> rate
;
; output a sample to the DAC and wait for ajusting to current sample rate
; = 48 --> 381 cycles

            macro sampleOutputAndWait

            sampleOutput            ;                           ' 30
            doWaitForRate           ;                           ' 18 --> 351

            endm                    ;                           ' 48 --> 381


; readAndWrite2
; -------------
; SP  <-> point to the sample source (ROM)
; HL  <-> point to the write output buffer
; DE  <-  ?
;
; read 2 samples and write them in output buffer
; = 34 cycles

            macro readAndWrite2

            POP     DE              ; read 2 samples from ROM   ' 10+2 (rom access latency)
            LD      (HL), E         ;                           ' 7
            INC     L               ;                           ' 4
            LD      (HL), D         ;                           ' 7
            INC     L               ;                           ' 4

            endm                    ;                           ' 34


; ###########################       init       ##############################

            ORG     $0000

init
            DI                      ; disable ints
            LD      SP, $2000       ; setup stack
            IM      $01             ; set int mode 1
            XOR     A
            LD      (STATUS), A     ; driver not ready
            LD      (COMMAND), A    ; command cleared
            JP      start           ; jump to start


; ###########################     interrupt    ##############################

            BLOCK   $0038-$

interrupt                           ; do nothing in this program
            RETI


; ###########################       main       ##############################

            BLOCK   $0200-$

start
            LD      HL, PARAMS
            LD      A, $00
            LD      B, $20

cp_loop
            LD      (HL), A         ; clear sample parameters
            INC     HL
            DJNZ    cp_loop

            LD      HL, WAVBUFFER0
            LD      A, $00
            LD      B, $00          ; for 256 * 2 bytes to clear

cb_loop
            LD      (HL), A         ; initialize buffers to silent
            INC     HL
            LD      (HL), A
            INC     HL
            DJNZ    cb_loop

            CALL    initDAC         ; prepare DAC for output

            LD      HL, WAVBUFFER0  ; initialize write and read buffer
            LD      (WRITEBUF), HL
            LD      HL, WAVBUFFER1
            LD      (READBUF), HL

            LD      BC, HL          ; BC' point to read buffer
            LD      D, $80          ; D' = $80 (fast unsign)
            LD      HL, YMPORT1     ; HL' point to YMPORT1
            EXX

wait_nullwav
            LD      HL, (NULLWAVADR)
            LD      A, H
            OR      L
            JP      Z, wait_nullwav ; wait for null wav address being initialised
            LD      HL, (NULLWAVLEN)
            LD      A, H
            OR      L
            JP      Z, wait_nullwav ; wait for null wav len being initialised

            stopChannel             ; stop channel

            LD      A, STATREADY
            LD      (STATUS), A     ; driver ready

main_loop

; channel 0
; ---------

; $00-$03
            handlePlayCommandWhilePlay4 ;                       ' 440   | 440 (4*110)

; $04-$07
            prepareChannelWhilePlay4    ;                       ' 402   |
            LD      C, 128              ; prepare loop counter  ' 7     | 440 (4*110)
            wait31                      ;                       ' 4     |

; $08-$87
loop_ch
            sampleOutputAndWait         ;                       ' 48    |
            readAndWrite2               ; process 2  samples    ' 34    |
            wait14                      ; synchro               ' 14    | 110
            DEC     C                   ;                       ' 4     |
            JP      NZ, loop_ch         ;                       ' 10    |

; $88-$89
            updateChannelDataWhilePlay2 ;                       ' 180   | 220 (2*110)
            wait40                      ;                       ' 40    |

; $8A-$8D
            checkChannelEndWhilePlay4   ;                       ' 430   | 440 (4*110)
            wait10                      ;                       ' 10    |


; free time here
; --------------

; $8E
            sampleOutputAndWait         ;                       ' 48    |
            LD      C, $6F              ; prepare loop          ' 7     | 110
            LD      HL, YMPORT0         ; and DAC set           ' 10    |
            wait45                      ;                       ' 45    |

; $8F-FD
loop_freetime
            sampleOutputAndWait         ;                       ' 48    |
            wait48                      ;                       ' 48    | 110
            DEC     C                   ;                       ' 4     |
            JP      NZ, loop_freetime   ;                       ' 10    |

; $FE-$FF
            swapBufferWhilePlay2        ;                       ' 174   |
            EXX                         ;                       ' 4     |
            LD      BC, (READBUF)       ; read buffer           ' 20    | 220
            EXX                         ;                       ' 4     |
            wait8                       ;                       ' 8     |
            JP      main_loop           ;                       ' 10    |


; ##############################  functions  ################################

            INCLUDE "z80_fct.i80"   ; basic functions


            END
