Sona 0.50 Source

Sona 0.50/src-z80/pcm_render.z80

;****************************************************************************
; PcmRender0ch
; "Render" routine for 0ch PCM. This is called when all PCM channels go mute,
; to shut down PCM playback once the PCM buffer is emptied.
;----------------------------------------------------------------------------
; breaks ... a
;****************************************************************************

PcmRender0ch:
    xor     a                           ; Mark all channels free
    ld      (PcmChInUse), a
    
    ld      a, Z80OP_RET                ; Stop PCM playback
    ld      (OutputPcm), a
    
    ld      (ix+0), YMREG_DACON         ; Disable DAC output
    ld      (ix+1), $00
    
    ret                                 ; End of subroutine

;****************************************************************************
; PcmRender1ch
;
; Render helper routine for when one PCM channel is playing, used as the
; backbone for the actual render routines. Also called to fill in one channel
; for further postprocessing. It fills the backbuffer with the next block of
; samples from that channel.
;----------------------------------------------------------------------------
; input b ..... channel ID (mask)
; input c ..... waveform bank
; input hl .... waveform address
; input de .... pointer to buffer
;----------------------------------------------------------------------------
; output c .... new waveform bank
; output hl ... new waveform address
;----------------------------------------------------------------------------
; breaks ..... a,bc,de,hl
;----------------------------------------------------------------------------
; notes: cases in which it may be called
;
; - for PCM1, when PCM1 alone is playing
; - for PCM2, when PCM2 alone is playing
; - for PCM1, when PCM1 + PCM2 are playing
;
; It will *not* be called for PCM2 when both are playing since this routine
; doesn't do sample mixing (it's called for PCM1 to fill in the buffer). If
; PCM1 stops in this situation, rather than returning back to PcmRender2ch it
; swaps the call to PcmRender1ch_B.
;****************************************************************************

PcmRender1ch:
    push    bc                          ; Save bank and channel ID for later
                                        ; since we're out of registers
    
    ReadRomByte                         ; Read first byte the slow way (so
                                        ; it does any bank switching that
                                        ; may be needed)
    
    ld      a, b                        ; Check if the first byte in this
    inc     a                           ; block is $FF, in which case we've
    jr      z, PcmRender1ch_Stop        ; reached the end of the waveform
    
    dec     l                           ; Roll back the pointer so the value
                                        ; is reread again (faster than trying
                                        ; to compensate for that one read in
                                        ; the loop below)
    
    ld      bc,((PCM_BUFSIZE/8)<<8)|$FF ; Set up loop counter. Note that LDI
                                        ; will decrement BC and we want to
                                        ; keep B intact, so we put a value in
                                        ; C that will ensure that B never
                                        ; decrements during the loop
    
