diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index a4c3b9136..f32240a91 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -58,6 +58,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwsound soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output loudness movieaudiofactory alext efx efx-presets regionsoundselector watersoundupdater volumesettings + sound_buffer ) add_openmw_dir (mwworld diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 9d264e1b6..d2e65c989 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -7,6 +7,13 @@ namespace MWSound { + // Extra play flags, not intended for caller use + enum PlayModeEx + { + Play_2D = 0, + Play_3D = 1 << 31, + }; + // For testing individual PlayMode flags inline int operator&(int a, PlayMode b) { return a & static_cast(b); } inline int operator&(PlayMode a, PlayMode b) { return static_cast(a) & static_cast(b); } diff --git a/apps/openmw/mwsound/sound_buffer.cpp b/apps/openmw/mwsound/sound_buffer.cpp new file mode 100644 index 000000000..0e25ff601 --- /dev/null +++ b/apps/openmw/mwsound/sound_buffer.cpp @@ -0,0 +1,154 @@ +#include "sound_buffer.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/esmstore.hpp" + +#include +#include +#include + +#include +#include + +namespace MWSound +{ + namespace + { + struct AudioParams + { + float mAudioDefaultMinDistance; + float mAudioDefaultMaxDistance; + float mAudioMinDistanceMult; + float mAudioMaxDistanceMult; + }; + + AudioParams makeAudioParams(const MWBase::World& world) + { + const auto& settings = world.getStore().get(); + AudioParams params; + params.mAudioDefaultMinDistance = settings.find("fAudioDefaultMinDistance")->mValue.getFloat(); + params.mAudioDefaultMaxDistance = settings.find("fAudioDefaultMaxDistance")->mValue.getFloat(); + params.mAudioMinDistanceMult = settings.find("fAudioMinDistanceMult")->mValue.getFloat(); + params.mAudioMaxDistanceMult = settings.find("fAudioMaxDistanceMult")->mValue.getFloat(); + return params; + } + } + + SoundBufferPool::SoundBufferPool(const VFS::Manager& vfs, Sound_Output& output) : + mVfs(&vfs), + mOutput(&output), + mBufferCacheMax(std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1) * 1024 * 1024), + mBufferCacheMin(std::min(static_cast(std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1)) * 1024 * 1024, mBufferCacheMax)) + { + } + + SoundBufferPool::~SoundBufferPool() + { + clear(); + } + + Sound_Buffer* SoundBufferPool::lookup(const std::string& soundId) const + { + const auto it = mBufferNameMap.find(soundId); + if (it != mBufferNameMap.end()) + { + Sound_Buffer* sfx = it->second; + if (sfx->getHandle() != nullptr) + return sfx; + } + return nullptr; + } + + Sound_Buffer* SoundBufferPool::load(const std::string& soundId) + { + if (mBufferNameMap.empty()) + { + for (const ESM::Sound& sound : MWBase::Environment::get().getWorld()->getStore().get()) + insertSound(Misc::StringUtils::lowerCase(sound.mId), sound); + } + + Sound_Buffer* sfx; + const auto it = mBufferNameMap.find(soundId); + if (it != mBufferNameMap.end()) + sfx = it->second; + else + { + const ESM::Sound *sound = MWBase::Environment::get().getWorld()->getStore().get().search(soundId); + if (sound == nullptr) + return {}; + sfx = insertSound(soundId, *sound); + } + + if (sfx->getHandle() == nullptr) + { + Sound_Handle handle; + size_t size; + std::tie(handle, size) = mOutput->loadSound(sfx->getResourceName()); + if (handle == nullptr) + return {}; + + sfx->mHandle = handle; + + mBufferCacheSize += size; + if (mBufferCacheSize > mBufferCacheMax) + { + unloadUnused(); + if (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMax) + Log(Debug::Warning) << "No unused sound buffers to free, using " << mBufferCacheSize << " bytes!"; + } + mUnusedBuffers.push_front(sfx); + } + + return sfx; + } + + void SoundBufferPool::clear() + { + for (auto &sfx : mSoundBuffers) + { + if(sfx.mHandle) + mOutput->unloadSound(sfx.mHandle); + sfx.mHandle = nullptr; + } + mUnusedBuffers.clear(); + } + + Sound_Buffer* SoundBufferPool::insertSound(const std::string& soundId, const ESM::Sound& sound) + { + static const AudioParams audioParams = makeAudioParams(*MWBase::Environment::get().getWorld()); + + float volume = static_cast(std::pow(10.0, (sound.mData.mVolume / 255.0 * 3348.0 - 3348.0) / 2000.0)); + float min = sound.mData.mMinRange; + float max = sound.mData.mMaxRange; + if (min == 0 && max == 0) + { + min = audioParams.mAudioDefaultMinDistance; + max = audioParams.mAudioDefaultMaxDistance; + } + + min *= audioParams.mAudioMinDistanceMult; + max *= audioParams.mAudioMaxDistanceMult; + min = std::max(min, 1.0f); + max = std::max(min, max); + + Sound_Buffer& sfx = mSoundBuffers.emplace_back("Sound/" + sound.mSound, volume, min, max); + mVfs->normalizeFilename(sfx.mResourceName); + + mBufferNameMap.emplace(soundId, &sfx); + return &sfx; + } + + void SoundBufferPool::unloadUnused() + { + while (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMin) + { + Sound_Buffer* const unused = mUnusedBuffers.back(); + + mBufferCacheSize -= mOutput->unloadSound(unused->getHandle()); + unused->mHandle = nullptr; + + mUnusedBuffers.pop_back(); + } + } +} diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index 83b08d6be..e62392300 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -1,27 +1,100 @@ #ifndef GAME_SOUND_SOUND_BUFFER_H #define GAME_SOUND_SOUND_BUFFER_H +#include #include +#include +#include #include "sound_output.hpp" +namespace ESM +{ + struct Sound; +} + +namespace VFS +{ + class Manager; +} + namespace MWSound { + class SoundBufferPool; + class Sound_Buffer { - public: - std::string mResourceName; + public: + Sound_Buffer(std::string resname, float volume, float mindist, float maxdist) + : mResourceName(std::move(resname)), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist) + {} + + const std::string& getResourceName() const noexcept { return mResourceName; } + + Sound_Handle getHandle() const noexcept { return mHandle; } + + float getVolume() const noexcept { return mVolume; } + + float getMinDist() const noexcept { return mMinDist; } + + float getMaxDist() const noexcept { return mMaxDist; } + + private: + std::string mResourceName; + float mVolume = 0; + float mMinDist = 0; + float mMaxDist = 0; + Sound_Handle mHandle = nullptr; + std::size_t mUses = 0; + + friend class SoundBufferPool; + }; + + class SoundBufferPool + { + public: + SoundBufferPool(const VFS::Manager& vfs, Sound_Output& output); + + SoundBufferPool(const SoundBufferPool&) = delete; + + ~SoundBufferPool(); + + Sound_Buffer* lookup(const std::string& soundId) const; + + Sound_Buffer* load(const std::string& soundId); + + void use(Sound_Buffer& sfx) + { + if (sfx.mUses++ == 0) + { + const auto it = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), &sfx); + if (it != mUnusedBuffers.end()) + mUnusedBuffers.erase(it); + } + } + + void release(Sound_Buffer& sfx) + { + if (--sfx.mUses == 0) + mUnusedBuffers.push_front(&sfx); + } - float mVolume; - float mMinDist, mMaxDist; + void clear(); - Sound_Handle mHandle; + private: + const VFS::Manager* const mVfs; + Sound_Output* mOutput; + std::deque mSoundBuffers; + std::unordered_map mBufferNameMap; + std::size_t mBufferCacheMax; + std::size_t mBufferCacheMin; + std::size_t mBufferCacheSize = 0; + // NOTE: unused buffers are stored in front-newest order. + std::deque mUnusedBuffers; - size_t mUses; + inline Sound_Buffer* insertSound(const std::string& soundId, const ESM::Sound& sound); - Sound_Buffer(std::string resname, float volume, float mindist, float maxdist) - : mResourceName(resname), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist), mHandle(nullptr), mUses(0) - { } + inline void unloadUnused(); }; } diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 4075e36cc..9ec8b17dc 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -5,7 +5,7 @@ #include #include -#include "soundmanagerimp.hpp" +#include "../mwbase/soundmanager.hpp" namespace MWSound { @@ -25,6 +25,12 @@ namespace MWSound Auto }; + enum Environment + { + Env_Normal, + Env_Underwater + }; + class Sound_Output { SoundManager &mManager; @@ -81,6 +87,7 @@ namespace MWSound friend class OpenAL_Output; friend class SoundManager; + friend class SoundBufferPool; }; } diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 37ba80820..03ad58088 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -56,8 +56,7 @@ namespace MWSound : mVFS(vfs) , mOutput(new DEFAULT_OUTPUT(*this)) , mWaterSoundUpdater(makeWaterSoundUpdaterSettings()) - , mSoundBuffers(new SoundBufferList::element_type()) - , mBufferCacheSize(0) + , mSoundBuffers(*vfs, *mOutput) , mListenerUnderwater(false) , mListenerPos(0,0,0) , mListenerDir(1,0,0) @@ -69,11 +68,6 @@ namespace MWSound , mLastCell(nullptr) , mCurrentRegionSound(nullptr) { - mBufferCacheMin = std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1); - mBufferCacheMax = std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1); - mBufferCacheMax *= 1024*1024; - mBufferCacheMin = std::min(mBufferCacheMin*1024*1024, mBufferCacheMax); - if(!useSound) { Log(Debug::Info) << "Sound disabled."; @@ -116,13 +110,7 @@ namespace MWSound SoundManager::~SoundManager() { clear(); - for(Sound_Buffer &sfx : *mSoundBuffers) - { - if(sfx.mHandle) - mOutput->unloadSound(sfx.mHandle); - sfx.mHandle = nullptr; - } - mUnusedBuffers.clear(); + mSoundBuffers.clear(); mOutput.reset(); } @@ -132,112 +120,18 @@ namespace MWSound return DecoderPtr(new DEFAULT_DECODER (mVFS)); } - Sound_Buffer *SoundManager::insertSound(const std::string &soundId, const ESM::Sound *sound) - { - MWBase::World* world = MWBase::Environment::get().getWorld(); - static const float fAudioDefaultMinDistance = world->getStore().get().find("fAudioDefaultMinDistance")->mValue.getFloat(); - static const float fAudioDefaultMaxDistance = world->getStore().get().find("fAudioDefaultMaxDistance")->mValue.getFloat(); - static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->mValue.getFloat(); - static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->mValue.getFloat(); - float volume, min, max; - - volume = static_cast(pow(10.0, (sound->mData.mVolume / 255.0*3348.0 - 3348.0) / 2000.0)); - min = sound->mData.mMinRange; - max = sound->mData.mMaxRange; - if (min == 0 && max == 0) - { - min = fAudioDefaultMinDistance; - max = fAudioDefaultMaxDistance; - } - - min *= fAudioMinDistanceMult; - max *= fAudioMaxDistanceMult; - min = std::max(min, 1.0f); - max = std::max(min, max); - - Sound_Buffer *sfx = &*mSoundBuffers->insert(mSoundBuffers->end(), - Sound_Buffer("Sound/"+sound->mSound, volume, min, max) - ); - mVFS->normalizeFilename(sfx->mResourceName); - - mBufferNameMap.insert(std::make_pair(soundId, sfx)); - - return sfx; - } - // Lookup a soundId for its sound data (resource name, local volume, // minRange, and maxRange) Sound_Buffer *SoundManager::lookupSound(const std::string &soundId) const { - NameBufferMap::const_iterator snd = mBufferNameMap.find(soundId); - if(snd != mBufferNameMap.end()) - { - Sound_Buffer *sfx = snd->second; - if(sfx->mHandle) return sfx; - } - return nullptr; + return mSoundBuffers.lookup(soundId); } // Lookup a soundId for its sound data (resource name, local volume, // minRange, and maxRange), and ensure it's ready for use. Sound_Buffer *SoundManager::loadSound(const std::string &soundId) { -#ifdef __GNUC__ -#define LIKELY(x) __builtin_expect((bool)(x), true) -#define UNLIKELY(x) __builtin_expect((bool)(x), false) -#else -#define LIKELY(x) (bool)(x) -#define UNLIKELY(x) (bool)(x) -#endif - if(UNLIKELY(mBufferNameMap.empty())) - { - MWBase::World *world = MWBase::Environment::get().getWorld(); - for(const ESM::Sound &sound : world->getStore().get()) - insertSound(Misc::StringUtils::lowerCase(sound.mId), &sound); - } - - Sound_Buffer *sfx; - NameBufferMap::const_iterator snd = mBufferNameMap.find(soundId); - if(LIKELY(snd != mBufferNameMap.end())) - sfx = snd->second; - else - { - MWBase::World *world = MWBase::Environment::get().getWorld(); - const ESM::Sound *sound = world->getStore().get().search(soundId); - if(!sound) return nullptr; - sfx = insertSound(soundId, sound); - } -#undef LIKELY -#undef UNLIKELY - - if(!sfx->mHandle) - { - size_t size; - std::tie(sfx->mHandle, size) = mOutput->loadSound(sfx->mResourceName); - if(!sfx->mHandle) return nullptr; - - mBufferCacheSize += size; - if(mBufferCacheSize > mBufferCacheMax) - { - do { - if(mUnusedBuffers.empty()) - { - Log(Debug::Warning) << "No unused sound buffers to free, using " << mBufferCacheSize << " bytes!"; - break; - } - Sound_Buffer *unused = mUnusedBuffers.back(); - - size = mOutput->unloadSound(unused->mHandle); - mBufferCacheSize -= size; - unused->mHandle = nullptr; - - mUnusedBuffers.pop_back(); - } while(mBufferCacheSize > mBufferCacheMin); - } - mUnusedBuffers.push_front(sfx); - } - - return sfx; + return mSoundBuffers.load(soundId); } DecoderPtr SoundManager::loadVoice(const std::string &voicefile) @@ -624,23 +518,18 @@ namespace MWSound SoundPtr sound = getSoundRef(); sound->init([&] { SoundParams params; - params.mVolume = volume * sfx->mVolume; + params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mPitch = pitch; params.mFlags = mode | type | Play_2D; return params; } ()); - if(!mOutput->playSound(sound.get(), sfx->mHandle, offset)) + if(!mOutput->playSound(sound.get(), sfx->getHandle(), offset)) return nullptr; - if(sfx->mUses++ == 0) - { - SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx); - if(iter != mUnusedBuffers.end()) - mUnusedBuffers.erase(iter); - } Sound* result = sound.get(); mActiveSounds[MWWorld::ConstPtr()].emplace_back(std::move(sound), sfx); + mSoundBuffers.use(*sfx); return result; } @@ -668,40 +557,35 @@ namespace MWSound { sound->init([&] { SoundParams params; - params.mVolume = volume * sfx->mVolume; + params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mPitch = pitch; params.mFlags = mode | type | Play_2D; return params; } ()); - played = mOutput->playSound(sound.get(), sfx->mHandle, offset); + played = mOutput->playSound(sound.get(), sfx->getHandle(), offset); } else { sound->init([&] { SoundParams params; params.mPos = objpos; - params.mVolume = volume * sfx->mVolume; + params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mPitch = pitch; - params.mMinDistance = sfx->mMinDist; - params.mMaxDistance = sfx->mMaxDist; + params.mMinDistance = sfx->getMinDist(); + params.mMaxDistance = sfx->getMaxDist(); params.mFlags = mode | type | Play_3D; return params; } ()); - played = mOutput->playSound3D(sound.get(), sfx->mHandle, offset); + played = mOutput->playSound3D(sound.get(), sfx->getHandle(), offset); } if(!played) return nullptr; - if(sfx->mUses++ == 0) - { - SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx); - if(iter != mUnusedBuffers.end()) - mUnusedBuffers.erase(iter); - } Sound* result = sound.get(); mActiveSounds[ptr].emplace_back(std::move(sound), sfx); + mSoundBuffers.use(*sfx); return result; } @@ -720,25 +604,20 @@ namespace MWSound sound->init([&] { SoundParams params; params.mPos = initialPos; - params.mVolume = volume * sfx->mVolume; + params.mVolume = volume * sfx->getVolume(); params.mBaseVolume = volumeFromType(type); params.mPitch = pitch; - params.mMinDistance = sfx->mMinDist; - params.mMaxDistance = sfx->mMaxDist; + params.mMinDistance = sfx->getMinDist(); + params.mMaxDistance = sfx->getMaxDist(); params.mFlags = mode | type | Play_3D; return params; } ()); - if(!mOutput->playSound3D(sound.get(), sfx->mHandle, offset)) + if(!mOutput->playSound3D(sound.get(), sfx->getHandle(), offset)) return nullptr; - if(sfx->mUses++ == 0) - { - SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx); - if(iter != mUnusedBuffers.end()) - mUnusedBuffers.erase(iter); - } Sound* result = sound.get(); mActiveSounds[MWWorld::ConstPtr()].emplace_back(std::move(sound), sfx); + mSoundBuffers.use(*sfx); return result; } @@ -921,7 +800,7 @@ namespace MWSound case WaterSoundAction::DoNothing: break; case WaterSoundAction::SetVolume: - mNearWaterSound->setVolume(update.mVolume * sfx->mVolume); + mNearWaterSound->setVolume(update.mVolume * sfx->getVolume()); break; case WaterSoundAction::FinishSound: mOutput->finishSound(mNearWaterSound); @@ -1028,7 +907,6 @@ namespace MWSound while(sndidx != snditer->second.end()) { Sound *sound = sndidx->first.get(); - Sound_Buffer *sfx = sndidx->second; if(!ptr.isEmpty() && sound->getIs3D()) { @@ -1050,8 +928,7 @@ namespace MWSound mUnderwaterSound = nullptr; if (sound == mNearWaterSound) mNearWaterSound = nullptr; - if(sfx->mUses-- == 1) - mUnusedBuffers.push_front(sfx); + mSoundBuffers.release(*sndidx->second); sndidx = snditer->second.erase(sndidx); } else @@ -1313,9 +1190,7 @@ namespace MWSound for(SoundBufferRefPair &sndbuf : snd.second) { mOutput->finishSound(sndbuf.first.get()); - Sound_Buffer *sfx = sndbuf.second; - if(sfx->mUses-- == 1) - mUnusedBuffers.push_front(sfx); + mSoundBuffers.release(*sndbuf.second); } } mActiveSounds.clear(); diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index f69171a09..21004dc94 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -18,6 +17,7 @@ #include "watersoundupdater.hpp" #include "type.hpp" #include "volumesettings.hpp" +#include "sound_buffer.hpp" namespace VFS { @@ -36,17 +36,6 @@ namespace MWSound struct Sound_Decoder; class Sound; class Stream; - class Sound_Buffer; - - enum Environment { - Env_Normal, - Env_Underwater - }; - // Extra play flags, not intended for caller use - enum PlayModeEx { - Play_2D = 0, - Play_3D = 1<<31 - }; using SoundPtr = Misc::ObjectPtr; using StreamPtr = Misc::ObjectPtr; @@ -66,21 +55,7 @@ namespace MWSound WaterSoundUpdater mWaterSoundUpdater; - typedef std::unique_ptr > SoundBufferList; - // List of sound buffers, grown as needed. New enties are added to the - // back, allowing existing Sound_Buffer references/pointers to remain - // valid. - SoundBufferList mSoundBuffers; - size_t mBufferCacheMin; - size_t mBufferCacheMax; - size_t mBufferCacheSize; - - typedef std::unordered_map NameBufferMap; - NameBufferMap mBufferNameMap; - - // NOTE: unused buffers are stored in front-newest order. - typedef std::deque SoundList; - SoundList mUnusedBuffers; + SoundBufferPool mSoundBuffers; Misc::ObjectPool mSounds;