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"
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"
38 ISoundManager
* g_SoundManager
= NULL
;
44 class CSoundManagerWorker
46 NONCOPYABLE(CSoundManagerWorker
);
51 m_Items
= new ItemsList
;
52 m_DeadItems
= new ItemsList
;
55 m_WorkerThread
= std::thread(Threading::HandleExceptions
<RunThread
>::Wrapper
, this);
58 ~CSoundManagerWorker()
68 std::lock_guard
<std::mutex
> lock(m_WorkerMutex
);
72 ItemsList::iterator lstr
= m_Items
->begin();
73 while (lstr
!= m_Items
->end())
81 m_WorkerThread
.join();
86 void addItem(ISoundItem
* anItem
)
88 std::lock_guard
<std::mutex
> lock(m_WorkerMutex
);
89 m_Items
->push_back(anItem
);
94 std::lock_guard
<std::mutex
> lock(m_DeadItemsMutex
);
96 ItemsList::iterator deadItems
= m_DeadItems
->begin();
97 while (deadItems
!= m_DeadItems
->end())
104 m_DeadItems
->clear();
108 static void RunThread(CSoundManagerWorker
* data
)
110 debug_SetThreadName("CSoundManagerWorker");
111 g_Profiler2
.RegisterCurrentThread("soundmanager");
120 // Handle shutdown requests as soon as possible
125 if (g_SoundManager
->InDistress())
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())
137 if ((*lstr
)->IdleTask())
139 if ((pauseTime
== 500) && (*lstr
)->IsFading())
142 nextItemList
->push_back(*lstr
);
146 std::lock_guard
<std::mutex
> deadItemsLock(m_DeadItemsMutex
);
147 m_DeadItems
->push_back(*lstr
);
155 m_Items
= nextItemList
;
159 SDL_Delay(pauseTime
);
165 std::lock_guard
<std::mutex
> lock(m_WorkerMutex
);
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
178 ItemsList
* m_DeadItems
;
182 CSoundManagerWorker(ISoundManager
* UNUSED(other
)){};
185 void ISoundManager::CreateSoundManager()
190 ALCdevice
* device
= alcOpenDevice(nullptr);
193 LOGWARNING("No audio device was found.");
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
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
);
258 SetMasterGain(m_Gain
);
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);
279 m_Worker
->Shutdown();
281 m_Worker
->CleanupItems();
288 for (const std::pair
<const std::wstring
, CSoundGroup
*>& p
: m_SoundGroups
)
290 m_SoundGroups
.clear();
293 delete m_PlayListItems
;
295 if (m_ALSourceBuffer
!= NULL
)
296 delete[] m_ALSourceBuffer
;
299 alcDestroyContext(m_Context
);
302 alcCloseDevice(m_Device
);
305 void CSoundManager::StartWorker()
308 m_Worker
= new CSoundManagerWorker();
311 Status
CSoundManager::AlcInit()
313 Status ret
= INFO::OK
;
316 m_Device
= alcOpenDevice(nullptr);
320 ALCint attribs
[] = {ALC_STEREO_SOURCES
, 16, 0};
321 m_Context
= alcCreateContext(m_Device
, &attribs
[0]);
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
;
343 LOGERROR("error in gensource = %d", err
);
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
);
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
372 bool CSoundManager::InDistress()
374 std::lock_guard
<std::mutex
> lock(m_DistressMutex
);
376 if (m_DistressTime
== 0)
378 else if ((timer_Time() - m_DistressTime
) > 10)
381 // Coming out of distress mode
382 m_DistressErrCount
= 0;
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();
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
;
436 long CSoundManager::GetBufferCount()
438 return m_BufferCount
;
440 long CSoundManager::GetBufferSize()
445 void CSoundManager::AddPlayListItem(const VfsPath
& itemPath
)
448 m_PlayListItems
->push_back(itemPath
);
451 void CSoundManager::ClearPlayListItems()
455 if (m_PlayingPlaylist
)
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)));
485 void CSoundManager::SetMasterGain(float gain
)
490 alListenerf(AL_GAIN
, m_Gain
);
495 void CSoundManager::SetMusicGain(float gain
)
500 m_CurrentTune
->SetGain(m_MusicGain
);
502 void CSoundManager::SetAmbientGain(float gain
)
504 m_AmbientGain
= gain
;
506 void CSoundManager::SetActionGain(float gain
)
510 void CSoundManager::SetUIGain(float gain
)
516 ISoundItem
* CSoundManager::LoadItem(const VfsPath
& itemPath
)
522 CSoundData
* itemData
= CSoundData::SoundDataFromFile(itemPath
);
526 return CSoundManager::ItemForData(itemData
);
532 ISoundItem
* CSoundManager::ItemForData(CSoundData
* itemData
)
535 ISoundItem
* answer
= NULL
;
539 if (m_Enabled
&& (itemData
!= NULL
))
541 if (itemData
->IsOneShot())
543 if (itemData
->GetBufferCount() == 1)
544 answer
= new CSoundItem(itemData
);
546 answer
= new CBufferItem(itemData
);
550 answer
= new CStreamItem(itemData
);
553 if (answer
&& m_Worker
)
554 m_Worker
->addItem(answer
);
560 void CSoundManager::IdleTask()
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())
578 PlayList::iterator it
= find(m_PlayListItems
->begin(), m_PlayListItems
->end(), m_CurrentTune
->GetName());
579 if (it
!= m_PlayListItems
->end())
584 if (it
== m_PlayListItems
->end())
585 nextPath
= m_PlayListItems
->at(0);
589 ISoundItem
* aSnd
= LoadItem(nextPath
);
598 if (m_CurrentEnvirons
)
599 m_CurrentEnvirons
->EnsurePlay();
602 m_Worker
->CleanupItems();
606 ISoundItem
* CSoundManager::ItemForEntity(entity_id_t
UNUSED(source
), CSoundData
* sndData
)
608 ISoundItem
* currentItem
= NULL
;
611 currentItem
= ItemForData(sndData
);
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
)
634 if (m_Enabled
&& (m_ActionGain
> 0))
636 anItem
->SetGain(m_ActionGain
* groupGain
);
637 anItem
->PlayAndDelete();
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
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());
666 // Cache the sound group (or the null, if it failed)
667 m_SoundGroups
[groupPath
.string()] = group
;
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
)
685 ISoundItem
* aSnd
= LoadItem(itemPath
);
691 void CSoundManager::PlayAsAmbient(const VfsPath
& itemPath
, bool looping
)
696 ISoundItem
* aSnd
= LoadItem(itemPath
);
698 SetAmbientItem(aSnd
);
703 void CSoundManager::PlayAsUI(const VfsPath
& itemPath
, bool looping
)
709 if (ISoundItem
* anItem
= LoadItem(itemPath
))
713 anItem
->SetGain(m_UIGain
);
714 anItem
->SetLooping(looping
);
715 anItem
->PlayAndDelete();
722 void CSoundManager::Pause(bool 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
)
766 m_CurrentTune
->FadeAndDelete(2.00);
767 m_CurrentTune
= NULL
;
776 m_CurrentTune
= anItem
;
777 m_CurrentTune
->SetGain(0);
779 if (m_PlayingPlaylist
)
781 m_RunningPlaylist
= true;
782 m_CurrentTune
->Play();
785 m_CurrentTune
->PlayLoop();
787 m_MusicPaused
= false;
788 m_CurrentTune
->FadeToIn(m_MusicGain
, 1.00);
792 anItem
->StopAndDelete();
799 void CSoundManager::SetAmbientItem(ISoundItem
* anItem
)
803 if (m_CurrentEnvirons
)
805 m_CurrentEnvirons
->FadeAndDelete(3.00);
806 m_CurrentEnvirons
= NULL
;
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);
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.
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
);
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
+= "; ";
852 const ALCchar
* al_version
= alGetString(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