Sona 0.50 Source

Sona 0.50/src-z80/stream_process.z80

;****************************************************************************
; UpdateSfx
; Called whenever SFXs tick, updates all the SFX streams.
;----------------------------------------------------------------------------
; input a ..... new value for SfxSubticks
;----------------------------------------------------------------------------
; modifies .... a,bc,de,hl,a',bc',de',hl',iy
;****************************************************************************

UpdateSfx:
    ld      (SfxSubticks), a            ; Store the updated subticks (since
                                        ; IdleLoop jumped here directly after
                                        ; the comparison without bothering to
                                        ; do it yet)
    
    PollPcm
    
    ld      a, (CpuMeterCount)          ; We're done counting for this tick,
    ld      (CpuMeter), a               ; take the CPU meter counter and
                                        ; store the result where the 68000
                                        ; can see it
    
    xor     a                           ; Reset the CPU meter counter so it
    ld      (CpuMeterCount), a          ; starts counting for the new tick
    
    dec     a                           ; Store into the I register that
    ld      i, a                        ; we're processing BGM streams (this
                                        ; is used by channel locking code)
    
    ld      (ThisOwner), a              ; Set up owner ID (to one less than
                                        ; the first SFX owner since it's
                                        ; incremented *before* each stream
                                        ; is processed)
    
    ld      hl, ProcessSfxStream        ; Set up SFX stream handler
    ld      (ProcessStreamCall), hl
    
    ld      a, FIRST_SFXSTREAM          ; Process SFX streams
    ld      (StreamNum), a
    ld      a, StreamSfxFirst&$FF
    ld      b, StreamSfxEnd&$FF
    call    UpdateStreams
    
    call    UpdatePsg                   ; Update PSG channels (ADSR, etc.)
    
    jp      UpdatePlaybackStatus        ; Update the playback status flags
                                        ; that the 68000 can query

;****************************************************************************
; UpdateBgm
; Called whenever BGM ticks, updates all the BGM streams.
;----------------------------------------------------------------------------
; input a .... numbers of ticks to process
;----------------------------------------------------------------------------
; modifies .... a,bc,de,hl,a',bc',de',hl',iy
;****************************************************************************

UpdateBgm:
    ld      b, a                        ; Copy the tick count into the loop
    xor     a                           ; counter then reset the count
    ld      (BgmTicksHi), a
    
    ld      i, a                        ; Store into the I register that
                                        ; we're processing BGM streams (this
                                        ; is used by channel locking code)
    
    ld      (ThisPriority), a           ; BGM always has priority = 0 (this
                                        ; should prevent reckless locking
                                        ; from working, the meaning of the
                                        ; lock commands may change later)
    
    dec     a                           ; BGM's "owner ID" is $FF (-1)
    ld      (ThisOwner), a

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

    push    bc                          ; Save number of BGM ticks to process
                                        ; since we need these registers
    
FmChanToReload: equ $+1
    ld      a, $00                      ; Mask of FM channels to restore
    ld      b, 8                        ; This loop's counter :P
    
FmChanReloadLoop:
    add     a, a                        ; Do we need to restore this channel?
    jr      c, FmChanDoReload
    jr      z, FmChanReloadEnd
    djnz    FmChanReloadLoop            ; Nope, try next channel
    jr      FmChanReloadEnd             ; Checked all channels
    
FmChanDoReload:
    dec     b                           ; Decrement loop counter in order
                                        ; to get the correct channel ID
    
    ld      c, a                        ; Save loop registers
    push    bc
    
    ld      a, b                        ; Compute pointer to where the
    add     a, BgmFmInstr&$FF           ; instrument ID for this channel
    ld      h, BgmFmInstr>>8            ; for BGM is stored
    ld      l, a
    
    ld      a, b                        ; Put channel ID where it belongs
    ld      b, (hl)                     ; Get instrument ID for this channel
    call    LoadFmInstr                 ; Reload the instrument
    
    pop     bc                          ; Restore loop registers and resume
    ld      a, c                        ; the check looping
    jr      FmChanReloadLoop
    
