Files
z80_monitor/apps/sn76489/psglib_zasm.asm
T

1107 lines
41 KiB
NASM

;================================================================
; PSGlib - Programmable Sound Generator audio library - by sverx
; https://github.com/sverx/PSGlib
;================================================================
; zasm port for z80_monitor. The code is included in the application ROM and
; its workspace occupies RAM immediately above the application's private stack.
PSG_STOPPED .equ 0
PSG_PLAYING .equ 1
PSGDataPort .equ 0x19
PSGLatch .equ 0x80
PSGData .equ 0x40
PSGChannel0 .equ 00000000b
PSGChannel1 .equ 00100000b
PSGChannel2 .equ 01000000b
PSGChannel3 .equ 01100000b
PSGVolumeData .equ 00010000b
PSGWait .equ 0x38
PSGSubString .equ 0x08
PSGLoop .equ 0x01
PSGEnd .equ 0x00
SFX_CHANNEL2 .equ 0x01
SFX_CHANNEL3 .equ 0x02
SFX_CHANNELS2AND3 .equ SFX_CHANNEL2|SFX_CHANNEL3
SFX_CHANNEL0 .equ 0x04
SFX_CHANNEL1 .equ 0x08
; ************************************************************************************
; initializes the PSG 'engine'
; destroys AF
PSGInit:
xor a ; ld a,PSG_STOPPED
ld (PSGMusicStatus),a ; set music status to PSG_STOPPED
ld (PSGSFXStatus),a ; set SFX status to PSG_STOPPED
ld (PSGChannel0SFX),a ; set channel 0 SFX to PSG_STOPPED
ld (PSGChannel1SFX),a ; set channel 1 SFX to PSG_STOPPED
ld (PSGChannel2SFX),a ; set channel 2 SFX to PSG_STOPPED
ld (PSGChannel3SFX),a ; set channel 3 SFX to PSG_STOPPED
ld (PSGMusicVolumeAttenuation),a ; volume attenuation = none
ret
; ************************************************************************************
; all receive in HL the address of the PSG to start playing
; PSGPlayLoops also receives in A the number of times to loop the PSG
; destroys AF
PSGPlayNoRepeat: ; we don't want the song ever to loop
xor a
PSGPlayLoops: ; we want the song to loop a certain amount of times
ld (PSGLoopCounter),a
xor a ; we don't want the song to loop forever
jp _psglib_anon_01
PSGPlay:
ld a,0x1 ; we want the song to loop forever
_psglib_anon_01:ld (PSGLoopFlag),a
call PSGStop ; if there's a tune already playing, we should stop it!
ld (PSGMusicStart),hl ; store the begin point of music
ld (PSGMusicPointer),hl ; set music pointer to begin of music
ld (PSGMusicLoopPoint),hl ; looppointer points to begin too
xor a
ld (PSGMusicSkipFrames),a ; reset the skip frames
ld (PSGMusicSubstringLen),a ; reset the substring len (for compression)
ld a,PSGLatch|PSGChannel0|PSGVolumeData|0x0F ; latch channel 0, volume=0xF (silent)
ld (PSGMusicLastLatch),a ; reset last latch to chn 0 volume 0
ld a,0x0F ; ensure unused channels remain silent
ld (PSGChan0Volume),a
ld (PSGChan1Volume),a
ld (PSGChan2Volume),a
ld (PSGChan3Volume),a
ld a,PSG_PLAYING
ld (PSGMusicStatus),a ; set status to PSG_PLAYING
ret
; ************************************************************************************
; stops the music (leaving the SFX on, if it's playing)
; destroys AF
PSGStop:
ld a,(PSGMusicStatus) ; if it's already stopped, leave
or a
ret z
ld a,(PSGChannel0SFX)
or a
jr nz,_psglib_anon_02
ld a,PSGLatch|PSGChannel0|PSGVolumeData|0x0F ; latch channel 0, volume=0xF (silent)
out (PSGDataPort),a
_psglib_anon_02:ld a,(PSGChannel1SFX)
or a
jr nz,_psglib_anon_03
ld a,PSGLatch|PSGChannel1|PSGVolumeData|0x0F ; latch channel 1, volume=0xF (silent)
out (PSGDataPort),a
_psglib_anon_03:ld a,(PSGChannel2SFX)
or a
jr nz,_psglib_anon_04
ld a,PSGLatch|PSGChannel2|PSGVolumeData|0x0F ; latch channel 2, volume=0xF (silent)
out (PSGDataPort),a
_psglib_anon_04:ld a,(PSGChannel3SFX)
or a
jr nz,_psglib_anon_05
ld a,PSGLatch|PSGChannel3|PSGVolumeData|0x0F ; latch channel 3, volume=0xF (silent)
out (PSGDataPort),a
_psglib_anon_05:xor a ; ld a,PSG_STOPPED
ld (PSGMusicStatus),a ; set status to PSG_STOPPED
ret
; ************************************************************************************
; resume a previously stopped music (with volume attenuation if needed)
; destroys AF,HL
PSGResume:
ld a,(PSGMusicStatus) ; if it's already playing, leave
or a
ret nz
ld hl,PSGMusicVolumeAttenuation
ld l,(hl) ; load volume attenuation in L
ld a,(PSGChannel0SFX)
or a
jr nz,_ch1
ld a,(PSGChan0LowTone) ; restore channel 0 frequency
or PSGLatch|PSGChannel0
out (PSGDataPort),a
ld a,(PSGChan0HighTone)
out (PSGDataPort),a
ld a,(PSGChan0Volume) ; restore channel 0 volume
and 0x0F
add a,l
cp 0x0F ; check overflow
jr c,_psglib_anon_06 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_psglib_anon_06:or PSGLatch|PSGChannel0|PSGVolumeData
out (PSGDataPort),a ; output the byte
_ch1:
ld a,(PSGChannel1SFX)
or a
jr nz,_ch2
ld a,(PSGChan1LowTone) ; restore channel 1 frequency
or PSGLatch|PSGChannel1
out (PSGDataPort),a
ld a,(PSGChan1HighTone)
out (PSGDataPort),a
ld a,(PSGChan1Volume) ; restore channel 1 volume
and 0x0F
add a,l
cp 0x0F ; check overflow
jr c,_psglib_anon_07 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_psglib_anon_07:or PSGLatch|PSGChannel1|PSGVolumeData
out (PSGDataPort),a
_ch2:
ld a,(PSGChannel2SFX)
or a
jr nz,_ch3
ld a,(PSGChan2LowTone) ; restore channel 2 frequency
or PSGLatch|PSGChannel2
out (PSGDataPort),a
ld a,(PSGChan2HighTone)
out (PSGDataPort),a
ld a,(PSGChan2Volume) ; restore channel 2 volume
and 0x0F
add a,l
cp 0x0F ; check overflow
jr c,_psglib_anon_08 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_psglib_anon_08:or PSGLatch|PSGChannel2|PSGVolumeData
out (PSGDataPort),a
_ch3:
ld a,(PSGChannel3SFX)
or a
jr nz,_leave
ld a,(PSGChan3LowTone) ; restore channel 3 frequency
or PSGLatch|PSGChannel3
out (PSGDataPort),a
ld a,(PSGChan3Volume) ; restore channel 3 volume
and 0x0F
add a,l
cp 0x0F ; check overflow
jr c,_psglib_anon_09 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_psglib_anon_09:or PSGLatch|PSGChannel3|PSGVolumeData
out (PSGDataPort),a
_leave:
ld a,PSG_PLAYING
ld (PSGMusicStatus),a ; set status to PSG_PLAYING
ret
; ************************************************************************************
; sets the currently looping music to no more loops after the current
; destroys AF
PSGCancelLoop:
xor a
ld (PSGLoopFlag),a
ld (PSGLoopCounter),a
ret
; ************************************************************************************
; gets the current status of music into register A
PSGGetStatus:
ld a,(PSGMusicStatus)
ret
; ************************************************************************************
; receives in L the volume attenuation for the music (0-15)
; destroys AF
PSGSetMusicVolumeAttenuation:
ld a,l
ld (PSGMusicVolumeAttenuation),a
ld a,(PSGMusicStatus) ; if tune is not playing, leave
or a
ret z
ld a,(PSGChannel0SFX) ; channel 0 busy with SFX?
or a
jr nz,_music_restore_channel1 ; if so, skip channel 0
ld a,(PSGChan0Volume)
and 0x0F
add a,l
cp 0x0F ; check overflow
jr c,_psglib_anon_10 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_psglib_anon_10:or PSGLatch|PSGChannel0|PSGVolumeData
out (PSGDataPort),a ; output the byte
_music_restore_channel1:
ld a,(PSGChannel1SFX) ; channel 1 busy with SFX?
or a
jr nz,_music_restore_channel2 ; if so, skip channel 1
ld a,(PSGChan1Volume)
and 0x0F
add a,l
cp 0x0F ; check overflow
jr c,_psglib_anon_11 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_psglib_anon_11:or PSGLatch|PSGChannel1|PSGVolumeData
out (PSGDataPort),a ; output the byte
_music_restore_channel2:
ld a,(PSGChannel2SFX) ; channel 2 busy with SFX?
or a
jr nz,_music_restore_channel3 ; if so, skip channel 2
ld a,(PSGChan2Volume)
and 0x0F
add a,l
cp 0x0F ; check overflow
jr c,_psglib_anon_12 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_psglib_anon_12:or PSGLatch|PSGChannel2|PSGVolumeData
out (PSGDataPort),a ; output the byte
_music_restore_channel3:
ld a,(PSGChannel3SFX) ; channel 3 busy with SFX?
or a
ret nz ; if so, we're done
ld a,(PSGChan3Volume)
and 0x0F
add a,l
cp 0x0F ; check overflow
jr c,_psglib_anon_13 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_psglib_anon_13:or PSGLatch|PSGChannel3|PSGVolumeData
out (PSGDataPort),a ; output the byte
ret
; ************************************************************************************
; receives in L the volume attenuation for the SFXs (0-15)
; destroys AF
PSGSetSFXVolumeAttenuation:
ld a,l
ld (PSGSFXVolumeAttenuation),a
ld a,(PSGSFXStatus) ; if an SFX is not playing, leave
or a
ret z
ld a,(PSGChannel0SFX) ; channel 0 used by SFX?
or a
jr z,_sfx_restore_channel1 ; if not, skip channel 0
ld a,(PSGSFXChan0Volume)
and 0x0F
add a,l
cp 0x0F ; check overflow
jr c,_psglib_anon_14 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_psglib_anon_14:or PSGLatch|PSGChannel0|PSGVolumeData
out (PSGDataPort),a ; output the byte
_sfx_restore_channel1:
ld a,(PSGChannel1SFX) ; channel 1 used by SFX?
or a
jr z,_sfx_restore_channel2 ; if not, skip channel 1
ld a,(PSGSFXChan1Volume)
and 0x0F
add a,l
cp 0x0F ; check overflow
jr c,_psglib_anon_15 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_psglib_anon_15:or PSGLatch|PSGChannel1|PSGVolumeData
out (PSGDataPort),a ; output the byte
_sfx_restore_channel2:
ld a,(PSGChannel2SFX) ; channel 2 used by SFX?
or a
jr z,_sfx_restore_channel3 ; if not, skip channel 2
ld a,(PSGSFXChan2Volume)
and 0x0F
add a,l
cp 0x0F ; check overflow
jr c,_psglib_anon_16 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_psglib_anon_16:or PSGLatch|PSGChannel2|PSGVolumeData
out (PSGDataPort),a ; output the byte
_sfx_restore_channel3:
ld a,(PSGChannel3SFX) ; channel 3 used by SFX?
or a
ret z ; if not, we're done
ld a,(PSGSFXChan3Volume)
and 0x0F
add a,l
cp 0x0F ; check overflow
jr c,_psglib_anon_17 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_psglib_anon_17:or PSGLatch|PSGChannel3|PSGVolumeData
out (PSGDataPort),a ; output the byte
ret
; ************************************************************************************
; sets all PSG channels to volume ZERO
; destroys AF
PSGSilenceChannels:
ld a,PSGLatch|PSGChannel0|PSGVolumeData|0x0F ; latch channel 0, volume=0xF (silent)
out (PSGDataPort),a
ld a,PSGLatch|PSGChannel1|PSGVolumeData|0x0F ; latch channel 1, volume=0xF (silent)
out (PSGDataPort),a
ld a,PSGLatch|PSGChannel2|PSGVolumeData|0x0F ; latch channel 2, volume=0xF (silent)
out (PSGDataPort),a
ld a,PSGLatch|PSGChannel3|PSGVolumeData|0x0F ; latch channel 3, volume=0xF (silent)
out (PSGDataPort),a
ret
; ************************************************************************************
; resets all PSG channels to previous volume
; destroys AF,HL
PSGRestoreVolumes:
ld a,(PSGChannel0SFX) ; channel 0 busy with SFX?
jr nz,_restoreSFX0
ld a,(PSGMusicStatus) ; check if tune is playing
or a
jr z,_chkchn1 ; if not, skip channel 0
ld a,(PSGChan0Volume)
and 0x0F
ld hl,PSGMusicVolumeAttenuation
add a,(hl)
cp 0x0F ; check overflow
jr c,_write0 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
jp _write0
_restoreSFX0:
ld a,(PSGSFXChan0Volume)
and 0x0F
ld hl,PSGSFXVolumeAttenuation
add a,(hl)
cp 0x0F ; check overflow
jr c,_write0 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_write0:
or PSGLatch|PSGChannel0|PSGVolumeData
out (PSGDataPort),a ; output the byte
_chkchn1:
ld a,(PSGChannel1SFX) ; channel 1 busy with SFX?
jr nz,_restoreSFX1
ld a,(PSGMusicStatus) ; check if tune is playing
or a
jr z,_chkchn2 ; if not, skip channel 1
ld a,(PSGChan1Volume)
and 0x0F
ld hl,PSGMusicVolumeAttenuation
add a,(hl)
cp 0x0F ; check overflow
jr c,_write1 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
jp _write1
_restoreSFX1:
ld a,(PSGSFXChan1Volume)
and 0x0F
ld hl,PSGSFXVolumeAttenuation
add a,(hl)
cp 0x0F ; check overflow
jr c,_write1 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_write1:
or PSGLatch|PSGChannel1|PSGVolumeData
out (PSGDataPort),a ; output the byte
_chkchn2:
ld a,(PSGChannel2SFX) ; channel 2 busy with SFX?
jr nz,_restoreSFX2
ld a,(PSGMusicStatus) ; check if tune is playing
or a
jr z,_chkchn3 ; if not, skip chn2
ld a,(PSGChan2Volume)
and 0x0F
ld hl,PSGMusicVolumeAttenuation
add a,(hl)
cp 0x0F ; check overflow
jr c,_write2 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
jp _write2
_restoreSFX2:
ld a,(PSGSFXChan2Volume)
and 0x0F
ld hl,PSGSFXVolumeAttenuation
add a,(hl)
cp 0x0F ; check overflow
jr c,_write2 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_write2:
or PSGLatch|PSGChannel2|PSGVolumeData
out (PSGDataPort),a ; output the byte
_chkchn3:
ld a,(PSGChannel3SFX) ; channel 3 busy with SFX?
jr nz,_restoreSFX3
ld a,(PSGMusicStatus) ; check if tune is playing
or a
ret z ; if not, we've done
ld a,(PSGChan3Volume)
and 0x0F
ld hl,PSGMusicVolumeAttenuation
add a,(hl)
cp 0x0F ; check overflow
jr c,_write3 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
jp _write3
_restoreSFX3:
ld a,(PSGSFXChan3Volume)
and 0x0F
ld hl,PSGSFXVolumeAttenuation
add a,(hl)
cp 0x0F ; check overflow
jr c,_write3 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_write3:
or PSGLatch|PSGChannel3|PSGVolumeData
out (PSGDataPort),a ; output the byte
ret
; ************************************************************************************
; receives in HL the address of the SFX PSG to start
; receives in C the mask that indicates which channel(s) the SFX will use
; destroys AF
PSGSFXPlayLoop:
ld a,1 ; SFX _IS_ a looping one
jp _psglib_anon_18
PSGSFXPlay:
xor a ; SFX is _NOT_ a looping one
_psglib_anon_18:ld (PSGSFXLoopFlag),a
call PSGSFXStop ; if there's a SFX already playing, we should stop it!
ld (PSGSFXStart),hl ; store begin of SFX
ld (PSGSFXPointer),hl ; set the pointer to begin of SFX
ld (PSGSFXLoopPoint),hl ; looppointer points to begin too
xor a
ld (PSGSFXSkipFrames),a ; reset the skip frames
ld (PSGSFXSubstringLen),a ; reset the substring len
bit 2,c ; channel 0 needed?
jr z,_psglib_anon_19
ld a,PSG_PLAYING
ld (PSGChannel0SFX),a
_psglib_anon_19:bit 3,c ; channel 1 needed?
jr z,_psglib_anon_20
ld a,PSG_PLAYING
ld (PSGChannel1SFX),a
_psglib_anon_20:bit 0,c ; channel 2 needed?
jr z,_psglib_anon_21
ld a,PSG_PLAYING
ld (PSGChannel2SFX),a
_psglib_anon_21:bit 1,c ; channel 3 needed?
jr z,_psglib_anon_22
ld a,PSG_PLAYING
ld (PSGChannel3SFX),a
_psglib_anon_22:ld (PSGSFXStatus),a ; set SFXstatus to PSG_PLAYING if SFX uses at least one channel (0=PSG_STOPPED otherwise)
ld a,(PSGMusicStatus) ; check if a tune is currently playing
or a
ret z ; if not, we've done
ld a,(PSGChan3LowTone) ; test if music playing on channel 3 uses the frequency of channel 2
and 3
cp 3
ret nz ; if channel 3 doesn't use the frequency of channel 2 we're done
ld a,PSG_PLAYING
ld (PSGChannel3SFX),a ; otherwise mark channel 3 as occupied by the SFX
ld a,PSGLatch|PSGChannel3|PSGVolumeData|0x0F ; and silence channel 3
out (PSGDataPort),a
ret
; ************************************************************************************
; stops the SFX (leaving the music on, if it's playing)
; destroys AF
PSGSFXStop:
ld a,(PSGSFXStatus) ; check status
or a
ret z ; no SFX playing, leave
ld a,(PSGChannel0SFX) ; channel 0 playing?
or a
jr z,_psglib_anon_23
ld a,PSGLatch|PSGChannel0|PSGVolumeData|0x0F ; latch channel 0, volume=0xF (silent)
out (PSGDataPort),a
_psglib_anon_23:ld a,(PSGChannel1SFX) ; channel 1 playing?
or a
jr z,_psglib_anon_24
ld a,PSGLatch|PSGChannel1|PSGVolumeData|0x0F ; latch channel 1, volume=0xF (silent)
out (PSGDataPort),a
_psglib_anon_24:ld a,(PSGChannel2SFX) ; channel 2 playing?
or a
jr z,_psglib_anon_25
ld a,PSGLatch|PSGChannel2|PSGVolumeData|0x0F ; latch channel 2, volume=0xF (silent)
out (PSGDataPort),a
_psglib_anon_25:ld a,(PSGChannel3SFX) ; channel 3 playing?
or a
jr z,_psglib_anon_26
ld a,PSGLatch|PSGChannel3|PSGVolumeData|0x0F ; latch channel 3, volume=0xf (silent)
out (PSGDataPort),a
_psglib_anon_26:ld a,(PSGMusicStatus) ; check if a tune is playing
or a
jp z,_skipRestore ; if it's not playing, skip restoring PSG values
ld a,(PSGChannel0SFX) ; channel 0 playing?
or a
jr z,_skip_chn0
ld a,(PSGChan0LowTone)
and 0x0F ; use only low 4 bits of byte
or PSGLatch|PSGChannel0 ; latch channel 0, low part of tone
out (PSGDataPort),a
ld a,(PSGChan0HighTone) ; high part of tone (latched channel 0, tone)
and 0x3F ; use only low 6 bits of byte
out (PSGDataPort),a
ld a,(PSGChan0Volume) ; restore music' channel 0 volume
and 0x0F ; use only low 4 bits of byte
push bc
ld b,a ; save it temporary
ld a,(PSGMusicVolumeAttenuation) ;
add a,b
cp 0x0F ; check overflow
jr c,_psglib_anon_27 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_psglib_anon_27: or PSGLatch|PSGChannel0|PSGVolumeData
out (PSGDataPort),a ; output the byte
pop bc
_skip_chn0:
ld a,(PSGChannel1SFX) ; channel 1 playing?
or a
jr z,_skip_chn1
ld a,(PSGChan1LowTone)
and 0x0F ; use only low 4 bits of byte
or PSGLatch|PSGChannel1 ; latch channel 1, low part of tone
out (PSGDataPort),a
ld a,(PSGChan1HighTone) ; high part of tone (latched channel 1, tone)
and 0x3F ; use only low 6 bits of byte
out (PSGDataPort),a
ld a,(PSGChan1Volume) ; restore music' channel 1 volume
and 0x0F ; use only low 4 bits of byte
push bc
ld b,a ; save it temporary
ld a,(PSGMusicVolumeAttenuation) ;
add a,b
cp 0x0F ; check overflow
jr c,_psglib_anon_28 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_psglib_anon_28: or PSGLatch|PSGChannel1|PSGVolumeData
out (PSGDataPort),a ; output the byte
pop bc
_skip_chn1:
ld a,(PSGChannel2SFX) ; channel 2 playing?
or a
jr z,_skip_chn2
ld a,(PSGChan2LowTone)
and 0x0F ; use only low 4 bits of byte
or PSGLatch|PSGChannel2 ; latch channel 2, low part of tone
out (PSGDataPort),a
ld a,(PSGChan2HighTone) ; high part of tone (latched channel 2, tone)
and 0x3F ; use only low 6 bits of byte
out (PSGDataPort),a
ld a,(PSGChan2Volume) ; restore music' channel 2 volume
and 0x0F ; use only low 4 bits of byte
push bc
ld b,a ; save it temporary
ld a,(PSGMusicVolumeAttenuation) ;
add a,b
cp 0x0F ; check overflow
jr c,_psglib_anon_29 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_psglib_anon_29: or PSGLatch|PSGChannel2|PSGVolumeData
out (PSGDataPort),a ; output the byte
pop bc
_skip_chn2:
ld a,(PSGChannel3SFX) ; channel 3 playing?
or a
jr z,_skip_chn3
ld a,(PSGChan3LowTone)
and 0x07 ; use only low 3 bits of byte
or PSGLatch|PSGChannel3 ; latch channel 3, low part of tone (no high part)
out (PSGDataPort),a
ld a,(PSGChan3Volume) ; restore music' channel 3 volume
and 0x0F ; use only low 4 bits of byte
push bc
ld b,a ; save it temporary
ld a,(PSGMusicVolumeAttenuation) ;
add a,b
cp 0x0F ; check overflow
jr c,_psglib_anon_30 ; if it's <=15 then ok
ld a,0x0F ; else, reset to 15
_psglib_anon_30: or PSGLatch|PSGChannel3|PSGVolumeData
out (PSGDataPort),a ; output the byte
pop bc
_skip_chn3:
xor a ; ld a,PSG_STOPPED
_skipRestore:
ld (PSGChannel0SFX),a
ld (PSGChannel1SFX),a
ld (PSGChannel2SFX),a
ld (PSGChannel3SFX),a
ld (PSGSFXStatus),a ; set status to PSG_STOPPED
ret
; ************************************************************************************
; sets the currently looping SFX to no more loops after the current
; destroys AF
PSGSFXCancelLoop:
xor a
ld (PSGSFXLoopFlag),a
ret
; ************************************************************************************
; gets the current SFX status into register A
PSGSFXGetStatus:
ld a,(PSGSFXStatus)
ret
; ************************************************************************************
; processes a music frame
; destroys AF,HL,BC
PSGFrame:
ld a,(PSGMusicStatus) ; check if we have got to play a tune
or a
ret z
ld a,(PSGMusicSkipFrames) ; check if we havve got to skip frames
or a
jp nz,_skipFrame
ld hl,(PSGMusicPointer) ; read current address
_intLoop:
ld b,(hl) ; load PSG byte (in B)
inc hl ; point to next byte
ld a,(PSGMusicSubstringLen) ; read substring len
or a
jr z,_continue ; check if it is 0 (we are not in a substring)
dec a ; decrease len
ld (PSGMusicSubstringLen),a ; save len
jr nz,_continue
ld hl,(PSGMusicSubstringRetAddr) ; substring is over, retrieve return address
_continue:
ld a,b ; copy PSG byte into A
cp PSGLatch ; is it a latch?
jr c,_noLatch ; if < 0x80 then it is NOT a latch
ld (PSGMusicLastLatch),a ; it is a latch - save it in "LastLatch"
; we have got the latch PSG byte both in A and in B
; and we have to check if the value should pass to PSG or not
bit 4,a ; test if it is a volume
jr nz,_latch_Volume ; jump if volume data
bit 6,a ; test if the latch it is for channels 0-1 or for 2-3
jp nz,_latch_chn23 ; jump if it is for channels 2-3
; we have got the latch (tone, chn 0 or 1) PSG byte both in A and in B
; and we have to check if the value should be passed to PSG or not
bit 5,a ; test if tone it is for channel 0 or 1
jr z,_ifchn0 ; jump if channel 0
ld (PSGChan1LowTone),a ; save tone LOW data
ld a,(PSGChannel1SFX) ; channel 1 available for music?
or a
jr z,_send2PSG_B ; then send data
jp _intLoop
_ifchn0:
ld (PSGChan0LowTone),a ; save tone LOW data
ld a,(PSGChannel0SFX) ; channel 0 available for music?
or a
jr z,_send2PSG_B ; then send data
jp _intLoop
_latch_chn23:
; we have got the latch (tone, chn 2 or 3) PSG byte both in A and in B
; and we have to check if the value should be passed to PSG or not
bit 5,a ; test if tone it is for channel 2 or 3
jr z,_ifchn2 ; jump if channel 2
ld (PSGChan3LowTone),a ; save tone LOW data
ld a,(PSGChannel3SFX) ; channel 3 available for music?
or a
jp nz,_intLoop
ld a,(PSGChan3LowTone)
and 3 ; test if channel 3 is set to use the frequency of channel 2
cp 3
jr nz,_send2PSG_B ; if channel 3 does not use frequency of channel 2 jump
ld a,(PSGChannel2SFX) ; test if an SFX is playing on channel 2
or a
jr z,_send2PSG_B ; if no SFX is playing on channel 2 jump
ld (PSGChannel3SFX),a ; otherwise mark channel 3 as occupied
ld a,PSGLatch|PSGChannel3|PSGVolumeData|0x0F ; and silence channel 3
out (PSGDataPort),a
jp _intLoop
_ifchn2:
ld (PSGChan2LowTone),a ; save tone LOW data
ld a,(PSGChannel2SFX) ; channel 2 available for music?
or a
jr z,_send2PSG_B
jp _intLoop
_noLatch:
cp PSGData
jr c,_command ; if < 0x40 then it is a command
; it's a data
ld a,(PSGMusicLastLatch) ; retrieve last latch
jp _output_NoLatch
_latch_Volume:
bit 6,a ; test if the latch it is for channels 0-1 or for 2-3
jr nz,_latch_Volume_23 ; volume is for channel 2 or 3
bit 5,a ; test if volume it is for channel 0 or 1
jr z,_ifchn0v ; jump for channel 0
ld (PSGChan1Volume),a ; save volume data
ld a,(PSGChannel1SFX) ; channel 1 available for music?
or a
jr z,_sendVolume2PSG_B
jp _intLoop
_ifchn0v:
ld (PSGChan0Volume),a ; save volume data
ld a,(PSGChannel0SFX) ; channel 0 available for music?
or a
jr z,_sendVolume2PSG_B
jp _intLoop
_send2PSG_B:
ld a,b
_send2PSG_A:
out (PSGDataPort),a ; output the byte
jp _intLoop
_latch_Volume_23:
bit 5,a ; test if volume it is for channel 2 or 3
jr z,_chn2 ; jump for channel 2
ld (PSGChan3Volume),a ; save volume data
ld a,(PSGChannel3SFX) ; channel 3 available for music?
or a
jr z,_sendVolume2PSG_B
jp _intLoop
_chn2:
ld (PSGChan2Volume),a ; save volume data
ld a,(PSGChannel2SFX) ; channel 2 available for music?
or a
jr z,_sendVolume2PSG_B
jp _intLoop
_skipFrame:
dec a
ld (PSGMusicSkipFrames),a
ret
_command:
cp PSGWait
jr z,_done ; no additional frames
jr c,_otherCommands ; other commands?
and 0x07 ; take only the last 3 bits for skip frames
ld (PSGMusicSkipFrames),a ; we got additional frames
_done:
ld (PSGMusicPointer),hl ; save current address
ret ; frame done
_otherCommands:
cp PSGSubString
jr nc,_substring
cp PSGEnd
jr z,_musicLoop
cp PSGLoop
jr z,_setLoopPoint
; ***************************************************************************
; we should never get here!
; if we do, it means the PSG file is probably corrupted, so we just RET
; ***************************************************************************
ret
_sendVolume2PSG_B:
ld a,b
_sendVolume2PSG_A:
ld c,a ; save the PSG command byte
and 0x0F ; keep lower nibble
ld b,a ; save value
ld a,(PSGMusicVolumeAttenuation) ; load volume attenuation
add a,b ; add value
cp 0x0F ; check overflow
jr c,_no_overflow ; if it is <=15 then ok
ld a,0x0F ; else, reset to 15
_no_overflow:
ld b,a ; save new attenuated volume value
ld a,c ; retrieve PSG command
and 0xF0 ; keep upper nibble
or b ; set attenuated volume
out (PSGDataPort),a ; output the byte
jp _intLoop
_output_NoLatch:
; we got the last latch in A and the PSG data in B
; and we have to check if the value should pass to PSG or not
; note that non-latch commands can be only contain frequencies (no volumes)
; for channels 0,1,2 only (no noise)
bit 6,a ; test if the latch it is for channels 0-1 or for chn 2
jr nz,_high_part_Tone_ch2 ; it is tone data for channel 2
bit 5,a ; test if latch is for channel 0 or 1
jr nz,_high_part_Tone_ch1 ; it is tone data for channel 1
; we got the last latch in A and the PSG data in B
; and we have to check if the value should pass to PSG or not
ld a,b ; move PSG data in A
ld (PSGChan0HighTone),a ; save channel 0 tone HIGH data
ld a,(PSGChannel0SFX) ; channel 0 available for music?
or a
jr z,_send2PSG_B
jp _intLoop
_high_part_Tone_ch1:
; we got the last latch in A and the PSG data in B
; and we have to check if the value should pass to PSG or not
ld a,b ; move PSG data in A
ld (PSGChan1HighTone),a ; save channel 1 tone HIGH data
ld a,(PSGChannel1SFX) ; channel 1 available for music?
or a
jr z,_send2PSG_B
jp _intLoop
_high_part_Tone_ch2:
; we got the last latch in A and the PSG data in B
; and we have to check if the value should pass to PSG or not
; PSG data can only be for channel 2, here
ld a,b ; move PSG data in A
ld (PSGChan2HighTone),a ; save channel 2 tone HIGH data
ld a,(PSGChannel2SFX) ; channel 2 available for music
or a
jp z,_send2PSG_B ; TOO FAR for JR here :|
jp _intLoop
_setLoopPoint:
ld (PSGMusicLoopPoint),hl
jp _intLoop
_musicLoop:
ld hl,(PSGMusicLoopPoint)
ld a,(PSGLoopFlag) ; infinite loop requested?
or a
jp nz,_intLoop ; Yes: do loop
ld a,(PSGLoopCounter) ; one more loop requested?
or a
jp z,PSGStop ; No: stop the music! (tail call optimization)
dec a
ld (PSGLoopCounter),a ; decrement loop counter
jp _intLoop ; do loop
_substring:
sub PSGSubString-4 ; len is value - 0x08 + 4
ld (PSGMusicSubstringLen),a ; save len
ld c,(hl) ; load substring address (offset)
inc hl
ld b,(hl)
inc hl
ld (PSGMusicSubstringRetAddr),hl ; save return address
ld hl,(PSGMusicStart)
add hl,bc ; make substring current
jp _intLoop
; ************************************************************************************
; processes a SFX frame
; destroys AF,HL,BC
PSGSFXFrame:
ld a,(PSGSFXStatus) ; check if we have got to play SFX
or a
ret z
ld a,(PSGSFXSkipFrames) ; check if we have got to skip frames
or a
jp nz,_skipSFXFrame
ld hl,(PSGSFXPointer) ; read current SFX address
_intSFXLoop:
ld b,(hl) ; load a byte in B, temporary
inc hl ; point to next byte
ld a,(PSGSFXSubstringLen) ; read substring len
or a ; check if it is 0 (we are not in a substring)
jr z,_SFXcontinue
dec a ; decrease len
ld (PSGSFXSubstringLen),a ; save len
jr nz,_SFXcontinue
ld hl,(PSGSFXSubstringRetAddr) ; substring over, retrieve return address
_SFXcontinue:
ld a,b ; restore byte
cp PSGData
jp c,_SFXcommand ; if less than 0x40 then it is a command
bit 7,a ; check if it is a latch
jr z,_SFXoutbyte ; if not, output it directly
bit 4,a ; check if it is a volume byte
jr z,_SFXoutbyte ; if not, output it directly
bit 6,a ; check if it is volume for channels 0-1 or for 2-3
jr nz,_SFXchn23
bit 5,a ; check if it is volume for channel 0 or channel 1
jr nz,_SFXvolumechn1
ld (PSGSFXChan0Volume),a
jr _SFXoutvolume
_SFXvolumechn1:
ld (PSGSFXChan1Volume),a
jr _SFXoutvolume
_SFXchn23:
bit 5,a ; check if it is volume for channel 2 or channel 3
jr nz,_SFXvolumechn3
ld (PSGSFXChan2Volume),a
jr _SFXoutvolume
_SFXvolumechn3:
ld (PSGSFXChan3Volume),a
_SFXoutvolume:
and 0x0F ; keep lower nibble
ld c,a ; save value
ld a,(PSGSFXVolumeAttenuation) ; load volume attenuation
add a,c ; add value
cp 0x0F ; check overflow
jr c,_no_overflow_SFX ; if it is <=15 then ok
ld a,0x0F ; else, reset to 15
_no_overflow_SFX:
ld c,a ; save new attenuated volume value
ld a,b ; retrieve original PSG command
and 0xF0 ; keep upper nibble (channel+volume bits)
or c ; set attenuated SFX volume
_SFXoutbyte:
out (PSGDataPort),a ; output the byte
jp _intSFXLoop
_skipSFXFrame:
dec a
ld (PSGSFXSkipFrames),a
ret
_SFXcommand:
cp PSGWait
jr z,_SFXdone ; no additional frames
jr c,_SFXotherCommands ; other commands?
and 0x07 ; take only the last 3 bits for skip frames
ld (PSGSFXSkipFrames),a ; we got additional frames to skip
_SFXdone:
ld (PSGSFXPointer),hl ; save current address
ret ; frame done
_SFXotherCommands:
cp PSGSubString
jr nc,_SFXsubstring
cp PSGEnd
jr z,_sfxLoop
cp PSGLoop
jr z,_SFXsetLoopPoint
; ***************************************************************************
; we should never get here!
; if we do, it means the PSG SFX file is probably corrupted, so we just RET
; ***************************************************************************
ret
_SFXsetLoopPoint:
ld (PSGSFXLoopPoint),hl
jp _intSFXLoop
_sfxLoop:
ld a,(PSGSFXLoopFlag) ; is it a looping SFX?
or a
jp z,PSGSFXStop ; No:stop it! (tail call optimization)
ld hl,(PSGSFXLoopPoint)
ld (PSGSFXPointer),hl
jp _intSFXLoop
_SFXsubstring:
sub PSGSubString-4 ; len is value - 0x08 + 4
ld (PSGSFXSubstringLen),a ; save len
ld c,(hl) ; load substring address (offset)
inc hl
ld b,(hl)
inc hl
ld (PSGSFXSubstringRetAddr),hl ; save return address
ld hl,(PSGSFXStart)
add hl,bc ; make substring current
jp _intSFXLoop
; PSGlib workspace in the RAM reserved by the SN76489 application.
PSGLIB_RAM_START .equ STACK_TOP+1
; fundamental vars
PSGMusicStatus .equ PSGLIB_RAM_START+0x00 ; are we playing a background music?
PSGMusicStart .equ PSGLIB_RAM_START+0x01 ; the pointer to the beginning of music
PSGMusicPointer .equ PSGLIB_RAM_START+0x03 ; the pointer to the current
PSGMusicLoopPoint .equ PSGLIB_RAM_START+0x05 ; the pointer to the loop begin
PSGMusicSkipFrames .equ PSGLIB_RAM_START+0x07 ; the frames we need to skip
PSGLoopFlag .equ PSGLIB_RAM_START+0x08 ; the tune should loop forever or not (flag)
PSGLoopCounter .equ PSGLIB_RAM_START+0x09 ; how many times the tune should loop
PSGMusicLastLatch .equ PSGLIB_RAM_START+0x0a ; the last PSG music latch
; decompression vars
PSGMusicSubstringLen .equ PSGLIB_RAM_START+0x0b ; lenght of the substring we are playing
PSGMusicSubstringRetAddr .equ PSGLIB_RAM_START+0x0c ; return to this address when substring is over
; command buffers
PSGChan0Volume .equ PSGLIB_RAM_START+0x0e ; the volume for channel 0
PSGChan1Volume .equ PSGLIB_RAM_START+0x0f ; the volume for channel 1
PSGChan2Volume .equ PSGLIB_RAM_START+0x10 ; the volume for channel 2
PSGChan3Volume .equ PSGLIB_RAM_START+0x11 ; the volume for channel 3
PSGChan0LowTone .equ PSGLIB_RAM_START+0x12 ; the low tone bits for channels 0
PSGChan1LowTone .equ PSGLIB_RAM_START+0x13 ; the low tone bits for channels 1
PSGChan2LowTone .equ PSGLIB_RAM_START+0x14 ; the low tone bits for channels 2
PSGChan3LowTone .equ PSGLIB_RAM_START+0x15 ; the low tone bits for channels 3
PSGChan0HighTone .equ PSGLIB_RAM_START+0x16 ; the high tone bits for channel 0
PSGChan1HighTone .equ PSGLIB_RAM_START+0x17 ; the high tone bits for channel 1
PSGChan2HighTone .equ PSGLIB_RAM_START+0x18 ; the high tone bits for channel 2
PSGMusicVolumeAttenuation .equ PSGLIB_RAM_START+0x19 ; the volume attenuation applied to the tune (0-15)
; ******* SFX *************
; flags for SFX channels usage
PSGChannel0SFX .equ PSGLIB_RAM_START+0x1a ; !0 means channel 0 is allocated to SFX
PSGChannel1SFX .equ PSGLIB_RAM_START+0x1b ; !0 means channel 1 is allocated to SFX
PSGChannel2SFX .equ PSGLIB_RAM_START+0x1c ; !0 means channel 2 is allocated to SFX
PSGChannel3SFX .equ PSGLIB_RAM_START+0x1d ; !0 means channel 3 is allocated to SFX
; command buffers for SFX
PSGSFXChan0Volume .equ PSGLIB_RAM_START+0x1e ; the volume for channel 0
PSGSFXChan1Volume .equ PSGLIB_RAM_START+0x1f ; the volume for channel 1
PSGSFXChan2Volume .equ PSGLIB_RAM_START+0x20 ; the volume for channel 2
PSGSFXChan3Volume .equ PSGLIB_RAM_START+0x21 ; the volume for channel 3
PSGSFXVolumeAttenuation .equ PSGLIB_RAM_START+0x22 ; the volume attenuation applied to the SFX (0-15)
; fundamental vars for SFX
PSGSFXStatus .equ PSGLIB_RAM_START+0x23 ; are we playing a SFX?
PSGSFXStart .equ PSGLIB_RAM_START+0x24 ; the pointer to the beginning of SFX
PSGSFXPointer .equ PSGLIB_RAM_START+0x26 ; the pointer to the current address
PSGSFXLoopPoint .equ PSGLIB_RAM_START+0x28 ; the pointer to the loop begin
PSGSFXSkipFrames .equ PSGLIB_RAM_START+0x2a ; the frames we need to skip
PSGSFXLoopFlag .equ PSGLIB_RAM_START+0x2b ; the SFX should loop or not (flag)
; decompression vars for SFX
PSGSFXSubstringLen .equ PSGLIB_RAM_START+0x2c ; lenght of the substring we are playing
PSGSFXSubstringRetAddr .equ PSGLIB_RAM_START+0x2d ; return to this address when substring is over
PSGLIB_RAM_SIZE .equ 0x2f
#assert PSGLIB_RAM_START+PSGLIB_RAM_SIZE <= SAVED_SP