Skip to content

Instantly share code, notes, and snippets.

@suzukiplan
Last active March 12, 2024 10:15
Show Gist options
  • Save suzukiplan/272930bf40b6e476f743d431ce78af45 to your computer and use it in GitHub Desktop.
Save suzukiplan/272930bf40b6e476f743d431ce78af45 to your computer and use it in GitHub Desktop.
PSGlib.inc for z88dk-z80asm
; PSGlib.inc を z88dk-z80asm 向けに変更したものです
; copied from https://github.com/sverx/PSGlib/blob/c21bb78d3e02c47fac9f669c9d33b4d668799170/src/PSGlib.inc
;================================================================
; PSGlib - Programmable Sound Generator audio library - by sverx
; https://github.com/sverx/PSGlib
;================================================================
; NOTE: this uses a WLA-DX 'ramsection' at slot 3
; If you want to change or remove that,
; see the note at the end of this file
#define PSG_STOPPED 0
#define PSG_PLAYING 1
#define PSGDataPort $7f
#define PSGLatch $80
#define PSGData $40
#define PSGChannel0 %00000000
#define PSGChannel1 %00100000
#define PSGChannel2 %01000000
#define PSGChannel3 %01100000
#define PSGVolumeData %00010000
#define PSGWait $38
#define PSGSubString $08
#define PSGLoop $01
#define PSGEnd $00
#define SFX_CHANNEL2 $01
#define SFX_CHANNEL3 $02
#define SFX_CHANNELS2AND3 SFX_CHANNEL2|SFX_CHANNEL3
;.section "PSGInit" free
; ************************************************************************************
; 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 (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
;.ends
;.section "PSGPlay and PSGPlayNoRepeat and PSGPlayLoops" free
; ************************************************************************************
; 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 PSGPlay_1
PSGPlay:
ld a,$1 ; we want the song to loop forever
PSGPlay_1:
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|$0F ; latch channel 0, volume=0xF (silent)
ld (PSGMusicLastLatch),a ; reset last latch to chn 0 volume 0
ld a,PSG_PLAYING
ld (PSGMusicStatus),a ; set status to PSG_PLAYING
ret
;.ends
;.section "PSGStop" free
; ************************************************************************************
; 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,PSGLatch|PSGChannel0|PSGVolumeData|$0F ; latch channel 0, volume=0xF (silent)
out (PSGDataPort),a
ld a,PSGLatch|PSGChannel1|PSGVolumeData|$0F ; latch channel 1, volume=0xF (silent)
out (PSGDataPort),a
ld a,(PSGChannel2SFX)
or a
jr nz,PSGStop_1
ld a,PSGLatch|PSGChannel2|PSGVolumeData|$0F ; latch channel 2, volume=0xF (silent)
out (PSGDataPort),a
PSGStop_1:
ld a,(PSGChannel3SFX)
or a
jr nz,PSGStop_2
ld a,PSGLatch|PSGChannel3|PSGVolumeData|$0F ; latch channel 3, volume=0xF (silent)
out (PSGDataPort),a
PSGStop_2:
xor a ; ld a,PSG_STOPPED
ld (PSGMusicStatus),a ; set status to PSG_STOPPED
ret
;.ends
;.section "PSGResume" free
; ************************************************************************************
; resume a previously stopped music
; destroys AF
PSGResume:
ld a,(PSGMusicStatus) ; if it's already playing, leave
or a
ret nz
ld a,(PSGChan0Volume) ; restore channel 0 volume
or PSGLatch|PSGChannel0|PSGVolumeData
out (PSGDataPort),a
ld a,(PSGChan1Volume) ; restore channel 1 volume
or PSGLatch|PSGChannel1|PSGVolumeData
out (PSGDataPort),a
ld a,(PSGChannel2SFX)
or a
jr nz,PSGResume_1
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
or PSGLatch|PSGChannel2|PSGVolumeData
out (PSGDataPort),a
PSGResume_1:
ld a,(PSGChannel3SFX)
or a
jr nz,PSGResume_2
ld a,(PSGChan3LowTone) ; restore channel 3 frequency
or PSGLatch|PSGChannel3
out (PSGDataPort),a
ld a,(PSGChan3Volume) ; restore channel 3 volume
or PSGLatch|PSGChannel3|PSGVolumeData
out (PSGDataPort),a
PSGResume_2:
ld a,PSG_PLAYING
ld (PSGMusicStatus),a ; set status to PSG_PLAYING
ret
;.ends
;.section "PSGCancelLoop" free
; ************************************************************************************
; sets the currently looping music to no more loops after the current
; destroys AF
PSGCancelLoop:
xor a
ld (PSGLoopFlag),a
ld (PSGLoopCounter),a
ret
;.ends
;.section "PSGGetStatus" free
; ************************************************************************************
; gets the current status of music into register A
PSGGetStatus:
ld a,(PSGMusicStatus)
ret
;.ends
;.section "PSGSetMusicVolumeAttenuation" free
; ************************************************************************************
; 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,(PSGChan0Volume)
and $0F
add a,l
cp $0F ; check overflow
jr c,PSGSetMusicVolumeAttenuation_1 ; if it's <=15 then ok
ld a,$0F ; else, reset to 15
PSGSetMusicVolumeAttenuation_1:
or PSGLatch|PSGChannel0|PSGVolumeData
out (PSGDataPort),a ; output the byte
ld a,(PSGChan1Volume)
and $0F
add a,l
cp $0F ; check overflow
jr c,PSGSetMusicVolumeAttenuation_2 ; if it's <=15 then ok
ld a,$0F ; else, reset to 15
PSGSetMusicVolumeAttenuation_2:
or PSGLatch|PSGChannel1|PSGVolumeData
out (PSGDataPort),a ; output the byte
ld a,(PSGChannel2SFX) ; channel 2 busy with SFX?
or a
jr nz,_restore_channel3 ; if so, skip channel 2
ld a,(PSGChan2Volume)
and $0F
add a,l
cp $0F ; check overflow
jr c,PSGSetMusicVolumeAttenuation_3 ; if it's <=15 then ok
ld a,$0F ; else, reset to 15
PSGSetMusicVolumeAttenuation_3:
or PSGLatch|PSGChannel2|PSGVolumeData
out (PSGDataPort),a ; output the byte
_restore_channel3:
ld a,(PSGChannel3SFX) ; channel 3 busy with SFX?
or a
ret nz ; if so, we're done
ld a,(PSGChan3Volume)
and $0F
add a,l
cp $0F ; check overflow
jr c,PSGSetMusicVolumeAttenuation_4 ; if it's <=15 then ok
ld a,$0F ; else, reset to 15
PSGSetMusicVolumeAttenuation_4:
or PSGLatch|PSGChannel3|PSGVolumeData
out (PSGDataPort),a ; output the byte
ret
;.ends
;.section "PSGSilenceChannels" free
; ************************************************************************************
; sets all PSG channels to volume ZERO (useful if you need to pause music)
; destroys AF
PSGSilenceChannels:
ld a,PSGLatch|PSGChannel0|PSGVolumeData|$0F ; latch channel 0, volume=0xF (silent)
out (PSGDataPort),a
ld a,PSGLatch|PSGChannel1|PSGVolumeData|$0F ; latch channel 1, volume=0xF (silent)
out (PSGDataPort),a
ld a,PSGLatch|PSGChannel2|PSGVolumeData|$0F ; latch channel 2, volume=0xF (silent)
out (PSGDataPort),a
ld a,PSGLatch|PSGChannel3|PSGVolumeData|$0F ; latch channel 3, volume=0xF (silent)
out (PSGDataPort),a
ret
;.ends
;.section "PSGRestoreVolumes" free
; ************************************************************************************
; resets all PSG channels to previous volume
; destroys AF,HL
PSGRestoreVolumes:
ld a,(PSGMusicStatus) ; check if tune is playing
or a
jr z,_chkchn2 ; if not, skip chn0 and chn1
ld hl,PSGMusicVolumeAttenuation
ld a,(PSGChan0Volume)
and $0F
add a,(hl)
cp $0F ; check overflow
jr c,PSGRestoreVolumes_1 ; if it's <=15 then ok
ld a,$0F ; else, reset to 15
PSGRestoreVolumes_1:
or PSGLatch|PSGChannel0|PSGVolumeData
out (PSGDataPort),a ; output the byte
ld a,(PSGChan1Volume)
and $0F
add a,(hl)
cp $0F ; check overflow
jr c,PSGRestoreVolumes_2 ; if it's <=15 then ok
ld a,$0F ; else, reset to 15
PSGRestoreVolumes_2:
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 $0F
add a,(hl)
cp $0F ; check overflow
jr c,PSGRestoreVolumes_3 ; if it's <=15 then ok
ld a,$0F ; else, reset to 15
jr c,PSGRestoreVolumes_3
_restoreSFX2:
ld a,(PSGSFXChan2Volume)
and $0F
PSGRestoreVolumes_3:
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 $0F
add a,(hl)
cp $0F ; check overflow
jr c,PSGRestoreVolumes_4 ; if it's <=15 then ok
ld a,$0F ; else, reset to 15
jr c,PSGRestoreVolumes_4
_restoreSFX3:
ld a,(PSGSFXChan3Volume)
and $0F
PSGRestoreVolumes_4:
or PSGLatch|PSGChannel3|PSGVolumeData
out (PSGDataPort),a ; output the byte
ret
;.ends
;.section "PSGSFXPlay and PSGSFXPlayLoop" free
; ************************************************************************************
; 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 PSGSFXPlayLoop_1
PSGSFXPlay:
xor a ; SFX is _NOT_ a looping one
PSGSFXPlayLoop_1:
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 0,c ; channel 2 needed?
jr z,PSGSFXPlayLoop_2
ld a,PSG_PLAYING
ld (PSGChannel2SFX),a
PSGSFXPlayLoop_2:
bit 1,c ; channel 3 needed?
jr z,PSGSFXPlayLoop_3
ld a,PSG_PLAYING
ld (PSGChannel3SFX),a
PSGSFXPlayLoop_3:
ld (PSGSFXStatus),a ; set status to PSG_PLAYING
ld a,(PSGChan3LowTone) ; test if 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|$0F ; and silence channel 3
out (PSGDataPort),a
ret
;.ends
;.section "PSGSFXStop" free
; ************************************************************************************
; 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,(PSGChannel2SFX) ; channel 2 playing?
or a
jr z,PSGSFXStop_1
ld a,PSGLatch|PSGChannel2|PSGVolumeData|$0F ; latch channel 2, volume=0xF (silent)
out (PSGDataPort),a
PSGSFXStop_1:
ld a,(PSGChannel3SFX) ; channel 3 playing?
or a
jr z,PSGSFXStop_2
ld a,PSGLatch|PSGChannel3|PSGVolumeData|$0F ; latch channel 3, volume=0xf (silent)
out (PSGDataPort),a
PSGSFXStop_2:
ld a,(PSGMusicStatus) ; check if a tune is playing
or a
jr z,_skipRestore ; if it's not playing, skip restoring PSG values
ld a,(PSGChannel2SFX) ; channel 2 playing?
or a
jr z,_skip_chn2
ld a,(PSGChan2LowTone)
and $0F ; 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 $3F ; use only low 6 bits of byte
out (PSGDataPort),a
ld a,(PSGChan2Volume) ; restore music' channel 2 volume
and $0F ; use only low 4 bits of byte
push bc
ld b,a ; save it temporary
ld a,(PSGMusicVolumeAttenuation) ;
add a,b
cp $0F ; check overflow
jr c,PSGSFXStop_3 ; if it's <=15 then ok
ld a,$0F ; else, reset to 15
PSGSFXStop_3:
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 $07 ; 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 $0F ; use only low 4 bits of byte
push bc
ld b,a ; save it temporary
ld a,(PSGMusicVolumeAttenuation) ;
add a,b
cp $0F ; check overflow
jr c,PSGSFXStop_4 ; if it's <=15 then ok
ld a,$0F ; else, reset to 15
PSGSFXStop_4:
or PSGLatch|PSGChannel3|PSGVolumeData
out (PSGDataPort),a ; output the byte
pop bc
_skip_chn3:
xor a ; ld a,PSG_STOPPED
_skipRestore:
ld (PSGChannel2SFX),a
ld (PSGChannel3SFX),a
ld (PSGSFXStatus),a ; set status to PSG_STOPPED
ret
;.ends
;.section "PSGSFXCancelLoop" free
; ************************************************************************************
; sets the currently looping SFX to no more loops after the current
; destroys AF
PSGSFXCancelLoop:
xor a
ld (PSGSFXLoopFlag),a
ret
;.ends
;.section "PSGSFXGetStatus" free
; ************************************************************************************
; gets the current SFX status into register A
PSGSFXGetStatus:
ld a,(PSGSFXStatus)
ret
;.ends
;.section "PSGFrame" free
; ************************************************************************************
; 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 < $80 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 z,_send2PSG_A ; send data to PSG if it is for channels 0-1
; 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 free?
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,(PSGSFXStatus) ; test if an SFX is playing
or a
jr z,_send2PSG_B ; if no SFX is playing jump
ld (PSGChannel3SFX),a ; otherwise mark channel 3 as occupied
ld a,PSGLatch|PSGChannel3|PSGVolumeData|$0F ; and silence channel 3
out (PSGDataPort),a
jp _intLoop
_ifchn2:
ld (PSGChan2LowTone),a ; save tone LOW data
ld a,(PSGChannel2SFX) ; channel 2 free?
or a
jr z,_send2PSG_B
jp _intLoop
_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,_ifchn0 ; jump for channel 0
ld (PSGChan1Volume),a ; save volume data
jp _sendVolume2PSG_A
_ifchn0:
ld (PSGChan0Volume),a ; save volume data
jp _sendVolume2PSG_A
_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 free?
or a
jr z,_sendVolume2PSG_B
jp _intLoop
_chn2:
ld (PSGChan2Volume),a ; save volume data
ld a,(PSGChannel2SFX) ; channel 2 free?
or a
jr z,_sendVolume2PSG_B
jp _intLoop
_skipFrame:
dec a
ld (PSGMusicSkipFrames),a
ret
_noLatch:
cp PSGData
jr c,_command ; if < $40 then it is a command
; it's a data
ld a,(PSGMusicLastLatch) ; retrieve last latch
jp _output_NoLatch
_command:
cp PSGWait
jr z,_done ; no additional frames
jr c,_otherCommands ; other commands?
and $07 ; 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
_send2PSG_B:
ld a,b
_send2PSG_A:
out (PSGDataPort),a ; output the byte
jp _intLoop
_sendVolume2PSG_B:
ld a,b
_sendVolume2PSG_A:
ld c,a ; save the PSG command byte
and $0F ; keep lower nibble
ld b,a ; save value
ld a,(PSGMusicVolumeAttenuation) ; load volume attenuation
add a,b ; add value
cp $0F ; check overflow
jr c,_no_overflow ; if it is <=15 then ok
ld a,$0F ; else, reset to 15
_no_overflow:
ld b,a ; save new attenuated volume value
ld a,c ; retrieve PSG command
and $F0 ; 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 ; it is tone data for channel 2
jp _send2PSG_B ; otherwise, it is for chn 0 or 1 so we have done!
_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 - $08 + 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
_high_part_Tone:
; 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 free?
or a
jr z,_send2PSG_B
jp _intLoop
;.ends
;.section "PSGSFXFrame" free
; ************************************************************************************
; 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 $40 then it is a command
bit 4,a ; check if it is a volume byte
jr z,_SFXoutbyte ; if not, output it
bit 5,a ; check if it is volume for channel 2 or channel 3
jr nz,_SFXvolumechn3
ld (PSGSFXChan2Volume),a
jr _SFXoutbyte
_SFXvolumechn3:
ld (PSGSFXChan3Volume),a
_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 $07 ; 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 - $08 + 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
;.ends
; NOTE: if you don't want to use a ramsection,
; comment the ".ramsection" line and
; uncomment the next ".enum" one,
; setting .enum start RAM address
; according to your needs.
; Also change ";.ends" into ".ende" at end of this file
;.ramsection "PSGlib variables" slot 3
;.enum $c000 export ; PSGlib variables location in RAM
defvars $c000
{
; fundamental vars
PSGMusicStatus ds.b 1 ; are we playing a background music?
PSGMusicStart ds.w 1 ; the pointer to the beginning of music
PSGMusicPointer ds.w 1 ; the pointer to the current
PSGMusicLoopPoint ds.w 1 ; the pointer to the loop begin
PSGMusicSkipFrames ds.b 1 ; the frames we need to skip
PSGLoopFlag ds.b 1 ; the tune should loop forever or not (flag)
PSGLoopCounter ds.b 1 ; how many times the tune should loop
PSGMusicLastLatch ds.b 1 ; the last PSG music latch
; decompression vars
PSGMusicSubstringLen ds.b 1 ; lenght of the substring we are playing
PSGMusicSubstringRetAddr ds.w 1 ; return to this address when substring is over
; command buffers
PSGChan0Volume ds.b 1 ; the volume for channel 0
PSGChan1Volume ds.b 1 ; the volume for channel 1
PSGChan2Volume ds.b 1 ; the volume for channel 2
PSGChan3Volume ds.b 1 ; the volume for channel 3
PSGChan2LowTone ds.b 1 ; the low tone bits for channels 2
PSGChan3LowTone ds.b 1 ; the low tone bits for channels 3
PSGChan2HighTone ds.b 1 ; the high tone bits for channel 2
PSGMusicVolumeAttenuation ds.b 1 ; the volume attenuation applied to the tune (0-15)
; ******* SFX *************
; flags for channels 2-3 access
PSGChannel2SFX ds.b 1 ; !0 means channel 2 is allocated to SFX
PSGChannel3SFX ds.b 1 ; !0 means channel 3 is allocated to SFX
; command buffers for SFX
PSGSFXChan2Volume ds.b 1 ; the volume for channel 2
PSGSFXChan3Volume ds.b 1 ; the volume for channel 3
; fundamental vars for SFX
PSGSFXStatus ds.b 1 ; are we playing a SFX?
PSGSFXStart ds.w 1 ; the pointer to the beginning of SFX
PSGSFXPointer ds.w 1 ; the pointer to the current address
PSGSFXLoopPoint ds.w 1 ; the pointer to the loop begin
PSGSFXSkipFrames ds.b 1 ; the frames we need to skip
PSGSFXLoopFlag ds.b 1 ; the SFX should loop or not (flag)
; decompression vars for SFX
PSGSFXSubstringLen ds.b 1 ; lenght of the substring we are playing
PSGSFXSubstringRetAddr ds.w 1 ; return to this address when substring is over
}
;.ende ; in case you want to use .enum instead of .ramsection
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment