diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b036f1c72..572ceafcda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -307,6 +307,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Fixed `AHuman:EquipShieldInBGArm()` being able to take the wrong item out of the inventory, which left the device it equipped both held and in the inventory. The device then got dropped twice when its holder died, and the game could crash when quitting. +- Fixed a memory leak where every positional sound that played left its lowpass EQ behind in FMOD, never released. + - Fixed AI actors refusing to fire the Coalition Missile Launcher at anything beyond point-blank range, because the AI took the range of its short-lived dummy round instead of the missile's. diff --git a/Source/Managers/AudioMan.cpp b/Source/Managers/AudioMan.cpp index b3d8e210af..02f600e9f8 100644 --- a/Source/Managers/AudioMan.cpp +++ b/Source/Managers/AudioMan.cpp @@ -27,6 +27,7 @@ void AudioMan::Clear() { m_AudioEnabled = false; m_CurrentActivityHumanPlayerPositions.clear(); m_SoundChannelMinimumAudibleDistances.clear(); + m_SoundChannelLowpassEQs.clear(); m_MuteMaster = false; m_MuteMusic = false; @@ -417,7 +418,7 @@ bool AudioMan::PlaySoundContainer(SoundContainer* soundContainer, int player) { result = (result == FMOD_OK) ? channel->setVolume(soundContainer->GetVolume()) : result; } else { - FMOD::DSP* dsp_multibandeq; + FMOD::DSP* dsp_multibandeq = nullptr; result = (result == FMOD_OK) ? m_AudioSystem->createDSPByType(FMOD_DSP_TYPE_MULTIBAND_EQ, &dsp_multibandeq) : result; result = (result == FMOD_OK) ? dsp_multibandeq->setParameterFloat(1, 22000.0f) : result; // Functionally inactive lowpass filter result = (result == FMOD_OK) ? channel->addDSP(0, dsp_multibandeq) : result; @@ -425,6 +426,9 @@ bool AudioMan::PlaySoundContainer(SoundContainer* soundContainer, int player) { { std::scoped_lock lock(m_SoundChannelMinimumAudibleDistancesMutex); m_SoundChannelMinimumAudibleDistances.insert({channelIndex, soundData->MinimumAudibleDistance}); + if (dsp_multibandeq) { + m_SoundChannelLowpassEQs.insert({channelIndex, dsp_multibandeq}); + } } result = (result == FMOD_OK) ? channel->set3DLevel(m_SoundPanningEffectStrength * soundContainer->GetPanningStrengthMultiplier()) : result; @@ -844,8 +848,18 @@ FMOD_RESULT F_CALLBACK AudioMan::SoundChannelEndedCallback(FMOD_CHANNELCONTROL* } result = (result == FMOD_OK) ? channel->setUserData(nullptr) : result; - if (g_AudioMan.m_SoundChannelMinimumAudibleDistances.find(channelIndex) != g_AudioMan.m_SoundChannelMinimumAudibleDistances.end()) { - g_AudioMan.m_SoundChannelMinimumAudibleDistances.erase(channelIndex); + { + std::scoped_lock lock(g_AudioMan.m_SoundChannelMinimumAudibleDistancesMutex); + if (g_AudioMan.m_SoundChannelMinimumAudibleDistances.find(channelIndex) != g_AudioMan.m_SoundChannelMinimumAudibleDistances.end()) { + g_AudioMan.m_SoundChannelMinimumAudibleDistances.erase(channelIndex); + } + + // FMOD takes the lowpass EQ off a positional sound's channel when it ends, but doesn't release it. + if (auto lowpassEQ = g_AudioMan.m_SoundChannelLowpassEQs.find(channelIndex); lowpassEQ != g_AudioMan.m_SoundChannelLowpassEQs.end()) { + FMOD_RESULT releaseResult = lowpassEQ->second->release(); + result = (result == FMOD_OK) ? releaseResult : result; + g_AudioMan.m_SoundChannelLowpassEQs.erase(lowpassEQ); + } } if (result != FMOD_OK) { diff --git a/Source/Managers/AudioMan.h b/Source/Managers/AudioMan.h index 570919b15a..e1dba35de2 100644 --- a/Source/Managers/AudioMan.h +++ b/Source/Managers/AudioMan.h @@ -330,6 +330,7 @@ namespace RTE { bool m_AudioEnabled; //!< Bool to tell whether audio is enabled or not. std::vector> m_CurrentActivityHumanPlayerPositions; //!< The stored positions of each human player in the current activity. Only filled when there's an activity running. std::unordered_map m_SoundChannelMinimumAudibleDistances; //!< An unordered map of sound channel indices to floats representing each Sound Channel's minimum audible distances. This is necessary to keep safe data in case the SoundContainer is destroyed while the sound is still playing, as happens often with TDExplosives. + std::unordered_map m_SoundChannelLowpassEQs; //!< An unordered map of sound channel indices to the lowpass EQ each positional Sound Channel gets, so it can be released when the channel ends. bool m_MuteMaster; //!< Whether all the audio is muted. bool m_MuteMusic; //!< Whether the music channel is muted. @@ -354,7 +355,7 @@ namespace RTE { std::list m_SoundEvents[c_MaxClients]; //!< Lists of per player sound events. std::mutex g_SoundEventsListMutex[c_MaxClients]; //!< A list for locking sound events for multiplayer to avoid race conditions and other such problems. - std::mutex m_SoundChannelMinimumAudibleDistancesMutex; //!, As above but for m_SoundChannelMinimumAudibleDistances + std::mutex m_SoundChannelMinimumAudibleDistancesMutex; //!< As above but for m_SoundChannelMinimumAudibleDistances and m_SoundChannelLowpassEQs. private: #pragma region Sound Container Actions and Modifications