/*========================================================================= xmplay.cpp Author: PKG Created: Project: Spongebob Purpose: Copyright (c) 2000 Climax Development Ltd ===========================================================================*/ /*---------------------------------------------------------------------- Includes -------- */ #include "xmplay.h" #ifndef __SYSTEM_DBG_H__ #include "system\dbg.h" #endif #ifndef __VID_HEADER_ #include "system\vid.h" #endif #ifndef XMPlayer #include "system\global.h" #include #endif /* Std Lib ------- */ /* Data ---- */ /*---------------------------------------------------------------------- Tyepdefs && Defines ------------------- */ /*---------------------------------------------------------------------- Structure defintions -------------------- */ /*---------------------------------------------------------------------- Function Prototypes ------------------- */ /*---------------------------------------------------------------------- Vars ---- */ /*---------------------------------------------------------------------- Function: Purpose: Params: Returns: ---------------------------------------------------------------------- */ void CXMPlaySound::initialise() { int size,i; #if defined(__TERRITORY_USA__) || defined(__TERRITORY_JAP__) XM_OnceOffInit(XM_NTSC); #else XM_OnceOffInit(XM_PAL); #endif setStereo(true); size=XM_GetSongSize(); for(i=0;im_refCount=0; } for(i=0;im_refCount=0; } for(i=0;im_useType],ch->m_locked,ch->m_playingId); } PAUL_DBGMSG("======="); dump=false; } #endif int i; int id; spuChannelUse *ch; XM_Feedback fb; // Check to see if any of the sounds have finished id=-1; for(i=0;im_useType) { // Silent and unlocked sounds can be freed case SILENT: if(!ch->m_locked) { do { PAUL_DBGMSG("freed channel %d ( %d )",i,ch->m_playingId); ch->m_useType=FREE; #ifdef __VERSION_DEBUG__ ch->m_internalId=0; ch->m_playingId=NOT_PLAYING; ch->m_priority=0; ch->m_locked=false; ch->m_vol=MIN_VOLUME; ch->m_pan=PAN_CENTRE; #endif ch++; } while(ch->m_useType==CONTINUE); } break; // See if these have finished playing case SONG: case SFX: if(XM_GetFeedback(ch->m_internalId,&fb)) { do { // Just mark it as silent, if it's unlocked then it'll die next frame ch->m_useType=SILENT; ch++; } while(ch->m_useType==CONTINUE); } break; case LOOPINGSFX: case FREE: case CONTINUE: break; } } } /*---------------------------------------------------------------------- Function: Purpose: Params: Returns: ---------------------------------------------------------------------- */ void CXMPlaySound::setMasterSongVolume(unsigned char _vol) { int i; spuChannelUse *ch; xmPlayingId lastId; // New volume if(m_masterSongVolume==_vol) return; m_masterSongVolume=_vol; // Now update any active songs ch=m_spuChannelUse; lastId=NOT_PLAYING; for(i=0;im_playingId!=lastId&&ch->m_useType==SONG) { int oldLock=ch->m_locked; // hmm.. ch->m_locked=true; // not too.. setVolume(ch->m_playingId,ch->m_vol); ch->m_locked=oldLock; // ..ugly I suppose lastId=ch->m_playingId; } } } /*---------------------------------------------------------------------- Function: Purpose: Params: Returns: ---------------------------------------------------------------------- */ void CXMPlaySound::setMasterSfxVolume(unsigned char _vol) { int i; spuChannelUse *ch; xmPlayingId lastId; // New volume if(m_masterSfxVolume==_vol) return; m_masterSfxVolume=_vol; // Now update any active sfx ch=m_spuChannelUse; lastId=NOT_PLAYING; for(i=0;im_playingId!=lastId&&ch->m_useType==SFX||ch->m_useType==LOOPINGSFX) { int oldLock=ch->m_locked; // hmm.. ch->m_locked=true; // not too.. setVolume(ch->m_playingId,ch->m_vol); ch->m_locked=oldLock; // ..ugly I suppose lastId=ch->m_playingId; } } } /*---------------------------------------------------------------------- Function: Purpose: Params: Returns: ---------------------------------------------------------------------- */ xmSampleId CXMPlaySound::loadSampleData(FileEquate _vhFe,FileEquate _vbFe) { int vabId; XMVab *vab; unsigned char *VbPtr,*VhPtr; // Is the bank already loaded? vab=m_xmVabs; for(vabId=0;vabIdm_refCount&&vab->m_vhFile==_vhFe&&vab->m_vbFile==_vbFe) { // Yup.. vab->m_refCount++; return(xmSampleId)vabId; } } // Find next free vab slot vabId=0; vab=m_xmVabs; while(1) { ASSERT(vabIdm_refCount==0) { VhPtr=(u8*)CFileIO::loadFile(_vhFe); VbPtr=(u8*)CFileIO::loadFile(_vbFe); vab->m_vabId=XM_VABInit(VhPtr,VbPtr); MemFree(VhPtr); MemFree(VbPtr); vab->m_vhFile=_vhFe; vab->m_vbFile=_vbFe; vab->m_refCount=1; break; } vabId++;vab++; } return (xmSampleId)vabId; } /*---------------------------------------------------------------------- Function: Purpose: Params: Returns: ---------------------------------------------------------------------- */ xmModId CXMPlaySound::loadModData(FileEquate _modFe) { int modId; XMMod *mod; // Is the mod already loaded? mod=m_xmMods; for(modId=0;modIdm_refCount&&mod->m_file==_modFe) { // Yup.. mod->m_refCount++; return(xmModId)modId; } } // Find next free song slot mod=m_xmMods; modId=0; while(1) { ASSERT(modIdm_refCount==0) { mod->m_xmData=(u8*)CFileIO::loadFile(_modFe); InitXMData(mod->m_xmData,modId,XM_UseXMPanning); mod->m_file=_modFe; mod->m_refCount=1; break; } modId++;mod++; } return (xmModId)modId; } /*---------------------------------------------------------------------- Function: Purpose: Params: Returns: ---------------------------------------------------------------------- */ void CXMPlaySound::dumpSampleData(xmSampleId _sampleId) { XMVab *vab; vab=&m_xmVabs[_sampleId]; vab->m_refCount--; if(vab->m_refCount==0) { XM_CloseVAB(vab->m_vabId); } } /*---------------------------------------------------------------------- Function: Purpose: Params: Returns: ---------------------------------------------------------------------- */ void CXMPlaySound::dumpModData(xmModId _modId) { XMMod *mod; mod=&m_xmMods[_modId]; mod->m_refCount--; if(mod->m_refCount==0) { MemFree(mod->m_xmData); } } /*---------------------------------------------------------------------- Function: Purpose: Params: Returns: ---------------------------------------------------------------------- */ void CXMPlaySound::setStereo(int _stereo) { if(_stereo) XM_SetStereo(); else XM_SetMono(); } /*---------------------------------------------------------------------- Function: Purpose: Params: Returns: ---------------------------------------------------------------------- */ void CXMPlaySound::setVolume(xmPlayingId _playingId,unsigned char _volume) { // int i; spuChannelUse *ch; int vol; ch=&m_spuChannelUse[_playingId&0xff]; // for(i=0;im_playingId==_playingId) { ASSERT(ch->m_locked!=false); // Cant alter unlocked channels! ch->m_vol=_volume; // Update volume switch(ch->m_useType) { case SILENT: break; case SONG: vol=(_volume*m_masterSongVolume)>>8; XM_SetMasterVol(ch->m_internalId,vol>>1); break; case SFX: vol=(_volume*m_masterSfxVolume)>>8; XM_SetMasterVol(ch->m_internalId,vol>>1); break; case LOOPINGSFX: updateLoopingSfx(ch); break; case FREE: case CONTINUE: ASSERT(0); break; } return; } } ASSERT(0); // Couldn't find the sound to unlock it! } /*---------------------------------------------------------------------- Function: Purpose: Params: Returns: ---------------------------------------------------------------------- */ void CXMPlaySound::setPanning(xmPlayingId _playingId,char _pan) { // int i; spuChannelUse *ch; ch=&m_spuChannelUse[_playingId&0xff]; // for(i=0;im_playingId==_playingId) { ASSERT(ch->m_locked!=false); // Cant alter unlocked channels! ch->m_pan=_pan; // Update pan switch(ch->m_useType) { case SILENT: break; case SONG: XM_SetMasterPan(ch->m_internalId,_pan-128); break; case SFX: XM_SetMasterPan(ch->m_internalId,_pan-128); break; case LOOPINGSFX: updateLoopingSfx(ch); break; case FREE: case CONTINUE: ASSERT(0); break; } return; } } ASSERT(0); // Couldn't find the sound to unlock it! } /*---------------------------------------------------------------------- Function: Purpose: Params: Returns: ---------------------------------------------------------------------- */ void CXMPlaySound::stopAndUnlockAllSound() { int i; spuChannelUse *ch; ch=m_spuChannelUse; for(i=0;im_useType==SONG|| ch->m_useType==SFX|| ch->m_useType==LOOPINGSFX) { ch->m_locked=true; // hmm.. not too ugly I suppose setVolume(ch->m_playingId,0); stopPlayingId(ch->m_playingId); // Need to unlock too unlockPlayingId(ch->m_playingId); } } } /*---------------------------------------------------------------------- Function: Purpose: Params: Returns: ---------------------------------------------------------------------- */ xmPlayingId CXMPlaySound::playSong(xmSampleId _sampleId,xmModId _modId) { int channelCount,baseChannel; XMVab *vab; int id; xmPlayingId retId; ASSERT(m_xmVabs[_sampleId].m_refCount!=0); ASSERT(m_xmMods[_modId].m_refCount!=0); // Let's grab the channel count from the XM data.. :) channelCount=(short int)*(m_xmMods[_modId].m_xmData+68); SOUND_DBGMSG("Playing song with %d channels reserved",channelCount); baseChannel=findSpareChannels(channelCount,255); if(baseChannel!=-1) { retId=getNextSparePlayingId(baseChannel); vab=&m_xmVabs[_sampleId]; id=XM_Init(vab->m_vabId, // id from XM_VABInit _modId, // XM id ( as passed to InitXMData ) -1, // Let xmplay give us a song id baseChannel, // First channel XM_Loop, // Play song continuously -1, // Play all channels XM_Music, // Music 0); // Begin at the beginning markChannelsAsActive(baseChannel,channelCount,SONG,retId,id,255); setVolume(retId,MAX_VOLUME); //PAUL_DBGMSG("playSong %d/%d ( on %d-%d )",retId,id,baseChannel,baseChannel+channelCount-1); } else { retId=NOT_PLAYING; } return retId; } /*---------------------------------------------------------------------- Function: Purpose: Params: Returns: ---------------------------------------------------------------------- */ void CXMPlaySound::unlockPlayingId(xmPlayingId _playingId) { int i; spuChannelUse *ch; ch=m_spuChannelUse; for(i=0;im_playingId==_playingId) { //PAUL_DBGMSG("unlocking %d",_playingId); ASSERT(ch->m_locked!=false); do { ch->m_locked=false; ch++; } while(ch->m_useType==CONTINUE); return; } } ASSERT(0); // Couldn't find the sound to unlock it! } /*---------------------------------------------------------------------- Function: Purpose: Params: Returns: ---------------------------------------------------------------------- */ void CXMPlaySound::stopPlayingId(xmPlayingId _playingId) { // int i; spuChannelUse *ch; ch=&m_spuChannelUse[_playingId&0xff]; // for(i=0;im_playingId==_playingId) { ASSERT(ch->m_locked!=false); // Cant stop unlocked channels! switch(ch->m_useType) { case SONG: case SFX: { XM_Feedback fb; do { XM_PlayStop(ch->m_internalId); XM_GetFeedback(ch->m_internalId,&fb); } while(fb.Status!=XM_STOPPED); } break; case LOOPINGSFX: XM_StopSample(ch->m_internalId); break; case SILENT: case FREE: case CONTINUE: ASSERT(0); break; } do { ch->m_useType=SILENT; ch++; } while(ch->m_useType==CONTINUE); return; } } ASSERT(0); // Couldn't find the sound to stop it! } /*---------------------------------------------------------------------- Function: Purpose: Params: Returns: ---------------------------------------------------------------------- */ xmPlayingId CXMPlaySound::playSfx(xmSampleId _sampleId,xmModId _modId,int _sfxPattern,int _playMask,u8 _priority) { int i,j; int maskCopy,channelCount; int valid; int baseChannel; XMVab *vab; int songId; int id; xmPlayingId retId; ASSERT(m_xmVabs[_sampleId].m_refCount!=0); ASSERT(m_xmMods[_modId].m_refCount!=0); // Count channels maskCopy=_playMask; channelCount=0; for(i=0;i>=1; } // Play the sound baseChannel=findSpareChannels(channelCount,_priority); if(baseChannel!=-1) { retId=getNextSparePlayingId(baseChannel); vab=&m_xmVabs[_sampleId]; XM_SetSFXRange(baseChannel,channelCount); id=XM_Init(vab->m_vabId, // id from XM_VABInit _modId, // XM id ( as passed to InitXMData ) -1, // Let xmplay give us a song id -1, // Use SFX range to get first channel XM_NoLoop, // One-shot _playMask, // Play mask XM_SFX, // SFX _sfxPattern); // SFX pattern to play XM_ClearSFXRange(); //PAUL_DBGMSG("playSfx %d/%d ( on %d-%d )",retId,id,baseChannel,baseChannel+channelCount-1); markChannelsAsActive(baseChannel,channelCount,SFX,retId,id,_priority); setVolume(retId,MAX_VOLUME); } else { retId=NOT_PLAYING; } return retId; } /*---------------------------------------------------------------------- Function: Purpose: Params: Returns: ---------------------------------------------------------------------- */ xmPlayingId CXMPlaySound::playLoopingSfx(xmSampleId _sampleId,xmModId _modId,int _soundId,u8 _priority,int _pitch) { int baseChannel; xmPlayingId retId; baseChannel=findSpareChannels(1,_priority); if(baseChannel!=-1) { retId=getNextSparePlayingId(baseChannel); //PAUL_DBGMSG("playLoopingSfx %d/- ( on %d-%d )",retId,baseChannel,baseChannel); XM_PlaySample(XM_GetSampleAddress(_sampleId,_soundId),baseChannel,0x3fff,0x3fff,_pitch); markChannelsAsActive(baseChannel,1,LOOPINGSFX,retId,baseChannel,_priority); setVolume(retId,MAX_VOLUME); } else { retId=NOT_PLAYING; } return retId; } /*---------------------------------------------------------------------- Function: Purpose: Params: Returns: ---------------------------------------------------------------------- */ xmPlayingId CXMPlaySound::getNextSparePlayingId(int _baseChannel) { xmPlayingId validId; int i; spuChannelUse *ch; static xmPlayingId nextId=(xmPlayingId)(0-1); // fudge to make first id=0 validId=NOT_PLAYING; while(validId==NOT_PLAYING) { nextId=(xmPlayingId)((nextId+1)&0xff); // gives 256 unique id numbers // Is this id still in use? validId=(xmPlayingId)((nextId<<8)+_baseChannel); ch=m_spuChannelUse; for(i=0;im_playingId==validId&&!(ch->m_locked==false&&ch->m_useType==SILENT)) validId=NOT_PLAYING; ch++; } } return validId; } /*---------------------------------------------------------------------- Function: Purpose: Returns the base channel of a slot big enuf for the requested number of channels Params: Returns: baseChannel or -1 if no space ---------------------------------------------------------------------- */ int CXMPlaySound::findSpareChannels(int _channelCount,int _priority) { int i,j; int valid; // First we search for channels that are marked as unused valid=false; for(i=0;im_useType=_useType; ch->m_internalId=_internalId; ch->m_playingId=_playingId; ch->m_priority=_priority; ch->m_locked=true; ch->m_vol=MAX_VOLUME; ch->m_pan=PAN_CENTRE; ch++; for(i=_baseChannel+1;i<_baseChannel+_channelCount;i++,ch++) { ch->m_useType=CONTINUE; #ifdef __VERSION_DEBUG__ ch->m_internalId=0; ch->m_playingId=NOT_PLAYING; ch->m_priority=0; ch->m_locked=false; ch->m_vol=MIN_VOLUME; ch->m_pan=PAN_CENTRE; #endif } } /*---------------------------------------------------------------------- Function: Purpose: Defrag SPU memory by reloading sample banks. Should mean that bank load order isn't so vital. Really needs to check for and pause playing sounds first.. hmm.. Params: Returns: ---------------------------------------------------------------------- */ void CXMPlaySound::defragSpuMemory() { int vabId; XMVab *vab; unsigned char *VbPtr,*VhPtr; SOUND_DBGMSG("CXMPlaySound is defragging.."); // Dump banks vab=m_xmVabs; for(vabId=0;vabIdm_refCount) { XM_CloseVAB(vab->m_vabId); } } // Now reload them vab=m_xmVabs; for(vabId=0;vabIdm_refCount) { VhPtr=(u8*)CFileIO::loadFile(vab->m_vhFile); VbPtr=(u8*)CFileIO::loadFile(vab->m_vbFile); vab->m_vabId=XM_VABInit(VhPtr,VbPtr); MemFree(VhPtr); MemFree(VbPtr); } } SOUND_DBGMSG("..done!"); } /*---------------------------------------------------------------------- Function: Purpose: Params: Returns: ---------------------------------------------------------------------- */ void CXMPlaySound::updateLoopingSfx(spuChannelUse *ch) { int actualVol,actualPan; int leftVol,rightVol; SpuVoiceAttr attr; actualVol=(ch->m_vol*m_masterSfxVolume)>>8; // 0=silent, 255=full vol actualPan=ch->m_pan; // 0=hard left, 255=hard right leftVol=(actualVol*actualPan)>>2; rightVol=(actualVol*(255-actualPan))>>2; ASSERT(leftVol<=0x3fff); ASSERT(leftVol>=0); ASSERT(rightVol<=0x3fff); ASSERT(rightVol>=0); attr.voice=(1<m_internalId); attr.mask=(SPU_VOICE_VOLL|SPU_VOICE_VOLR); attr.volume.left=leftVol; attr.volume.right=rightVol; SpuSetVoiceAttr(&attr); } /*=========================================================================== end */