1
0
forked from 0ad/0ad
0ad/source/soundmanager/SoundManager.cpp
wraitii ba736916fc Clean up ThreadUtil, use standard C++11 constructs instead of custom ones.
ThreadUtil shipped a scope lock and a mutex implementation, which can be
removed since we now have these in the standard library.
This lets us clean up this header which get included everywhere (through
profiler includes).

Tested By: Angen and Stan
Differential Revision: https://code.wildfiregames.com/D1915
This was SVN commit r22344.
2019-06-06 19:30:48 +00:00

820 lines
16 KiB
C++

/* Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "ISoundManager.h"
#include "SoundManager.h"
#include "data/SoundData.h"
#include "items/CBufferItem.h"
#include "items/CSoundItem.h"
#include "items/CStreamItem.h"
#include "lib/external_libraries/libsdl.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#include "ps/ConfigDB.h"
#include "ps/Filesystem.h"
#include "ps/Profiler2.h"
#include "ps/XML/Xeromyces.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;
int ret = pthread_create(&m_WorkerThread, NULL, &RunThread, this);
ENSURE(ret == 0);
}
~CSoundManagerWorker()
{
delete m_Items;
CleanupItems();
delete m_DeadItems;
}
bool Shutdown()
{
{
std::lock_guard<std::mutex> lock(m_WorkerMutex);
m_Shutdown = true;
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)
{
std::lock_guard<std::mutex> lock(m_WorkerMutex);
m_Items->push_back(anItem);
}
void CleanupItems()
{
std::lock_guard<std::mutex> 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<CSoundManagerWorker*>(data)->Run();
return NULL;
}
void Run()
{
while (true)
{
// Handle shutdown requests as soon as possible
if (GetShutdown())
return;
int pauseTime = 500;
if (g_SoundManager->InDistress())
pauseTime = 50;
{
std::lock_guard<std::mutex> workerLock(m_WorkerMutex);
ItemsList::iterator lstr = m_Items->begin();
ItemsList* nextItemList = new ItemsList;
while (lstr != m_Items->end())
{
AL_CHECK;
if ((*lstr)->IdleTask())
{
if ((pauseTime == 500) && (*lstr)->IsFading())
pauseTime = 100;
nextItemList->push_back(*lstr);
}
else
{
std::lock_guard<std::mutex> deadItemsLock(m_DeadItemsMutex);
m_DeadItems->push_back(*lstr);
}
++lstr;
AL_CHECK;
}
delete m_Items;
m_Items = nextItemList;
AL_CHECK;
}
SDL_Delay(pauseTime);
}
}
bool GetShutdown()
{
std::lock_guard<std::mutex> lock(m_WorkerMutex);
return m_Shutdown;
}
private:
// Thread-related members:
pthread_t m_WorkerThread;
std::mutex m_WorkerMutex;
std::mutex 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_Shutdown;
CSoundManagerWorker(ISoundManager* UNUSED(other)){};
};
void ISoundManager::CreateSoundManager()
{
if (!g_SoundManager)
{
g_SoundManager = new CSoundManager();
g_SoundManager->StartWorker();
}
}
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("OpenAL error: %s; called from %s (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))
{
// TODO implement sound file hotloading
return INFO::OK;
}
/*static*/ Status CSoundManager::ReloadChangedFileCB(void* param, const VfsPath& path)
{
return static_cast<CSoundManager*>(param)->ReloadChangedFiles(path);
}
CSoundManager::CSoundManager()
: m_Context(nullptr), m_Device(nullptr), m_ALSourceBuffer(nullptr),
m_CurrentTune(nullptr), m_CurrentEnvirons(nullptr),
m_Worker(nullptr), m_DistressMutex(), m_PlayListItems(nullptr), m_SoundGroups(),
m_Gain(.5f), m_MusicGain(.5f), m_AmbientGain(.5f), m_ActionGain(.5f), m_UIGain(.5f),
m_Enabled(false), m_BufferSize(98304), m_BufferCount(50),
m_SoundEnabled(true), m_MusicEnabled(true), m_MusicPaused(false),
m_AmbientPaused(false), m_ActionPaused(false),
m_RunningPlaylist(false), m_PlayingPlaylist(false), m_LoopingPlaylist(false),
m_PlaylistGap(0), m_DistressErrCount(0), m_DistressTime(0)
{
CFG_GET_VAL("sound.mastergain", m_Gain);
CFG_GET_VAL("sound.musicgain", m_MusicGain);
CFG_GET_VAL("sound.ambientgain", m_AmbientGain);
CFG_GET_VAL("sound.actiongain", m_ActionGain);
CFG_GET_VAL("sound.uigain", m_UIGain);
AlcInit();
if (m_Enabled)
{
SetMasterGain(m_Gain);
InitListener();
m_PlayListItems = new PlayList;
}
if (!CXeromyces::AddValidator(g_VFS, "sound_group", "audio/sound_group.rng"))
LOGERROR("CSoundManager: failed to load grammar file 'audio/sound_group.rng'");
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 (const std::pair<std::wstring, CSoundGroup*>& p : m_SoundGroups)
delete p.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);
}
void CSoundManager::StartWorker()
{
if (m_Enabled)
m_Worker = new CSoundManagerWorker();
}
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 < SOURCE_NUM; x++)
{
m_ALSourceBuffer[x].ALSource = sourceList[x];
m_ALSourceBuffer[x].SourceItem = NULL;
}
m_Enabled = true;
}
else
{
LOGERROR("error in gensource = %d", err);
}
delete[] sourceList;
}
}
// check if init succeeded.
// some OpenAL implementations don't indicate failure here correctly;
// we need to check if the device and context pointers are actually valid.
ALCenum err = alcGetError(m_Device);
const char* dev_name = (const char*)alcGetString(m_Device, ALC_DEVICE_SPECIFIER);
if (err == ALC_NO_ERROR && m_Device && m_Context)
debug_printf("Sound: AlcInit success, using %s\n", dev_name);
else
{
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);
// FIXME Hack to get around exclusive access to the sound device
#if OS_UNIX
ret = INFO::OK;
#else
ret = ERR::FAIL;
#endif // !OS_UNIX
}
return ret;
}
bool CSoundManager::InDistress()
{
std::lock_guard<std::mutex> lock(m_DistressMutex);
if (m_DistressTime == 0)
return false;
else if ((timer_Time() - m_DistressTime) > 10)
{
m_DistressTime = 0;
// Coming out of distress mode
m_DistressErrCount = 0;
return false;
}
return true;
}
void CSoundManager::SetDistressThroughShortage()
{
std::lock_guard<std::mutex> lock(m_DistressMutex);
// Going into distress for normal reasons
m_DistressTime = timer_Time();
}
void CSoundManager::SetDistressThroughError()
{
std::lock_guard<std::mutex> 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; x < SOURCE_NUM; x++)
{
if (!m_ALSourceBuffer[x].SourceItem)
{
m_ALSourceBuffer[x].SourceItem = anItem;
return m_ALSourceBuffer[x].ALSource;
}
}
SetDistressThroughShortage();
return 0;
}
void CSoundManager::ReleaseALSource(ALuint theSource)
{
for (int x = 0; x < SOURCE_NUM; x++)
{
if (m_ALSourceBuffer[x].ALSource == theSource)
{
m_ALSourceBuffer[x].SourceItem = NULL;
return;
}
}
}
long CSoundManager::GetBufferCount()
{
return m_BufferCount;
}
long CSoundManager::GetBufferSize()
{
return m_BufferSize;
}
void CSoundManager::AddPlayListItem(const VfsPath& itemPath)
{
if (m_Enabled)
m_PlayListItems->push_back(itemPath);
}
void CSoundManager::ClearPlayListItems()
{
if (m_Enabled)
{
if (m_PlayingPlaylist)
SetMusicItem(NULL);
m_PlayingPlaylist = false;
m_LoopingPlaylist = false;
m_RunningPlaylist = false;
m_PlayListItems->clear();
}
}
void CSoundManager::StartPlayList(bool doLoop)
{
if (m_Enabled && 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;
if (m_CurrentTune)
m_CurrentTune->SetGain(m_MusicGain);
}
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)
{
if (m_CurrentTune && !isEnabled)
{
m_CurrentTune->FadeAndDelete(1.00);
m_CurrentTune = NULL;
}
m_MusicEnabled = isEnabled;
}
void CSoundManager::PlayAsGroup(const VfsPath& groupPath, const 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("Failed to load sound group '%s'", groupPath.string8());
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)
{
if (m_Enabled)
{
UNUSED2(looping);
ISoundItem* aSnd = LoadItem(itemPath);
if (aSnd != NULL)
SetMusicItem(aSnd);
}
}
void CSoundManager::PlayAsAmbient(const VfsPath& itemPath, bool looping)
{
if (m_Enabled)
{
UNUSED2(looping);
ISoundItem* aSnd = LoadItem(itemPath);
if (aSnd != NULL)
SetAmbientItem(aSnd);
}
}
void CSoundManager::PlayAsUI(const VfsPath& itemPath, bool looping)
{
if (m_Enabled)
{
IdleTask();
if (ISoundItem* anItem = LoadItem(itemPath))
{
if (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)
{
if (m_Enabled)
{
AL_CHECK;
if (m_CurrentTune)
{
m_CurrentTune->FadeAndDelete(2.00);
m_CurrentTune = NULL;
}
IdleTask();
if (anItem)
{
if (m_MusicEnabled)
{
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_Enabled)
{
if (m_CurrentEnvirons)
{
m_CurrentEnvirons->FadeAndDelete(3.00);
m_CurrentEnvirons = NULL;
}
IdleTask();
if (anItem)
{
if (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