Changeset View
Standalone View
source/soundmanager/SoundManager.cpp
/* Copyright (C) 2019 Wildfire Games. | /* Copyright (C) 2020 Wildfire Games. | ||||
* This file is part of 0 A.D. | * This file is part of 0 A.D. | ||||
* | * | ||||
* 0 A.D. is free software: you can redistribute it and/or modify | * 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 | * it under the terms of the GNU General Public License as published by | ||||
* the Free Software Foundation, either version 2 of the License, or | * the Free Software Foundation, either version 2 of the License, or | ||||
* (at your option) any later version. | * (at your option) any later version. | ||||
* | * | ||||
* 0 A.D. is distributed in the hope that it will be useful, | * 0 A.D. is distributed in the hope that it will be useful, | ||||
Show All 35 Lines | class CSoundManagerWorker | ||||
NONCOPYABLE(CSoundManagerWorker); | NONCOPYABLE(CSoundManagerWorker); | ||||
public: | public: | ||||
CSoundManagerWorker() | CSoundManagerWorker() | ||||
{ | { | ||||
m_Items = new ItemsList; | m_Items = new ItemsList; | ||||
m_DeadItems = new ItemsList; | m_DeadItems = new ItemsList; | ||||
m_Shutdown = false; | m_Shutdown = false; | ||||
m_Hotloading = false; | |||||
m_WorkerThread = std::thread(RunThread, this); | m_WorkerThread = std::thread(RunThread, this); | ||||
} | } | ||||
~CSoundManagerWorker() | ~CSoundManagerWorker() | ||||
{ | { | ||||
delete m_Items; | delete m_Items; | ||||
CleanupItems(); | CleanupItems(); | ||||
▲ Show 20 Lines • Show All 54 Lines • ▼ Show 20 Lines | private: | ||||
void Run() | void Run() | ||||
{ | { | ||||
while (true) | while (true) | ||||
{ | { | ||||
// Handle shutdown requests as soon as possible | // Handle shutdown requests as soon as possible | ||||
if (GetShutdown()) | if (GetShutdown()) | ||||
return; | return; | ||||
g_SoundManager->Hotloader(); //Check if there're any files pending | |||||
Stan: Comments on top. Ends with periods :) | |||||
int pauseTime = 500; | int pauseTime = 500; | ||||
if (g_SoundManager->InDistress()) | if (g_SoundManager->InDistress()) | ||||
pauseTime = 50; | pauseTime = 50; | ||||
{ | { | ||||
std::lock_guard<std::mutex> workerLock(m_WorkerMutex); | std::lock_guard<std::mutex> workerLock(m_WorkerMutex); | ||||
ItemsList::iterator lstr = m_Items->begin(); | ItemsList::iterator lstr = m_Items->begin(); | ||||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | private: | ||||
std::mutex m_DeadItemsMutex; | std::mutex m_DeadItemsMutex; | ||||
// Shared by main thread and worker thread: | // Shared by main thread and worker thread: | ||||
// These variables are all protected by a mutexes | // These variables are all protected by a mutexes | ||||
ItemsList* m_Items; | ItemsList* m_Items; | ||||
ItemsList* m_DeadItems; | ItemsList* m_DeadItems; | ||||
bool m_Shutdown; | bool m_Shutdown; | ||||
bool m_Hotloading; | |||||
CSoundManagerWorker(ISoundManager* UNUSED(other)){}; | CSoundManagerWorker(ISoundManager* UNUSED(other)){}; | ||||
}; | }; | ||||
void ISoundManager::CreateSoundManager() | void ISoundManager::CreateSoundManager() | ||||
{ | { | ||||
if (!g_SoundManager) | if (!g_SoundManager) | ||||
{ | { | ||||
Show All 22 Lines | |||||
void CSoundManager::al_check(const char* caller, int line) | void CSoundManager::al_check(const char* caller, int line) | ||||
{ | { | ||||
ALenum err = alGetError(); | ALenum err = alGetError(); | ||||
if (err != AL_NO_ERROR) | if (err != AL_NO_ERROR) | ||||
al_ReportError(err, caller, line); | al_ReportError(err, caller, line); | ||||
} | } | ||||
Status CSoundManager::ReloadChangedFiles(const VfsPath& UNUSED(path)) | Status CSoundManager::ReloadChangedFiles(const VfsPath& path) | ||||
{ | |||||
CStrW sPrefix = L"audio/"; | |||||
std::vector<Path> accExt = {Path(".xml").Extension(), Path{".ogg"}.Extension()}; | |||||
Not Done Inline ActionsDifferent braces. vladislavbelov: Different braces. | |||||
Path extension = path.Extension(); | |||||
//Only try to reload .ogg or .xml files in the audio/ directory. | |||||
Not Done Inline ActionsSpace after //. vladislavbelov: Space after `//`. | |||||
if ( (path.string().find(sPrefix) != std::string::npos) || | |||||
Not Done Inline ActionsWe don't use space after if (. Also some braces aren't needed. vladislavbelov: We don't use space after `if (`. Also some braces aren't needed. | |||||
(std::find(accExt.begin(), accExt.end(), extension) != accExt.end()) ) | |||||
{ | |||||
while (InHotloading()){}; // Wait for current hotloading to finish | |||||
SilierUnsubmitted Not Done Inline Actionscould you please take a look into wait notify for example? http://www.cplusplus.com/reference/condition_variable/condition_variable/ active waiting is not good idea Silier: could you please take a look into wait notify for example? http://www.cplusplus. | |||||
Not Done Inline ActionsAgree with @Angen. This loop makes one core doing nothing. vladislavbelov: Agree with @Angen. This loop makes one core doing nothing. | |||||
SetHotloading(true); // Block worker thread from accessing the list at this time | |||||
ReloadList::iterator it = std::find(m_ReloadList.begin(), m_ReloadList.end(), path); | |||||
if (it == m_ReloadList.end()) | |||||
{ | { | ||||
SilierUnsubmitted Not Done Inline ActionsYou can omit { and } when only one line is part of the branch, for loop, while loop etc Silier: You can omit { and } when only one line is part of the branch, for loop, while loop etc | |||||
// TODO implement sound file hotloading | m_ReloadList.push_back(path); | ||||
} | |||||
SetHotloading(false); | |||||
} | |||||
Not Done Inline ActionsThe s letter seems useless here. vladislavbelov: The `s` letter seems useless here. | |||||
return INFO::OK; | return INFO::OK; | ||||
} | } | ||||
void CSoundManager::HotloadSoundGroup(const VfsPath& path) | |||||
{ | |||||
if (path.Extension() != L".xml") | |||||
StanUnsubmitted Not Done Inline ActionsNot sure if we use the XML counterpart XMB (Binary xml files) Stan: Not sure if we use the XML counterpart XMB (Binary xml files) | |||||
return; | |||||
// Need to strip leading 'audio/' in path as that is not stored in m_SoundGroups | |||||
CStrW sPrefix = L"audio/"; | |||||
CStrW sPath = path.string(); | |||||
VfsPath vfsPath = sPath.erase(0, sPrefix.size()); | |||||
if (m_SoundGroups.find(vfsPath.string()) != m_SoundGroups.end()) | |||||
ReloadGroup(vfsPath); | |||||
return; | |||||
SilierUnsubmitted Not Done Inline Actionsremove return Silier: remove return | |||||
} | |||||
void CSoundManager::HotloadOggFile(const VfsPath& path) | |||||
{ | |||||
if (path.Extension() != L".ogg") | |||||
StanUnsubmitted Not Done Inline ActionsIs .ogg defined somewhere? Stan: Is .ogg defined somewhere? | |||||
return; | |||||
ALSourceHolder* thisSourceHolder = GetSourceHolder(path); | |||||
if (!thisSourceHolder) | |||||
Not Done Inline Actions{ under if Silier: { under if | |||||
{ | |||||
// As there is no current sourceHolder for this file it probably | |||||
// belongs to one or more(?) soundGroup(s) so reload all. | |||||
for (SoundGroupMap::iterator it=m_SoundGroups.begin(); it!=m_SoundGroups.end(); ++it) | |||||
StanUnsubmitted Not Done Inline ActionsI guss you can use a range based loop here :) Stan: I guss you can use a range based loop here :) | |||||
{ | |||||
ReloadGroup(it->first); | |||||
} | |||||
return; | |||||
} | |||||
else | |||||
{ | |||||
ISoundItem* newItem = LoadItem(path); // A new ALSource will be allocated | |||||
ALSourceHolder* newSourceHolder = GetSourceHolder(newItem); | |||||
ISoundItem* replacedItem = thisSourceHolder->SourceItem; //Store the old soundItem | |||||
thisSourceHolder = newSourceHolder; | |||||
// Check if the file changed is currently playing and replace in that case | |||||
if (IsCurrentMusic(replacedItem) && replacedItem->IsPlaying()) | |||||
{ | |||||
Not Done Inline Actionsplease remove this empty line Silier: please remove this empty line | |||||
replacedItem->StopAndDelete(); | |||||
SetMusicItem(newItem); | |||||
} | |||||
else if (IsCurrentAmbient(replacedItem) && replacedItem->IsPlaying()) | |||||
{ | |||||
replacedItem->StopAndDelete(); | |||||
SetAmbientItem(newItem); | |||||
} | |||||
} | |||||
} | |||||
void CSoundManager::Hotloader() | |||||
{ | |||||
if (InHotloading()) | |||||
return; | |||||
SetHotloading(true); | |||||
for (const VfsPath& path : m_ReloadList) | |||||
{ | |||||
HotloadSoundGroup(path); | |||||
HotloadOggFile(path); | |||||
} | |||||
m_ReloadList.clear(); | |||||
SetHotloading(false); | |||||
} | |||||
/*static*/ Status CSoundManager::ReloadChangedFileCB(void* param, const VfsPath& path) | /*static*/ Status CSoundManager::ReloadChangedFileCB(void* param, const VfsPath& path) | ||||
{ | { | ||||
return static_cast<CSoundManager*>(param)->ReloadChangedFiles(path); | return static_cast<CSoundManager*>(param)->ReloadChangedFiles(path); | ||||
} | } | ||||
CSoundManager::CSoundManager() | CSoundManager::CSoundManager() | ||||
: m_Context(nullptr), m_Device(nullptr), m_ALSourceBuffer(nullptr), | : m_Context(nullptr), m_Device(nullptr), m_ALSourceBuffer(nullptr), | ||||
m_CurrentTune(nullptr), m_CurrentEnvirons(nullptr), | m_CurrentTune(nullptr), m_CurrentEnvirons(nullptr), | ||||
m_Worker(nullptr), m_DistressMutex(), m_PlayListItems(nullptr), m_SoundGroups(), | m_Worker(nullptr), m_DistressMutex(), m_HotloadingMutex(), m_PlayListItems(nullptr), | ||||
m_SoundGroups(), m_ReloadList(), | |||||
m_Gain(.5f), m_MusicGain(.5f), m_AmbientGain(.5f), m_ActionGain(.5f), m_UIGain(.5f), | 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_Enabled(false), m_BufferSize(98304), m_BufferCount(50), | ||||
m_SoundEnabled(true), m_MusicEnabled(true), m_MusicPaused(false), | m_SoundEnabled(true), m_MusicEnabled(true), m_MusicPaused(false), | ||||
m_AmbientPaused(false), m_ActionPaused(false), | m_AmbientPaused(false), m_ActionPaused(false), | ||||
m_RunningPlaylist(false), m_PlayingPlaylist(false), m_LoopingPlaylist(false), | m_RunningPlaylist(false), m_PlayingPlaylist(false), m_LoopingPlaylist(false), | ||||
m_PlaylistGap(0), m_DistressErrCount(0), m_DistressTime(0) | m_PlaylistGap(0), m_DistressErrCount(0), m_DistressTime(0), m_Hotloading(false) | ||||
{ | { | ||||
CFG_GET_VAL("sound.mastergain", m_Gain); | CFG_GET_VAL("sound.mastergain", m_Gain); | ||||
CFG_GET_VAL("sound.musicgain", m_MusicGain); | CFG_GET_VAL("sound.musicgain", m_MusicGain); | ||||
CFG_GET_VAL("sound.ambientgain", m_AmbientGain); | CFG_GET_VAL("sound.ambientgain", m_AmbientGain); | ||||
CFG_GET_VAL("sound.actiongain", m_ActionGain); | CFG_GET_VAL("sound.actiongain", m_ActionGain); | ||||
CFG_GET_VAL("sound.uigain", m_UIGain); | CFG_GET_VAL("sound.uigain", m_UIGain); | ||||
AlcInit(); | AlcInit(); | ||||
▲ Show 20 Lines • Show All 122 Lines • ▼ Show 20 Lines | |||||
// Coming out of distress mode | // Coming out of distress mode | ||||
m_DistressErrCount = 0; | m_DistressErrCount = 0; | ||||
return false; | return false; | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
bool CSoundManager::InHotloading() | |||||
{ | |||||
std::lock_guard<std::mutex> lock(m_HotloadingMutex); | |||||
return m_Hotloading; | |||||
} | |||||
void CSoundManager::SetDistressThroughShortage() | void CSoundManager::SetDistressThroughShortage() | ||||
{ | { | ||||
std::lock_guard<std::mutex> lock(m_DistressMutex); | std::lock_guard<std::mutex> lock(m_DistressMutex); | ||||
// Going into distress for normal reasons | // Going into distress for normal reasons | ||||
m_DistressTime = timer_Time(); | m_DistressTime = timer_Time(); | ||||
} | } | ||||
void CSoundManager::SetDistressThroughError() | void CSoundManager::SetDistressThroughError() | ||||
{ | { | ||||
std::lock_guard<std::mutex> lock(m_DistressMutex); | std::lock_guard<std::mutex> lock(m_DistressMutex); | ||||
// Going into distress due to unknown error | // Going into distress due to unknown error | ||||
m_DistressTime = timer_Time(); | m_DistressTime = timer_Time(); | ||||
m_DistressErrCount++; | m_DistressErrCount++; | ||||
} | } | ||||
void CSoundManager::SetHotloading(bool hotloading) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(m_HotloadingMutex); | |||||
m_Hotloading = hotloading; | |||||
} | |||||
ALuint CSoundManager::GetALSource(ISoundItem* anItem) | ALuint CSoundManager::GetALSource(ISoundItem* anItem) | ||||
{ | { | ||||
for (int x = 0; x < SOURCE_NUM; x++) | for (int x = 0; x < SOURCE_NUM; x++) | ||||
{ | { | ||||
if (!m_ALSourceBuffer[x].SourceItem) | if (!m_ALSourceBuffer[x].SourceItem) | ||||
{ | { | ||||
Show All 12 Lines | for (int x = 0; x < SOURCE_NUM; x++) | ||||
if (m_ALSourceBuffer[x].ALSource == theSource) | if (m_ALSourceBuffer[x].ALSource == theSource) | ||||
{ | { | ||||
m_ALSourceBuffer[x].SourceItem = NULL; | m_ALSourceBuffer[x].SourceItem = NULL; | ||||
return; | return; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
ALSourceHolder* CSoundManager::GetSourceHolder(const VfsPath& itemPath) | |||||
{ | |||||
for (int x = 0; x < SOURCE_NUM; ++x) | |||||
{ | |||||
Not Done Inline ActionsIn that cases braces aren't needed. vladislavbelov: In that cases braces aren't needed. | |||||
if (m_ALSourceBuffer[x].SourceItem != NULL && m_ALSourceBuffer[x].SourceItem->GetName() == itemPath) | |||||
{ | |||||
return &m_ALSourceBuffer[x]; | |||||
} | |||||
} | |||||
return 0; | |||||
SilierUnsubmitted Not Done Inline Actionsnullptr Silier: nullptr | |||||
} | |||||
ALSourceHolder* CSoundManager::GetSourceHolder(ISoundItem* theItem) | |||||
{ | |||||
for (int x = 0; x < SOURCE_NUM; ++x) | |||||
{ | |||||
if (m_ALSourceBuffer[x].SourceItem != NULL && m_ALSourceBuffer[x].SourceItem == theItem) | |||||
StanUnsubmitted Not Done Inline Actionsnullptr :) Stan: nullptr :) | |||||
{ | |||||
return &m_ALSourceBuffer[x]; | |||||
} | |||||
} | |||||
return 0; | |||||
SilierUnsubmitted Not Done Inline Actionsnullptr Silier: nullptr | |||||
} | |||||
long CSoundManager::GetBufferCount() | long CSoundManager::GetBufferCount() | ||||
{ | { | ||||
return m_BufferCount; | return m_BufferCount; | ||||
} | } | ||||
long CSoundManager::GetBufferSize() | long CSoundManager::GetBufferSize() | ||||
{ | { | ||||
return m_BufferSize; | return m_BufferSize; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 80 Lines • ▼ Show 20 Lines | if (m_Enabled) | ||||
AL_CHECK; | AL_CHECK; | ||||
if (itemData) | if (itemData) | ||||
return CSoundManager::ItemForData(itemData); | return CSoundManager::ItemForData(itemData); | ||||
} | } | ||||
return NULL; | return NULL; | ||||
} | } | ||||
CSoundGroup* CSoundManager::LoadGroup(const VfsPath& groupPath, CSoundGroup* group) | |||||
SilierUnsubmitted Not Done Inline ActionsSecond parameter is super redundant, please remove it. Silier: Second parameter is super redundant, please remove it.
Also deleting not owned pointer is not… | |||||
{ | |||||
if (group == NULL) | |||||
StanUnsubmitted Not Done Inline Actionsnullptr :) Stan: nullptr :) | |||||
SilierUnsubmitted Not Done Inline Actionsnuke Silier: nuke | |||||
{ | |||||
group = new CSoundGroup(); | |||||
SilierUnsubmitted Not Done Inline ActionsLocale variable Silier: Locale variable | |||||
} | |||||
Not Done Inline ActionsWrong indentation. vladislavbelov: Wrong indentation. | |||||
if (!group->LoadSoundGroup(L"audio/" + groupPath.string())) | |||||
{ | |||||
Not Done Inline Actionsplease replace that two lines with SAFE_DELETE(group) https://trac.wildfiregames.com/wiki/Coding_Conventions Silier: please replace that two lines with SAFE_DELETE(group) https://trac.wildfiregames. | |||||
Not Done Inline ActionsI'd suggest to use std::unique_ptr. vladislavbelov: I'd suggest to use `std::unique_ptr`. | |||||
LOGERROR("Failed to load sound group '%s'", groupPath.string8()); | |||||
delete group; | |||||
group = NULL; | |||||
StanUnsubmitted Not Done Inline Actionsnullptr :) Stan: nullptr :) | |||||
} | |||||
// Cache the sound group (or the null, if it failed) | |||||
m_SoundGroups[groupPath.string()] = group; | |||||
return group; | |||||
} | |||||
void CSoundManager::ReloadGroup(const VfsPath& groupPath) | |||||
{ | |||||
Not Done Inline Actionscould you invert if and do early return ? Silier: could you invert if and do early return ? | |||||
CStrW sPath = groupPath.string(); | |||||
SoundGroupMap::iterator it = m_SoundGroups.find(sPath); | |||||
if (it != m_SoundGroups.end()) | |||||
{ | |||||
delete it->second; | |||||
LoadGroup(groupPath, NULL); | |||||
StanUnsubmitted Not Done Inline Actionsnullptr :) Stan: nullptr :) | |||||
} | |||||
} | |||||
ISoundItem* CSoundManager::ItemForData(CSoundData* itemData) | ISoundItem* CSoundManager::ItemForData(CSoundData* itemData) | ||||
{ | { | ||||
AL_CHECK; | AL_CHECK; | ||||
ISoundItem* answer = NULL; | ISoundItem* answer = NULL; | ||||
AL_CHECK; | AL_CHECK; | ||||
if (m_Enabled && (itemData != NULL)) | if (m_Enabled && (itemData != NULL)) | ||||
▲ Show 20 Lines • Show All 108 Lines • ▼ Show 20 Lines | if (m_CurrentTune && !isEnabled) | ||||
m_CurrentTune = NULL; | m_CurrentTune = NULL; | ||||
} | } | ||||
m_MusicEnabled = isEnabled; | m_MusicEnabled = isEnabled; | ||||
} | } | ||||
void CSoundManager::PlayAsGroup(const VfsPath& groupPath, const CVector3D& sourcePos, entity_id_t source, bool ownedSound) | void CSoundManager::PlayAsGroup(const VfsPath& groupPath, const CVector3D& sourcePos, entity_id_t source, bool ownedSound) | ||||
{ | { | ||||
// Make sure the sound group is loaded | // Make sure the sound group is loaded | ||||
CSoundGroup* group; | CSoundGroup* group = NULL; | ||||
StanUnsubmitted Not Done Inline ActionsUse nullptr instead. Stan: Use nullptr instead. | |||||
if (m_SoundGroups.find(groupPath.string()) == m_SoundGroups.end()) | if (m_SoundGroups.find(groupPath.string()) == m_SoundGroups.end()) | ||||
{ | group = LoadGroup(groupPath, group); | ||||
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 | else | ||||
{ | |||||
group = m_SoundGroups[groupPath.string()]; | group = m_SoundGroups[groupPath.string()]; | ||||
} | |||||
// Failed to load group -> do nothing | // Failed to load group -> do nothing | ||||
if (group && (ownedSound || !group->TestFlag(eOwnerOnly))) | if (group && (ownedSound || !group->TestFlag(eOwnerOnly))) | ||||
group->PlayNext(sourcePos, source); | group->PlayNext(sourcePos, source); | ||||
} | } | ||||
void CSoundManager::PlayAsMusic(const VfsPath& itemPath, bool looping) | void CSoundManager::PlayAsMusic(const VfsPath& itemPath, bool looping) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 134 Lines • ▼ Show 20 Lines | if (anItem) | ||||
m_CurrentEnvirons->SetGain(0); | m_CurrentEnvirons->SetGain(0); | ||||
m_CurrentEnvirons->PlayLoop(); | m_CurrentEnvirons->PlayLoop(); | ||||
m_CurrentEnvirons->FadeToIn(m_AmbientGain, 2.00); | m_CurrentEnvirons->FadeToIn(m_AmbientGain, 2.00); | ||||
} | } | ||||
} | } | ||||
AL_CHECK; | AL_CHECK; | ||||
} | } | ||||
} | } | ||||
bool CSoundManager::IsCurrentAmbient(ISoundItem* anItem) | |||||
StanUnsubmitted Not Done Inline ActionsNot sure it's worth the function call, I guess it won't be called often. Stan: Not sure it's worth the function call, I guess it won't be called often.
item maybe? could be a… | |||||
{ | |||||
return m_CurrentEnvirons == anItem; | |||||
} | |||||
bool CSoundManager::IsCurrentMusic(ISoundItem* anItem) | |||||
{ | |||||
return m_CurrentTune == anItem; | |||||
} | |||||
#else // CONFIG2_AUDIO | #else // CONFIG2_AUDIO | ||||
void ISoundManager::CreateSoundManager(){} | void ISoundManager::CreateSoundManager(){} | ||||
void ISoundManager::SetEnabled(bool UNUSED(doEnable)){} | void ISoundManager::SetEnabled(bool UNUSED(doEnable)){} | ||||
void ISoundManager::CloseGame(){} | void ISoundManager::CloseGame(){} | ||||
#endif // CONFIG2_AUDIO | #endif // CONFIG2_AUDIO | ||||
Comments on top. Ends with periods :)