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
This commit is contained in:
Kelsi 2026-02-09 16:12:06 -08:00
parent bbfab23566
commit d6f0c2ec46
3 changed files with 321 additions and 0 deletions

View file

@ -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<int>(currentWeather_),
" to ", static_cast<int>(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<int>(currentZone_),
" to ", static_cast<int>(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<AmbientSample>* 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<int>(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<AmbientSample>* 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<int>(currentZone_),
" (", isDay ? "day" : "night", ")");
zoneLoopTime_ = 0.0f;
}
}
}
} // namespace audio
} // namespace wowee