FmChanReloadEnd:
    ld      (FmChanToReload), a         ; Store the cleared mask to indicate
                                        ; that we restored all FM channels
                                        ; already
    
    pop     bc                          ; Retrieve back the number of
                                        ; BGM ticks to process

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

    ld      hl, ProcessBgmStream        ; Set up BGM stream handler
    ld      (ProcessStreamCall), hl
    
UpdateBgmLoop:
    push    bc                          ; Save loop counter since pretty
                                        ; much every register will be
                                        ; clobbered below
    
    xor     a                           ; Process all BGM streams
    ld      (StreamNum), a
    ld      b, StreamBgmEnd&$FF
    call    UpdateStreams
    
    pop     bc                          ; Process remaining ticks
    djnz    UpdateBgmLoop
    ret

;****************************************************************************
; UpdateStreams
; Takes care of processing every stream. Called every tick.
;
; Note that there are two processing routines (ProcessBgmStream for BGM,
; ProcessSfxStream for SFX), you should write the address of the routine to
; use to ProcessStreamCall before calling this routine.
;----------------------------------------------------------------------------
; input a ..... offset of first stream state to process (low byte only)
; input b ..... offset of first stream state to stop (low byte only)
;----------------------------------------------------------------------------
; modifies .... a,bc,de,hl,a'
;****************************************************************************

UpdateStreams:
    ld      (StreamPtr), a              ; Set up pointer for first stream
    
    ld      a, b                        ; Set up loop terminator
    ld      (UpdateStreamsTeminator), a
    
UpdateStreamsLoop:
ProcessStreamCall: equ $+1
    call    ProcessBgmStream            ; Process this stream
    
    PollPcm
    
    ld      hl, StreamNum               ; Advance to next stream
    inc     (hl)
    inc     hl
    ld      a, (hl)
    add     SIZEOF_STREAM
    
UpdateStreamsTeminator: equ $+1
    cp      StreamStateEnd&$FF          ; Was it the last one?
    ret     z
    
    ld      (hl), a                     ; Nope, keep processing
    jr      UpdateStreamsLoop

;****************************************************************************
; ProcessBgmStream
; ProcessSfxStream
;
; The routine in charge of processing the current stream. It proceeds to
; parse every opcode until it runs into a delay or end of stream. It will
; also conveniently skip processing streams that aren't playing.
;
; The difference between both routines is how the stream owner ID is handled,
; you should change ProcessStreamCall to point to the correct routine before
; calling UpdateStreams.
;----------------------------------------------------------------------------
; modifies ... a,bc,de,hl,a'
;****************************************************************************

ProcessSfxStream:
    ld      hl, ThisOwner               ; Increment the owner number to match
    inc     (hl)                        ; the current SFX stream (this step
                                        ; is skipped for BGM streams which
                                        ; always have -1 as the owner ID)

