#include "stdafx.h" #include "SoundEngine.h" #include "EterBase/Random.h" #include "EterBase/Timer.h" #include "PackLib/PackManager.h" SoundEngine::SoundEngine() { } SoundEngine::~SoundEngine() { for (auto& [name, instance] : m_Sounds2D) instance.Destroy(); for (auto& instance : m_Sounds3D) instance.Destroy(); ma_engine_uninit(&m_Engine); m_Files.clear(); m_Sounds2D.clear(); } bool SoundEngine::Initialize() { if (!MD_ASSERT(ma_engine_init(NULL, &m_Engine) == MA_SUCCESS)) { TraceError("SoundEngine::Initialize: Failed to initialize engine."); return false; } ma_engine_listener_set_position(&m_Engine, 0, 0, 0, 0); // engine SetListenerPosition(0.0f, 0.0f, 0.0f); // character SetListenerOrientation(0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f); return true; } void SoundEngine::SetSoundVolume(float volume) { m_SoundVolume = std::clamp(volume, 0.0, 1.0); } bool SoundEngine::PlaySound2D(const std::string& name) { if (!Internal_LoadSoundFromPack(name)) return false; auto& instance = m_Sounds2D[name]; // 2d sounds are persistent, no need to destroy instance.InitFromBuffer(m_Engine, m_Files[name], name); instance.Config3D(false); instance.SetVolume(m_SoundVolume); return instance.Play(); } MaSoundInstance* SoundEngine::PlaySound3D(const std::string& name, float fx, float fy, float fz) { if (auto instance = Internal_GetInstance3D(name)) { constexpr float minDist = 100.0f; // 1m constexpr float maxDist = 5000.0f; // 50m instance->SetPosition(fx - m_CharacterPosition.x, fy - m_CharacterPosition.y, fz - m_CharacterPosition.z); instance->Config3D(true, minDist, maxDist); instance->SetVolume(m_SoundVolume); instance->Play(); return instance; } return nullptr; } MaSoundInstance* SoundEngine::PlayAmbienceSound3D(float fx, float fy, float fz, const std::string& name, int loopCount) { auto vec3 = ma_engine_listener_get_position(&m_Engine, 0); float dx = fx - vec3.x; float dy = fy - vec3.y; float dz = fz - vec3.z; float distance = sqrtf(dx * dx + dy * dy + dz * dz); return PlaySound3D(name, fx, fy, fz); } void SoundEngine::StopAllSound3D() { for (auto& instance : m_Sounds3D) instance.Stop(); } void SoundEngine::UpdateSoundInstance(float fx, float fy, float fz, uint32_t dwcurFrame, const NSound::TSoundInstanceVector* c_pSoundInstanceVector, bool checkFrequency) { for (uint32_t i = 0; i < c_pSoundInstanceVector->size(); ++i) { const NSound::TSoundInstance& c_rSoundInstance = c_pSoundInstanceVector->at(i); if (c_rSoundInstance.dwFrame == dwcurFrame) { if (checkFrequency) { float& lastPlay = m_PlaySoundHistoryMap[c_rSoundInstance.strSoundFileName]; float diff = CTimer::Instance().GetCurrentSecond() - lastPlay; if (CTimer::Instance().GetCurrentSecond() - lastPlay < 0.3f) return; lastPlay = CTimer::Instance().GetCurrentSecond(); } PlaySound3D(c_rSoundInstance.strSoundFileName, fx, fy, fz); } } } bool SoundEngine::FadeInMusic(const std::string& path, float targetVolume /* 1.0f by default */, float fadeInDurationSecondsFromMin) { if (path.empty()) return false; auto& fadeOutMusic = m_Music[m_CurrentMusicIndex]; if (fadeOutMusic.IsPlaying() && path == fadeOutMusic.GetIdentity()) { fadeOutMusic.Fade(targetVolume, fadeInDurationSecondsFromMin); return fadeOutMusic.Resume(); } // We're basically just swapping FadeOutMusic(fadeOutMusic.GetIdentity()); m_CurrentMusicIndex = int(!m_CurrentMusicIndex); auto& music = m_Music[m_CurrentMusicIndex]; music.Destroy(); music.InitFromFile(m_Engine, path); music.Config3D(false); music.Loop(); music.SetVolume(0.0f); music.Fade(targetVolume, fadeInDurationSecondsFromMin); return music.Play(); } void SoundEngine::FadeOutMusic(const std::string& name, float targetVolume, float fadeOutDurationSecondsFromMax) { for (auto& music : m_Music) { if (music.GetIdentity() == name) music.Fade(targetVolume, fadeOutDurationSecondsFromMax); } } void SoundEngine::FadeOutAllMusic() { for (auto& music : m_Music) FadeOutMusic(music.GetIdentity()); } void SoundEngine::SetMusicVolume(float volume) { m_MusicVolume = std::clamp(volume, 0.0f, 1.0f); m_Music[m_CurrentMusicIndex].StopFading(); m_Music[m_CurrentMusicIndex].SetVolume(m_MusicVolume); } float SoundEngine::GetMusicVolume() const { return m_MusicVolume; } void SoundEngine::SaveVolume(bool isMinimized) { constexpr float ratePerSecond = 1.0f / CS_CLIENT_FPS; // 1.0 to 0 in 1s if minimized, 3s if just out of focus const float durationOnFullVolume = isMinimized ? 1.0f : 3.0f; float outOfFocusVolume = 0.35f; if (m_MasterVolume <= outOfFocusVolume) outOfFocusVolume = m_MasterVolume; m_MasterVolumeFadeTarget = isMinimized ? 0.0f : outOfFocusVolume; m_MasterVolumeFadeRatePerFrame = -ratePerSecond / durationOnFullVolume; } void SoundEngine::RestoreVolume() { constexpr float ratePerSecond = 1.0f / CS_CLIENT_FPS; constexpr float durationToFullVolume = 4.0f; // 0 to 1.0 in 4s m_MasterVolumeFadeTarget = m_MasterVolume; m_MasterVolumeFadeRatePerFrame = ratePerSecond / durationToFullVolume; } void SoundEngine::SetMasterVolume(float volume) { m_MasterVolume = volume; ma_engine_set_volume(&m_Engine, volume); } void SoundEngine::SetListenerPosition(float x, float y, float z) { m_CharacterPosition.x = x; m_CharacterPosition.y = y; m_CharacterPosition.z = z; } void SoundEngine::SetListenerOrientation(float forwardX, float forwardY, float forwardZ, float upX, float upY, float upZ) { ma_engine_listener_set_direction(&m_Engine, 0, forwardX, forwardY, -forwardZ); ma_engine_listener_set_world_up(&m_Engine, 0, upX, -upY, upZ); } void SoundEngine::Update() { for (auto& music : m_Music) music.Update(); if (m_MasterVolumeFadeRatePerFrame) { float volume = ma_engine_get_volume(&m_Engine) + m_MasterVolumeFadeRatePerFrame; if ((m_MasterVolumeFadeRatePerFrame > 0.0f && volume >= m_MasterVolumeFadeTarget) || (m_MasterVolumeFadeRatePerFrame < 0.0f && volume <= m_MasterVolumeFadeTarget)) { volume = m_MasterVolumeFadeTarget; m_MasterVolumeFadeRatePerFrame = 0.0f; } ma_engine_set_volume(&m_Engine, volume); } } MaSoundInstance* SoundEngine::Internal_GetInstance3D(const std::string& name) { if (Internal_LoadSoundFromPack(name)) { for (auto& instance : m_Sounds3D) { if (!instance.IsPlaying()) { instance.Destroy(); instance.InitFromBuffer(m_Engine, m_Files[name], name); return &instance; } } } return nullptr; } bool SoundEngine::Internal_LoadSoundFromPack(const std::string& name) { if (m_Files.find(name) == m_Files.end()) { TPackFile soundFile; if (!CPackManager::Instance().GetFile(name, soundFile)) { TraceError("Internal_LoadSoundFromPack: SoundEngine: Failed to register file '%s' - not found.", name.c_str()); return false; } auto& buffer = m_Files[name]; buffer.resize(soundFile.size()); memcpy(buffer.data(), soundFile.data(), soundFile.size()); } return true; }