;****************************************************************************
; FmSetVolume [SONAOP_VOLUME_FM1..FM6]
; Handles volume events for FM channels.
;----------------------------------------------------------------------------
; input b ..... channel ID (bits 2~0 only)
; input c ..... current bank
; input hl .... current address
;----------------------------------------------------------------------------
; output c .... new bank
; output hl ... new address
;----------------------------------------------------------------------------
; modifies .... a,bc,de,hl,a'
;****************************************************************************
FmSetVolume:
PollPcm
ld a, b ; Make sure to filter away the opcode
and $07 ; bits for the channel ID (it messes
cp $03 ; with some of the math later)
jr nz, FmSetVolumeChOk ; While we're at it check for FM3
dec a ; special, in which case we treat it
FmSetVolumeChOk: ; the same way as FM3 normal.
ld b, a
CheckOwner ; Now check if we own the channel
; (we need to do this after dealing
; with the ch3 special case)
ld a, i ; Compute pointer to where we're
and SfxFmVol-BgmFmVol ; storing the FM volume for this
add a, b ; stream
add BgmFmVol&$FF
ld (FmSetVolumePtr), a
PollPcm
ld a, b ; Compute pointer to where we're
and $07 ; storing the actual FM volume so
add a, RealFmVol&$FF ; LoadFmInstr can keep track of it
ld (FmSetVolumeRealPtr), a
ld a, b ; Determine YM2612 base address
and $04 ; for this channel
rrca ; ch1~ch3 = YmAddr1/YmData1
ld (FmSetVolume_Base), a ; ch4~ch6 = YmAddr2/YmData2
ld a, b ; Determine YM2612 register offset
and $03 ; for this channel
ld (FmSetVolume_Offset), a ; ch1/ch4 = +0
; ch2/ch5 = +1
; ch3/ch6 = +2
PollPcm
ld a, b ; Determine pointer to this channel's
add a, a ; algorithm and TL values, which are
add a, a ; needed to compute volume properly
add a, b
add a, FmVolumeRegs&$FF
ld iyh, FmVolumeRegs>>8
ld iyl, a
FmSetVolumePtr: equ $+1
ld de, BgmFmVol ; Read volume argument
call ReadVolArg
RetIfNotOwner ; Don't change the TL values in the
; YM2612 if we don't control this
; channel
push bc ; Save registers since we won't use
push hl ; the address now and we really need
; to reuse the registers
ld a, (de) ; Get new volume level
ld l, a
FmSetVolumeRealPtr: equ $+1
ld (RealFmVol), a ; Store it where LoadFmInstr can
; find it, in case the instrument
; needs to be reloaded
ld c, (iy+FMVOLREG_TL_S1) ; Get S1's TL value at 100%
ld e, (iy+FMVOLREG_TL_S2) ; Get S2's TL value at 100%
ld b, (iy+FMVOLREG_TL_S3) ; Get S3's TL value at 100%
ld d, (iy+FMVOLREG_TL_S4) ; Get S4's TL value at 100%
PollPcm
ld a, (iy+FMVOLREG_ALGO) ; Get instrument's algorithm
call AdjustFmTL ; Compute new TL levels for all
ld (LoadFmInstr_TL_S1), bc ; operators and store them in a
ld (LoadFmInstr_TL_S2), de ; buffer for the loop below
FmSetVolume_Base: equ $+2
ld iy, YmAddr1 ; YM2612 port base address
FmSetVolume_Offset: equ $+1
ld bc, $0400 ; Loop count + register offset
ld hl, FmSetVolume_Table ; Pointer to look-up table below
ld de, LoadFmInstr_TL_S1 ; Pointer to TL values to load
PollPcm
FmSetVolume_Loop:
ld a, (hl)
add a, c
ld (iy+0), a
ld a, (de)
ld (iy+1), a
inc hl
inc de
djnz FmSetVolume_Loop
PollPcm
pop hl ; Restore registers
pop bc
ret ; Back to stream processing
;----------------------------------------------------------------------------
FmSetVolume_Table:
db YMREG_TL+0
db YMREG_TL+4
db YMREG_TL+8
db YMREG_TL+12
;****************************************************************************
; AdjustFmTL
;
; Adjusts the TL values for all operators in a FM channel to apply volume
; (well, attenuation). This is done taking into account the instrument's
; algorithm (only output operators should be affected). It also makes sure
; to clamp the values if they go past the limit.
;----------------------------------------------------------------------------
; input b .... original S3 TL value
; input c .... original S1 TL value
; input d .... original S4 TL value
; input e .... original S2 TL value
; input a .... algorithm
; input l .... attenuation (0..127)
;----------------------------------------------------------------------------
; output b ... adjusted S3 TL value
; output c ... adjusted S1 TL value
; output d ... adjusted S4 TL value
; output e ... adjusted S2 TL value
;----------------------------------------------------------------------------
; modifies ... a,bc,de,hl
;----------------------------------------------------------------------------
; notes: the reason why operator order is so awkward is so they can match
; YM2612 register order and be loaded into Z80 registers as 16-bit (which
; shuffles them further by virtue of being little endian). So it's really BC
; and DE as four consecutive bytes in memory.
;****************************************************************************
AdjustFmTL:
ld h, 127 ; Value to clamp against (used to
; try shrinking the code below a
; bit, even if it's not much)
cp 4 ; Determine which operators are
jr c, AdjustFmTL_S4 ; affected by this algorithm and skip
jr z, AdjustFmTL_S2 ; to that part (thankfully the way
cp 7 ; they're arranged means that we can
jr c, AdjustFmTL_S3 ; resort to fallthrough for this)
AdjustFmTL_S1: ; Apply attenuation to S1's TL
PollPcm ; (only algorithm 7) and clamp
ld a, l ; as needed if it overflows
add a, c
jp p, AdjustFmTL_S1Ok
ld a, h
AdjustFmTL_S1Ok:
ld c, a
AdjustFmTL_S3: ; Apply attenuation to S3's TL
PollPcm ; (algorithms 5 onwards) and clamp
ld a, l ; as needed if it overflows
add a, b
jp p, AdjustFmTL_S3Ok
ld a, h
AdjustFmTL_S3Ok:
ld b, a
AdjustFmTL_S2: ; Apply attenuation to S2's TL
PollPcm ; (algorithms 4 onwards) and clamp
ld a, l ; as needed if it overflows
add a, e
jp p, AdjustFmTL_S2Ok
ld a, h
AdjustFmTL_S2Ok:
ld e, a
AdjustFmTL_S4: ; Apply attenuation to S4's TL
PollPcm ; (all algorithms) and clamp as
ld a, l ; needed if it overflows
add a, d
jp p, AdjustFmTL_S4Ok
ld a, h
AdjustFmTL_S4Ok:
ld d, a
ret ; End of subroutine