Sona 0.50 Source

Sona 0.50/src-z80/psg_update.z80

;****************************************************************************
; UpdatePsg
;
; Processes and updates all PSG channels. Essentially it's what takes the
; PSG instruments, pitch and volume and outputs the actual commands to the
; PSG hardware to output sound.
;----------------------------------------------------------------------------
; modifies ... a,bc,de,hl,a'
;****************************************************************************

UpdatePsg:
    call    UpdatePsgAdsr               ; Update ADSR envelope
    call    UpdatePsgPitch              ; Update pitch output
    jp      UpdatePsgVolume             ; Update volume output
    ret

;****************************************************************************
; UpdatePsgPitch
; Outputs the pitch for all square PSG channels.
;----------------------------------------------------------------------------
; modifies ... a,bc,de,hl,a'
;****************************************************************************

UpdatePsgPitch:
    ld      b, 3                        ; How many channels to process
    ld      de, PsgPitch                ; Pointer to all pitch values
    
UpdatePsgPitch_Loop:
    PollPcm
    ex      de, hl
    call    PollTimerB
    ex      de, hl
    
    ld      a, b                        ; Save loop counter for later
    ld      (UpdatePsgPitch_Counter), a
    
    cpl                                 ; Determine which bits need to be
    and     $03                         ; set to select this channel in the
    rrca                                ; PSG command and store them too
    rrca                                ;
    scf                                 ; PSG1 = $80 (%10000000)
    rra                                 ; PSG2 = $A0 (%10100000)
    ld      (UpdatePsgPitch_ChBits), a  ; PSG3 = $C0 (%11000000)
    
    PollPcm
    
    ld      a, (de)                     ; Read octave
    inc     e
    
    cp      6                           ; PSG octaves are 0~5, make sure that
    jr      nc, UpdatePsgPitch_Broken   ; the current octave is within range
                                        ; and freeze the channel if not.
                                        ; We're doing this because we'll do a
                                        ; jump for the bit shift and invalid
                                        ; values will execute arbitrary code.
    
    cpl                                 ; We need to divide the frequency by
    add     6                           ; two for every octave. Z80 doesn't
    add     a, a                        ; have multibit shift instructions,
    add     a, a                        ; so instead we have to resort to a
                                        ; Duff's device (see below) to shift
                                        ; it one bit at a time
    
    ld      (UpdatePsgPitch_ShiftJump), a
    
    PollPcm
    
    ld      a, (de)                     ; Read semitone+fraction and convert
    inc     e                           ; it to its PSG frequency for the
    rrca                                ; lowest octave
    and     $3F<<1
    add     a, PsgFreqTable&$FF
    ld      h, PsgFreqTable>>8
    ld      l, a
    ld      c, (hl)                     ; C = low byte
    inc     l                           ; B = high byte
    ld      b, (hl)
    
    PollPcm
    
UpdatePsgPitch_ShiftJump: equ $+1       ; Now divide it
    jr      $
    srl     b                           ; frequency >> 5
    rr      c
    srl     b                           ; frequency >> 4
    rr      c
    srl     b                           ; frequency >> 3
    rr      c
    srl     b                           ; frequency >> 2
    rr      c
    srl     b                           ; frequency >> 1
    rr      c
                                        ; frequency >> 0
    
    PollPcm
    
    ld      l, c                        ; PSG frequency is split into two
    ld      h, b                        ; parts: bits 3-0 and bits 9-4, so
    add     hl, hl                      ; we need to split them.
    add     hl, hl                      ;
    add     hl, hl                      ; First push bits 9-4 into B (to do:
    add     hl, hl                      ; figure out if there's a better way
    ld      b, h                        ; to approach this?)
    
    PollPcm
    
    ld      a, c                        ; Now leave only bits 3-0 into A
    and     $0F
    
UpdatePsgPitch_Write:
    ld      hl, PsgPort                 ; Write both frequency bytes to the
UpdatePsgPitch_ChBits: equ $+1          ; PSG to change the channel's pitch
    or      $00                         ; (the $00 was replaced earlier with
    ld      (hl), a                     ; the bits needed to set the channel)
    ld      (hl), b
    
UpdatePsgPitch_Counter: equ $+1
    ld      b, 0                        ; Go for next channel, if any
    djnz    UpdatePsgPitch_Loop
    
    ret                                 ; All square channels updated
                                        ; End of subroutine
    
UpdatePsgPitch_Broken:
    PollPcm
    
    inc     e                           ; We get here if the pitch is invalid
    xor     a                           ; (out of range), output a frequency
    ld      b, a                        ; of 0 to freeze it
    jr      UpdatePsgPitch_Write

;****************************************************************************
; UpdatePsgVolume
; Computes and outputs the final volume for all PSG channels.
;----------------------------------------------------------------------------
; modifies ... a,bc,de,hl,a'
;****************************************************************************

UpdatePsgVolume:
    ld      b, 4                            ; How many channels to process
    ld      de, PsgAdsr_Psg1+PSGADSR_LEVEL  ; Pointer to ADSR state
    ld      hl, RealPsgVol                  ; Pointer to volume levels
    
UpdatePsgVolume_Loop:
    PollPcm
    push    de
    call    PollTimerB
    pop     de
    
    ld      a, b                        ; Save loop counter for later
    ld      (UpdatePsgVolume_Counter), a
    
    dec     a                           ; Determine command bits for this
    cpl                                 ; channel to OR against the volume
    and     $03                         ; value and store them for the PSG
    rrca                                ; write later
    rrca
    rrca
    or      $90
    ld      (UpdatePsgVolume_ChBits), a
    
    PollPcm
    
    ld      a, (de)                     ; Get current ADSR level and
    cpl                                 ; convert it to PSG attenuation
    rrca
    rrca
    rrca
    rrca
    and     $0F
    
    add     a, (hl)                     ; Apply the channel's intended
    cp      $10                         ; volume level. Make sure that it
    jr      c, UpdatePsgVolume_NoClamp  ; doesn't overflow (clamp it).
    ld      a, $0F
UpdatePsgVolume_NoClamp:
    
UpdatePsgVolume_ChBits: equ $+1
    or      $00                         ; Write to the PSG
    ld      (PsgPort), a                ; That 0 is overwritten by the
                                        ; channel bits earlier in the
                                        ; subroutine
    
    PollPcm
    
    ld      a, e                        ; Advance pointers
    add     SIZEOF_PSGADSR
    ld      e, a
    inc     l
    
UpdatePsgVolume_Counter: equ $+1
    ld      b, 0                        ; Go for next channel, if any (the
    djnz    UpdatePsgVolume_Loop        ; 0 was overwritten with the current
                                        ; counter value at the beginning of
                                        ; the loop)
    
    ret                                 ; All PSG channels updated
                                        ; End of subroutine