ProcessBgmStream:
    PollPcm
    
    ld      hl, (StreamPtr)             ; Check if the stream is active,
    ld      a, (hl)                     ; otherwise return immediately
    or      a
    ret     m
    
    inc     l                           ; Check if the stream is still
    dec     (hl)                        ; waiting or if it's time to resume
    ret     nz                          ; (return immediately if waiting)
    
    PollPcm
    
    inc     l                           ; Read stream's current position
    ld      e, (hl)
    inc     l
    ld      d, (hl)
    inc     l
    ld      c, (hl)
    ex      de, hl
    
    call    ProcessStreamLoop           ; Go into the loop. The loop will
                                        ; return when the stream either goes
                                        ; into a delay or stops running.
    
    PollPcm
    
    ex      de, hl                      ; Store stream's new current position
    ld      hl, (StreamPtr)             ; so we can resume from where we left
    inc     l                           ; (assuming the stream isn't over)
    inc     l
    ld      (hl), e
    inc     l
    ld      (hl), d
    inc     l
    ld      (hl), c
    
    ret                                 ; End of subroutine

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

ProcessStreamLoop:
    call    PollTimerB                  ; Check if timer B triggered
                                        ;
                                        ; It doesn't need to be done as
                                        ; regularly as PCM but it *can*
                                        ; happen mid-stream (if the stream
                                        ; takes more than around 2ms) so
                                        ; we poll it between every event
                                        ; to make sure
    
    ld      de, ReadByteFrom68k         ; Restore subroutine call used to
    ld      (ReadArgByteFunc), de       ; read from ROM (to-do: make it so
                                        ; this is only done when needed?
                                        ; since most of the time we don't
                                        ; need to do this)
    
ProcessOpcodeDirectly:
    ReadRomByte                         ; Read opcode
    
    ld      a, b                        ; Isolate the opcode "category"
    and     $F0                         ; (the upper nibble)
    
    add     $100-$C0                    ; There's a big gap in the middle of
                                        ; the opcode numbering, so we don't
                                        ; include it in the table. The table
                                        ; needs entries to be consecutive, so
                                        ; we rotate the opcode categories to
                                        ; make it work
    
    rrca                                ; Compute offset into the jump table.
    rrca                                ;
    rrca                                ; This looks weird because we need to
    ld      e, a                        ; multiply by 3, so we threw that in
    rrca                                ; the shift too. Effectively we're
    add     a, e                        ; doing: a = (a >> 3) + (a >> 4)
    ld      (ProcessStreamJump), a
    
    PollPcm
    
    ld      de, ProcessStreamLoop       ; Not the most orthodox way to set
    push    de                          ; up a return address but this helps
                                        ; simplify the code (as all handlers
                                        ; return with RET and we use a jump
                                        ; table to get to them instead of
                                        ; using CALL)
    
ProcessStreamJump: equ $+1
    jr      $
    
    jp      CxOpcodeGroup               ; $Cx  VM "ALU" commands
    jp      ShortDelayOp                ; $Dx  SONAOP_QDELAY_*
    jp      LockChannel                 ; $Ex  SONAOP_LOCK_*
    jp      FxOpcodeGroup               ; $Fx  miscellaneous
    jp      InstrLoadGroup              ; $0x  SONAOP_LOAD_*
    jp      KeyOnGroup                  ; $1x  SONAOP_KEYON_*
    jp      KeyOffGroup                 ; $2x  SONAOP_KEYOFF_*
    jp      SetPitchGroup               ; $3x  SONAOP_PITCH_*
    jp      VolumeGroup                 ; $4x  SONAOP_VOLUME_*
    jp      PanGroup                    ; $5x  SONAOP_PAN/PMSAMS_*

;****************************************************************************
; InstrLoadGroup
; Handles the load instrument stream opcodes group.
;----------------------------------------------------------------------------
; input b ..... opcode
; input c ..... current stream bank
; input hl .... current stream address
;----------------------------------------------------------------------------
; continues in the respective handler, then ProcessStreamLoop
;****************************************************************************

InstrLoadGroup:
    PollPcm
    
    ld      a, b                        ; Check whether it's a FM or PSG
    cp      SONAOP_LOAD_PSG1            ; channel and call the corresponding
    jp      c, LoadFmOp                 ; routine
    jp      LoadPsgOp

;****************************************************************************
; KeyOnGroup
; Handles the key-on stream opcodes group.
;----------------------------------------------------------------------------
; input b ..... opcode
; input c ..... current stream bank
; input hl .... current stream address
;----------------------------------------------------------------------------
; continues in the respective handler, then ProcessStreamLoop
;****************************************************************************

KeyOnGroup:
    PollPcm
    ld      a, b
    
    cp      SONAOP_KEYON_FM3            ; Check if it's within the
    jp      z, Fm3NormalKeyOnOp         ; FM channel group
    cp      SONAOP_KEYON_FM3SP
    jp      z, Fm3SpecialKeyOnOp
    cp      SONAOP_KEYON_FM6+1
    jp      c, FmKeyOnOp
    
    PollPcm
    ld      a, b
    
    cp      SONAOP_KEYON_PSG4           ; Check if it's within the
    jp      c, PsgKeyOnOp               ; PSG channel group
    jp      z, NoiseKeyOnOp
    
    jp      KeyOnPcm                    ; Assume PCM channel range

;****************************************************************************
; KeyOffGroup
; Handles the key-off stream opcodes group.
;----------------------------------------------------------------------------
; input b ..... opcode
; input c ..... current stream bank
; input hl .... current stream address
;----------------------------------------------------------------------------
; continues in the respective handler, then ProcessStreamLoop
;****************************************************************************

KeyOffGroup:
    PollPcm
    ld      a, b
    
    cp      SONAOP_KEYOFF_FM6+1         ; Check if it's within the
    jp      c, FmKeyOffOp               ; FM channel range
    
    cp      SONAOP_KEYOFF_PSG4+1        ; Check if it's within the
    jp      c, PsgKeyOffOp              ; PSG channel range
    
    jp      KeyOffPcm                   ; Assume PCM channel range

;****************************************************************************
; SetPitchGroup
; Handles the set pitch stream opcodes group.
;----------------------------------------------------------------------------
; input b ..... opcode
; input c ..... current stream bank
; input hl .... current stream address
;----------------------------------------------------------------------------
; continues in the respective handler, then ProcessStreamLoop
;****************************************************************************

SetPitchGroup:
    PollPcm
    
    ld      a, b                        ; Check if it's within the
    cp      SONAOP_PITCH_FM3            ; FM channel range
    jp      z, Fm3NormalSetPitchOp
    cp      SONAOP_PITCH_FM3SP
    jp      z, Fm3SpecialSetPitchOp
    cp      SONAOP_PITCH_FM6+1
    jp      c, FmSetPitchOp
    
    PollPcm
    
    ld      a, b                        ; Check if it's within the
    cp      SONAOP_PITCH_PSG4           ; PSG channel range
    jp      c, PsgSetPitchOp
    jp      NoiseSetPitchOp

;****************************************************************************
; VolumeGroup
; Handles the set volume stream opcodes group.
;----------------------------------------------------------------------------
; input b ..... opcode
; input c ..... current stream bank
; input hl .... current stream address
;----------------------------------------------------------------------------
; continues in the respective handler, then ProcessStreamLoop
;****************************************************************************

VolumeGroup:
    PollPcm
    
    ld      a, b                        ; Check whether it's a FM or PSG
    cp      SONAOP_VOLUME_PSG1          ; channel and call the corresponding
    jp      c, FmSetVolume              ; subroutine
    jp      PsgSetVolume

;****************************************************************************
; PanGroup
; Handles the panning and PMS/AMS stream opcodes group.
;----------------------------------------------------------------------------
; input b ..... opcode
; input c ..... current stream bank
; input hl .... current stream address
;----------------------------------------------------------------------------
; continues in the respective handler, then ProcessStreamLoop
;****************************************************************************

PanGroup:
    PollPcm
    
    ld      a, b                        ; Check whether it's a panning or a
    cp      SONAOP_PMSAMS_FM1           ; PMS/AMS opcode and call the
    jp      c, SetPan                   ; corresponding subroutine
    jp      SetPmsAms

;****************************************************************************
; FxOpcodeGroup
; Handles the stream opcodes within $F0..$FF
;----------------------------------------------------------------------------
; input b ..... opcode
; input c ..... current stream bank
; input hl .... current stream address
;----------------------------------------------------------------------------
; continues in the respective handler, then ProcessStreamLoop (except for
; a few opcodes which make the caller return instead)
;****************************************************************************

FxOpcodeGroup:
    PollPcm
    
    ld      a, b                        ; The routines in this group are
    sub     FxOpcodeFirst               ; very disparate, so let's use the
    ld      b, a                        ; opcode as an index into a jump
    add     a, b                        ; table pointing to each handler
    add     a, b
    ld      (FxOpcodeJump), a
FxOpcodeJump: equ $+1
    jr      $
    
FxOpcodeTable:
    jp      SubStreamOpcode             ; $F5  SONAOP_SUBSTREAM
    jp      ReplaceInstrument           ; $F6  SONAOP_REPLINSTR
    jp      UseReadFromVar              ; $F7  SONAOP_VARARG
    jp      FmRegWrite                  ; $F8  SONAOP_YMREG1
    jp      FmRegWrite                  ; $F9  SONAOP_YMREG2
    jp      SetBgmSpeed                 ; $FA  SONAOP_SETSPEED
    jp      SetFmLfo                    ; $FB  SONAOP_LFO
    jp      SetLoopPoint                ; $FC  SONAOP_SETLOOP
    jp      GoToLoopPoint               ; $FD  SONAOP_GOTOLOOP
    jp      LongDelayOp                 ; $FE  SONAOP_DELAY
    jp      StopStream                  ; $FF  SONAOP_END

FxOpcodeFirst: equ $100-($-FxOpcodeTable)/3