#include "sound_buffer.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" #include #include #include #include #include #include #include namespace MWSound { namespace { struct AudioParams { float mAudioDefaultMinDistance; float mAudioDefaultMaxDistance; float mAudioMinDistanceMult; float mAudioMaxDistanceMult; }; AudioParams makeAudioParams(const MWWorld::Store& settings) { 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(Sound_Output& output) : mOutput(&output) , mBufferCacheMax(Settings::sound().mBufferCacheMax * 1024 * 1024) , mBufferCacheMin( std::min(static_cast(Settings::sound().mBufferCacheMin) * 1024 * 1024, mBufferCacheMax)) { } SoundBufferPool::~SoundBufferPool() { clear(); } Sound_Buffer* SoundBufferPool::lookup(const ESM::RefId& 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::lookup(std::string_view fileName) const { const auto it = mBufferFileNameMap.find(std::string(fileName)); if (it != mBufferFileNameMap.end()) { Sound_Buffer* sfx = it->second; if (sfx->getHandle() != nullptr) return sfx; } return nullptr; } Sound_Buffer* SoundBufferPool::loadSfx(Sound_Buffer* sfx) { if (sfx->getHandle() != nullptr) return sfx; auto [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; } Sound_Buffer* SoundBufferPool::load(const ESM::RefId& soundId) { if (mBufferNameMap.empty()) { for (const ESM::Sound& sound : MWBase::Environment::get().getESMStore()->get()) insertSound(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().getESMStore()->get().search(soundId); if (sound == nullptr) return {}; sfx = insertSound(soundId, *sound); } return loadSfx(sfx); } Sound_Buffer* SoundBufferPool::load(std::string_view fileName) { Sound_Buffer* sfx; const auto it = mBufferFileNameMap.find(std::string(fileName)); if (it != mBufferFileNameMap.end()) sfx = it->second; else { sfx = insertSound(fileName); } return loadSfx(sfx); } void SoundBufferPool::clear() { for (auto& sfx : mSoundBuffers) { if (sfx.mHandle) mOutput->unloadSound(sfx.mHandle); sfx.mHandle = nullptr; } mBufferFileNameMap.clear(); mBufferNameMap.clear(); mUnusedBuffers.clear(); } Sound_Buffer* SoundBufferPool::insertSound(std::string_view fileName) { static const AudioParams audioParams = makeAudioParams(MWBase::Environment::get().getESMStore()->get()); float volume = 1.f; float min = std::max(audioParams.mAudioDefaultMinDistance * audioParams.mAudioMinDistanceMult, 1.f); float max = std::max(min, audioParams.mAudioDefaultMaxDistance * audioParams.mAudioMaxDistanceMult); min = std::max(min, 1.0f); max = std::max(min, max); Sound_Buffer& sfx = mSoundBuffers.emplace_back(fileName, volume, min, max); mBufferFileNameMap.emplace(fileName, &sfx); return &sfx; } Sound_Buffer* SoundBufferPool::insertSound(const ESM::RefId& soundId, const ESM::Sound& sound) { static const AudioParams audioParams = makeAudioParams(MWBase::Environment::get().getESMStore()->get()); 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( Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(sound.mSound)), volume, min, max); 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(); } } }