;****************************************************************************
; KeyOnPcm [SONAOP_KEYON_PCM1..2]
; Handles the stream opcode for playing in a hardcoded PCM channel.
;----------------------------------------------------------------------------
; input b ..... PCM channel ID (bit 0 only)
; input c ..... current stream bank
; input hl .... current stream address
;----------------------------------------------------------------------------
; output c .... new stream bank
; output hl ... new stream address
;----------------------------------------------------------------------------
; breaks ...... a,bc,de,hl,a'
;****************************************************************************
KeyOnPcm:
CheckOwner
ld a, b ; Determine channel ID mask (PCM1 =
and $01 ; $01, PCM2 = $02) and save it for
inc a ; use later
ex af, af'
call ReadInstrumentId ; Read instrument ID
RetIfNotOwner ; Don't touch the PCM channel if we
; don't control it (we need to do
; this after fetching the operand,
; hence why we wait until now)
push bc ; Save the stream position since we
push hl ; need to return it later and we need
; to use these registers
ld h, InstrList>>8 ; Load instrument bank+address
ld l, b ; into C and HL
ld e, (hl)
inc h
ld d, (hl)
inc h
ld c, (hl)
ex de, hl
PollPcm
ex af, af' ; Set up channel ID mask
ld b, a
call StartPcm ; Start the playback!
pop hl ; Restore stream position
pop bc
ret ; End of subroutine
;****************************************************************************
; KeyOffPcm [SONAOP_KEYON_PCM1..2]
; Handles the stream opcode for stopping a hardcoded PCM channel.
;----------------------------------------------------------------------------
; input b ..... PCM channel ID (bit 0 only)
; input c ..... current stream bank
; input hl .... current stream address
;----------------------------------------------------------------------------
; output c .... new stream bank
; output hl ... new stream address
;----------------------------------------------------------------------------
; breaks ...... a,bc,de,hl,a'
;****************************************************************************
KeyOffPcm:
CheckOwner ; Don't touch the PCM channel if
RetIfNotOwner ; we don't control it
push hl ; StopPcm will clobber HL but we need
; to return it intact so save it now
ld a, b ; Turn channel ID into its mask
and $01
inc a
ld b, a
call StopPcm ; Stop the PCM channel
pop hl ; Restore register
ret ; End of subroutine
;****************************************************************************
; PlayPcmCmdCh1 [CMD_PLAYPCM1]
; PlayPcmCmdCh2 [CMD_PLAYPCM2]
;
; Processes a 68000 command to play a waveform in one of the PCM channels
; directly.
;----------------------------------------------------------------------------
; continues in EndOfCommand
;****************************************************************************
PlayPcmCmdCh1:
ld b, $01 ; Set up channel mask for PCM1
jr PlayPcmCmdCommon
PlayPcmCmdCh2:
ld b, $02 ; Set up channel mask for PCM2
;----------------------------------------------------------------------------
PlayPcmCmdCommon:
ld hl, (CmdArg1) ; Retrieve waveform address
ld a, (CmdArg3)
ToAddrBank
ld c, a
call StartPcm ; Start the channel
jp EndOfCommand ; Pop command off queue
;****************************************************************************
; StopPcmCmdCh1 [CMD_STOPPCM1]
; StopPcmCmdCh2 [CMD_STOPPCM2]
;
; Processes a 68000 command to stop one of the PCM channels.
;----------------------------------------------------------------------------
; continues in EndOfCommand
;****************************************************************************
StopPcmCmdCh1:
ld b, $01 ; Set up channel mask for PCM1
jr StopPcmCmdCommon
StopPcmCmdCh2:
ld b, $02 ; Set up channel mask for PCM2
;----------------------------------------------------------------------------
StopPcmCmdCommon:
call StopPcm ; Stop the channel
jp EndOfCommand ; Pop command off queue
;****************************************************************************
; StartPcm
; Starts PCM playback.
;----------------------------------------------------------------------------
; input b .... channel ID (as a mask)
; input c .... waveform bank
; input hl ... waveform address
;----------------------------------------------------------------------------
; breaks ..... a,bc,de,hl
;****************************************************************************
StartPcm:
ld a, (PcmChInUse) ; Update list of channels in use
or b
ld (PcmChInUse), a
dec a ; What we need to do depends on
add a ; which channels are active now,
ld (StartPcm_Jump), a ; so call a different handler
StartPcm_Jump: equ $+1 ; depending on the case
jr $
jr StartPcm1ch_A ; PCM1 only
jr StartPcm1ch_B ; PCM2 only
jr StartPcm2ch ; PCM1+PCM2
;----------------------------------------------------------------------------
; PCM1 or PCM2 only
;----------------------------------------------------------------------------
StartPcm1ch_A:
ld a, c ; Store waveform address
ld (PcmAddrCh1), hl
ld (PcmBankCh1), a
ld hl, PcmRender1ch_A ; Set up render callback
ld (OutputPcmCall), hl
ld (StartPcm1ch_Call), hl
jp StartPcm1ch_Common
;----------------------------------------------------------------------------
StartPcm1ch_B:
ld a, c ; Store waveform address
ld (PcmAddrCh2), hl
ld (PcmBankCh2), a
ld hl, PcmRender1ch_B ; Set up render callback
ld (OutputPcmCall), hl
ld (StartPcm1ch_Call), hl
;----------------------------------------------------------------------------
StartPcm1ch_Common:
ld a, Z80OP_RET ; Neutralize OutputPcm so calling
ld (OutputPcm), a ; the PCM rendering routine doesn't
; lead to anything awful
ld (ix+0), YMREG_DAC ; Enable DAC output
ld (ix+1), $80
ld (ix+0), YMREG_DACON
ld (ix+1), $80
exx
ld bc, PcmBuffer1 ; Set up PCM buffers
exx
ld a, PcmBuffer2>>8
ld (OutputPcmOutBuf), a
ld de, PcmBuffer1 ; Call the render callback so it
StartPcm1ch_Call: equ $+1 ; fills in the first buffer with
call $ ; something meaningful
ld a, Z80OP_LD_A_MEM ; Allow OutputPcm to work
ld (OutputPcm), a
ret ; End of subroutine
;----------------------------------------------------------------------------
; PCM1+PCM2
;----------------------------------------------------------------------------
StartPcm2ch:
ld de, PcmRender2ch ; Tell OutputPcm to use the 2ch
ld (OutputPcmCall), de ; rendering routine from here on
rr b ; Did PCM1 or PCM2 just start?
jr nc, StartPcm2ch_B
StartPcm2ch_A:
ld a, c ; Store waveform address for PCM1
ld (PcmAddrCh1), hl
ld (PcmBankCh1), a
ret ; End of subroutine
StartPcm2ch_B:
ld a, c ; Store waveform address for PCM2
ld (PcmAddrCh2), hl
ld (PcmBankCh2), a
ret ; End of subroutine
;****************************************************************************
; StopPcm
; Stops playback in a PCM channel (interrupts early if it was still playing,
; does nothing if it wasn't or if it was already about to end anyway).
;----------------------------------------------------------------------------
; input b ... channel ID mask
;----------------------------------------------------------------------------
; breaks .... a,de,hl
;****************************************************************************
StopPcm:
ld a, (PcmChInUse) ; Mask away the channel
or b
xor b
ld (PcmChInUse), a
add a ; We'll shut off the PCM channel by
add PcmRenderTable&$FF ; swapping which rendering routine is
ld h, PcmRenderTable>>8 ; in use, let's retrieve it from the
ld l, a ; PCM routine table
ld e, (hl) ; Replace PCM handler so it takes
inc l ; effect the next time the PCM
ld d, (hl) ; buffer is flushed away
ld (OutputPcmCall), de
ret ; End of subroutine
;****************************************************************************
; StopAllPcm
; Interrupts all PCM playback.
;----------------------------------------------------------------------------
; breaks .... a,bc,de,hl,a'
;****************************************************************************
StopAllPcm: equ PcmRender0ch