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