/* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "ISoundManager.h" #include "SoundManager.h" #include "ps/Filesystem.h" #include "soundmanager/data/SoundData.h" #include "soundmanager/items/CSoundItem.h" #include "soundmanager/items/CBufferItem.h" #include "soundmanager/items/CStreamItem.h" #include "lib/external_libraries/libsdl.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Profiler2.h" ISoundManager* g_SoundManager = NULL; #define SOURCE_NUM 64 #if CONFIG2_AUDIO class CSoundManagerWorker { NONCOPYABLE(CSoundManagerWorker); public: CSoundManagerWorker() { m_Items = new ItemsList; m_DeadItems = new ItemsList; m_Shutdown = false; m_Enabled = false; int ret = pthread_create(&m_WorkerThread, NULL, &RunThread, this); ENSURE(ret == 0); } ~CSoundManagerWorker() { delete m_Items; CleanupItems(); delete m_DeadItems; } void SetEnabled(bool enabled) { CScopeLock lock(m_WorkerMutex); if (enabled != m_Enabled) { m_Enabled = enabled; } } bool Shutdown() { { CScopeLock lock(m_WorkerMutex); m_Shutdown = true; m_Enabled = false; ItemsList::iterator lstr = m_Items->begin(); while (lstr != m_Items->end()) { delete *lstr; ++lstr; } } pthread_join(m_WorkerThread, NULL); return true; } void addItem( ISoundItem* anItem ) { CScopeLock lock(m_WorkerMutex); m_Items->push_back( anItem ); } void CleanupItems() { CScopeLock lock(m_DeadItemsMutex); AL_CHECK ItemsList::iterator deadItems = m_DeadItems->begin(); while (deadItems != m_DeadItems->end()) { delete *deadItems; ++deadItems; AL_CHECK } m_DeadItems->clear(); } private: static void* RunThread(void* data) { debug_SetThreadName("CSoundManagerWorker"); g_Profiler2.RegisterCurrentThread("soundmanager"); static_cast(data)->Run(); return NULL; } void Run() { while ( true ) { g_Profiler2.RecordRegionLeave("semaphore wait"); // Handle shutdown requests as soon as possible if (GetShutdown()) return; // If we're not enabled, ignore this wakeup if (!GetEnabled()) continue; int pauseTime = 500; if ( g_SoundManager->InDistress() ) pauseTime = 50; { CScopeLock lock(m_WorkerMutex); ItemsList::iterator lstr = m_Items->begin(); ItemsList* nextItemList = new ItemsList; while (lstr != m_Items->end()) { AL_CHECK if ((*lstr)->IdleTask()) { if ( (pauseTime == 1000) && (*lstr)->IsFading() ) pauseTime = 100; nextItemList->push_back(*lstr); } else { CScopeLock lock(m_DeadItemsMutex); m_DeadItems->push_back(*lstr); } ++lstr; AL_CHECK } delete m_Items; m_Items = nextItemList; AL_CHECK } SDL_Delay( pauseTime ); } } bool GetEnabled() { CScopeLock lock(m_WorkerMutex); return m_Enabled; } bool GetShutdown() { CScopeLock lock(m_WorkerMutex); return m_Shutdown; } private: // Thread-related members: pthread_t m_WorkerThread; CMutex m_WorkerMutex; CMutex m_DeadItemsMutex; // Shared by main thread and worker thread: // These variables are all protected by a mutexes ItemsList* m_Items; ItemsList* m_DeadItems; bool m_Enabled; bool m_Shutdown; CSoundManagerWorker(ISoundManager* UNUSED(other)){}; }; void ISoundManager::CreateSoundManager() { if ( !g_SoundManager ) g_SoundManager = new CSoundManager(); } void ISoundManager::SetEnabled(bool doEnable) { if ( g_SoundManager && !doEnable ) { SAFE_DELETE(g_SoundManager); } else if ( !g_SoundManager && doEnable ) { ISoundManager::CreateSoundManager(); } } void ISoundManager::CloseGame() { if ( CSoundManager* aSndMgr = (CSoundManager*)g_SoundManager ) aSndMgr->SetAmbientItem( NULL ); } void CSoundManager::al_ReportError(ALenum err, const char* caller, int line) { LOGERROR(L"OpenAL error: %hs; called from %hs (line %d)\n", alGetString(err), caller, line); } void CSoundManager::al_check(const char* caller, int line) { ALenum err = alGetError(); if (err != AL_NO_ERROR) al_ReportError(err, caller, line); } Status CSoundManager::ReloadChangedFiles(const VfsPath& UNUSED(path)) { // LOGERROR(L"GUI file '%ls' changed - reloading page", path.string().c_str()); return INFO::OK; } /*static*/ Status CSoundManager::ReloadChangedFileCB(void* param, const VfsPath& path) { return static_cast(param)->ReloadChangedFiles(path); } CSoundManager::CSoundManager() { m_CurrentEnvirons = 0; m_ALSourceBuffer = NULL; m_Device = NULL; m_Context = NULL; m_Worker = NULL; m_PlayListItems = NULL; m_CurrentTune = 0; m_Gain = 1; m_MusicGain = 1; m_AmbientGain = 1; m_ActionGain = 1; m_UIGain = 1; m_BufferCount = 50; m_BufferSize = 98304; m_SoundEnabled = true; m_MusicEnabled = true; m_MusicPaused = false; m_AmbientPaused = false; m_ActionPaused = false; m_DistressTime = 0; m_DistressErrCount = 0; m_PlayingPlaylist = false; m_LoopingPlaylist = false; m_RunningPlaylist = false; m_PlaylistGap = 0; m_Enabled = false; AlcInit(); if ( m_Enabled ) { InitListener(); m_PlayListItems = new PlayList; m_Worker = new CSoundManagerWorker(); m_Worker->SetEnabled( true ); } RegisterFileReloadFunc(ReloadChangedFileCB, this); } CSoundManager::~CSoundManager() { UnregisterFileReloadFunc(ReloadChangedFileCB, this); if (m_Worker ) { AL_CHECK m_Worker->Shutdown(); AL_CHECK m_Worker->CleanupItems(); AL_CHECK delete m_Worker; } AL_CHECK for (std::map::iterator it = m_SoundGroups.begin(); it != m_SoundGroups.end(); ++it) delete it->second; m_SoundGroups.clear(); if ( m_PlayListItems ) delete m_PlayListItems; if ( m_ALSourceBuffer != NULL ) delete[] m_ALSourceBuffer; if ( m_Context ) alcDestroyContext(m_Context); if ( m_Device ) alcCloseDevice(m_Device); } Status CSoundManager::AlcInit() { Status ret = INFO::OK; m_Device = alcOpenDevice(NULL); if (m_Device) { ALCint attribs[] = {ALC_STEREO_SOURCES, 16, 0}; m_Context = alcCreateContext(m_Device, &attribs[0]); if (m_Context) { alcMakeContextCurrent(m_Context); m_ALSourceBuffer = new ALSourceHolder[SOURCE_NUM]; ALuint* sourceList = new ALuint[SOURCE_NUM]; alGenSources( SOURCE_NUM, sourceList); ALCenum err = alcGetError(m_Device); if ( err == ALC_NO_ERROR ) { for ( int x=0; x 10 ) { m_DistressTime = 0; // Coming out of distress mode m_DistressErrCount = 0; return false; } return true; } void CSoundManager::SetDistressThroughShortage() { CScopeLock lock(m_DistressMutex); // Going into distress for normal reasons m_DistressTime = timer_Time(); } void CSoundManager::SetDistressThroughError() { CScopeLock lock(m_DistressMutex); // Going into distress due to unknown error m_DistressTime = timer_Time(); m_DistressErrCount++; } ALuint CSoundManager::GetALSource( ISoundItem* anItem) { for ( int x=0; xpush_back( *itemPath ); } void CSoundManager::ClearPlayListItems() { if ( m_PlayingPlaylist ) SetMusicItem( NULL ); m_PlayingPlaylist = false; m_LoopingPlaylist = false; m_RunningPlaylist = false; m_PlayListItems->clear(); } void CSoundManager::StartPlayList( bool doLoop ) { if ( m_MusicEnabled ) { if ( m_PlayListItems->size() > 0 ) { m_PlayingPlaylist = true; m_LoopingPlaylist = doLoop; m_RunningPlaylist = false; ISoundItem* aSnd = LoadItem( (m_PlayListItems->at( 0 )) ); if ( aSnd ) SetMusicItem( aSnd ); else { SetMusicItem( NULL ); } } } } void CSoundManager::SetMasterGain(float gain) { if ( m_Enabled ) { m_Gain = gain; alListenerf( AL_GAIN, m_Gain); AL_CHECK } } void CSoundManager::SetMusicGain(float gain) { m_MusicGain = gain; } void CSoundManager::SetAmbientGain(float gain) { m_AmbientGain = gain; } void CSoundManager::SetActionGain(float gain) { m_ActionGain = gain; } void CSoundManager::SetUIGain(float gain) { m_UIGain = gain; } ISoundItem* CSoundManager::LoadItem(const VfsPath& itemPath) { AL_CHECK if ( m_Enabled ) { CSoundData* itemData = CSoundData::SoundDataFromFile(itemPath); AL_CHECK if ( itemData ) return CSoundManager::ItemForData( itemData ); } return NULL; } ISoundItem* CSoundManager::ItemForData(CSoundData* itemData) { AL_CHECK ISoundItem* answer = NULL; AL_CHECK if ( m_Enabled && (itemData != NULL) ) { if (itemData->IsOneShot()) { if (itemData->GetBufferCount() == 1) answer = new CSoundItem(itemData); else answer = new CBufferItem(itemData); } else { answer = new CStreamItem(itemData); } if ( answer && m_Worker ) m_Worker->addItem( answer ); } return answer; } void CSoundManager::IdleTask() { if ( m_Enabled ) { if (m_CurrentTune) { m_CurrentTune->EnsurePlay(); if ( m_PlayingPlaylist && m_RunningPlaylist ) { if ( m_CurrentTune->Finished() ) { if ( m_PlaylistGap == 0 ) { m_PlaylistGap = timer_Time() + 15; } else if ( m_PlaylistGap < timer_Time() ) { m_PlaylistGap = 0; PlayList::iterator it = find (m_PlayListItems->begin(), m_PlayListItems->end(), *(m_CurrentTune->GetName()) ); if ( it != m_PlayListItems->end() ) { ++it; Path nextPath; if ( it == m_PlayListItems->end() ) nextPath = m_PlayListItems->at( 0 ); else nextPath = *it; ISoundItem* aSnd = LoadItem( nextPath ); if ( aSnd ) SetMusicItem( aSnd ); } } } } } if (m_CurrentEnvirons) m_CurrentEnvirons->EnsurePlay(); if (m_Worker) m_Worker->CleanupItems(); } } ISoundItem* CSoundManager::ItemForEntity( entity_id_t UNUSED(source), CSoundData* sndData) { ISoundItem* currentItem = NULL; if ( m_Enabled ) currentItem = ItemForData( sndData ); return currentItem; } void CSoundManager::InitListener() { ALfloat listenerPos[]={0.0,0.0,0.0}; ALfloat listenerVel[]={0.0,0.0,0.0}; ALfloat listenerOri[]={0.0,0.0,-1.0, 0.0,1.0,0.0}; alListenerfv(AL_POSITION,listenerPos); alListenerfv(AL_VELOCITY,listenerVel); alListenerfv(AL_ORIENTATION,listenerOri); alDistanceModel(AL_LINEAR_DISTANCE); } void CSoundManager::PlayGroupItem(ISoundItem* anItem, ALfloat groupGain ) { if (anItem) { if (m_Enabled && (m_ActionGain > 0)) { anItem->SetGain(m_ActionGain * groupGain); anItem->PlayAndDelete(); AL_CHECK } } } void CSoundManager::SetMusicEnabled (bool isEnabled) { LOGERROR(L"entering enabled area", isEnabled); if (m_CurrentTune && !isEnabled) { m_CurrentTune->FadeAndDelete(1.00); m_CurrentTune = 0L; } LOGERROR(L"exiting enabled area", isEnabled); m_MusicEnabled = isEnabled; } void CSoundManager::PlayAsGroup(const VfsPath& groupPath, CVector3D sourcePos, entity_id_t source, bool ownedSound) { // Make sure the sound group is loaded CSoundGroup* group; if (m_SoundGroups.find(groupPath.string()) == m_SoundGroups.end()) { group = new CSoundGroup(); if (!group->LoadSoundGroup(L"audio/" + groupPath.string() )) { LOGERROR(L"Failed to load sound group '%ls'", groupPath.string().c_str()); delete group; group = NULL; } // Cache the sound group (or the null, if it failed) m_SoundGroups[groupPath.string()] = group; } else { group = m_SoundGroups[groupPath.string()]; } // Failed to load group -> do nothing if ( group && ( ownedSound || !group->TestFlag( eOwnerOnly ) ) ) group->PlayNext(sourcePos, source); } void CSoundManager::PlayAsMusic( const VfsPath& itemPath, bool looping ) { UNUSED2( looping ); ISoundItem* aSnd = LoadItem(itemPath); if (aSnd != NULL) SetMusicItem( aSnd ); } void CSoundManager::PlayAsAmbient( const VfsPath& itemPath, bool looping ) { UNUSED2( looping ); ISoundItem* aSnd = LoadItem(itemPath); if (aSnd != NULL) SetAmbientItem( aSnd ); } void CSoundManager::PlayAsUI(const VfsPath& itemPath, bool looping) { IdleTask(); if ( ISoundItem* anItem = LoadItem(itemPath) ) { if (m_Enabled && (m_UIGain > 0)) { anItem->SetGain(m_UIGain); anItem->SetLooping( looping ); anItem->PlayAndDelete(); } } AL_CHECK } void CSoundManager::Pause(bool pauseIt) { PauseMusic(pauseIt); PauseAmbient(pauseIt); PauseAction(pauseIt); } void CSoundManager::PauseMusic (bool pauseIt) { if (m_CurrentTune && pauseIt && !m_MusicPaused ) { m_CurrentTune->FadeAndPause( 1.0 ); } else if ( m_CurrentTune && m_MusicPaused && !pauseIt && m_MusicEnabled ) { m_CurrentTune->SetGain(0); m_CurrentTune->Resume(); m_CurrentTune->FadeToIn( m_MusicGain, 1.0); } m_MusicPaused = pauseIt; } void CSoundManager::PauseAmbient (bool pauseIt) { if (m_CurrentEnvirons && pauseIt) m_CurrentEnvirons->Pause(); else if ( m_CurrentEnvirons ) m_CurrentEnvirons->Resume(); m_AmbientPaused = pauseIt; } void CSoundManager::PauseAction (bool pauseIt) { m_ActionPaused = pauseIt; } void CSoundManager::SetMusicItem(ISoundItem* anItem) { AL_CHECK if (m_CurrentTune) { m_CurrentTune->FadeAndDelete(2.00); m_CurrentTune = 0L; } IdleTask(); if (anItem) { if (m_MusicEnabled && m_Enabled) { m_CurrentTune = anItem; m_CurrentTune->SetGain(0); if ( m_PlayingPlaylist ) { m_RunningPlaylist = true; m_CurrentTune->Play(); } else m_CurrentTune->PlayLoop(); m_MusicPaused = false; m_CurrentTune->FadeToIn( m_MusicGain, 1.00); } else { anItem->StopAndDelete(); } } AL_CHECK } void CSoundManager::SetAmbientItem(ISoundItem* anItem) { if (m_CurrentEnvirons) { m_CurrentEnvirons->FadeAndDelete(3.00); m_CurrentEnvirons = 0L; } IdleTask(); if (anItem) { if (m_Enabled && (m_AmbientGain > 0)) { m_CurrentEnvirons = anItem; m_CurrentEnvirons->SetGain(0); m_CurrentEnvirons->PlayLoop(); m_CurrentEnvirons->FadeToIn( m_AmbientGain, 2.00); } } AL_CHECK } #else // CONFIG2_AUDIO void ISoundManager::CreateSoundManager(){} void ISoundManager::SetEnabled(bool UNUSED(doEnable)){} void ISoundManager::CloseGame(){} #endif // CONFIG2_AUDIO