Sona 0.50 Source

Sona 0.50/src-z80/pcm_play.z80

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