Sona 0.50 Source

Sona 0.50/src-z80/fm_stop.z80

;****************************************************************************
; StopAllFm
; Stops all FM channels (see StopFmChannel for details).
;----------------------------------------------------------------------------
; breaks .... a,bc,de,hl,iy,a'
;****************************************************************************

StopAllFm:
;    ld      c, 6                        ; Stop every FM channel
;StopAllFmLoop:                          ;
;    ld      a, c                        ; Note that we scan backwards to make
;    cp      3                           ; the loop simpler, and that we skip
;    call    nz, StopFmChannel           ; channel $03 since it isn't real and
;    dec     c                           ; can cause trouble
;    jp      p, StopAllFmLoop            ;
;                                        ; Using JP P instead of JP NC because
;                                        ; turns out that DEC doesn't change
;                                        ; the carry flag (what?)
;    
;    ret                                 ; End of subroutine
    
    ld      e, $77                      ; Set up channel mask to include
    ; fallthrough to StopFmByMask...    ; every FM channel and reuse the
                                        ; routine below to do the job

;****************************************************************************
; StopFmByMask
;
; Stops FM channels as specified by the passed bit mask (see StopFmChannel
; for details). It's one bit per channel, which bit = 1 to indicate that we
; want to stop that channel. UNUSED BITS MUST BE 0 FOR CORRECT FUNCTION.
;----------------------------------------------------------------------------
; input e ... bit 0 = set to mute FM1
;             bit 1 = set to mute FM2
;             bit 2 = set to mute FM3
;             bit 3 = must be 0
;             bit 4 = set to mute FM4
;             bit 5 = set to mute FM5
;             bit 6 = set to mute FM6
;             bit 7 = must be 0
;----------------------------------------------------------------------------
; breaks .... a,bc,de,iy
;****************************************************************************

StopFmByMask:
    ld      d, 7                        ; Loop through every channel
StopFmByMaskLoop:                       ;
    ld      a, d                        ; D = FM channel ID
    sla     e                           ; E = FM channel mask
    call    c, StopFmChannel            ;
    dec     d                           ; This loop pushes the next channel's
    jp      p, StopFmByMaskLoop         ; bit into carry and then we call
                                        ; StopFmChannel whenever that bit was
                                        ; set (indicating we should stop it)
                                        ;
                                        ; Using JP P instead of JP NC because
                                        ; turns out that DEC doesn't change
                                        ; the carry flag (what?)
    
    ret                                 ; End of subroutine

;****************************************************************************
; StopFmChannel
;
; Stops a FM channel's output immediately. Used to "deinitialize" a FM
; channel when we need to shut down all sound or are about to load a new
; instrument or something like that.
;----------------------------------------------------------------------------
; input a .... channel ID (0,1,2,4,5,6)
; input ix ... pointer to YmAddr1
;----------------------------------------------------------------------------
; breaks ..... a,b,iyl
;----------------------------------------------------------------------------
; note: this "mutes" the FM channel by setting release rate to maximum and
; forcing key-off, but it still takes 128 samples to actually decay
; completely. Make sure to account for the need to wait this long (or at
; least long enough that it's practically unhearable).
;****************************************************************************

StopFmChannel:
    ld      b, a                        ; Keep a copy of the channel ID
                                        ; since we're gonna manipulate it
                                        ; a bunch of times
    
    ld      (StopFmKeyCmd), a           ; Store the channel ID into the
                                        ; instruction that does the key-off
                                        ; write later to shut down output
    
    and     $04                         ; Determine which bank
    rrca                                ; this channel uses
    ld      iy, YmAddr1                 ;
    ld      iyl, a                      ; ch1~3 = YmAddr1/YmData1
                                        ; ch4~6 = YmAddr2/YmData2
    
    ld      a, b                        ; Get release rate register number
    and     $03
    add     a, YMREG_RR
    
    ld      b, 4                        ; Go through all operators and set
StopFmLoopRR:                           ; their release rate to maximum
    ld      (iy+0), a                   ; (which should make their release
    ld      (iy+1), $0F                 ; instantaneous on key-off)
    push    hl
    pop     hl
    push    hl
    pop     hl
    add     4
    djnz    StopFmLoopRR
    
    ld      (ix+0), YMREG_KEY           ; Key-off the channel to force it
    ld      (ix+1), $00                 ; into the release phase, which is
StopFmKeyCmd: equ $-1                   ; what should mute it for good
    
    ret                                 ; End of subroutine