#include "SoundSystem.h" #include #include // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- void SoundSystem::CheckError(FMOD_RESULT result, const char* context) { if (result != FMOD_OK) { std::cout << "FMOD error [" << context << "]: " << FMOD_ErrorString(result) << "\n"; } } FMOD_VECTOR SoundSystem::ToFMOD(const glm::vec3& v) { return { v.x, v.y, v.z }; } // --------------------------------------------------------------------------- // Construction / destruction // --------------------------------------------------------------------------- SoundSystem::SoundSystem() { FMOD_RESULT result; result = FMOD::System_Create(&system); CheckError(result, "System_Create"); // 512 max channels – raise if you need more simultaneous voices. result = system->init(512, FMOD_INIT_NORMAL | FMOD_INIT_3D_RIGHTHANDED, nullptr); CheckError(result, "system->init"); // One unit is one meter. Rolloff is per sound, see SetDistances. system->set3DSettings(1.0f, // doppler scale 1.0f, // distance factor (metres per unit) 1.0f); // rolloff scale, 1 is real world falloff between a sound's min and max distance // Default listener orientation (matches XAudio2 original). SetListenerPosition({ 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 1.0f }, { 0.0f, 1.0f, 0.0f }); } SoundSystem::~SoundSystem() { for (auto& [key, sd] : cache) { if (sd && sd->sound) sd->sound->release(); delete sd; } cache.clear(); if (system) { system->close(); system->release(); } } // --------------------------------------------------------------------------- // Asset loading // --------------------------------------------------------------------------- SoundData* SoundSystem::LoadSound(const std::string& filename, bool is3D) { // Return cached version if already loaded. auto it = cache.find(filename); if (it != cache.end()) return it->second; FMOD_MODE mode = FMOD_DEFAULT | FMOD_CREATECOMPRESSEDSAMPLE; // Inverse falloff like real sound, tapering to silence at max distance so far off sounds don't linger at a murmur if (is3D) mode |= FMOD_3D | FMOD_3D_INVERSETAPEREDROLLOFF; else mode |= FMOD_2D; SoundData* sd = new SoundData(); sd->is3D = is3D; FMOD_RESULT result = system->createSound(filename.c_str(), mode, nullptr, &sd->sound); CheckError(result, "createSound"); if (result != FMOD_OK) { delete sd; return nullptr; } if (is3D) sd->sound->set3DMinMaxDistance(DEFAULT_MIN_DISTANCE, DEFAULT_MAX_DISTANCE); cache[filename] = sd; return sd; } void SoundSystem::SetDistances(SoundData* data, float minDistance, float maxDistance) { if (!data || !data->sound || !data->is3D) return; minDistance = std::max(minDistance, 0.01f); CheckError(data->sound->set3DMinMaxDistance(minDistance, std::max(maxDistance, minDistance + 0.01f)), "set3DMinMaxDistance"); } // --------------------------------------------------------------------------- // Playback // --------------------------------------------------------------------------- // Looping set on the channel, so one play of a sound looping doesn't make every other play of it loop. // Keeps the rest of the sound's mode, like being 3D and how it rolls off. static void SetChannelLooping(FMOD::Sound* sound, FMOD::Channel* channel, bool loop) { FMOD_MODE mode = 0; sound->getMode(&mode); mode &= ~(FMOD_LOOP_OFF | FMOD_LOOP_NORMAL | FMOD_LOOP_BIDI); mode |= loop ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF; channel->setMode(mode); channel->setLoopCount(loop ? -1 : 0); } FMOD::Channel* SoundSystem::PlaySound(SoundData* data, bool loop) { if (!data || !data->sound) return nullptr; FMOD::Channel* channel = nullptr; FMOD_RESULT result = system->playSound(data->sound, nullptr, true, &channel); CheckError(result, "playSound"); if (result != FMOD_OK || !channel) return nullptr; SetChannelLooping(data->sound, channel, loop); channel->setPaused(false); return channel; } FMOD::Channel* SoundSystem::PlaySound3D(SoundData* data, const glm::vec3& position, bool loop) { if (!data || !data->sound) return nullptr; // Start paused so we can set position before the first audio callback. FMOD::Channel* channel = nullptr; FMOD_RESULT result = system->playSound(data->sound, nullptr, true, &channel); CheckError(result, "playSound3D"); if (result != FMOD_OK || !channel) return nullptr; FMOD_VECTOR pos = ToFMOD(position); FMOD_VECTOR vel = { 0.0f, 0.0f, 0.0f }; channel->set3DAttributes(&pos, &vel); SetChannelLooping(data->sound, channel, loop); channel->setPaused(false); return channel; } // --------------------------------------------------------------------------- // Listener // --------------------------------------------------------------------------- void SoundSystem::SetListenerPosition(const glm::vec3& position, const glm::vec3& forward, const glm::vec3& up, const glm::vec3& velocity) { FMOD_VECTOR pos = ToFMOD(position); FMOD_VECTOR vel = ToFMOD(velocity); FMOD_VECTOR fwd = ToFMOD(forward); FMOD_VECTOR up_ = ToFMOD(up); system->set3DListenerAttributes(0, &pos, &vel, &fwd, &up_); } // --------------------------------------------------------------------------- // Per-channel 3D update // --------------------------------------------------------------------------- void SoundSystem::SetChannelPosition(FMOD::Channel* channel, const glm::vec3& position, const glm::vec3& velocity) { if (!channel) return; FMOD_VECTOR pos = ToFMOD(position); FMOD_VECTOR vel = ToFMOD(velocity); channel->set3DAttributes(&pos, &vel); } // --------------------------------------------------------------------------- // Frame update – must be called once per game loop tick // --------------------------------------------------------------------------- void SoundSystem::Update() { system->update(); }