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