/* * pssoundmngr.cpp. Saul Leite * * Copyright (C) 2001 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation (version 2 of the License) * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * The main manager of sounds and sound resource handler. */ #include // CS files #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // PS includes #include "pssoundmngr.h" #include "util/psxmlparser.h" #include "util/log.h" #include "net/messages.h" // system message type #defines // Max volume is 90% of overall max. At 100% distortion can occur #define MAX_VOLUME 0.90f // Minimum volume before pausing #define MIN_VOLUME 0.001f psSoundManager::psSoundManager(iBase* iParent) : scfImplementationType(this, iParent) , sndmngr(this) { soundEnabled = true; musicEnabled = true; musicCombat = false; musicVolume = 1.0; ambientVolume = 1.0; actionsVolume = 1.0; guiVolume = 1.0; eventHandler = NULL; currentSectorName = csString(""); mapSoundSystem = NULL; currentSoundSector = NULL; lastSoundSector = NULL; lastlastSoundSector = NULL; backgroundSong = 0; ListenerInitialized = false; overSongName = NULL; } psSoundManager::~psSoundManager() { csRef queue = CS_QUERY_REGISTRY( object_reg, iEventQueue ); if ( eventHandler && queue ) { queue->RemoveListener( eventHandler ); } delete backgroundSong; } bool psSoundManager::Initialize(iObjectRegistry* objectReg) { object_reg = objectReg; pslog::Initialize (object_reg); eventHandler = new EventHandler( this ); if ( !eventHandler ) return false; csRef queue = CS_QUERY_REGISTRY( objectReg, iEventQueue ); if ( !queue ) return false; csEventID esub[] = { csevFrame (object_reg), CS_EVENTLIST_END }; queue->RegisterListener( eventHandler, esub ); mapSoundSystem = new psMapSoundSystem( this, objectReg ); csRef cfg = CS_QUERY_REGISTRY(object_reg, iConfigManager); if (!cfg) { return false; } soundLib = cfg->GetStr("Planeshift.Sound.SoundLib", "/planeshift/art/soundlib.xml"); return true; } void psSoundManager::ChangeTimeOfDay( int newTime ) { if ( currentSoundSector ) currentSoundSector->ChangeTime( newTime ); } void psSoundManager::StartMapSoundSystem() { mapSoundSystem->Initialize(); } void psSoundManager::Update( iView* view ) { if (!soundSystem) return; SOUND_LISTENER_TYPE *sndListener = soundSystem->GetListener(); if (sndListener) { ListenerInitialized=true; // take position/direction from view->GetCamera () csVector3 v = view->GetCamera ()->GetTransform ().GetOrigin (); csMatrix3 m = view->GetCamera ()->GetTransform ().GetT2O(); csVector3 f = m.Col3(); csVector3 t = m.Col2(); sndListener->SetPosition(v); sndListener->SetDirection(f,t); } } void psSoundManager::Update( csVector3 pos ) { static unsigned int count = 0; if (++count%5 != 0) // Update emitter positions once every 5th frame return; mapSoundSystem->Update( pos ); } void psSoundManager::UpdateWeather(int weather) { if(!currentSoundSector) return; currentSoundSector->UpdateWeather(weather); } void psSoundManager::UpdateWeather(int weather,const char* sector) { psSectorSoundManager* sec = mapSoundSystem->GetSoundSectorByName(sector); if(!sec) return; sec->UpdateWeather(weather); } bool psSoundManager::Setup() { if (soundSystem.IsValid()) return true; engine = CS_QUERY_REGISTRY(object_reg, iEngine); if (!engine) { Error1("Couldn't find iEngine!"); return false; } if (!sndmngr.Initialize()) { Error1("Couldn't initialize sndmngr!"); return false; } if (!sndmngr.LoadSoundLib(soundLib.GetData())) { Error1("Couldn't load Soundlib!"); return false; } soundSystem = CS_QUERY_REGISTRY(object_reg, iSndSysRenderer); if (!soundSystem) { Error1("Couldn't find iSoundRender!"); return false; } return true; } csRef psSoundManager::StartMusicSound(const char* name,bool loop) { if(musicEnabled) return StartSound(name,musicVolume,loop); else return NULL; } csRef psSoundManager::StartAmbientSound(const char* name,bool loop) { if(soundEnabled) return StartSound(name,ambientVolume,loop); else return NULL; } csRef psSoundManager::StartGUISound(const char* name,bool loop) { if(guiEnabled) return StartSound(name,guiVolume,loop); else return NULL; } csRef psSoundManager::StartActionsSound(const char* name,bool loop) { if(actionsEnabled) return StartSound(name,actionsVolume,loop); else return NULL; } csRef psSoundManager::StartSound(const char* name,float volume,bool loop) { if ( !name ) return NULL; if (!soundSystem) return NULL; if (!soundEnabled) return NULL; csRef handle = sndmngr.CreateSound(name); if (!handle) { Error2("Sound '%s' not found!", name); return NULL; } csRef sndstream = soundSystem->CreateStream(handle->snddata, CS_SND3D_DISABLE); if (!sndstream) { Error2("Failed to create stream for '%s'!", name); return NULL; } csRef sndsource = soundSystem->CreateSource(sndstream); if (!sndsource.IsValid()) { Error2("Failed to create source for '%s'!", name); return NULL; } if (loop) sndstream->SetLoopState(CS_SNDSYS_STREAM_LOOP); else sndstream->SetLoopState(CS_SNDSYS_STREAM_DONTLOOP); sndstream->SetAutoUnregister(true); sndsource->SetVolume(volume); sndstream->Unpause(); return sndsource; } void psSoundManager::HandleSoundType(int type) { if(!actionsEnabled) // In-game action sounds are turned off. return; switch (type) { case MSG_ERROR: StartGUISound("sound.msg.error"); break; case MSG_INFO: StartGUISound("sound.msg.info"); break; /* case MSG_COMBAT_OWN_DEATH: StartMusicSound("sound.msg.death"); break; case MSG_COMBAT_VICTORY: StartMusicSound("sound.msg.victory"); break;*/ case MSG_PURCHASE: StartGUISound("sound.msg.purchase"); break; } } void psSoundManager::SetVolume(float vol) { if (!soundSystem) return; soundSystem->SetVolume(vol); } float psSoundManager::GetVolume() { if (!soundSystem) return -1; return soundSystem->GetVolume(); } void psSoundManager::SetMusicVolume(float vol) { musicVolume = vol; if(backgroundSong) { backgroundSong->SetVolume(vol); backgroundSong->Update(); } if(mapSoundSystem) { mapSoundSystem->SetMusicVolumes(musicVolume); } } float psSoundManager::GetMusicVolume() { return musicVolume; } void psSoundManager::SetAmbientVolume(float vol) { ambientVolume = vol; if(mapSoundSystem) { mapSoundSystem->SetAmbientVolumes(ambientVolume); mapSoundSystem->SetEmitterVolumes(ambientVolume); } } float psSoundManager::GetAmbientVolume() { return ambientVolume; } void psSoundManager::SetActionsVolume(float vol) { actionsVolume = vol; } float psSoundManager::GetActionsVolume() { return actionsVolume; } void psSoundManager::SetGUIVolume(float vol) { guiVolume = vol; } float psSoundManager::GetGUIVolume() { return guiVolume; } void psSoundManager::StopOverrideBG() { if ( backgroundSong ) backgroundSong->Stop(); delete backgroundSong; backgroundSong = NULL; } bool psSoundManager::OverrideBGSong(const char* name, bool loop, float fadeTime) { if (!soundSystem || !musicEnabled) return false; overSongName = name; if ( !backgroundSong ) { csRef snddata = GetSoundResource( name ); if (!snddata.IsValid()) return false; csRef sndstream = soundSystem->CreateStream(snddata, CS_SND3D_DISABLE); if (!sndstream.IsValid()) return false; backgroundSong = new psSoundObject(sndstream,mapSoundSystem, 1.0, 0.0, (int)fadeTime, 0, 0 ); backgroundSong->SetVolume( musicVolume ); backgroundSong->SetResource( name ); backgroundSong->StartFade( FADE_UP ); } else { csRef snddata = GetSoundResource( name ); if (!snddata.IsValid()) return false; csRef sndstream = soundSystem->CreateStream(snddata, CS_SND3D_DISABLE); if (!sndstream.IsValid()) return false; psSoundObject* backgroundSongNew = new psSoundObject( sndstream,mapSoundSystem, 1.0, 0.0, (int)fadeTime, 0, 0 ); backgroundSongNew->SetVolume( musicVolume ); backgroundSongNew->SetResource( name ); if ( backgroundSongNew->Same(backgroundSong) ) { delete backgroundSongNew; backgroundSongNew = NULL; return true; } else { StopOverrideBG(); backgroundSong = backgroundSongNew; backgroundSong->StartFade( FADE_UP ); } } return true; } void psSoundManager::ToggleMusic(bool toggle) { if (!soundSystem) return; // Enable Music if (toggle) { musicEnabled = true; if ( currentSoundSector ) currentSoundSector->StartBackground(); if (musicCombat) { OverrideBGSong("combat",iSoundManager::LOOP_SOUND, 2.0); FadeSectorSounds( FADE_DOWN ); } // if ( lastSoundSector ) lastSoundSector->StartBackground(); // Disable Music } else { // stop background music if (backgroundSong) { backgroundSong->Stop(); } musicEnabled = false; if ( currentSoundSector ) currentSoundSector->StopBackground(); if ( lastSoundSector ) lastSoundSector->StopBackground(); if (lastlastSoundSector ) lastlastSoundSector->StopBackground(); } mapSoundSystem->EnableMusic( musicEnabled ); } void psSoundManager::ToggleSounds(bool toggle) { if (!soundSystem) return; // Enable Sounds if (toggle) { soundEnabled = true; if ( currentSoundSector ) { int weather = currentSoundSector->GetWeather(); //TODO: Getting the time currentSoundSector->StartSounds(weather); } // if ( lastSoundSector ) lastSoundSector->StartSounds(); // Disable Sounds } else { soundEnabled = false; if ( currentSoundSector ) currentSoundSector->StopSounds( ); if ( lastSoundSector ) lastSoundSector->StopSounds(); } mapSoundSystem->EnableSounds( soundEnabled ); } void psSoundManager::ToggleActions(bool toggle) { actionsEnabled = toggle; } void psSoundManager::ToggleGUI(bool toggle) { guiEnabled = toggle; } void psSoundManager::ToggleLoop(bool toggle) { loopBGM = toggle; } void psSoundManager::ToggleCombatMusic(bool toggle) { combatMusicEnabled = toggle; if (!combatMusicEnabled) musicCombat = false; } bool psSoundManager::HandleEvent( iEvent& event ) { if ( backgroundSong ) { backgroundSong->Update(); if(!backgroundSong->IsPlaying()) { StopOverrideBG(); FadeSectorSounds( FADE_UP ); } } mapSoundSystem->Update(); return false; } csRef psSoundManager::GetSoundResource(const char *name) { csRef soundhandle; soundhandle=sndmngr.CreateSound(name); if (!soundhandle.IsValid()) return NULL; return soundhandle->snddata; } //--------------------------------------------------------------------------- struct psSoundFileInfo { csString name; csString file; }; psSoundManager::psSndSourceMngr::psSndSourceMngr(psSoundManager* nparent) : parent(nparent) { } psSoundManager::psSndSourceMngr::~psSndSourceMngr() { csHash::GlobalIterator it (sndfiles.GetIterator ()); while (it.HasNext ()) { psSoundFileInfo* sndfile = it.Next (); delete sndfile; } } bool psSoundManager::psSndSourceMngr::Initialize() { sndloader = CS_QUERY_REGISTRY(parent->object_reg, iSndSysLoader); if (!sndloader) { Error1("psSndSourceMngr: Could not initialize. Cannot find iSndSysLoader"); return false; } vfs = CS_QUERY_REGISTRY(parent->object_reg, iVFS); if (!vfs) { Error1("psSndSourceMngr: Could not initialize. Cannot find iVFS"); return false; } return true; } bool psSoundManager::psSndSourceMngr::LoadSoundLib(const char* fname) { csRef xml = CS_QUERY_REGISTRY (parent->object_reg, iDocumentSystem); if (!xml) xml = csPtr (new csTinyDocumentSystem); csRef buff = vfs->ReadFile( fname ); if ( !buff || !buff->GetSize() ) { return false; } csRef doc=xml->CreateDocument(); const char* error=doc->Parse(buff); if (error) { Error3("Parsing file %s gave error %s", fname, error); return false; } csRef root=doc->GetRoot(); if(!root) { Error1("No XML root in soundlib.xml"); return false; } csRef topNode=root->GetNode("Sounds"); if(!topNode) { Error1("No tag in soundlib.xml"); return false; } csRef iter = topNode->GetNodes(); while ( iter->HasNext() ) { csRef node = iter->Next(); if ( node->GetType() != CS_NODE_ELEMENT ) continue; if ( strcmp( node->GetValue(), "Sound" ) == 0 ) { psSoundFileInfo* info = new psSoundFileInfo; info->name = node->GetAttributeValue("name"); info->file = node->GetAttributeValue("file"); sndfiles.Put(csHashCompute(info->name), info); } } return true; } csRef psSoundManager::psSndSourceMngr::LoadSound(const char* fname) { if (!parent->soundSystem) return NULL; csRef buf = vfs->ReadFile(fname); if (!buf) { Error2("Error while reading file '%s'", fname); return NULL; } csRef sounddata = sndloader->LoadSound (buf); if (!sounddata) { Error2("Cannot create sound data from file '%s'", fname); return NULL; } return sounddata; } csRef psSoundManager::psSndSourceMngr::CreateSound (const char* name) { csRef rest = CreateResource(name); csRef res = (psSoundHandle*) ((psTemplateRes*) rest); if (!res) return NULL; return res; } /** * Check to see if a background song is already playing. * * Get the file info from both the resource names. * If they are the same file then return true. */ bool psSoundManager::psSndSourceMngr::CheckAlreadyPlaying( psSoundHandle* oldResourceHandle, const char* newResource ) { if ( !oldResourceHandle ) return false; const char* oldResource = oldResourceHandle->GetName(); csHash::Iterator i = sndfiles.GetIterator(csHashCompute(oldResource)); psSoundFileInfo* oldResourceInfo = NULL; while ( (oldResourceInfo = i.Next()) ) { if (!strcmp(oldResourceInfo->name, oldResource)) break; } if ( !oldResourceInfo ) return false; csHash::Iterator itr = sndfiles.GetIterator(csHashCompute(newResource)); psSoundFileInfo* newResourceInfo = NULL; while ( (newResourceInfo = itr.Next()) ) { if (!strcmp(newResourceInfo->name, newResource)) break; } if ( !newResourceInfo ) return false; if ( !strcmp(oldResourceInfo->file, newResourceInfo->file) ) return true; return false; } csPtr psSoundManager::psSndSourceMngr::LoadResource (const char* name) { csHash::Iterator i = sndfiles.GetIterator(csHashCompute(name)); psSoundFileInfo* info = NULL; while ( i.HasNext() ) { info = i.Next(); if (!strcmp(info->name, name)) break; } if (!info) { Error2("Couldn't find a definition for '%s'", name); return NULL; } csRef snddata = LoadSound(info->file); if (!snddata) return NULL; return csPtr (new psSoundHandle(snddata)); } //------------------------------------------------------------------- void psSoundManager::EnterSector( const char* sector, int timeOfDay, int weather, csVector3& position ) { if ( currentSectorName == sector ) return; else currentSectorName = sector; lastlastSoundSector = lastSoundSector; lastSoundSector = currentSoundSector; currentSoundSector = mapSoundSystem->GetSoundSectorByName(sector); // Transfer sound settings if ( currentSoundSector ) { currentSoundSector->Music(musicEnabled); currentSoundSector->Sounds(soundEnabled); } // mapSoundSystem::EnterSector must be called BEFORE entering the new sector or the new // sector's emitters will be pruned to only what can be heard at the zone-in mapSoundSystem->EnterSector( currentSoundSector ); if ( currentSoundSector ) currentSoundSector->Enter( lastSoundSector, timeOfDay, weather, position ); } void psSoundManager::FadeSectorSounds( Fade_Direction dir ) { if ( currentSoundSector ) { currentSoundSector->Fade( dir ); } } SCF_IMPLEMENT_FACTORY (psSoundManager) //----------------------------------------------------------------------------- psMapSoundSystem::psMapSoundSystem( psSoundManager* mngr, iObjectRegistry* object ) { objectReg = object; sndmngr = mngr; } void psMapSoundSystem::EnableMusic( bool enable ) { BinaryRBIterator iter(§ors); for ( psSectorSoundManager* sect = iter.First(); sect; sect = ++iter ) { sect->Music( enable ); } } void psMapSoundSystem::EnableSounds( bool enable ) { BinaryRBIterator iter(§ors); Fade_Direction dir; if ( enable ) dir = FADE_UP; else dir = FADE_DOWN; for ( psSectorSoundManager* sect = iter.First(); sect; sect = ++iter ) { sect->Sounds( enable ); } } psSectorSoundManager* psMapSoundSystem::GetSoundSectorByName(const char* name) { csString sectorStr(name); psSectorSoundManager key( sectorStr, NULL, this ); psSectorSoundManager* sectora = sectors.Find(&key); return sectora; } // This is when we need to create the sector sound manager before it should have been psSectorSoundManager* psMapSoundSystem::GetPendingSoundSector(const char* name) { csString sector(name); for(size_t i = 0; i < pendingSectors.Length(); i++) { psSectorSoundManager* obj = pendingSectors[i]; if( sector == obj->GetSector() ) return obj; } return NULL; } psSectorSoundManager* psMapSoundSystem::GetOrCreateSector(const char* name) { psSectorSoundManager* trigSector = NULL; csString sec(name); // Check if we loaded that sector trigSector = GetSoundSectorByName(sec); if(!trigSector) trigSector = GetPendingSoundSector(sec); // Try pending too if(!trigSector) // still no? create a pending one { trigSector = new psSectorSoundManager( sec, sndmngr->GetEngine(), this ); pendingSectors.Push(trigSector); } return trigSector; } int psMapSoundSystem::TriggerStringToInt(const char* str) { csString trigS(str); // Convert to enum int trigger = 0; if(trigS.CompareNoCase("weather")) trigger = SOUND_EVENT_WEATHER; return trigger; } bool psMapSoundSystem::Initialize() { Debug1( LOG_SOUND, 0, "Starting Map Sound System"); csRef vfs = CS_QUERY_REGISTRY ( objectReg, iVFS); if (!vfs) return false; csRef xpath = vfs->ExpandPath("/planeshift/world/"); const char* dir = **xpath; csRef files = vfs->FindFiles(dir); if (!files) return false; for (size_t i=0; i < files->Length(); i++) { // Skip server's "cleaned" files, to suppress warnings if ( strstr(files->Get(i),"_cleaned") ) continue; csString name( files->Get(i) ); name.Append("sound.xml"); csRef doc; csRef root, mapNode; if ( (doc=ParseFile( objectReg, name )) && (root=doc->GetRoot()) && (mapNode=root->GetNode("MAP_SOUNDS")) ) { csRef sectorIter = mapNode->GetNodes("SECTOR"); while ( sectorIter->HasNext() ) { csRef sector = sectorIter->Next(); csString sectorName = sector->GetAttributeValue("NAME"); Debug2(LOG_SOUND, 0, "Loading Sound System for Sector: %s", sectorName.GetData() ); psSectorSoundManager* manager = NULL; // See if it's already created if(!(manager = GetSoundSectorByName(sectorName))) { if( (manager = GetPendingSoundSector(sectorName)) ) { // Delete the pending one from the list and use it pendingSectors.Delete(manager); } else { // Create new manager = new psSectorSoundManager( sectorName, sndmngr->GetEngine(), this ); } } // we can now be sure we have a manager sectors.Insert(manager); Debug2( LOG_SOUND,0, "Added sector %s to the list",manager->GetSector()); csRef ambientItr = sector->GetNodes("AMBIENT"); while ( ambientItr->HasNext() ) { csRef ambientNode = ambientItr->Next(); csString resource = ambientNode->GetAttributeValue("RESOURCE"); float minVol = ambientNode->GetAttributeValueAsFloat("MINVOL"); float maxVol = ambientNode->GetAttributeValueAsFloat("MAXVOL"); int fadeDelay = ambientNode->GetAttributeValueAsInt("FADEDELAY"); int timeOfDay = ambientNode->GetAttributeValueAsInt("TIME"); int timeOfDayRange = ambientNode->GetAttributeValueAsInt("TIME_RANGE"); int weather = ambientNode->GetAttributeValueAsInt("WEATHER"); csString trigS= ambientNode->GetAttributeValue("TRIGGER"); csString sec = ambientNode->GetAttributeValue("TRIG_SECTOR"); int trigger = TriggerStringToInt(trigS); psSectorSoundManager* trigSector = manager; if(sec.Length() > 0) trigSector = GetOrCreateSector(sec); csRef snddata = sndmngr->GetSoundResource( resource ); if (!snddata.IsValid()) { Error2("Failed to load Sound Handle: %s", resource.GetData()); continue; } csRef sndstream = sndmngr->soundSystem->CreateStream(snddata, CS_SND3D_DISABLE); if (!sndstream.IsValid()) { Error2("Failed to load Sound Handle: %s", resource.GetData()); continue; } psSoundObject* obj = new psSoundObject (sndstream, this, maxVol, minVol, fadeDelay, timeOfDay, timeOfDayRange, weather, true, trigSector, trigger); obj->SetResource( resource ); manager->NewAmbient( obj ); } csRef emitters = sector->GetNodes("EMITTER"); while ( emitters->HasNext() ) { csRef emitter = emitters->Next(); csString resource = emitter->GetAttributeValue("RESOURCE"); float minVol = emitter->GetAttributeValueAsFloat("MINVOL"); float maxVol = emitter->GetAttributeValueAsFloat("MAXVOL"); float maxRange = emitter->GetAttributeValueAsFloat("MAX_RANGE"); float minRange = emitter->GetAttributeValueAsFloat("MIN_RANGE"); csRef snddata = sndmngr->GetSoundResource( resource ); if (!snddata) { Error2("Failed to load Sound: %s", resource.GetData()); continue; } csRef sndstream = sndmngr->soundSystem->CreateStream(snddata, CS_SND3D_ABSOLUTE); if (!sndstream) { Error2("Failed to create Sound Stream: %s", resource.GetData()); continue; } psSoundObject* obj = new psSoundObject (sndstream, this, maxVol, minVol, true ); obj->SetResource( resource ); obj->SetRange( maxRange, minRange ); csRef mesh = emitter->GetAttribute("MESH"); csRef factory = emitter->GetAttribute("FACTORY"); if ( mesh ) { obj->AttachToMesh( mesh->GetValue() ); manager->New3DMeshSound( obj ); } else if ( factory ) { float prob = emitter->GetAttributeValueAsFloat("FACTORY_PROBABILITY"); manager->New3DFactorySound( obj, factory->GetValue(), prob ); } else { csVector3 pos( emitter->GetAttributeValueAsFloat("X"), emitter->GetAttributeValueAsFloat("Y"), emitter->GetAttributeValueAsFloat("Z") ); obj->SetPosition( pos ); manager->New3DSound( obj ); } } csRef backgroundIter = sector->GetNodes("BACKGROUND"); while ( backgroundIter->HasNext() ) { csRef background = backgroundIter->Next(); csString resource = background->GetAttributeValue("RESOURCE"); float minVol = background->GetAttributeValueAsFloat("MINVOL"); float maxVol = background->GetAttributeValueAsFloat("MAXVOL"); int fadeDelay = background->GetAttributeValueAsInt("FADEDELAY"); int timeOfDay = background->GetAttributeValueAsInt("TIME"); int timeOfDayRange = background->GetAttributeValueAsInt("TIME_RANGE"); int weather = background->GetAttributeValueAsInt("WEATHER"); csRef snddata = sndmngr->GetSoundResource( resource ); if (!snddata) { Error2("Failed to load Sound: %s", resource.GetData()); continue; } csRef sndstream = sndmngr->soundSystem->CreateStream(snddata, CS_SND3D_DISABLE); if ( !sndstream ) { Error2("Failed to create Sound Stream: %s", resource.GetData()); continue; } psSoundObject* obj = new psSoundObject (sndstream, this, maxVol, minVol, fadeDelay, timeOfDay, timeOfDayRange, weather, sndmngr->LoopBGM()); obj->SetResource( resource ); manager->NewBackground( obj ); } } } } // Remove pending ones pendingSectors.DeleteAll(); return true; } void psMapSoundSystem::RegisterActiveSong(psSoundObject *song) { if (csArrayItemNotFound == active_songs.Find(song)) { song->SetVolume(sndmngr->GetMusicVolume()); active_songs.Push(song); Debug2(LOG_SOUND, 0, "Registered active song: %s", song->GetName().GetData() ); } } void psMapSoundSystem::SetMusicVolumes(float volume) { size_t i,l; l=active_songs.Length(); for (i=0;iSetVolume(volume); } } void psMapSoundSystem::RemoveActiveSong(psSoundObject *song) { active_songs.Delete(song); Debug2(LOG_SOUND, 0, "Removed active song: %s", song->GetName().GetData() ); } psSoundObject *psMapSoundSystem::FindSameActiveSong(psSoundObject *other) { size_t i,l; l=active_songs.Length(); for (i=0;iSame(other)) return active_songs[i]; } return NULL; } void psMapSoundSystem::RegisterActiveAmbient(psSoundObject *ambient) { if (csArrayItemNotFound == active_ambient.Find(ambient)) { ambient->SetVolume(sndmngr->GetAmbientVolume()); active_ambient.Push(ambient); Debug2(LOG_SOUND, 0, "Registered active ambient: %s", ambient->GetName().GetData() ); } } void psMapSoundSystem::RemoveActiveAmbient(psSoundObject *ambient) { active_ambient.Delete(ambient); Debug2(LOG_SOUND, 0, "Removed active ambient: %s", ambient->GetName().GetData() ); } void psMapSoundSystem::SetAmbientVolumes(float volume) { size_t i,l; l=active_ambient.Length(); for (i=0;iSetVolume(volume); } } psSoundObject *psMapSoundSystem::FindSameActiveAmbient(psSoundObject *other) { size_t i,l; l=active_ambient.Length(); for (i=0;iSame(other)) return active_ambient[i]; } return NULL; } void psMapSoundSystem::RegisterActiveEmitter(psSoundObject *emitter) { if (csArrayItemNotFound == active_emitters.Find(emitter)) { emitter->SetVolume(sndmngr->GetAmbientVolume()); active_emitters.Push(emitter); Debug3(LOG_SOUND, 0, "Registered active emitter: %s (attached to %s)", emitter->GetName().GetData(), emitter->GetMesh().GetData() ); } } void psMapSoundSystem::RemoveActiveEmitter(psSoundObject *emitter) { active_emitters.Delete(emitter); Debug3(LOG_SOUND, 0, "Removed active emitter: %s (attached to %s)", emitter->GetName().GetData(), emitter->GetMesh().GetData() ); } void psMapSoundSystem::SetEmitterVolumes(float volume) { size_t i,l; l=active_emitters.Length(); for (i=0;iSetVolume(volume); } } psSoundObject *psMapSoundSystem::FindSameActiveEmitter(psSoundObject *other) { size_t i,l; l=active_emitters.Length(); for (i=0;iSame(other)) return active_emitters[i]; } return NULL; } void psMapSoundSystem::RemoveActiveAnyAudio(psSoundObject *soundobject) { RemoveActiveSong(soundobject); RemoveActiveAmbient(soundobject); RemoveActiveEmitter(soundobject); } void psMapSoundSystem::EnterSector(psSectorSoundManager *enterTo) { // Check the destination sector to see which (if any) sounds need to be faded out size_t i,l; l=active_emitters.Length(); for ( i = 0; iIsPlaying()) { RemoveActiveEmitter(active_emitters[i]); l--; // Length is now 1 shorter i--; // The index we we processing is gone } else { Debug2( LOG_SOUND, 0, "Emitter %s from previous sector is still audible in next sector", active_emitters[i]->GetName().GetData() ); } } l=active_songs.Length(); for ( i = 0; iGetName().GetData() ); if (!enterTo || enterTo->CheckSong( active_songs[i] ) ) active_songs[i]->StartFade( FADE_DOWN ); } l=active_ambient.Length(); for ( i = 0; iGetName().GetData() ); if (!enterTo || enterTo->CheckAmbient( active_ambient[i] )|| (active_ambient[i]->Triggered() )) { /* if(active_ambient[i]->Triggered()) continue;*/ active_ambient[i]->StartFade( FADE_DOWN ); } } } void psMapSoundSystem::Update( csVector3 & pos ) { for ( size_t z = 0; z < active_emitters.Length(); z++ ) { active_emitters[z]->Update(pos); } } void psMapSoundSystem::Update() { size_t z; for ( z = 0; z < active_songs.Length(); z++ ) { //printf("Song Update: Vol: %f\n", songs[z]->Volume() ); active_songs[z]->Update(); if (!active_songs[z]->IsPlaying()) { RemoveActiveSong(active_songs[z]); z--; } } for ( z = 0; z < active_ambient.Length(); z++ ) { active_ambient[z]->Update(); if (!active_ambient[z]->IsPlaying()) { RemoveActiveAmbient(active_ambient[z]); z--; } } } psSectorSoundManager::psSectorSoundManager( csString& sectorName, iEngine* engine, psMapSoundSystem *mapSS ) { sector = sectorName; sounds = true; music = true; this->engine = engine; mainBG = NULL; mapsoundsystem = mapSS; weather = 1; } psSectorSoundManager::~psSectorSoundManager() { size_t i,l; // All sounds must be removed from the psSoundManagers active list l = songs.Length(); for (i=0;iRemoveActiveSong(songs[i]); songs[i]->Stop(); } l = ambient.Length(); for (i=0;iRemoveActiveAmbient(ambient[i]); ambient[i]->Stop(); } l = emitters.Length(); for (i=0;iRemoveActiveEmitter(emitters[i]); emitters[i]->Stop(); } } void psSectorSoundManager::NewBackground( psSoundObject* song ) { songs.Push( song ); } void psSectorSoundManager::NewAmbient( psSoundObject* sound ) { ambient.Push( sound ); } void psSectorSoundManager::New3DSound( psSoundObject* sound ) { emitters.Push( sound ); } void psSectorSoundManager::New3DMeshSound( psSoundObject* sound ) { unAssignedEmitters.Push( sound ); } void psSectorSoundManager::New3DFactorySound( psSoundObject* sound, const char* factname, float probability ) { unAssignedEmitterFactories.Push( new ps3DFactorySound(sound,factname,probability) ); } void psSectorSoundManager::Init3DFactorySounds() { if (unAssignedEmitterFactories.IsEmpty()) return; for (size_t z=0; zsound, sndfact->meshfactname.GetData(), sndfact->probability ); iSector* searchSector = engine->FindSector( sector, NULL ); if (!searchSector) { Error3("Sector %s not found for adding sounds for factory %s\n", sector.GetData(), sndfact->meshfactname.GetData() ); return; } iMeshFactoryWrapper* factory = engine->GetMeshFactories()->FindByName(sndfact->meshfactname); if (!factory) { Error2("Could not find factory name %s", sndfact->meshfactname.GetData() ); return; } csRef snddata = mapsoundsystem->sndmngr->GetSoundResource(sndfact->sound->GetName()); if (!snddata) { Error2("Failed to load Sound: %s", sndfact->sound->GetName().GetData() ); continue; } // Init to exact same random pattern every time, on every system csRandomGen* rng = new csRandomGen(0); iMeshList* meshes = searchSector->GetMeshes(); for (int i=0; i < meshes->GetCount(); i++) { iMeshWrapper* mesh = meshes->Get(i); if (mesh->GetFactory() == factory) { if (rng->Get() <= sndfact->probability) { csRef sndstream = mapsoundsystem->sndmngr->soundSystem->CreateStream(snddata,CS_SND3D_ABSOLUTE); if (!sndstream) { Error2("Failed to create Sound Stream: %s", sndfact->sound->GetName().GetData() ); continue; } psSoundObject* obj = new psSoundObject(sndfact->sound,sndstream); obj->AttachToMesh(mesh->QueryObject()->GetName()); csVector3 pos = mesh->GetMovable()->GetPosition(); obj->SetPosition(pos); emitters.Push(obj); //printf("New sound emitter at: %f, %f, %f\n",pos.x,pos.y,pos.z); } } } delete rng; delete sndfact; } unAssignedEmitterFactories.Empty(); } void psSectorSoundManager::StartBackground() { music = true; if ( mainBG ) { mapsoundsystem->RegisterActiveSong(mainBG); if(mapsoundsystem->sndmngr->LoopBGM()) mainBG->SetLooping(true); else mainBG->SetLooping(false); mainBG->StartFade( FADE_UP ); } else { if ( songs.Length() > 0 ) { mapsoundsystem->RegisterActiveSong(songs[0]); if(mapsoundsystem->sndmngr->LoopBGM()) songs[0]->SetLooping(true); else songs[0]->SetLooping(false); songs[0]->StartFade( FADE_UP ); mainBG = songs[0]; } } } void psSectorSoundManager::StopBackground() { music = false; for ( size_t z = 0; z < songs.Length(); z++ ) { mapsoundsystem->RemoveActiveSong(songs[z]); songs[z]->Stop(); } } void psSectorSoundManager::StartSounds(int weather) { sounds = true; size_t z; for ( z = 0; z < ambient.Length(); z++ ) { //TODO: matching time as well if (!ambient[z]->Triggered() || ambient[z]->MatchWeather(weather)) { mapsoundsystem->RegisterActiveAmbient(ambient[z]); ambient[z]->StartFade( FADE_UP ); } } for ( z = 0; z < emitters.Length(); z++ ) { mapsoundsystem->RegisterActiveEmitter(emitters[z]); emitters[z]->StartSound(); } } void psSectorSoundManager::StopSounds() { sounds = false; size_t z; for ( z = 0; z < ambient.Length(); z++ ) { mapsoundsystem->RemoveActiveAmbient(ambient[z]); ambient[z]->Stop(); } for ( z = 0; z < emitters.Length(); z++ ) { mapsoundsystem->RemoveActiveEmitter(emitters[z]); emitters[z]->Stop(); } } void psSectorSoundManager::Fade( Fade_Direction dir ) { if ( mainBG && HasMusic())//We should check that the sector can play music. { if (dir == FADE_UP) { mapsoundsystem->RegisterActiveSong(mainBG); } mainBG->StartFade( dir ); } size_t z; if (HasSounds())//Check if the sector can play sounds { for ( z = 0; z < ambient.Length(); z++ ) { if (dir == FADE_UP) mapsoundsystem->RegisterActiveSong(ambient[z]); if(ambient[z]->MatchWeather(weather)) ambient[z]->StartFade( dir ); } for ( z = 0; z < emitters.Length(); z++ ) { if (dir == FADE_UP) mapsoundsystem->RegisterActiveEmitter(emitters[z]); emitters[z]->StartFade( dir ); } } } void psSectorSoundManager::ChangeTime( int timeOfDay ) { psSoundObject* bestTimeSong = NULL; size_t z; if ( music ) { for ( z = 0; z < songs.Length(); z++ ) { if ( songs[z]->MatchTime( timeOfDay ) ) bestTimeSong = songs[z]; } } if ( bestTimeSong ) { Debug2( LOG_SOUND, 0, "Song now playing is: %s", bestTimeSong->GetName().GetData() ); if (!mapsoundsystem->FindSameActiveSong( bestTimeSong )) { mainBG->StartFade( FADE_DOWN ); mapsoundsystem->RegisterActiveSong(bestTimeSong); bestTimeSong->StartFade( FADE_UP ); mainBG = bestTimeSong; } } } void psSectorSoundManager::Enter( psSectorSoundManager* leaveFrom, int timeOfDay, int weather, csVector3& position ) { psSoundObject* bestTimeSong = NULL; psSoundObject* bestWeatherSong = NULL; this->weather = weather; size_t z; if ( music ) { for ( z = 0; z < songs.Length(); z++ ) { if ( songs[z]->MatchTime( timeOfDay ) ) { bestTimeSong = songs[z]; } if ( songs[z]->MatchWeather( weather ) ) { bestWeatherSong = songs[z]; } } if ( bestTimeSong ) { if (!mapsoundsystem->FindSameActiveSong( bestTimeSong )) { mapsoundsystem->RegisterActiveSong(bestTimeSong); bestTimeSong->StartFade( FADE_UP ); mainBG = bestTimeSong; } } else if ( bestWeatherSong ) { if (!mapsoundsystem->FindSameActiveSong( bestWeatherSong )) { mapsoundsystem->RegisterActiveSong(bestWeatherSong); bestWeatherSong->StartFade( FADE_UP ); mainBG = bestWeatherSong; } } else { if ( songs.Length() > 0 ) { if (!mapsoundsystem->FindSameActiveSong( songs[0] )) { mapsoundsystem->RegisterActiveSong(songs[0]); if(mapsoundsystem->sndmngr->LoopBGM()) songs[0]->SetLooping(true); else songs[0]->SetLooping(false); songs[0]->StartFade( FADE_UP ); mainBG = songs[0]; } } } } if ( sounds ) { for ( z = 0; z < ambient.Length(); z++ ) { if (!mapsoundsystem->FindSameActiveAmbient( ambient[z] ) && !ambient[z]->Triggered()) // Only start non-trigger sounds { mapsoundsystem->RegisterActiveAmbient(ambient[z]); ambient[z]->StartFade( FADE_UP ); } } if ( unAssignedEmitters.Length() > 0 ) { for ( z = 0; z < unAssignedEmitters.Length(); z++ ) { iSector* searchSector = engine->FindSector( sector, NULL ); if ( searchSector ) { psSoundObject* pso = unAssignedEmitters[z]; if(pso->Triggered()) // Only start non-trigger sounds continue; csRef mesh = searchSector->GetMeshes()->FindByName( pso->GetMesh() ); //csRef move = mesh->GetMovable(); csVector3 pos = mesh->GetRadius().GetCenter(); pso->SetPosition( pos ); emitters.Push( pso ); } } unAssignedEmitters.Empty(); } Init3DFactorySounds(); for ( z = 0; z < emitters.Length(); z++ ) { if(emitters[z]->Triggered()) // Only start non-trigger sounds continue; mapsoundsystem->RegisterActiveEmitter(emitters[z]); emitters[z]->Start3DSound( position ); } // Update weather UpdateWeather(weather); } } void psSectorSoundManager::UpdateWeather(int weather) { this->weather =weather; for(size_t i = 0; i < weatherNotify.Length();i++) { psSoundObject* obj = weatherNotify[i]; obj->Notify(SOUND_EVENT_WEATHER); } } void psSectorSoundManager::ConnectObject(psSoundObject* obj,int to) { switch(to) { case SOUND_EVENT_WEATHER: { weatherNotify.Push(obj); break; } } } void psSectorSoundManager::DisconnectObject(psSoundObject* obj,int from) { csArray* array = NULL; switch(from) { case SOUND_EVENT_WEATHER: { array = &weatherNotify; break; } } if(!array) return; // Not implemented event (hm?) // Remove the event array->Delete(obj); } bool psSectorSoundManager::CheckSong( psSoundObject* bgSound ) { if (!bgSound) return true; for ( size_t z = 0; z < songs.Length(); z++ ) { //if ( songs[z]->IsPlaying() && songs[z]->Same( bgSound ) ) if ( songs[z]->Same( bgSound ) ) return false; } return true; } bool psSectorSoundManager::CheckAmbient( psSoundObject* ambientSnd ) { if ( !ambientSnd ) return true; for ( size_t z = 0; z < ambient.Length(); z++ ) if ( ambient[z]->Same( ambientSnd ) ) return false; return true; } psSoundObject::psSoundObject(csRef strm, psMapSoundSystem* mapSys, float maxVol, float minVol, int fadeTime, int timeOfDay, int timeOfDayRange, int weather, bool looping, psSectorSoundManager* sector,int connectWith ) : stream(strm) { this->maxVol = maxVol; this->minVol = minVol; isPlaying = false; fadeComplete = false; fadeDelay = fadeTime; this->timeOfDay = timeOfDay; this->timeOfDayRange = timeOfDayRange; this->weatherCondition = weather; currentVolume = 0.0f; ambientVolume = 1.0f; connectedSector = sector; mapSystem = mapSys; connectedWith = connectWith; loop = looping; if (strm->Get3dMode() == CS_SND3D_DISABLE) threeDee = false; else threeDee = true; if (sector) sector->ConnectObject(this, connectWith); } psSoundObject::psSoundObject(psSoundObject* other, csRef strm) : stream(strm) { isPlaying = false; fadeComplete = false; currentVolume = 0.0f; ambientVolume = 1.0f; this->maxVol = other->maxVol; this->minVol = other->minVol; this->fadeDelay = other->fadeDelay; this->timeOfDay = other->timeOfDay; this->timeOfDayRange = other->timeOfDayRange; this->weatherCondition = other->weatherCondition; this->connectedSector = other->connectedSector; this->mapSystem = other->mapSystem; this->connectedWith = other->connectedWith; this->threeDee = other->threeDee; this->loop = other->loop; this->resourceName = other->resourceName; this->rangeToStart = other->rangeToStart; this->minRange = other->minRange; this->rangeConstant = other->rangeConstant; if (connectedSector) connectedSector->ConnectObject(this, connectedWith); } psSoundObject::~psSoundObject() { // Halt the sound in the sound system if (mapSystem && mapSystem->sndmngr && mapSystem->sndmngr->soundSystem) { stream.Stop(mapSystem->sndmngr->soundSystem); } // Disconnect if (connectedSector) connectedSector->DisconnectObject(this,connectedWith); } void psSoundObject::SetVolume(float vol) { ambientVolume = vol; if (isPlaying) stream.SetVolume( currentVolume * ambientVolume ); } void psSoundObject::StartSound() { if ( !stream.Start(mapSystem->sndmngr->soundSystem,loop) ) return; if (threeDee) { soundSource3D = stream.GetSource3D(); if (!soundSource3D) threeDee=false; else { soundSource3D->SetMinimumDistance(minRange); soundSource3D->SetMaximumDistance(rangeToStart); soundSource3D->SetPosition( position ); } } } void psSoundObject::Start3DSound( csVector3& playerPos ) { StartSound(); Update( playerPos ); } bool psSoundObject::Triggered() { if(connectedWith != 0) return true; return false; } void psSoundObject::Notify(int event) { if(event != connectedWith) return; // Handle event switch(event) { case SOUND_EVENT_WEATHER: { UpdateWeather(connectedSector->GetWeather()); break; } } } void psSoundObject::UpdateWeather(int weather) { if(!Triggered()) return; // Check old weather objects and mute if there are any if(mapSystem->FindSameActiveAmbient(this) && !MatchWeather(weather)) { mapSystem->RemoveActiveAmbient(this); Stop(); return; } // Same weather and is not playing? if(MatchWeather(weather) && !mapSystem->FindSameActiveAmbient(this)) { if( mapSystem->sndmngr->PlayingSounds() ) { mapSystem->RegisterActiveAmbient(this); // We're playing this now StartFade(FADE_UP); // PLAY! } return; } } void psSoundObject::Update( csVector3& playerPos ) { csVector3 rangeVec = position - playerPos; float range = rangeVec.Norm(); //printf("RANGE: %f\n", range ); if ( isPlaying && range > rangeToStart ) { stream.SetVolume(0.0f); isPlaying = false; } else if ( !isPlaying && range < rangeToStart ) { isPlaying = true; // Emitter volume is based on 3d positional audio calculations handled inside the sound system currentVolume = maxVol; //printf("Emitter Sound Vol: %f\n", currentVolume ); stream.SetVolume( currentVolume * ambientVolume ); } } void psSoundObject::StartFade( Fade_Direction dir ) { Debug3( LOG_SOUND, 0, "Fading Song %s Direction %d", GetName().GetData(), dir ); if ( dir == FADE_DOWN && currentVolume == minVol ) { fadeComplete = true; Debug1( LOG_SOUND, 0, "Fading complete" ); if (isPlaying) { isPlaying = false; stream.SetVolume(0.0f); } return; } else if ( dir == FADE_UP && currentVolume == maxVol ) { Debug1( LOG_SOUND, 0, "Fading complete" ); fadeComplete = true; if(!isPlaying) { isPlaying = true; stream.Start(mapSystem->sndmngr->soundSystem,loop); stream.SetVolume(currentVolume); } return; } fadeDir = dir; startTime = csGetTicks(); fadeComplete = false; if (!stream.IsValid()) { currentVolume = minVol; StartSound(); stream.SetVolume( minVol * ambientVolume ); Debug2( LOG_SOUND,0, "Sound source for %s is NULL. Setting currentVolume to minVol.",GetName().GetData()); } if ( stream.IsValid() ) { if (!isPlaying) { isPlaying = true; Debug2( LOG_SOUND, 0, "Sound Playing at: %f vol", currentVolume ); stream.SetVolume( currentVolume*ambientVolume ); } else { /*This is necessary in order to get rain fading out in the new sector, otherwise it doesn't*/ Update(); } } return; } void psSoundObject::SetRange( float maxRange, float minRange ) { rangeToStart = maxRange; this->minRange = minRange; rangeConstant = ( (maxVol-minVol)/(rangeToStart-minRange)); } bool psSoundObject::MatchTime( int time ) { int timeEnd = timeOfDay + timeOfDayRange; if ( time >= timeOfDay && time < timeEnd ) return true; else return false; } void psSoundObject::Update() { if ( fadeComplete == true ) { return; } csTicks currentTime = csGetTicks(); float diff = currentTime - startTime; if ( diff > fadeDelay ) { if ( fadeDir == FADE_UP ) currentVolume = maxVol; if ( fadeDir == FADE_DOWN ) { Debug2( LOG_SOUND,0, "diff > fadeDelay for %s. Setting currentVolume to minVol.",GetName().GetData()); currentVolume = minVol; } fadeComplete = true; } else { float percent = (float)diff / (float)fadeDelay; if ( fadeDir == FADE_UP ) { currentVolume = minVol + (maxVol-minVol)*percent; if ( currentVolume > maxVol ) { currentVolume = maxVol; fadeComplete = true; } } else { currentVolume = maxVol - (maxVol-minVol)*percent; if ( currentVolume <= minVol ) { fadeComplete = true; currentVolume = minVol; Debug2( LOG_SOUND,0, "currentVolume <= minVol for %s. Setting currentVolume to minVol.",GetName().GetData()); } } } if ( currentVolume <= minVol && fadeDir == FADE_DOWN ) { isPlaying = false; stream.SetVolume(0.0f); } else { stream.SetVolume( currentVolume * ambientVolume ); } } bool psSndStreamHandle::Start(iSndSysRenderer* renderer, bool loop) { if (!soundSource) soundSource = renderer->CreateSource(soundStream); // Failed to create source if (!soundSource) return false; soundStream->SetLoopState(loop); SetVolume(0.0f); return true; } void psSndStreamHandle::Stop(iSndSysRenderer* renderer) { renderer->RemoveSource(soundSource); renderer->RemoveStream(soundStream); } void psSndStreamHandle::SetVolume(float volume) { if ( !IsValid() ) return; soundSource->SetVolume(volume); if (volume < MIN_VOLUME) soundStream->Pause(); else soundStream->Unpause(); } csRef psSndStreamHandle::GetSource3D() { return SCF_QUERY_INTERFACE(soundSource,SOUND_SOURCE3D_TYPE); }