PcmChInUse: db $00 ; PCM channels in use
; bit 0 = ch1 active
; bit 1 = ch2 active
PcmAddrCh1: ds 2 ; PCM ch1 address
PcmBankCh1: ds 1 ; PCM ch1 bank
PcmAddrCh2: ds 2 ; PCM ch2 address
PcmBankCh2: ds 1 ; PCM ch2 bank
StreamNum: db $00 ; Stream number (stream ID) being
; currently processed
StreamPtr: dw StreamState ; Pointer to stream being currently
; processed (makes things simpler)
;****************************************************************************
;****************************************************************************
;********** PAGE BOUNDARY ***************************************************
;****************************************************************************
;****************************************************************************
PadToPage
;****************************************************************************
; Stream playback state
; Holds the current playback state for every stream.
;****************************************************************************
STREAM_STACKLEVEL: equ 0 ; Current stack level (-1 if stopped)
STREAM_DELAY: equ 1 ; Ticks left before resuming
STREAM_CURRADDRLO: equ 2 ; Current position, address low
STREAM_CURRADDRHI: equ 3 ; Current position, address high
STREAM_CURRBANK: equ 4 ; Current position, bank
STREAM_LOOPADDRLO: equ 5 ; Loop point, address low
STREAM_LOOPADDRHI: equ 6 ; Loop point, address high
STREAM_LOOPBANK: equ 7 ; Loop point, bank
SIZEOF_STREAM: equ 8
StreamState:
db -1, 0, 0,0,0, 0,0,0 ; Stream #0
db -1, 0, 0,0,0, 0,0,0 ; Stream #1
db -1, 0, 0,0,0, 0,0,0 ; Stream #2
db -1, 0, 0,0,0, 0,0,0 ; Stream #3
db -1, 0, 0,0,0, 0,0,0 ; Stream #4
db -1, 0, 0,0,0, 0,0,0 ; Stream #5
db -1, 0, 0,0,0, 0,0,0 ; Stream #6
db -1, 0, 0,0,0, 0,0,0 ; Stream #7
db -1, 0, 0,0,0, 0,0,0 ; Stream #8
db -1, 0, 0,0,0, 0,0,0 ; Stream #9
db -1, 0, 0,0,0, 0,0,0 ; Stream #10
db -1, 0, 0,0,0, 0,0,0 ; Stream #11
db -1, 0, 0,0,0, 0,0,0 ; Stream #12
db -1, 0, 0,0,0, 0,0,0 ; Stream #13
db -1, 0, 0,0,0, 0,0,0 ; Stream #14
db -1, 0, 0,0,0, 0,0,0 ; Stream #15
StreamStateEnd:
;----------------------------------------------------------------------------
; Stream boundaries to check for in UpdateBgm and UpdateSfx.
;----------------------------------------------------------------------------
StreamBgmFirst: equ StreamState
StreamSfxFirst: equ StreamState+FIRST_SFXSTREAM*SIZEOF_STREAM
StreamBgmEnd: equ StreamSfxFirst
StreamSfxEnd: equ StreamStateEnd
;****************************************************************************
; Where the current pitch for each FM and PSG channel is stored.
;
; Each channel is a 16-bit word stored in 00000xxx yyyyzzzz format (where
; xxx = octave, yyyy = semitone, zzzz = fraction). All channels are stored
; twice, once for the current actual value, and once for the BGM pitches
; (needed for restoration when SFXs free up channels).
;****************************************************************************
;--- BGM --------------------------------------------------------------------
BgmFmPitch:
BgmFmPitch_Fm1: ds 2 ; 1st FM channel
BgmFmPitch_Fm2: ds 2 ; 2nd FM channel
BgmFmPitch_Fm3: ; 3rd FM channel, normal
BgmFmPitch_Fm3_S4: ds 2 ; 3rd FM channel, special S4
BgmFmPitch_Fm3_S3: ds 2 ; 3rd FM channel, special S3
BgmFmPitch_Fm4: ds 2 ; 4th FM channel
BgmFmPitch_Fm5: ds 2 ; 5th FM channel
BgmFmPitch_Fm6: ds 2 ; 6th FM channel
BgmFmPitch_Fm3_S2: ds 2 ; 3rd FM channel, special S2
BgmFmPitch_Fm3_S1: ds 2 ; 3rd FM channel, special S1
BgmPsgPitch:
BgmPsgPitch_Psg1: ds 2 ; 1st square channel
BgmPsgPitch_Psg2: ds 2 ; 2nd square channel
BgmPsgPitch_Psg3: ds 2 ; 3rd square channel
;--- SFX --------------------------------------------------------------------
SfxFmPitch:
SfxFmPitch_Fm1: ds 2 ; 1st FM channel
SfxFmPitch_Fm2: ds 2 ; 2nd FM channel
SfxFmPitch_Fm3: ; 3rd FM channel, normal
SfxFmPitch_Fm3_S4: ds 2 ; 3rd FM channel, special S4
SfxFmPitch_Fm3_S3: ds 2 ; 3rd FM channel, special S3
SfxFmPitch_Fm4: ds 2 ; 4th FM channel
SfxFmPitch_Fm5: ds 2 ; 5th FM channel
SfxFmPitch_Fm6: ds 2 ; 6th FM channel
SfxFmPitch_Fm3_S2: ds 2 ; 3rd FM channel, special S2
SfxFmPitch_Fm3_S1: ds 2 ; 3rd FM channel, special S1
PsgPitch:
PsgPitch_Psg1: ds 2 ; 1st square channel
PsgPitch_Psg2: ds 2 ; 2nd square channel
PsgPitch_Psg3: ds 2 ; 3rd square channel
;****************************************************************************
; Format of the PSG ADSR table
;****************************************************************************
PSGADSR_LEVEL: equ 0 ; Current envelope level
PSGADSR_PHASE: equ 1 ; Current envelope phase
PSGADSR_TL: equ 2 ; Total level (attack -> decay)
PSGADSR_AR: equ 3 ; Attack rate
PSGADSR_SL: equ 4 ; Sustain level (decay -> sustain)
PSGADSR_DR: equ 5 ; Decay rate
PSGADSR_SR: equ 6 ; Sustain rate
PSGADSR_RR: equ 7 ; Release rate
SIZEOF_PSGADSR: equ 8 ; Size of ADSR structure
;****************************************************************************
; Possible values for PSGADSR_PHASE
; Must be multiples of 2 (size of a JR instruction)
;****************************************************************************
PSGPHASE_ATTACK: equ 0 ; Attack (key-on)
PSGPHASE_DECAY: equ 2 ; Decay
PSGPHASE_SUSTAIN: equ 4 ; Sustain
PSGPHASE_RELEASE: equ 6 ; Release (key-off)
;****************************************************************************
; PSG ADSR state
;
; See above for table format. The defaults give a completely flat PSG
; envelope (as if we were using the PSG without any envelope). The rates
; and level values are given in 4.4 fixed point format.
;****************************************************************************
PsgAdsr:
PsgAdsr_Psg1: db 0,PSGPHASE_RELEASE, $FF,$FF, $00,$00,$00, $FF
PsgAdsr_Psg2: db 0,PSGPHASE_RELEASE, $FF,$FF, $00,$00,$00, $FF
PsgAdsr_Psg3: db 0,PSGPHASE_RELEASE, $FF,$FF, $00,$00,$00, $FF
PsgAdsr_Psg4: db 0,PSGPHASE_RELEASE, $FF,$FF, $00,$00,$00, $FF
PsgAdsr_End:
NUM_PSGADSR: equ ($-PsgAdsr_Psg1)/SIZEOF_PSGADSR
;****************************************************************************
; FM panning, PMS and AMS settings for every channel.
;
; FmParam is the current values, BgmFmParam is the back-up for channel
; restoration. They're stored in the same format as the YMREG_PAN register.
; There are gaps where channel IDs are unused, they may be repurposed them
; but beware that they may be overwritten on BGM init.
;****************************************************************************
BgmFmParam: db PAN_BOTH,PAN_BOTH,PAN_BOTH,0,PAN_BOTH,PAN_BOTH,PAN_BOTH,0
SfxFmParam: db PAN_BOTH,PAN_BOTH,PAN_BOTH,0,PAN_BOTH,PAN_BOTH,PAN_BOTH,0
SIZEOF_PAN_DATA: equ $-BgmFmParam
SfxFmParamFree1: equ SfxFmParam+3
SfxFmParamFree2: equ SfxFmParam+7
BgmFmParamFree1: equ BgmFmParam+3
BgmFmParamFree2: equ BgmFmParam+7
;****************************************************************************
; 1st PCM buffer
;
; The PCM buffers must be aligned at the end of a page boundary (*NOT* the
; beginning). That leaves quite a gap, so we cram in other stuff between the
; two buffers.
;****************************************************************************
ds (($100-PCM_BUFSIZE)-($&$FF))&$FF
PcmBuffer1: ds PCM_BUFSIZE, $80
;****************************************************************************
;****************************************************************************
;********** PAGE BOUNDARY ***************************************************
;****************************************************************************
;****************************************************************************
;****************************************************************************
; FM frequencies for all semitones within an octave, in 1/4 semitone steps.
;
; The values are an average of NTSC and PAL frequencies (meaning they're off
; by ±8 cents) due to MCLK differences, may need to find a way to hold proper
; tables for both in the future (or approximate one out of the other in a way
; that doesn't eat up too much memory).
;****************************************************************************
FmFreqTable:
dw 647, 656, 666, 675, 685, 695, 705, 716
dw 726, 736, 747, 758, 769, 780, 792, 803
dw 815, 827, 839, 851, 863, 876, 889, 901
dw 915, 928, 941, 955, 969, 983, 997,1012
dw 1027,1042,1057,1072,1088,1103,1120,1136
dw 1152,1169,1186,1203,1221,1239,1257,1275
;****************************************************************************
; PSG frequencies for all semitones within an octave, in 1/4 semitone steps.
;
; The values are an average of NTSC and PAL frequencies (meaning they're off
; by ±8 cents) due to MCLK differences. See comment for FM table.
;****************************************************************************
PsgFreqTable:
dw 851, 839, 827, 815, 803, 792, 781, 769
dw 758, 747, 737, 726, 716, 706, 695, 685
dw 676, 666, 656, 647, 638, 629, 620, 611
dw 602, 593, 585, 576, 568, 560, 552, 544
dw 536, 529, 521, 514, 506, 499, 492, 485
dw 478, 471, 464, 457, 451, 444, 438, 432
;****************************************************************************
; A scratch buffer for subroutines to use arbitrarily
;****************************************************************************
ScratchBuf: ds 32
; Variables used by LoadFmInstr
;--------------------------------
LoadFmInstr_Algo: equ ScratchBuf+0 ; Algorithm & Feedback
LoadFmInstr_TL_S1: equ ScratchBuf+5 ; TL for S1
LoadFmInstr_TL_S2: equ ScratchBuf+7 ; TL for S2
LoadFmInstr_TL_S3: equ ScratchBuf+6 ; TL for S3
LoadFmInstr_TL_S4: equ ScratchBuf+8 ; TL for S4
;****************************************************************************
; 2nd PCM buffer
; See comments from 1st buffer for more details.
;****************************************************************************
ds (($100-PCM_BUFSIZE)-($&$FF))&$FF
PcmBuffer2: ds PCM_BUFSIZE, $80
;****************************************************************************
;****************************************************************************
;********** PAGE BOUNDARY ***************************************************
;****************************************************************************
;****************************************************************************
;****************************************************************************
; Look-up table used to mix two PCM samples. It takes the average of both
; samples as the index, and returns the output amplified back and clamped.
; It *needs* to be aligned to a page boundary (the low byte of the address
; is used as the index).
;****************************************************************************
PadToPage
PcmMixTable2ch:
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $00,$02,$04,$06,$08,$0A,$0C,$0E,$10,$12,$14,$16,$18,$1A,$1C,$1E
db $20,$22,$24,$26,$28,$2A,$2C,$2E,$30,$32,$34,$36,$38,$3A,$3C,$3E
db $40,$42,$44,$46,$48,$4A,$4C,$4E,$50,$52,$54,$56,$58,$5A,$5C,$5E
db $60,$62,$64,$66,$68,$6A,$6C,$6E,$70,$72,$74,$76,$78,$7A,$7C,$7E
db $80,$82,$84,$86,$88,$8A,$8C,$8E,$90,$92,$94,$96,$98,$9A,$9C,$9E
db $A0,$A2,$A4,$A6,$A8,$AA,$AC,$AE,$B0,$B2,$B4,$B6,$B8,$BA,$BC,$BE
db $C0,$C2,$C4,$C6,$C8,$CA,$CC,$CE,$D0,$D2,$D4,$D6,$D8,$DA,$DC,$DE
db $E0,$E2,$E4,$E6,$E8,$EA,$EC,$EE,$F0,$F2,$F4,$F6,$F8,$FA,$FC,$FE
db $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
db $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
db $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
db $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
;****************************************************************************
;****************************************************************************
;********** PAGE BOUNDARY ***************************************************
;****************************************************************************
;****************************************************************************
;****************************************************************************
; List of who owns each channel. For the sake of optimization in the RST
; routine this *must* be at the beginning of the 256 byte boundary (i.e.
; ChanOwner's address must be $xx00).
;
; $FF = BGM
; $00 = SFX #1
; $01 = SFX #2
; $02 = SFX #3
;****************************************************************************
ChanOwner:
ChanOwner_Fm1: db $FF ; FM ch1
ChanOwner_Fm2: db $FF ; FM ch2
ChanOwner_Fm3: db $FF ; FM ch3
db $FF ; --
ChanOwner_Fm4: db $FF ; FM ch4
ChanOwner_Fm5: db $FF ; FM ch5
ChanOwner_Fm6: db $FF ; FM ch6
db $FF ; --
ChanOwner_Psg1: db $FF ; Square ch1
ChanOwner_Psg2: db $FF ; Square ch2
ChanOwner_Psg3: db $FF ; Square ch3
ChanOwner_Psg4: db $FF ; Noise
db $FF ; --
db $FF ; --
ChanOwner_Pcm1: db $FF ; PCM ch1
ChanOwner_Pcm2: db $FF ; PCM ch2
;****************************************************************************
; Current SFX priority for each channel. Used in case two SFXs try to take
; over the same channel (only the higher priority one will success).
;
; StopAllSound assumes that this is located right after ChanOwner.
;****************************************************************************
ChanPriority:
ChanPriority_Fm1: db $00 ; FM ch1
ChanPriority_Fm2: db $00 ; FM ch2
ChanPriority_Fm3: db $00 ; FM ch3
db $00 ; --
ChanPriority_Fm4: db $00 ; FM ch4
ChanPriority_Fm5: db $00 ; FM ch5
ChanPriority_Fm6: db $00 ; FM ch6
db $00 ; --
ChanPriority_Psg1: db $00 ; Square ch1
ChanPriority_Psg2: db $00 ; Square ch2
ChanPriority_Psg3: db $00 ; Square ch3
ChanPriority_Psg4: db $00 ; Noise
db $00 ; --
db $00 ; --
ChanPriority_Pcm1: db $00 ; PCM ch1
ChanPriority_Pcm2: db $00 ; PCM ch2
;****************************************************************************
; Where we store the current values for the FM registers relevant to volume
; computation (algorithm and TL for every operator). They hold the values for
; the current instrument loaded regardless of which stream did it.
;
; There's a gap in the middle of the table to make things simpler, so we
; can cram unrelated variables in there.
;****************************************************************************
FMVOLREG_ALGO: equ 0
FMVOLREG_TL_S1: equ 1
FMVOLREG_TL_S3: equ 2
FMVOLREG_TL_S2: equ 3
FMVOLREG_TL_S4: equ 4
SIZEOF_FMVOLREG: equ 5
FmVolumeRegs:
FmVolumeRegs_FM1: ds SIZEOF_FMVOLREG ; FM channel 1
FmVolumeRegs_FM2: ds SIZEOF_FMVOLREG ; FM channel 2
FmVolumeRegs_FM3: ds SIZEOF_FMVOLREG ; FM channel 3
FmVolumeRegs_Free: ds SIZEOF_FMVOLREG ; (gap)
FmVolumeRegs_FM4: ds SIZEOF_FMVOLREG ; FM channel 4
FmVolumeRegs_FM5: ds SIZEOF_FMVOLREG ; FM channel 5
FmVolumeRegs_FM6: ds SIZEOF_FMVOLREG ; FM channel 6
;****************************************************************************
; Current priority of all SFX channels.
;
; A SFX channel can be reused only when priority is the same or higher. When
; a channel is stopped its priority will be set back to 0.
;****************************************************************************
SfxPriority: equ FmVolumeRegs_Free
;****************************************************************************
; Instrument IDs for each channel for BGM streams
;****************************************************************************
BgmFmInstr: ds 7
BgmPsgInstr: ds 4
;****************************************************************************
; Where volume is stored
;----------------------------------------------------------------------------
; BgmFmVol and SfxFmVol store the volume values for each FM channel for
; BGM and SFX streams (respectively), in FM1,FM2,FM3,<unused>,FM4,FM5,FM6
; order. They're stored in -0.75dB steps, the same way as SonaStream and
; the YM2612 want.
;
; RealFmVol is the current volume being output to the actual FM channel
; (from whoever has control over it). Format is the same as for BgmFmVol
; and SfxFmVol. This is needed by LoadFmInstr so it can load an instrument
; with the correct volume.
;
; BgmPsgVol and SfxPsgVol store the volume values for each PSG channel for
; BGM and SFX streams (respectively), in PSG1,PSG2,PSG3,PSG4 order. They're
; stored in -0.75dB steps, as SonaStream wants.
;
; RealPsgVol is the current volume being output to the actual PSG channel
; (from whoever has control over it). This is used by the PSG update code.
; Unlike all the above, this is stored in -2dB steps, same format as the PSG
; wants it.
;----------------------------------------------------------------------------
; notes: code expects these variables to be placed in this order. These
; variables must NOT cross a page boundary.
;****************************************************************************
BgmFmVol: ds 7
SfxFmVol: ds 7
RealFmVol: ds 7
BgmPsgVol: ds 4
SfxPsgVol: ds 4
RealPsgVol: ds 4
;----------------------------------------------------------------------------
SIZEOF_VOL_DATA: equ $-BgmFmVol
;****************************************************************************
; PcmRenderTable
;
; List of PCM render routines for every PCM channel combination. Located here
; so we can more easily control if it crosses a page boundary (since dealing
; with tables crossing those boundaries is a pain).
;****************************************************************************
PcmRenderTable:
dw PcmRender0ch ; No PCM playback
dw PcmRender1ch_A ; PCM1 only
dw PcmRender1ch_B ; PCM2 only
;dw PcmRender2ch ; PCM1+PCM2
; This isn't actually needed yet so
; it's commented out to save space,
; but if you ever need it uncomment
; this entry
;****************************************************************************
;****************************************************************************
;****************************************************************************
InstrList: equ $1C00 ; Instrument list
; $1C00..$1CFF = addresses (low)
; $1D00..$1DFF = addresses (high)
; $1E00..$1EFF = banks
;****************************************************************************
; Variables at fixed locations so they can be found easily.
; Most of them are meant to be accessible by the 68000 side.
;****************************************************************************
VmVarList: equ $1F00 ; Where VM variables go
StackStart: equ $1FC0 ; Where stack starts
CpuMeter: equ $1FC1 ; Z80 CPU usage meter
PlaybackStatus: equ $1FC2 ; Playback status flags
;----------------------------------------------------------------------------
; 68000 command queue
;----------------------------------------------------------------------------
QueueMutex: equ $1FC0 ; Mutex for the command queue
; 0 = 68000 side, !0 = Z80 side
QueueSlot1: equ $1FF4 ; 1st queued command
QueueSlot2: equ $1FE8 ; 2nd queued command
QueueSlot3: equ $1FDC ; 3rd queued command
CmdType: equ QueueSlot1 ; Next command to execute
CmdArg1: equ QueueSlot1+1 ; 1st argument for command
CmdArg2: equ QueueSlot1+2 ; 2nd argument for command
CmdArg3: equ QueueSlot1+3 ; 3rd argument for command
CmdArg4: equ QueueSlot1+4 ; 4th argument for command
CmdArg5: equ QueueSlot1+5 ; 5th argument for command
CmdArg6: equ QueueSlot1+6 ; 6th argument for command
CmdArg7: equ QueueSlot1+7 ; 7th argument for command
CmdArg8: equ QueueSlot1+8 ; 8th argument for command
CmdArg9: equ QueueSlot1+9 ; 9th argument for command
CmdArg10: equ QueueSlot1+10 ; 10th argument for command
CmdArg11: equ QueueSlot1+11 ; 11th argument for command
CmdAddr1: equ CmdArg1 ; 1st address argument
CmdAddr2: equ CmdArg4 ; 2nd address argument
CmdByte1: equ CmdArg7 ; 1st numeric argument
CmdByte2: equ CmdArg8 ; 2nd numeric argument
CmdByte3: equ CmdArg9 ; 3rd numeric argument
CmdByte4: equ CmdArg10 ; 4th numeric argument
CmdByte5: equ CmdArg11 ; 5th numeric argument
CmdAddr: equ CmdAddr1 ; (for convenience)
CmdByte: equ CmdByte1 ; (for convenience)