mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-29 14:51:17 +00:00
557 lines
20 KiB
C
557 lines
20 KiB
C
|
/* **************************************************
|
||
|
PSGlib - C programming library for the SEGA PSG
|
||
|
( part of devkitSMS - github.com/sverx/devkitSMS )
|
||
|
************************************************** */
|
||
|
|
||
|
// http://www.smspower.org/forums/16925-PSGToolAVGMToPSGConvertor
|
||
|
// https://github.com/sverx/PSGlib/blob/master/tools/src/vgm2psg.c
|
||
|
// http://steveproxna.blogspot.com/2017/11/devkitsms-programming-sample.html
|
||
|
// http://battleofthebits.org/lyceum/View/Vortex+Tracker+II/
|
||
|
|
||
|
#include "PSGlib.h"
|
||
|
|
||
|
#define PSGDataPort #0x7f
|
||
|
|
||
|
#define PSGLatch #0x80
|
||
|
#define PSGData #0x40
|
||
|
|
||
|
#define PSGChannel0 #0b00000000
|
||
|
#define PSGChannel1 #0b00100000
|
||
|
#define PSGChannel2 #0b01000000
|
||
|
#define PSGChannel3 #0b01100000
|
||
|
#define PSGVolumeData #0b00010000
|
||
|
|
||
|
#define PSGWait #0x38
|
||
|
#define PSGSubString #0x08
|
||
|
#define PSGLoop #0x01
|
||
|
#define PSGEnd #0x00
|
||
|
|
||
|
/* define PSGPort (SDCC z80 syntax) */
|
||
|
__sfr __at 0x7F PSGPort;
|
||
|
|
||
|
// fundamental vars
|
||
|
unsigned char PSGMusicStatus; // are we playing a background music?
|
||
|
void *PSGMusicStart; // the pointer to the beginning of music
|
||
|
void *PSGMusicPointer; // the pointer to the current
|
||
|
void *PSGMusicLoopPoint; // the pointer to the loop begin
|
||
|
unsigned char PSGMusicSkipFrames; // the frames we need to skip
|
||
|
unsigned char PSGLoopFlag; // the tune should loop or not (flag)
|
||
|
unsigned char PSGMusicLastLatch; // the last PSG music latch
|
||
|
unsigned char PSGMusicVolumeAttenuation; // the volume attenuation applied to the tune (0-15)
|
||
|
|
||
|
// decompression vars
|
||
|
unsigned char PSGMusicSubstringLen; // lenght of the substring we are playing
|
||
|
void *PSGMusicSubstringRetAddr; // return to this address when substring is over
|
||
|
|
||
|
// volume/frequence buffering
|
||
|
unsigned char PSGChan0Volume; // the volume for channel 0
|
||
|
unsigned char PSGChan1Volume; // the volume for channel 1
|
||
|
unsigned char PSGChan2Volume; // the volume for channel 2
|
||
|
unsigned char PSGChan3Volume; // the volume for channel 3
|
||
|
unsigned char PSGChan2LowTone; // the low tone bits for channels 2
|
||
|
unsigned char PSGChan2HighTone; // the high tone bits for channel 2
|
||
|
unsigned char PSGChan3LowTone; // the tone bits for channels 3
|
||
|
|
||
|
// flags for channels 2-3 access
|
||
|
unsigned char PSGChannel2SFX; // !0 means channel 2 is allocated to SFX
|
||
|
unsigned char PSGChannel3SFX; // !0 means channel 3 is allocated to SFX
|
||
|
|
||
|
// volume/frequence buffering for SFX
|
||
|
unsigned char PSGSFXChan2Volume; // the volume for SFX channel 2
|
||
|
unsigned char PSGSFXChan3Volume; // the volume for SFX channel 3
|
||
|
|
||
|
// fundamental vars for SFX
|
||
|
unsigned char PSGSFXStatus; // are we playing a SFX?
|
||
|
void *PSGSFXStart; // the pointer to the beginning of SFX
|
||
|
void *PSGSFXPointer; // the pointer to the current address
|
||
|
void *PSGSFXLoopPoint; // the pointer to the loop begin
|
||
|
unsigned char PSGSFXSkipFrames; // the frames we need to skip
|
||
|
unsigned char PSGSFXLoopFlag; // the SFX should loop or not (flag)
|
||
|
|
||
|
// decompression vars for SFX
|
||
|
unsigned char PSGSFXSubstringLen; // lenght of the substring we are playing
|
||
|
void *PSGSFXSubstringRetAddr; // return to this address when substring is over
|
||
|
|
||
|
void PSGStop (void) {
|
||
|
/* *********************************************************************
|
||
|
stops the music (leaving the SFX on, if it's playing)
|
||
|
*/
|
||
|
if (PSGMusicStatus) {
|
||
|
PSGPort=PSGLatch|PSGChannel0|PSGVolumeData|0x0F; // latch channel 0, volume=0xF (silent)
|
||
|
PSGPort=PSGLatch|PSGChannel1|PSGVolumeData|0x0F; // latch channel 1, volume=0xF (silent)
|
||
|
if (!PSGChannel2SFX)
|
||
|
PSGPort=PSGLatch|PSGChannel2|PSGVolumeData|0x0F; // latch channel 2, volume=0xF (silent)
|
||
|
if (!PSGChannel3SFX)
|
||
|
PSGPort=PSGLatch|PSGChannel3|PSGVolumeData|0x0F; // latch channel 3, volume=0xF (silent)
|
||
|
PSGMusicStatus=PSG_STOPPED;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void PSGResume (void) {
|
||
|
/* *********************************************************************
|
||
|
resume the previously playing music
|
||
|
*/
|
||
|
if (!PSGMusicStatus) {
|
||
|
PSGPort=PSGLatch|PSGChannel0|PSGVolumeData|PSGChan0Volume; // restore channel 0 volume
|
||
|
PSGPort=PSGLatch|PSGChannel1|PSGVolumeData|PSGChan1Volume; // restore channel 1 volume
|
||
|
if (!PSGChannel2SFX) {
|
||
|
PSGPort=PSGLatch|PSGChannel2|(PSGChan2LowTone&0x0F); // restore channel 2 frequency
|
||
|
PSGPort=PSGChan2HighTone&0x3F;
|
||
|
PSGPort=PSGLatch|PSGChannel2|PSGVolumeData|PSGChan2Volume; // restore channel 2 volume
|
||
|
}
|
||
|
if (!PSGChannel3SFX) {
|
||
|
PSGPort=PSGLatch|PSGChannel3|(PSGChan3LowTone&0x0F); // restore channel 3 frequency
|
||
|
PSGPort=PSGLatch|PSGChannel3|PSGVolumeData|PSGChan3Volume; // restore channel 3 volume
|
||
|
}
|
||
|
PSGMusicStatus=PSG_PLAYING;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void PSGPlay (void *song) {
|
||
|
/* *********************************************************************
|
||
|
receives the address of the PSG to start playing (continuously)
|
||
|
*/
|
||
|
PSGStop();
|
||
|
PSGLoopFlag=1;
|
||
|
PSGMusicStart=song; // store the begin point of music
|
||
|
PSGMusicPointer=song; // set music pointer to begin of music
|
||
|
PSGMusicLoopPoint=song; // looppointer points to begin too
|
||
|
|
||
|
PSGMusicSkipFrames=0; // reset the skip frames
|
||
|
PSGMusicSubstringLen=0; // reset the substring len (for compression)
|
||
|
PSGMusicLastLatch=PSGLatch|PSGChannel0|PSGVolumeData|0x0F; // latch channel 0, volume=0xF (silent)
|
||
|
PSGMusicStatus=PSG_PLAYING;
|
||
|
}
|
||
|
|
||
|
void PSGCancelLoop (void) {
|
||
|
/* *********************************************************************
|
||
|
sets the currently looping music to no more loops after the current
|
||
|
*/
|
||
|
PSGLoopFlag=0;
|
||
|
}
|
||
|
|
||
|
void PSGPlayNoRepeat (void *song) {
|
||
|
/* *********************************************************************
|
||
|
receives the address of the PSG to start playing (once)
|
||
|
*/
|
||
|
PSGPlay(song);
|
||
|
PSGLoopFlag=0;
|
||
|
}
|
||
|
|
||
|
unsigned char PSGGetStatus (void) {
|
||
|
/* *********************************************************************
|
||
|
returns the current status of music
|
||
|
*/
|
||
|
return(PSGMusicStatus);
|
||
|
}
|
||
|
|
||
|
void PSGSilenceChannels (void) {
|
||
|
/* *********************************************************************
|
||
|
silence all the PSG channels
|
||
|
*/
|
||
|
PSGPort=PSGLatch|PSGChannel0|PSGVolumeData|0x0F;
|
||
|
PSGPort=PSGLatch|PSGChannel1|PSGVolumeData|0x0F;
|
||
|
PSGPort=PSGLatch|PSGChannel2|PSGVolumeData|0x0F;
|
||
|
PSGPort=PSGLatch|PSGChannel3|PSGVolumeData|0x0F;
|
||
|
}
|
||
|
|
||
|
void PSGRestoreVolumes (void) {
|
||
|
/* *********************************************************************
|
||
|
restore the PSG channels volumes (if a tune or an SFX uses them!)
|
||
|
*/
|
||
|
if (PSGMusicStatus) {
|
||
|
PSGPort=PSGLatch|PSGChannel0|PSGVolumeData|((PSGChan0Volume+PSGMusicVolumeAttenuation>15)?15:PSGChan0Volume+PSGMusicVolumeAttenuation);
|
||
|
PSGPort=PSGLatch|PSGChannel1|PSGVolumeData|((PSGChan1Volume+PSGMusicVolumeAttenuation>15)?15:PSGChan1Volume+PSGMusicVolumeAttenuation);
|
||
|
}
|
||
|
if (PSGChannel2SFX)
|
||
|
PSGPort=PSGLatch|PSGChannel2|PSGVolumeData|PSGSFXChan2Volume;
|
||
|
else if (PSGMusicStatus)
|
||
|
PSGPort=PSGLatch|PSGChannel2|PSGVolumeData|((PSGChan2Volume+PSGMusicVolumeAttenuation>15)?15:PSGChan2Volume+PSGMusicVolumeAttenuation);
|
||
|
if (PSGChannel3SFX)
|
||
|
PSGPort=PSGLatch|PSGChannel3|PSGVolumeData|PSGSFXChan3Volume;
|
||
|
else if (PSGMusicStatus)
|
||
|
PSGPort=PSGLatch|PSGChannel3|PSGVolumeData|((PSGChan3Volume+PSGMusicVolumeAttenuation>15)?15:PSGChan3Volume+PSGMusicVolumeAttenuation);
|
||
|
}
|
||
|
|
||
|
void PSGSetMusicVolumeAttenuation (unsigned char attenuation) {
|
||
|
/* *********************************************************************
|
||
|
sets the volume attenuation for the music (0-15)
|
||
|
*/
|
||
|
PSGMusicVolumeAttenuation=attenuation;
|
||
|
if (PSGMusicStatus) {
|
||
|
PSGPort=PSGLatch|PSGChannel0|PSGVolumeData|((PSGChan0Volume+PSGMusicVolumeAttenuation>15)?15:PSGChan0Volume+PSGMusicVolumeAttenuation);
|
||
|
PSGPort=PSGLatch|PSGChannel1|PSGVolumeData|((PSGChan1Volume+PSGMusicVolumeAttenuation>15)?15:PSGChan1Volume+PSGMusicVolumeAttenuation);
|
||
|
if (!PSGChannel2SFX)
|
||
|
PSGPort=PSGLatch|PSGChannel2|PSGVolumeData|((PSGChan2Volume+PSGMusicVolumeAttenuation>15)?15:PSGChan2Volume+PSGMusicVolumeAttenuation);
|
||
|
if (!PSGChannel3SFX)
|
||
|
PSGPort=PSGLatch|PSGChannel3|PSGVolumeData|((PSGChan3Volume+PSGMusicVolumeAttenuation>15)?15:PSGChan3Volume+PSGMusicVolumeAttenuation);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void PSGSFXStop (void) {
|
||
|
/* *********************************************************************
|
||
|
stops the SFX (leaving the music on, if it's playing)
|
||
|
*/
|
||
|
if (PSGSFXStatus) {
|
||
|
if (PSGChannel2SFX) {
|
||
|
if (PSGMusicStatus) {
|
||
|
PSGPort=PSGLatch|PSGChannel2|(PSGChan2LowTone&0x0F);
|
||
|
PSGPort=PSGChan2HighTone&0x3F;
|
||
|
PSGPort=PSGLatch|PSGChannel2|PSGVolumeData|(((PSGChan2Volume&0x0F)+PSGMusicVolumeAttenuation>15)?15:(PSGChan2Volume&0x0F)+PSGMusicVolumeAttenuation);
|
||
|
} else {
|
||
|
PSGPort=PSGLatch|PSGChannel2|PSGVolumeData|0x0F;
|
||
|
}
|
||
|
PSGChannel2SFX=PSG_STOPPED;
|
||
|
}
|
||
|
|
||
|
if (PSGChannel3SFX) {
|
||
|
if (PSGMusicStatus) {
|
||
|
PSGPort=PSGLatch|PSGChannel3|(PSGChan3LowTone&0x0F);
|
||
|
PSGPort=PSGLatch|PSGChannel3|PSGVolumeData|(((PSGChan3Volume&0x0F)+PSGMusicVolumeAttenuation>15)?15:(PSGChan3Volume&0x0F)+PSGMusicVolumeAttenuation);
|
||
|
} else {
|
||
|
PSGPort=PSGLatch|PSGChannel3|PSGVolumeData|0x0F;
|
||
|
}
|
||
|
PSGChannel3SFX=PSG_STOPPED;
|
||
|
}
|
||
|
PSGSFXStatus=PSG_STOPPED;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void PSGSFXPlay (void *sfx, unsigned char channels) {
|
||
|
/* *********************************************************************
|
||
|
receives the address of the SFX to start and the mask that indicates
|
||
|
which channel(s) the SFX will use
|
||
|
*/
|
||
|
PSGSFXStop();
|
||
|
PSGSFXLoopFlag=0;
|
||
|
PSGSFXStart=sfx; // store begin of SFX
|
||
|
PSGSFXPointer=sfx; // set the pointer to begin of SFX
|
||
|
PSGSFXLoopPoint=sfx; // looppointer points to begin too
|
||
|
PSGSFXSkipFrames=0; // reset the skip frames
|
||
|
PSGSFXSubstringLen=0; // reset the substring len
|
||
|
PSGChannel2SFX=(channels&SFX_CHANNEL2)?PSG_PLAYING:PSG_STOPPED;
|
||
|
PSGChannel3SFX=(channels&SFX_CHANNEL3)?PSG_PLAYING:PSG_STOPPED;
|
||
|
PSGSFXStatus=PSG_PLAYING;
|
||
|
}
|
||
|
|
||
|
void PSGSFXCancelLoop (void) {
|
||
|
/* *********************************************************************
|
||
|
sets the currently looping SFX to no more loops after the current
|
||
|
*/
|
||
|
PSGSFXLoopFlag=0;
|
||
|
}
|
||
|
|
||
|
unsigned char PSGSFXGetStatus (void) {
|
||
|
/* *********************************************************************
|
||
|
returns the current SFX status
|
||
|
*/
|
||
|
return(PSGSFXStatus);
|
||
|
}
|
||
|
|
||
|
void PSGSFXPlayLoop (void *sfx, unsigned char channels) {
|
||
|
/* *********************************************************************
|
||
|
receives the address of the SFX to start continuously and the mask
|
||
|
that indicates which channel(s) the SFX will use
|
||
|
*/
|
||
|
PSGSFXPlay(sfx, channels);
|
||
|
PSGSFXLoopFlag=1;
|
||
|
}
|
||
|
|
||
|
void PSGFrame (void) {
|
||
|
/* *********************************************************************
|
||
|
processes a music frame
|
||
|
*/
|
||
|
__asm
|
||
|
ld a,(_PSGMusicStatus) ; check if we have got to play a tune
|
||
|
or a
|
||
|
ret z
|
||
|
|
||
|
ld a,(_PSGMusicSkipFrames) ; check if we have 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|#0x0F ; 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,_chn0 ; jump for channel 0
|
||
|
ld (_PSGChan1Volume),a ; save volume data
|
||
|
jp _sendVolume2PSG_A
|
||
|
_chn0:
|
||
|
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 is 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 #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
|
||
|
|
||
|
_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 #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 ; 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 a,(_PSGLoopFlag) ; looping requested?
|
||
|
or a
|
||
|
jp z,_PSGStop ; No:stop it! (tail call optimization)
|
||
|
ld hl,(_PSGMusicLoopPoint)
|
||
|
jp _intLoop
|
||
|
|
||
|
_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
|
||
|
__endasm;
|
||
|
}
|
||
|
|
||
|
void PSGSFXFrame (void) {
|
||
|
/* ********************************************************************
|
||
|
processes a SFX frame
|
||
|
*/
|
||
|
__asm
|
||
|
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 #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 - $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
|
||
|
__endasm;
|
||
|
}
|