Sona 0.50 Source

Sona 0.50/src-z80/fm_volume.z80

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