PcmRender1ch_Loop:
    PollPcm
    
    ldi                                 ; Copy a bunch of samples
    ldi
    ldi
    ldi
    ldi
    ldi
    ldi
    ldi
    
    djnz    PcmRender1ch_Loop           ; Go for next bunch
    
    PollPcm
    
    ld      a, h                        ; Did we cross a bank boundary? (i.e.
    or      a                           ; was this the last block in the
    jr      z, PcmRender1ch_Fix         ; current bank?)
    
    pop     bc                          ; Restore bank (which didn't change)
    ret                                 ; End of subroutine
    
PcmRender1ch_Fix:
    pop     bc                          ; Restore bank and fix up the address
    ld      h, $80                      ; (moved onto next bank, HL becomes
    inc     c                           ; $8000)
    
    ret                                 ; End of subroutine

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

PcmRender1ch_Stop:
    ; note that by this point DE still has the original pointer
    ; to the buffer (this matters for part of the code below),
    ; but it's safe to trash BC and HL as we wish
    
    pop     bc                          ; Pop out the saved bank + ID
    
    ld      a, (PcmChInUse)             ; Mask away the channel
    or      b
    xor     b
    ld      (PcmChInUse), a
    
    pop     bc                          ; Don't return back to PcmRender1ch_A
                                        ; or B (since it's pointless to store
                                        ; the new bank/address)
    
    jp      z, PcmRender0ch             ; If the mask is 0, that means all
                                        ; channels have been shut down, so
                                        ; stop PCM playback
    
    ld      hl, PcmRender1ch_B          ; Otherwise it means PCM2 is still
    ld      (OutputPcmCall), hl         ; playing and we got called from
    pop     bc                          ; PcmRender2ch, so set up OutputPcm
    jp      (hl)                        ; and jump to the routine for playing
                                        ; PCM2 alone (and don't waste time
                                        ; returning back to PcmRender2ch)

;****************************************************************************
; PcmRender1ch_A
; Render routine for when only PCM ch1 is playing.
;----------------------------------------------------------------------------
; input de ... pointer to buffer
;----------------------------------------------------------------------------
; breaks ..... a,bc,de,hl
;****************************************************************************

PcmRender1ch_A:
    PollPcm
    
    ld      hl, (PcmAddrCh1)            ; Get current pointer for the
    ld      a, (PcmBankCh1)             ; waveform for this channel
    ld      c, a
    
    ld      b, $01                      ; Set up channel ID
    call    PcmRender1ch                ; Call the helper routine which
                                        ; fills in the buffer with the
                                        ; waveform we're passing
    
    PollPcm
    
    ld      a, c                        ; Store new waveform pointer
    ld      (PcmAddrCh1), hl
    ld      (PcmBankCh1), a
    
    ret                                 ; End of subroutine

;****************************************************************************
; PcmRender1ch_B
; Render routine for when only PCM ch2 is playing.
;----------------------------------------------------------------------------
; input de ... pointer to buffer
;----------------------------------------------------------------------------
; breaks ..... a,bc,de,hl
;****************************************************************************

PcmRender1ch_B:
    PollPcm
    
    ld      hl, (PcmAddrCh2)            ; Get current pointer for the
    ld      a, (PcmBankCh2)             ; waveform for this channel
    ld      c, a
    
    ld      b, $02                      ; Set up channel ID
    call    PcmRender1ch                ; Call the helper routine which
                                        ; fills in the buffer with the
                                        ; waveform we're passing
    
    PollPcm
    
    ld      a, c                        ; Store new waveform pointer
    ld      (PcmAddrCh2), hl
    ld      (PcmBankCh2), a
    
    ret                                 ; End of subroutine

;****************************************************************************
; PcmRender2ch
; Render routine for when both PCM ch1 and ch2 are playing.
;----------------------------------------------------------------------------
; input de ... pointer to buffer
;----------------------------------------------------------------------------
; breaks ... a,bc,de,hl
;****************************************************************************

PcmRender2ch:
    call    PcmRender1ch_A              ; Buffer the 1st channel normally
                                        ; We'll use its data to mix in with
                                        ; the 2nd channel's samples
    
    PollPcm
    
    ld      e, PcmBuffer1&$FF           ; Reload buffer pointer
    dec     d                           ; (this assumes that DE points right
                                        ; past the end of the buffer, which
                                        ; is what LDI will end up doing)
    
    ld      hl, (PcmAddrCh2)            ; Retrieve current position of
    ld      a, (PcmBankCh2)             ; the PCM2 waveform
    ld      c, a
    
    ReadRomByte                         ; Fetch first byte the slow way (so
                                        ; it switches to the correct bank)
    
    ld      a, b                        ; Check if the first byte is $FF
    inc     a                           ; which means PCM2 has stopped
    jr      z, PcmRender2ch_StopCh2
    
    dec     l                           ; Roll back the pointer so the loop
                                        ; below can take care of that sample
    
    ld      b, PcmMixTable2ch>>8        ; Upper byte of the pointer to the
                                        ; look-up table used to compute the
                                        ; mixer result of two samples (put
                                        ; the averaged samples in c, get back
                                        ; the result from (bc))
    
    ex      de, hl                      ; Due to the ADD instruction the loop
                                        ; below needs HL to point to the
                                        ; buffer instead of DE, so swap them
    
PcmRender2ch_Loop:
    PollPcm
    
    ld      a, (de)                     ; Load sample from PCM2
    add     a, (hl)                     ; Add sample from PCM1
    rra                                 ; Divide by 2 so they're averaged
    ld      c, a                        ; Use that for the look-up table
    ld      a, (bc)                     ; and read back the mixed sample
    ld      (hl), a                     ; Store resulting sample
    
    inc     de                          ; Advance PCM2 pointer
    inc     l                           ; Advance buffer pointer
    
    ld      a, (de)                     ; Load sample from PCM2
    add     a, (hl)                     ; Add sample from PCM1
    rra                                 ; Divide by 2 so they're averaged
    ld      c, a                        ; Use that for the look-up table
    ld      a, (bc)                     ; and read back the mixed sample
    ld      (hl), a                     ; Store resulting sample
    
    inc     de                          ; Advance PCM2 pointer
    inc     l                           ; Advance buffer pointer
    
    jp      nz, PcmRender2ch_Loop       ; Keep mixing if needed
    
    PollPcm
    
    ld      a, d                        ; Did we cross a bank boundary? (i.e.
    or      a                           ; was this the last block in the
    jr      z, PcmRender2ch_Fix         ; current bank?)
    
    ld      (PcmAddrCh2), de            ; Store new PCM2 address
    
    ret
    ;jp      PollTimerB                  ; End of subroutine
                                        ; Poll timer B just in case that we
                                        ; spent way too much time rendering
                                        ; and we skipped a tick
    
PcmRender2ch_Fix:
    PollPcm
    
    ld      d, $80                      ; Fix up the address (HL = $8000) and
    ld      (PcmAddrCh2), de            ; update the bank for PCM2
    ld      hl, PcmBankCh2
    inc     (hl)
    
    ret                                 ; End of subroutine

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

PcmRender2ch_StopCh2:
    PollPcm
    
    ld      a, $01                      ; Disable PCM2
    ld      (PcmChInUse), a
    
    ld      hl, PcmRender1ch_A          ; Tell OutputPcm to play only PCM1
    ld      (OutputPcmCall), hl         ; from here on (PCM1's samples are
                                        ; already in the buffer, so that
                                        ; part is safe)
    
    ret                                 ; End of subroutine