;================================================================ ; 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 $19 .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 .define SFX_CHANNEL0 $04 .define SFX_CHANNEL1 $08 .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 (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 .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: ld a,$1 ; we want the song to loop forever +: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,$0F ; 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 .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,(PSGChannel0SFX) or a jr nz,+ ld a,PSGLatch|PSGChannel0|PSGVolumeData|$0F ; latch channel 0, volume=0xF (silent) out (PSGDataPort),a +:ld a,(PSGChannel1SFX) or a jr nz,+ ld a,PSGLatch|PSGChannel1|PSGVolumeData|$0F ; latch channel 1, volume=0xF (silent) out (PSGDataPort),a +:ld a,(PSGChannel2SFX) or a jr nz,+ ld a,PSGLatch|PSGChannel2|PSGVolumeData|$0F ; latch channel 2, volume=0xF (silent) out (PSGDataPort),a +:ld a,(PSGChannel3SFX) or a jr nz,+ ld a,PSGLatch|PSGChannel3|PSGVolumeData|$0F ; latch channel 3, volume=0xF (silent) out (PSGDataPort),a +:xor a ; ld a,PSG_STOPPED ld (PSGMusicStatus),a ; set status to PSG_STOPPED ret .ends .section "PSGResume" free ; ************************************************************************************ ; 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 $0F add a,l cp $0F ; check overflow jr c,+ ; if it's <=15 then ok ld a,$0F ; else, reset to 15 +: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 $0F add a,l cp $0F ; check overflow jr c,+ ; if it's <=15 then ok ld a,$0F ; else, reset to 15 +: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 $0F add a,l cp $0F ; check overflow jr c,+ ; if it's <=15 then ok ld a,$0F ; else, reset to 15 +: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 $0F add a,l cp $0F ; check overflow jr c,+ ; if it's <=15 then ok ld a,$0F ; else, reset to 15 +:or PSGLatch|PSGChannel3|PSGVolumeData out (PSGDataPort),a _leave: 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,(PSGChannel0SFX) ; channel 0 busy with SFX? or a jr nz,_restore_channel1 ; if so, skip channel 0 ld a,(PSGChan0Volume) and $0F add a,l cp $0F ; check overflow jr c,+ ; if it's <=15 then ok ld a,$0F ; else, reset to 15 +:or PSGLatch|PSGChannel0|PSGVolumeData out (PSGDataPort),a ; output the byte _restore_channel1: ld a,(PSGChannel1SFX) ; channel 1 busy with SFX? or a jr nz,_restore_channel2 ; if so, skip channel 1 ld a,(PSGChan1Volume) and $0F add a,l cp $0F ; check overflow jr c,+ ; if it's <=15 then ok ld a,$0F ; else, reset to 15 +:or PSGLatch|PSGChannel1|PSGVolumeData out (PSGDataPort),a ; output the byte _restore_channel2: 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,+ ; if it's <=15 then ok ld a,$0F ; else, reset to 15 +: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,+ ; if it's <=15 then ok ld a,$0F ; else, reset to 15 +:or PSGLatch|PSGChannel3|PSGVolumeData out (PSGDataPort),a ; output the byte ret .ends .section "PSGSetSFXVolumeAttenuation" free ; ************************************************************************************ ; 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,_restore_channel1 ; if not, skip channel 0 ld a,(PSGSFXChan0Volume) and $0F add a,l cp $0F ; check overflow jr c,+ ; if it's <=15 then ok ld a,$0F ; else, reset to 15 +:or PSGLatch|PSGChannel0|PSGVolumeData out (PSGDataPort),a ; output the byte _restore_channel1: ld a,(PSGChannel1SFX) ; channel 1 used by SFX? or a jr z,_restore_channel2 ; if not, skip channel 1 ld a,(PSGSFXChan1Volume) and $0F add a,l cp $0F ; check overflow jr c,+ ; if it's <=15 then ok ld a,$0F ; else, reset to 15 +:or PSGLatch|PSGChannel1|PSGVolumeData out (PSGDataPort),a ; output the byte _restore_channel2: ld a,(PSGChannel2SFX) ; channel 2 used by SFX? or a jr z,_restore_channel3 ; if not, skip channel 2 ld a,(PSGSFXChan2Volume) and $0F add a,l cp $0F ; check overflow jr c,+ ; if it's <=15 then ok ld a,$0F ; else, reset to 15 +:or PSGLatch|PSGChannel2|PSGVolumeData out (PSGDataPort),a ; output the byte _restore_channel3: ld a,(PSGChannel3SFX) ; channel 3 used by SFX? or a ret z ; if not, we're done ld a,(PSGSFXChan3Volume) and $0F add a,l cp $0F ; check overflow jr c,+ ; if it's <=15 then ok ld a,$0F ; else, reset to 15 +:or PSGLatch|PSGChannel3|PSGVolumeData out (PSGDataPort),a ; output the byte ret .ends .section "PSGSilenceChannels" free ; ************************************************************************************ ; sets all PSG channels to volume ZERO ; 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,(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 $0F ld hl,PSGMusicVolumeAttenuation add a,(hl) cp $0F ; check overflow jr c,_write0 ; if it's <=15 then ok ld a,$0F ; else, reset to 15 jp _write0 _restoreSFX0: ld a,(PSGSFXChan0Volume) and $0F ld hl,PSGSFXVolumeAttenuation add a,(hl) cp $0F ; check overflow jr c,_write0 ; if it's <=15 then ok ld a,$0F ; 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 $0F ld hl,PSGMusicVolumeAttenuation add a,(hl) cp $0F ; check overflow jr c,_write1 ; if it's <=15 then ok ld a,$0F ; else, reset to 15 jp _write1 _restoreSFX1: ld a,(PSGSFXChan1Volume) and $0F ld hl,PSGSFXVolumeAttenuation add a,(hl) cp $0F ; check overflow jr c,_write1 ; if it's <=15 then ok ld a,$0F ; 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 $0F ld hl,PSGMusicVolumeAttenuation add a,(hl) cp $0F ; check overflow jr c,_write2 ; if it's <=15 then ok ld a,$0F ; else, reset to 15 jp _write2 _restoreSFX2: ld a,(PSGSFXChan2Volume) and $0F ld hl,PSGSFXVolumeAttenuation add a,(hl) cp $0F ; check overflow jr c,_write2 ; if it's <=15 then ok ld a,$0F ; 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 $0F ld hl,PSGMusicVolumeAttenuation add a,(hl) cp $0F ; check overflow jr c,_write3 ; if it's <=15 then ok ld a,$0F ; else, reset to 15 jp _write3 _restoreSFX3: ld a,(PSGSFXChan3Volume) and $0F ld hl,PSGSFXVolumeAttenuation add a,(hl) cp $0F ; check overflow jr c,_write3 ; if it's <=15 then ok ld a,$0F ; else, reset to 15 _write3: 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 + PSGSFXPlay: xor a ; SFX is _NOT_ a looping one +: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,+ ld a,PSG_PLAYING ld (PSGChannel0SFX),a +:bit 3,c ; channel 1 needed? jr z,+ ld a,PSG_PLAYING ld (PSGChannel1SFX),a +:bit 0,c ; channel 2 needed? jr z,+ ld a,PSG_PLAYING ld (PSGChannel2SFX),a +:bit 1,c ; channel 3 needed? jr z,+ ld a,PSG_PLAYING ld (PSGChannel3SFX),a +: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|$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,(PSGChannel0SFX) ; channel 0 playing? or a jr z,+ ld a,PSGLatch|PSGChannel0|PSGVolumeData|$0F ; latch channel 0, volume=0xF (silent) out (PSGDataPort),a +:ld a,(PSGChannel1SFX) ; channel 1 playing? or a jr z,+ ld a,PSGLatch|PSGChannel1|PSGVolumeData|$0F ; latch channel 1, volume=0xF (silent) out (PSGDataPort),a +:ld a,(PSGChannel2SFX) ; channel 2 playing? or a jr z,+ ld a,PSGLatch|PSGChannel2|PSGVolumeData|$0F ; latch channel 2, volume=0xF (silent) out (PSGDataPort),a +:ld a,(PSGChannel3SFX) ; channel 3 playing? or a jr z,+ ld a,PSGLatch|PSGChannel3|PSGVolumeData|$0F ; latch channel 3, volume=0xf (silent) out (PSGDataPort),a +: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 $0F ; 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 $3F ; use only low 6 bits of byte out (PSGDataPort),a ld a,(PSGChan0Volume) ; restore music' channel 0 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,+ ; if it's <=15 then ok ld a,$0F ; else, reset to 15 +: 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 $0F ; 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 $3F ; use only low 6 bits of byte out (PSGDataPort),a ld a,(PSGChan1Volume) ; restore music' channel 1 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,+ ; if it's <=15 then ok ld a,$0F ; else, reset to 15 +: 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 $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,+ ; if it's <=15 then ok ld a,$0F ; else, reset to 15 +: 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,+ ; if it's <=15 then ok ld a,$0F ; else, reset to 15 +: 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 .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 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|$0F ; 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 < $40 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 $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 _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_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 - $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 .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 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 $0F ; keep lower nibble ld c,a ; save value ld a,(PSGSFXVolumeAttenuation) ; load volume attenuation add a,c ; add value cp $0F ; check overflow jr c,_no_overflow_SFX ; if it is <=15 then ok ld a,$0F ; else, reset to 15 _no_overflow_SFX: ld c,a ; save new attenuated volume value ld a,b ; retrieve original PSG command and $F0 ; 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 $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 ; fundamental vars PSGMusicStatus db ; are we playing a background music? PSGMusicStart dw ; the pointer to the beginning of music PSGMusicPointer dw ; the pointer to the current PSGMusicLoopPoint dw ; the pointer to the loop begin PSGMusicSkipFrames db ; the frames we need to skip PSGLoopFlag db ; the tune should loop forever or not (flag) PSGLoopCounter db ; how many times the tune should loop PSGMusicLastLatch db ; the last PSG music latch ; decompression vars PSGMusicSubstringLen db ; lenght of the substring we are playing PSGMusicSubstringRetAddr dw ; return to this address when substring is over ; command buffers PSGChan0Volume db ; the volume for channel 0 PSGChan1Volume db ; the volume for channel 1 PSGChan2Volume db ; the volume for channel 2 PSGChan3Volume db ; the volume for channel 3 PSGChan0LowTone db ; the low tone bits for channels 0 PSGChan1LowTone db ; the low tone bits for channels 1 PSGChan2LowTone db ; the low tone bits for channels 2 PSGChan3LowTone db ; the low tone bits for channels 3 PSGChan0HighTone db ; the high tone bits for channel 0 PSGChan1HighTone db ; the high tone bits for channel 1 PSGChan2HighTone db ; the high tone bits for channel 2 PSGMusicVolumeAttenuation db ; the volume attenuation applied to the tune (0-15) ; ******* SFX ************* ; flags for SFX channels usage PSGChannel0SFX db ; !0 means channel 0 is allocated to SFX PSGChannel1SFX db ; !0 means channel 1 is allocated to SFX PSGChannel2SFX db ; !0 means channel 2 is allocated to SFX PSGChannel3SFX db ; !0 means channel 3 is allocated to SFX ; command buffers for SFX PSGSFXChan0Volume db ; the volume for channel 0 PSGSFXChan1Volume db ; the volume for channel 1 PSGSFXChan2Volume db ; the volume for channel 2 PSGSFXChan3Volume db ; the volume for channel 3 PSGSFXVolumeAttenuation db ; the volume attenuation applied to the SFX (0-15) ; fundamental vars for SFX PSGSFXStatus db ; are we playing a SFX? PSGSFXStart dw ; the pointer to the beginning of SFX PSGSFXPointer dw ; the pointer to the current address PSGSFXLoopPoint dw ; the pointer to the loop begin PSGSFXSkipFrames db ; the frames we need to skip PSGSFXLoopFlag db ; the SFX should loop or not (flag) ; decompression vars for SFX PSGSFXSubstringLen db ; lenght of the substring we are playing PSGSFXSubstringRetAddr dw ; return to this address when substring is over .ends ;.ende ; in case you want to use .enum instead of .ramsection