;****************************************************************************
; 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