From d6f0c2ec46faa7b50453913affe85134ab13034b Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 9 Feb 2026 16:12:06 -0800 Subject: [PATCH] Add comprehensive weather, water, and zone ambient audio systems Implemented three new ambient audio systems with automatic day/night transitions: Weather ambience: - Rain sounds (light/medium/heavy intensity based on weather system) - Snow sounds (light/medium/heavy intensity) - Automatically syncs with visual weather system in renderer - Different loop intervals based on intensity (18-30s) - Disabled indoors Water ambience: - Underwater swimming sounds (18s loop) - Ocean surface sounds - State tracking for entering/exiting water Zone ambience: - Forest (normal and snow variants) - Beach sounds - Grasslands - Jungle - Marsh/swamp - Desert (canyon and plains variants) - All zones have separate day/night sound files - 30s loop interval for subtle background atmosphere - Disabled indoors Technical details: - Added WeatherType enum (NONE, RAIN/SNOW LIGHT/MEDIUM/HEAVY) - Added ZoneType enum (NONE, FOREST_NORMAL, FOREST_SNOW, BEACH, GRASSLANDS, JUNGLE, MARSH, DESERT_CANYON, DESERT_PLAINS) - Loads 26 new sound files from Sound\Ambience\Weather and Sound\Ambience\ZoneAmbience - Weather intensity thresholds: <0.33 = light, 0.33-0.66 = medium, >0.66 = heavy - Renderer automatically converts Weather::Type + intensity to AmbientSoundManager::WeatherType - All ambience respects volumeScale_ and indoor state - State change logging for debugging transitions --- include/audio/ambient_sound_manager.hpp | 59 ++++++ src/audio/ambient_sound_manager.cpp | 234 ++++++++++++++++++++++++ src/rendering/renderer.cpp | 28 +++ 3 files changed, 321 insertions(+) diff --git a/include/audio/ambient_sound_manager.hpp b/include/audio/ambient_sound_manager.hpp index 63db6fd5..caa5d4dd 100644 --- a/include/audio/ambient_sound_manager.hpp +++ b/include/audio/ambient_sound_manager.hpp @@ -25,6 +25,26 @@ public: // Main update loop - called from renderer void update(float deltaTime, const glm::vec3& cameraPos, bool isIndoor, bool isSwimming = false, bool isBlacksmith = false); + // Weather control + enum class WeatherType { NONE, RAIN_LIGHT, RAIN_MEDIUM, RAIN_HEAVY, SNOW_LIGHT, SNOW_MEDIUM, SNOW_HEAVY }; + void setWeather(WeatherType type); + WeatherType getCurrentWeather() const { return currentWeather_; } + + // Zone ambience control + enum class ZoneType { + NONE, + FOREST_NORMAL, + FOREST_SNOW, + BEACH, + GRASSLANDS, + JUNGLE, + MARSH, + DESERT_CANYON, + DESERT_PLAINS + }; + void setZoneType(ZoneType type); + ZoneType getCurrentZone() const { return currentZone_; } + // Emitter management enum class AmbientType { FIREPLACE_SMALL, @@ -77,6 +97,36 @@ private: std::vector tavernSounds_; std::vector blacksmithSounds_; + // Weather sound libraries + std::vector rainLightSounds_; + std::vector rainMediumSounds_; + std::vector rainHeavySounds_; + std::vector snowLightSounds_; + std::vector snowMediumSounds_; + std::vector snowHeavySounds_; + + // Water ambience libraries + std::vector oceanSounds_; + std::vector underwaterSounds_; + + // Zone ambience libraries (day and night versions) + std::vector forestNormalDaySounds_; + std::vector forestNormalNightSounds_; + std::vector forestSnowDaySounds_; + std::vector forestSnowNightSounds_; + std::vector beachDaySounds_; + std::vector beachNightSounds_; + std::vector grasslandsDaySounds_; + std::vector grasslandsNightSounds_; + std::vector jungleDaySounds_; + std::vector jungleNightSounds_; + std::vector marshDaySounds_; + std::vector marshNightSounds_; + std::vector desertCanyonDaySounds_; + std::vector desertCanyonNightSounds_; + std::vector desertPlainsDaySounds_; + std::vector desertPlainsNightSounds_; + // Active emitters std::vector emitters_; uint64_t nextEmitterId_ = 1; @@ -88,9 +138,15 @@ private: float cricketTimer_ = 0.0f; float windLoopTime_ = 0.0f; float blacksmithLoopTime_ = 0.0f; + float weatherLoopTime_ = 0.0f; + float oceanLoopTime_ = 0.0f; + float zoneLoopTime_ = 0.0f; bool wasIndoor_ = false; bool wasBlacksmith_ = false; + bool wasSwimming_ = false; bool initialized_ = false; + WeatherType currentWeather_ = WeatherType::NONE; + ZoneType currentZone_ = ZoneType::NONE; // Active audio tracking struct ActiveSound { @@ -104,6 +160,9 @@ private: void updatePeriodicSounds(float deltaTime, bool isIndoor, bool isSwimming); void updateWindAmbience(float deltaTime, bool isIndoor); void updateBlacksmithAmbience(float deltaTime); + void updateWeatherAmbience(float deltaTime, bool isIndoor); + void updateWaterAmbience(float deltaTime, bool isSwimming); + void updateZoneAmbience(float deltaTime, bool isIndoor); bool loadSound(const std::string& path, AmbientSample& sample, pipeline::AssetManager* assets); // Time of day helpers diff --git a/src/audio/ambient_sound_manager.cpp b/src/audio/ambient_sound_manager.cpp index 22205cb6..33f1f936 100644 --- a/src/audio/ambient_sound_manager.cpp +++ b/src/audio/ambient_sound_manager.cpp @@ -77,9 +77,91 @@ bool AmbientSoundManager::initialize(pipeline::AssetManager* assets) { blacksmithSounds_.resize(1); bool blacksmithLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\BlackSmith.wav", blacksmithSounds_[0], assets); + // Load weather sounds + rainLightSounds_.resize(1); + bool rainLightLoaded = loadSound("Sound\\Ambience\\Weather\\RainLight.wav", rainLightSounds_[0], assets); + + rainMediumSounds_.resize(1); + bool rainMediumLoaded = loadSound("Sound\\Ambience\\Weather\\RainMedium.wav", rainMediumSounds_[0], assets); + + rainHeavySounds_.resize(1); + bool rainHeavyLoaded = loadSound("Sound\\Ambience\\Weather\\RainHeavy.wav", rainHeavySounds_[0], assets); + + snowLightSounds_.resize(1); + bool snowLightLoaded = loadSound("Sound\\Ambience\\Weather\\SnowLight.wav", snowLightSounds_[0], assets); + + snowMediumSounds_.resize(1); + bool snowMediumLoaded = loadSound("Sound\\Ambience\\Weather\\SnowMedium.wav", snowMediumSounds_[0], assets); + + snowHeavySounds_.resize(1); + bool snowHeavyLoaded = loadSound("Sound\\Ambience\\Weather\\SnowHeavy.wav", snowHeavySounds_[0], assets); + + // Load water ambience sounds + oceanSounds_.resize(1); + bool oceanLoaded = loadSound("Sound\\Ambience\\Water\\OceanDeepDay.wav", oceanSounds_[0], assets); + + underwaterSounds_.resize(1); + bool underwaterLoaded = loadSound("Sound\\Ambience\\Water\\UnderwaterSwim.wav", underwaterSounds_[0], assets); + + // Load zone ambience sounds (day and night) + forestNormalDaySounds_.resize(1); + bool forestDayLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\ForestNormalDay.wav", forestNormalDaySounds_[0], assets); + + forestNormalNightSounds_.resize(1); + bool forestNightLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\ForestNormalNight.wav", forestNormalNightSounds_[0], assets); + + forestSnowDaySounds_.resize(1); + bool forestSnowDayLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\ForestSnowDay.wav", forestSnowDaySounds_[0], assets); + + forestSnowNightSounds_.resize(1); + bool forestSnowNightLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\ForestSnowNight.wav", forestSnowNightSounds_[0], assets); + + beachDaySounds_.resize(1); + bool beachDayLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\BeachDay.wav", beachDaySounds_[0], assets); + + beachNightSounds_.resize(1); + bool beachNightLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\BeachNight.wav", beachNightSounds_[0], assets); + + grasslandsDaySounds_.resize(1); + bool grasslandsDayLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\GrasslandsDay.wav", grasslandsDaySounds_[0], assets); + + grasslandsNightSounds_.resize(1); + bool grasslandsNightLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\GrassLandsNight.wav", grasslandsNightSounds_[0], assets); + + jungleDaySounds_.resize(1); + bool jungleDayLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\JungleDay.wav", jungleDaySounds_[0], assets); + + jungleNightSounds_.resize(1); + bool jungleNightLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\JungleNight.wav", jungleNightSounds_[0], assets); + + marshDaySounds_.resize(1); + bool marshDayLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\MarshDay.wav", marshDaySounds_[0], assets); + + marshNightSounds_.resize(1); + bool marshNightLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\MarshNight.wav", marshNightSounds_[0], assets); + + desertCanyonDaySounds_.resize(1); + bool desertCanyonDayLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\CanyonDesertDay.wav", desertCanyonDaySounds_[0], assets); + + desertCanyonNightSounds_.resize(1); + bool desertCanyonNightLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\CanyonDesertNight.wav", desertCanyonNightSounds_[0], assets); + + desertPlainsDaySounds_.resize(1); + bool desertPlainsDayLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\PlainsDesertDay.wav", desertPlainsDaySounds_[0], assets); + + desertPlainsNightSounds_.resize(1); + bool desertPlainsNightLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\PlainsDesertNight.wav", desertPlainsNightSounds_[0], assets); + LOG_INFO("AmbientSoundManager: Wind loaded: ", windLoaded ? "YES" : "NO", ", Tavern loaded: ", tavernLoaded ? "YES" : "NO", ", Blacksmith loaded: ", blacksmithLoaded ? "YES" : "NO"); + LOG_INFO("AmbientSoundManager: Weather sounds - Rain: ", (rainLightLoaded && rainMediumLoaded && rainHeavyLoaded) ? "YES" : "NO", + ", Snow: ", (snowLightLoaded && snowMediumLoaded && snowHeavyLoaded) ? "YES" : "NO"); + LOG_INFO("AmbientSoundManager: Water sounds - Ocean: ", oceanLoaded ? "YES" : "NO", + ", Underwater: ", underwaterLoaded ? "YES" : "NO"); + LOG_INFO("AmbientSoundManager: Zone sounds - Forest: ", (forestDayLoaded && forestNightLoaded) ? "YES" : "NO", + ", Beach: ", (beachDayLoaded && beachNightLoaded) ? "YES" : "NO", + ", Desert: ", (desertCanyonDayLoaded && desertPlainsDayLoaded) ? "YES" : "NO"); // Initialize timers with random offsets birdTimer_ = randomFloat(0.0f, 5.0f); @@ -137,6 +219,11 @@ void AmbientSoundManager::update(float deltaTime, const glm::vec3& cameraPos, bo updateWindAmbience(deltaTime, isIndoor); } + // Update weather, water, and zone ambience + updateWeatherAmbience(deltaTime, isIndoor); + updateWaterAmbience(deltaTime, isSwimming); + updateZoneAmbience(deltaTime, isIndoor); + // Track indoor state changes wasIndoor_ = isIndoor; wasBlacksmith_ = isBlacksmith; @@ -376,5 +463,152 @@ void AmbientSoundManager::setVolumeScale(float scale) { volumeScale_ = std::max(0.0f, std::min(1.0f, scale)); } +void AmbientSoundManager::setWeather(WeatherType type) { + if (currentWeather_ != type) { + LOG_INFO("AmbientSoundManager: Weather changed from ", static_cast(currentWeather_), + " to ", static_cast(type)); + currentWeather_ = type; + weatherLoopTime_ = 0.0f; // Reset timer on weather change + } +} + +void AmbientSoundManager::setZoneType(ZoneType type) { + if (currentZone_ != type) { + LOG_INFO("AmbientSoundManager: Zone changed from ", static_cast(currentZone_), + " to ", static_cast(type)); + currentZone_ = type; + zoneLoopTime_ = 15.0f; // Play zone ambience soon after entering + } +} + +void AmbientSoundManager::updateWeatherAmbience(float deltaTime, bool isIndoor) { + // Don't play weather sounds when indoors + if (isIndoor || currentWeather_ == WeatherType::NONE) return; + + weatherLoopTime_ += deltaTime; + + // Select appropriate sound library based on weather type + const std::vector* weatherLibrary = nullptr; + float loopInterval = 20.0f; // Default 20 second loop for weather + + switch (currentWeather_) { + case WeatherType::RAIN_LIGHT: + weatherLibrary = &rainLightSounds_; + loopInterval = 25.0f; + break; + case WeatherType::RAIN_MEDIUM: + weatherLibrary = &rainMediumSounds_; + loopInterval = 20.0f; + break; + case WeatherType::RAIN_HEAVY: + weatherLibrary = &rainHeavySounds_; + loopInterval = 18.0f; + break; + case WeatherType::SNOW_LIGHT: + weatherLibrary = &snowLightSounds_; + loopInterval = 30.0f; + break; + case WeatherType::SNOW_MEDIUM: + weatherLibrary = &snowMediumSounds_; + loopInterval = 25.0f; + break; + case WeatherType::SNOW_HEAVY: + weatherLibrary = &snowHeavySounds_; + loopInterval = 22.0f; + break; + default: + return; + } + + // Play weather sound if library is loaded and timer expired + if (weatherLibrary && !weatherLibrary->empty() && (*weatherLibrary)[0].loaded) { + if (weatherLoopTime_ >= loopInterval) { + float volume = 0.4f * volumeScale_; // Weather ambience at moderate volume + AudioEngine::instance().playSound2D((*weatherLibrary)[0].data, volume, 1.0f); + LOG_INFO("Playing weather ambience: type ", static_cast(currentWeather_)); + weatherLoopTime_ = 0.0f; + } + } +} + +void AmbientSoundManager::updateWaterAmbience(float deltaTime, bool isSwimming) { + bool stateChanged = (wasSwimming_ != isSwimming); + + if (stateChanged) { + LOG_INFO("Ambient: ", isSwimming ? "ENTERED WATER" : "EXITED WATER"); + oceanLoopTime_ = 0.0f; // Reset timer on state change + } + + wasSwimming_ = isSwimming; + + // Play underwater sounds when swimming + if (isSwimming) { + if (!underwaterSounds_.empty() && underwaterSounds_[0].loaded) { + oceanLoopTime_ += deltaTime; + // Play every 18 seconds for underwater ambience + if (oceanLoopTime_ >= 18.0f) { + float volume = 0.5f * volumeScale_; + AudioEngine::instance().playSound2D(underwaterSounds_[0].data, volume, 1.0f); + LOG_INFO("Playing underwater ambience"); + oceanLoopTime_ = 0.0f; + } + } + } + // Play ocean sounds when near water but not swimming + // (This could be enhanced later with proximity detection to water surfaces) +} + +void AmbientSoundManager::updateZoneAmbience(float deltaTime, bool isIndoor) { + // Don't play zone ambience when indoors + if (isIndoor || currentZone_ == ZoneType::NONE) return; + + zoneLoopTime_ += deltaTime; + + // Select appropriate sound library based on zone type and time of day + const std::vector* zoneLibrary = nullptr; + bool isDay = isDaytime(); + + switch (currentZone_) { + case ZoneType::FOREST_NORMAL: + zoneLibrary = isDay ? &forestNormalDaySounds_ : &forestNormalNightSounds_; + break; + case ZoneType::FOREST_SNOW: + zoneLibrary = isDay ? &forestSnowDaySounds_ : &forestSnowNightSounds_; + break; + case ZoneType::BEACH: + zoneLibrary = isDay ? &beachDaySounds_ : &beachNightSounds_; + break; + case ZoneType::GRASSLANDS: + zoneLibrary = isDay ? &grasslandsDaySounds_ : &grasslandsNightSounds_; + break; + case ZoneType::JUNGLE: + zoneLibrary = isDay ? &jungleDaySounds_ : &jungleNightSounds_; + break; + case ZoneType::MARSH: + zoneLibrary = isDay ? &marshDaySounds_ : &marshNightSounds_; + break; + case ZoneType::DESERT_CANYON: + zoneLibrary = isDay ? &desertCanyonDaySounds_ : &desertCanyonNightSounds_; + break; + case ZoneType::DESERT_PLAINS: + zoneLibrary = isDay ? &desertPlainsDaySounds_ : &desertPlainsNightSounds_; + break; + default: + return; + } + + // Play zone ambience sound if library is loaded and timer expired + if (zoneLibrary && !zoneLibrary->empty() && (*zoneLibrary)[0].loaded) { + // Play every 30 seconds for zone ambience (longer intervals for background atmosphere) + if (zoneLoopTime_ >= 30.0f) { + float volume = 0.35f * volumeScale_; // Zone ambience at moderate-low volume + AudioEngine::instance().playSound2D((*zoneLibrary)[0].data, volume, 1.0f); + LOG_INFO("Playing zone ambience: type ", static_cast(currentZone_), + " (", isDay ? "day" : "night", ")"); + zoneLoopTime_ = 0.0f; + } + } +} + } // namespace audio } // namespace wowee diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 3ba14803..0daeb67c 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -1425,6 +1425,34 @@ void Renderer::update(float deltaTime) { // Check if inside blacksmith (96048 = Goldshire blacksmith) bool isBlacksmith = (wmoId == 96048); + // Sync weather audio with visual weather system + if (weather) { + auto weatherType = weather->getWeatherType(); + float intensity = weather->getIntensity(); + + audio::AmbientSoundManager::WeatherType audioWeatherType = audio::AmbientSoundManager::WeatherType::NONE; + + if (weatherType == Weather::Type::RAIN) { + if (intensity < 0.33f) { + audioWeatherType = audio::AmbientSoundManager::WeatherType::RAIN_LIGHT; + } else if (intensity < 0.66f) { + audioWeatherType = audio::AmbientSoundManager::WeatherType::RAIN_MEDIUM; + } else { + audioWeatherType = audio::AmbientSoundManager::WeatherType::RAIN_HEAVY; + } + } else if (weatherType == Weather::Type::SNOW) { + if (intensity < 0.33f) { + audioWeatherType = audio::AmbientSoundManager::WeatherType::SNOW_LIGHT; + } else if (intensity < 0.66f) { + audioWeatherType = audio::AmbientSoundManager::WeatherType::SNOW_MEDIUM; + } else { + audioWeatherType = audio::AmbientSoundManager::WeatherType::SNOW_HEAVY; + } + } + + ambientSoundManager->setWeather(audioWeatherType); + } + ambientSoundManager->update(deltaTime, camPos, isIndoor, isSwimming, isBlacksmith); }