[Gameplay] Reduce loom cost
[0ad.git] / source / soundmanager / SoundManager.cpp
blobb425491e2c63812d6c31df4f2a1e21289391529e
1 /* Copyright (C) 2021 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
18 #include "precompiled.h"
20 #include "ISoundManager.h"
21 #include "SoundManager.h"
22 #include "data/SoundData.h"
23 #include "items/CBufferItem.h"
24 #include "items/CSoundItem.h"
25 #include "items/CStreamItem.h"
27 #include "lib/external_libraries/libsdl.h"
28 #include "ps/CLogger.h"
29 #include "ps/CStr.h"
30 #include "ps/ConfigDB.h"
31 #include "ps/Filesystem.h"
32 #include "ps/Profiler2.h"
33 #include "ps/Threading.h"
34 #include "ps/XML/Xeromyces.h"
36 #include <thread>
38 ISoundManager* g_SoundManager = NULL;
40 #define SOURCE_NUM 64
42 #if CONFIG2_AUDIO
44 class CSoundManagerWorker
46 NONCOPYABLE(CSoundManagerWorker);
48 public:
49 CSoundManagerWorker()
51 m_Items = new ItemsList;
52 m_DeadItems = new ItemsList;
53 m_Shutdown = false;
55 m_WorkerThread = std::thread(Threading::HandleExceptions<RunThread>::Wrapper, this);
58 ~CSoundManagerWorker()
60 delete m_Items;
61 CleanupItems();
62 delete m_DeadItems;
65 bool Shutdown()
68 std::lock_guard<std::mutex> lock(m_WorkerMutex);
70 m_Shutdown = true;
72 ItemsList::iterator lstr = m_Items->begin();
73 while (lstr != m_Items->end())
75 delete *lstr;
76 ++lstr;
81 m_WorkerThread.join();
83 return true;
86 void addItem(ISoundItem* anItem)
88 std::lock_guard<std::mutex> lock(m_WorkerMutex);
89 m_Items->push_back(anItem);
92 void CleanupItems()
94 std::lock_guard<std::mutex> lock(m_DeadItemsMutex);
95 AL_CHECK;
96 ItemsList::iterator deadItems = m_DeadItems->begin();
97 while (deadItems != m_DeadItems->end())
99 delete *deadItems;
100 ++deadItems;
102 AL_CHECK;
104 m_DeadItems->clear();
107 private:
108 static void RunThread(CSoundManagerWorker* data)
110 debug_SetThreadName("CSoundManagerWorker");
111 g_Profiler2.RegisterCurrentThread("soundmanager");
113 data->Run();
116 void Run()
118 while (true)
120 // Handle shutdown requests as soon as possible
121 if (GetShutdown())
122 return;
124 int pauseTime = 500;
125 if (g_SoundManager->InDistress())
126 pauseTime = 50;
129 std::lock_guard<std::mutex> workerLock(m_WorkerMutex);
131 ItemsList::iterator lstr = m_Items->begin();
132 ItemsList* nextItemList = new ItemsList;
134 while (lstr != m_Items->end())
136 AL_CHECK;
137 if ((*lstr)->IdleTask())
139 if ((pauseTime == 500) && (*lstr)->IsFading())
140 pauseTime = 100;
142 nextItemList->push_back(*lstr);
144 else
146 std::lock_guard<std::mutex> deadItemsLock(m_DeadItemsMutex);
147 m_DeadItems->push_back(*lstr);
149 ++lstr;
151 AL_CHECK;
154 delete m_Items;
155 m_Items = nextItemList;
157 AL_CHECK;
159 SDL_Delay(pauseTime);
163 bool GetShutdown()
165 std::lock_guard<std::mutex> lock(m_WorkerMutex);
166 return m_Shutdown;
169 private:
170 // Thread-related members:
171 std::thread m_WorkerThread;
172 std::mutex m_WorkerMutex;
173 std::mutex m_DeadItemsMutex;
175 // Shared by main thread and worker thread:
176 // These variables are all protected by a mutexes
177 ItemsList* m_Items;
178 ItemsList* m_DeadItems;
180 bool m_Shutdown;
182 CSoundManagerWorker(ISoundManager* UNUSED(other)){};
185 void ISoundManager::CreateSoundManager()
187 if (g_SoundManager)
188 return;
190 ALCdevice* device = alcOpenDevice(nullptr);
191 if (!device)
193 LOGWARNING("No audio device was found.");
194 return;
197 g_SoundManager = new CSoundManager(device);
198 g_SoundManager->StartWorker();
201 void ISoundManager::SetEnabled(bool doEnable)
203 if (g_SoundManager && !doEnable)
204 SAFE_DELETE(g_SoundManager);
205 else if (!g_SoundManager && doEnable)
206 ISoundManager::CreateSoundManager();
208 void ISoundManager::CloseGame()
210 if (CSoundManager* aSndMgr = (CSoundManager*)g_SoundManager)
211 aSndMgr->SetAmbientItem(NULL);
214 void CSoundManager::al_ReportError(ALenum err, const char* caller, int line)
216 LOGERROR("OpenAL error: %s; called from %s (line %d)\n", alGetString(err), caller, line);
219 void CSoundManager::al_check(const char* caller, int line)
221 ALenum err = alGetError();
222 if (err != AL_NO_ERROR)
223 al_ReportError(err, caller, line);
226 Status CSoundManager::ReloadChangedFiles(const VfsPath& UNUSED(path))
228 // TODO implement sound file hotloading
229 return INFO::OK;
232 /*static*/ Status CSoundManager::ReloadChangedFileCB(void* param, const VfsPath& path)
234 return static_cast<CSoundManager*>(param)->ReloadChangedFiles(path);
237 CSoundManager::CSoundManager(ALCdevice* device)
238 : m_Context(nullptr), m_Device(device), m_ALSourceBuffer(nullptr),
239 m_CurrentTune(nullptr), m_CurrentEnvirons(nullptr),
240 m_Worker(nullptr), m_DistressMutex(), m_PlayListItems(nullptr), m_SoundGroups(),
241 m_Gain(.5f), m_MusicGain(.5f), m_AmbientGain(.5f), m_ActionGain(.5f), m_UIGain(.5f),
242 m_Enabled(false), m_BufferSize(98304), m_BufferCount(50),
243 m_SoundEnabled(true), m_MusicEnabled(true), m_MusicPaused(false),
244 m_AmbientPaused(false), m_ActionPaused(false),
245 m_RunningPlaylist(false), m_PlayingPlaylist(false), m_LoopingPlaylist(false),
246 m_PlaylistGap(0), m_DistressErrCount(0), m_DistressTime(0)
248 CFG_GET_VAL("sound.mastergain", m_Gain);
249 CFG_GET_VAL("sound.musicgain", m_MusicGain);
250 CFG_GET_VAL("sound.ambientgain", m_AmbientGain);
251 CFG_GET_VAL("sound.actiongain", m_ActionGain);
252 CFG_GET_VAL("sound.uigain", m_UIGain);
254 AlcInit();
256 if (m_Enabled)
258 SetMasterGain(m_Gain);
259 InitListener();
261 m_PlayListItems = new PlayList;
264 if (!CXeromyces::AddValidator(g_VFS, "sound_group", "audio/sound_group.rng"))
265 LOGERROR("CSoundManager: failed to load grammar file 'audio/sound_group.rng'");
267 RegisterFileReloadFunc(ReloadChangedFileCB, this);
269 RunHardwareDetection();
272 CSoundManager::~CSoundManager()
274 UnregisterFileReloadFunc(ReloadChangedFileCB, this);
276 if (m_Worker)
278 AL_CHECK;
279 m_Worker->Shutdown();
280 AL_CHECK;
281 m_Worker->CleanupItems();
282 AL_CHECK;
284 delete m_Worker;
286 AL_CHECK;
288 for (const std::pair<const std::wstring, CSoundGroup*>& p : m_SoundGroups)
289 delete p.second;
290 m_SoundGroups.clear();
292 if (m_PlayListItems)
293 delete m_PlayListItems;
295 if (m_ALSourceBuffer != NULL)
296 delete[] m_ALSourceBuffer;
298 if (m_Context)
299 alcDestroyContext(m_Context);
301 if (m_Device)
302 alcCloseDevice(m_Device);
305 void CSoundManager::StartWorker()
307 if (m_Enabled)
308 m_Worker = new CSoundManagerWorker();
311 Status CSoundManager::AlcInit()
313 Status ret = INFO::OK;
315 if(!m_Device)
316 m_Device = alcOpenDevice(nullptr);
318 if (m_Device)
320 ALCint attribs[] = {ALC_STEREO_SOURCES, 16, 0};
321 m_Context = alcCreateContext(m_Device, &attribs[0]);
323 if (m_Context)
325 alcMakeContextCurrent(m_Context);
326 m_ALSourceBuffer = new ALSourceHolder[SOURCE_NUM];
327 ALuint* sourceList = new ALuint[SOURCE_NUM];
329 alGenSources(SOURCE_NUM, sourceList);
330 ALCenum err = alcGetError(m_Device);
332 if (err == ALC_NO_ERROR)
334 for (int x = 0; x < SOURCE_NUM; x++)
336 m_ALSourceBuffer[x].ALSource = sourceList[x];
337 m_ALSourceBuffer[x].SourceItem = NULL;
339 m_Enabled = true;
341 else
343 LOGERROR("error in gensource = %d", err);
345 delete[] sourceList;
349 // check if init succeeded.
350 // some OpenAL implementations don't indicate failure here correctly;
351 // we need to check if the device and context pointers are actually valid.
352 ALCenum err = alcGetError(m_Device);
353 const char* dev_name = (const char*)alcGetString(m_Device, ALC_DEVICE_SPECIFIER);
355 if (err == ALC_NO_ERROR && m_Device && m_Context)
356 debug_printf("Sound: AlcInit success, using %s\n", dev_name);
357 else
359 LOGERROR("Sound: AlcInit failed, m_Device=%p m_Context=%p dev_name=%s err=%x\n", (void *)m_Device, (void *)m_Context, dev_name, err);
361 // FIXME Hack to get around exclusive access to the sound device
362 #if OS_UNIX
363 ret = INFO::OK;
364 #else
365 ret = ERR::FAIL;
366 #endif // !OS_UNIX
369 return ret;
372 bool CSoundManager::InDistress()
374 std::lock_guard<std::mutex> lock(m_DistressMutex);
376 if (m_DistressTime == 0)
377 return false;
378 else if ((timer_Time() - m_DistressTime) > 10)
380 m_DistressTime = 0;
381 // Coming out of distress mode
382 m_DistressErrCount = 0;
383 return false;
386 return true;
389 void CSoundManager::SetDistressThroughShortage()
391 std::lock_guard<std::mutex> lock(m_DistressMutex);
393 // Going into distress for normal reasons
395 m_DistressTime = timer_Time();
398 void CSoundManager::SetDistressThroughError()
400 std::lock_guard<std::mutex> lock(m_DistressMutex);
402 // Going into distress due to unknown error
404 m_DistressTime = timer_Time();
405 m_DistressErrCount++;
410 ALuint CSoundManager::GetALSource(ISoundItem* anItem)
412 for (int x = 0; x < SOURCE_NUM; x++)
414 if (!m_ALSourceBuffer[x].SourceItem)
416 m_ALSourceBuffer[x].SourceItem = anItem;
417 return m_ALSourceBuffer[x].ALSource;
420 SetDistressThroughShortage();
421 return 0;
424 void CSoundManager::ReleaseALSource(ALuint theSource)
426 for (int x = 0; x < SOURCE_NUM; x++)
428 if (m_ALSourceBuffer[x].ALSource == theSource)
430 m_ALSourceBuffer[x].SourceItem = NULL;
431 return;
436 long CSoundManager::GetBufferCount()
438 return m_BufferCount;
440 long CSoundManager::GetBufferSize()
442 return m_BufferSize;
445 void CSoundManager::AddPlayListItem(const VfsPath& itemPath)
447 if (m_Enabled)
448 m_PlayListItems->push_back(itemPath);
451 void CSoundManager::ClearPlayListItems()
453 if (m_Enabled)
455 if (m_PlayingPlaylist)
456 SetMusicItem(NULL);
458 m_PlayingPlaylist = false;
459 m_LoopingPlaylist = false;
460 m_RunningPlaylist = false;
462 m_PlayListItems->clear();
466 void CSoundManager::StartPlayList(bool doLoop)
468 if (m_Enabled && m_MusicEnabled)
470 if (m_PlayListItems->size() > 0)
472 m_PlayingPlaylist = true;
473 m_LoopingPlaylist = doLoop;
474 m_RunningPlaylist = false;
476 ISoundItem* aSnd = LoadItem((m_PlayListItems->at(0)));
477 if (aSnd)
478 SetMusicItem(aSnd);
479 else
480 SetMusicItem(NULL);
485 void CSoundManager::SetMasterGain(float gain)
487 if (m_Enabled)
489 m_Gain = gain;
490 alListenerf(AL_GAIN, m_Gain);
491 AL_CHECK;
495 void CSoundManager::SetMusicGain(float gain)
497 m_MusicGain = gain;
499 if (m_CurrentTune)
500 m_CurrentTune->SetGain(m_MusicGain);
502 void CSoundManager::SetAmbientGain(float gain)
504 m_AmbientGain = gain;
506 void CSoundManager::SetActionGain(float gain)
508 m_ActionGain = gain;
510 void CSoundManager::SetUIGain(float gain)
512 m_UIGain = gain;
516 ISoundItem* CSoundManager::LoadItem(const VfsPath& itemPath)
518 AL_CHECK;
520 if (m_Enabled)
522 CSoundData* itemData = CSoundData::SoundDataFromFile(itemPath);
524 AL_CHECK;
525 if (itemData)
526 return CSoundManager::ItemForData(itemData);
529 return NULL;
532 ISoundItem* CSoundManager::ItemForData(CSoundData* itemData)
534 AL_CHECK;
535 ISoundItem* answer = NULL;
537 AL_CHECK;
539 if (m_Enabled && (itemData != NULL))
541 if (itemData->IsOneShot())
543 if (itemData->GetBufferCount() == 1)
544 answer = new CSoundItem(itemData);
545 else
546 answer = new CBufferItem(itemData);
548 else
550 answer = new CStreamItem(itemData);
553 if (answer && m_Worker)
554 m_Worker->addItem(answer);
557 return answer;
560 void CSoundManager::IdleTask()
562 if (m_Enabled)
564 if (m_CurrentTune)
566 m_CurrentTune->EnsurePlay();
567 if (m_PlayingPlaylist && m_RunningPlaylist)
569 if (m_CurrentTune->Finished())
571 if (m_PlaylistGap == 0)
573 m_PlaylistGap = timer_Time() + 15;
575 else if (m_PlaylistGap < timer_Time())
577 m_PlaylistGap = 0;
578 PlayList::iterator it = find(m_PlayListItems->begin(), m_PlayListItems->end(), m_CurrentTune->GetName());
579 if (it != m_PlayListItems->end())
581 ++it;
583 Path nextPath;
584 if (it == m_PlayListItems->end())
585 nextPath = m_PlayListItems->at(0);
586 else
587 nextPath = *it;
589 ISoundItem* aSnd = LoadItem(nextPath);
590 if (aSnd)
591 SetMusicItem(aSnd);
598 if (m_CurrentEnvirons)
599 m_CurrentEnvirons->EnsurePlay();
601 if (m_Worker)
602 m_Worker->CleanupItems();
606 ISoundItem* CSoundManager::ItemForEntity(entity_id_t UNUSED(source), CSoundData* sndData)
608 ISoundItem* currentItem = NULL;
610 if (m_Enabled)
611 currentItem = ItemForData(sndData);
613 return currentItem;
617 void CSoundManager::InitListener()
619 ALfloat listenerPos[] = {0.0, 0.0, 0.0};
620 ALfloat listenerVel[] = {0.0, 0.0, 0.0};
621 ALfloat listenerOri[] = {0.0, 0.0, -1.0, 0.0, 1.0, 0.0};
623 alListenerfv(AL_POSITION, listenerPos);
624 alListenerfv(AL_VELOCITY, listenerVel);
625 alListenerfv(AL_ORIENTATION, listenerOri);
627 alDistanceModel(AL_LINEAR_DISTANCE);
630 void CSoundManager::PlayGroupItem(ISoundItem* anItem, ALfloat groupGain)
632 if (anItem)
634 if (m_Enabled && (m_ActionGain > 0))
636 anItem->SetGain(m_ActionGain * groupGain);
637 anItem->PlayAndDelete();
638 AL_CHECK;
643 void CSoundManager::SetMusicEnabled(bool isEnabled)
645 if (m_CurrentTune && !isEnabled)
647 m_CurrentTune->FadeAndDelete(1.00);
648 m_CurrentTune = NULL;
650 m_MusicEnabled = isEnabled;
653 void CSoundManager::PlayAsGroup(const VfsPath& groupPath, const CVector3D& sourcePos, entity_id_t source, bool ownedSound)
655 // Make sure the sound group is loaded
656 CSoundGroup* group;
657 if (m_SoundGroups.find(groupPath.string()) == m_SoundGroups.end())
659 group = new CSoundGroup();
660 if (!group->LoadSoundGroup(L"audio/" + groupPath.string()))
662 LOGERROR("Failed to load sound group '%s'", groupPath.string8());
663 delete group;
664 group = NULL;
666 // Cache the sound group (or the null, if it failed)
667 m_SoundGroups[groupPath.string()] = group;
669 else
671 group = m_SoundGroups[groupPath.string()];
674 // Failed to load group -> do nothing
675 if (group && (ownedSound || !group->TestFlag(eOwnerOnly)))
676 group->PlayNext(sourcePos, source);
679 void CSoundManager::PlayAsMusic(const VfsPath& itemPath, bool looping)
681 if (m_Enabled)
683 UNUSED2(looping);
685 ISoundItem* aSnd = LoadItem(itemPath);
686 if (aSnd != NULL)
687 SetMusicItem(aSnd);
691 void CSoundManager::PlayAsAmbient(const VfsPath& itemPath, bool looping)
693 if (m_Enabled)
695 UNUSED2(looping);
696 ISoundItem* aSnd = LoadItem(itemPath);
697 if (aSnd != NULL)
698 SetAmbientItem(aSnd);
703 void CSoundManager::PlayAsUI(const VfsPath& itemPath, bool looping)
705 if (m_Enabled)
707 IdleTask();
709 if (ISoundItem* anItem = LoadItem(itemPath))
711 if (m_UIGain > 0)
713 anItem->SetGain(m_UIGain);
714 anItem->SetLooping(looping);
715 anItem->PlayAndDelete();
718 AL_CHECK;
722 void CSoundManager::Pause(bool pauseIt)
724 PauseMusic(pauseIt);
725 PauseAmbient(pauseIt);
726 PauseAction(pauseIt);
729 void CSoundManager::PauseMusic(bool pauseIt)
731 if (m_CurrentTune && pauseIt && !m_MusicPaused)
733 m_CurrentTune->FadeAndPause(1.0);
735 else if (m_CurrentTune && m_MusicPaused && !pauseIt && m_MusicEnabled)
737 m_CurrentTune->SetGain(0);
738 m_CurrentTune->Resume();
739 m_CurrentTune->FadeToIn(m_MusicGain, 1.0);
741 m_MusicPaused = pauseIt;
744 void CSoundManager::PauseAmbient(bool pauseIt)
746 if (m_CurrentEnvirons && pauseIt)
747 m_CurrentEnvirons->Pause();
748 else if (m_CurrentEnvirons)
749 m_CurrentEnvirons->Resume();
751 m_AmbientPaused = pauseIt;
754 void CSoundManager::PauseAction(bool pauseIt)
756 m_ActionPaused = pauseIt;
759 void CSoundManager::SetMusicItem(ISoundItem* anItem)
761 if (m_Enabled)
763 AL_CHECK;
764 if (m_CurrentTune)
766 m_CurrentTune->FadeAndDelete(2.00);
767 m_CurrentTune = NULL;
770 IdleTask();
772 if (anItem)
774 if (m_MusicEnabled)
776 m_CurrentTune = anItem;
777 m_CurrentTune->SetGain(0);
779 if (m_PlayingPlaylist)
781 m_RunningPlaylist = true;
782 m_CurrentTune->Play();
784 else
785 m_CurrentTune->PlayLoop();
787 m_MusicPaused = false;
788 m_CurrentTune->FadeToIn(m_MusicGain, 1.00);
790 else
792 anItem->StopAndDelete();
795 AL_CHECK;
799 void CSoundManager::SetAmbientItem(ISoundItem* anItem)
801 if (m_Enabled)
803 if (m_CurrentEnvirons)
805 m_CurrentEnvirons->FadeAndDelete(3.00);
806 m_CurrentEnvirons = NULL;
808 IdleTask();
810 if (anItem)
812 if (m_AmbientGain > 0)
814 m_CurrentEnvirons = anItem;
815 m_CurrentEnvirons->SetGain(0);
816 m_CurrentEnvirons->PlayLoop();
817 m_CurrentEnvirons->FadeToIn(m_AmbientGain, 2.00);
820 AL_CHECK;
824 void CSoundManager::RunHardwareDetection()
826 // OpenAL alGetString might not return anything interesting on certain platforms
827 // (see https://stackoverflow.com/questions/28960638 for an example).
828 // However our previous code supported only Windows, and alGetString does work on
829 // Windows, so this is an improvement.
831 // Sound cards
833 const ALCchar* devices = nullptr;
834 if (alcIsExtensionPresent(nullptr, "ALC_enumeration_EXT") == AL_TRUE)
836 if (alcIsExtensionPresent(nullptr, "ALC_enumerate_all_EXT") == AL_TRUE)
837 devices = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
838 else
839 devices = alcGetString(nullptr, ALC_DEVICE_SPECIFIER);
841 WARN_IF_FALSE(devices);
843 m_SoundCardNames.clear();
846 m_SoundCardNames += devices;
847 devices += strlen(devices) + 1;
848 m_SoundCardNames += "; ";
849 } while (*devices);
851 // Driver version
852 const ALCchar* al_version = alGetString(AL_VERSION);
853 if (al_version)
854 m_OpenALVersion = al_version;
857 CStr8 CSoundManager::GetOpenALVersion() const
859 return m_OpenALVersion;
862 CStr8 CSoundManager::GetSoundCardNames() const
864 return m_SoundCardNames;
867 #else // CONFIG2_AUDIO
869 void ISoundManager::CreateSoundManager(){}
870 void ISoundManager::SetEnabled(bool UNUSED(doEnable)){}
871 void ISoundManager::CloseGame(){}
872 void ISoundManager::RunHardwareDetection() {}
873 CStr8 ISoundManager::GetSoundCardNames() const { return CStr8(); };
874 CStr8 ISoundManager::GetOpenALVersion() const { return CStr8(); };
875 #endif // CONFIG2_AUDIO