diff --git a/common/src/outcome.rs b/common/src/outcome.rs index 7a7c3cc734..69ef8fb3da 100644 --- a/common/src/outcome.rs +++ b/common/src/outcome.rs @@ -111,7 +111,9 @@ impl Outcome { | Outcome::Utterance { pos, .. } | Outcome::Glider { pos, .. } => Some(*pos), Outcome::BreakBlock { pos, .. } => Some(pos.map(|e| e as f32 + 0.5)), - Outcome::ExpChange { .. } | Outcome::ComboChange { .. } | Outcome::SkillPointGain { .. } => None, + Outcome::ExpChange { .. } + | Outcome::ComboChange { .. } + | Outcome::SkillPointGain { .. } => None, } } } diff --git a/voxygen/src/audio/ambient.rs b/voxygen/src/audio/ambient.rs index d0c89e8c0d..bd3da0e618 100644 --- a/voxygen/src/audio/ambient.rs +++ b/voxygen/src/audio/ambient.rs @@ -1,6 +1,9 @@ //! Handles ambient non-positional sounds use crate::{ - audio::{channel::AmbientChannelTag, AudioFrontend}, + audio::{ + channel::{AmbientChannel, AmbientChannelTag}, + AudioFrontend, + }, scene::Camera, }; use client::Client; @@ -10,12 +13,13 @@ use common::{ }; use common_state::State; use serde::Deserialize; +use strum::IntoEnumIterator; use std::time::Instant; use tracing::warn; use vek::*; #[derive(Debug, Default, Deserialize)] -struct AmbientCollection { +pub struct AmbientCollection { tracks: Vec, } @@ -29,49 +33,11 @@ pub struct AmbientItem { tag: AmbientChannelTag, } -pub struct AmbientWindMgr { - ambience: AssetHandle, - began_playing: Instant, - next_track_change: f32, - volume: f32, - tree_multiplier: f32, +pub struct AmbientMgr { + pub ambience: AssetHandle, } -pub struct AmbientRainMgr { - ambience: AssetHandle, - began_playing: Instant, - next_track_change: f32, - volume: f32, - rain_intensity: f32, -} - -impl Default for AmbientWindMgr { - fn default() -> Self { - Self { - ambience: load_ambience_items(), - began_playing: Instant::now(), - next_track_change: 0.0, - volume: 0.0, - tree_multiplier: 0.0, - } - } -} - -impl Default for AmbientRainMgr { - fn default() -> Self { - Self { - ambience: load_ambience_items(), - began_playing: Instant::now(), - next_track_change: 0.0, - volume: 0.0, - rain_intensity: 0.0, - } - } -} - -impl AmbientWindMgr { - /// Checks whether the previous track has completed. If so, sends a - /// request to play the next (random) track +impl AmbientMgr { pub fn maintain( &mut self, audio: &mut AudioFrontend, @@ -79,157 +45,156 @@ impl AmbientWindMgr { client: &Client, camera: &Camera, ) { - if audio.sfx_enabled() && !self.ambience.read().tracks.is_empty() { - let focus_off = camera.get_focus_pos().map(f32::trunc); - let cam_pos = camera.dependents().cam_pos + focus_off; - - let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() { - (chunk.meta().alt(), chunk.meta().tree_density()) - } else { - (0.0, 0.0) - }; - - // The following code is specifically for wind, as it is the only - // non-positional ambient sound in the game. Others can be added - // as seen fit. - - let target_volume = { - // Wind volume increases with altitude - let alt_multiplier = (cam_pos.z / 1200.0).abs(); - - // Tree density factors into wind volume. The more trees, - // the lower wind volume. The trees make more of an impact - // the closer the camera is to the ground. - self.tree_multiplier = ((1.0 - tree_density) - + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)) - .min(1.0); - - let mut volume_multiplier = alt_multiplier * self.tree_multiplier; - - // Checks if the camera is underwater to stop ambient sounds - if state - .terrain() - .get((cam_pos).map(|e| e.floor() as i32)) - .map(|b| b.is_liquid()) - .unwrap_or(false) - { - volume_multiplier *= 0.1; - } - // Is the camera roughly under the terrain? - if cam_pos.z < terrain_alt - 10.0 { - volume_multiplier = 0.0; + let sfx_volume = audio.get_sfx_volume(); + // iterate through each tag + for tag in AmbientChannelTag::iter() { + // iterate through the supposed number of channels - one for each tag + for index in 0..AmbientChannelTag::iter().len() { + // if index would exceed current number of channels, create a new one with + // current tag + if index >= audio.ambient_channels.len() { + audio.new_ambient_channel(tag); } + // update with sfx volume + audio.ambient_channels[index].set_volume(sfx_volume); + // if current channel's tag is not the current tag, move on to next channel + if audio.ambient_channels[index].get_tag() == tag { + // maintain: get the correct multiplier of whatever the tag of the current + // channel is + let target_volume = + audio.ambient_channels[index].maintain(state, client, camera); + // get multiplier of the current channel + let initial_volume = audio.ambient_channels[index].get_multiplier(); - volume_multiplier.clamped(0.0, 1.0) - }; + // lerp multiplier of current channel + audio.ambient_channels[index].set_multiplier(Lerp::lerp( + initial_volume, + target_volume, + 0.01, + )); - // Transitions the ambient sounds (more) smoothly - if audio.get_ambient_channel(AmbientChannelTag::Wind).is_none() { - audio.new_ambient_channel(AmbientChannelTag::Wind); - } else { - self.volume = audio.get_ambient_volume(AmbientChannelTag::Wind); - audio.set_ambient_volume( - AmbientChannelTag::Wind, - Lerp::lerp(self.volume, target_volume, 0.01), - ); - } + // set the duration of the loop to whatever the current value is (0.0 by + // default) + let next_track_change = audio.ambient_channels[index].get_next_track_change(); - if self.began_playing.elapsed().as_secs_f32() > self.next_track_change { - let ambience = self.ambience.read(); - let wind_track = &ambience - .tracks - .iter() - .find(|track| track.tag == AmbientChannelTag::Wind); - self.began_playing = Instant::now(); - if let Some(wind_track) = wind_track { - self.next_track_change = wind_track.length; - audio.play_ambient(AmbientChannelTag::Wind, &wind_track.path, target_volume); - } - } - } - } -} + // if the sound should loop at this point: + if audio.ambient_channels[index] + .get_began_playing() + .elapsed() + .as_secs_f32() + > next_track_change + { + let ambience = self.ambience.read(); + let track = ambience.tracks.iter().find(|track| track.tag == tag); + // set the track's start point at this instant + audio.ambient_channels[index].set_began_playing(Instant::now()); + if let Some(track) = track { + // set loop duration to the one specified in the ron + audio.ambient_channels[index].set_next_track_change(track.length); + // play the file of the current tag at the current multiplier + let current_multiplier = audio.ambient_channels[index].get_multiplier(); + audio.play_ambient(tag, &track.path, current_multiplier); + } + }; -impl AmbientRainMgr { - /// Checks whether the previous track has completed. If so, sends a - /// request to play the next (random) track - pub fn maintain( - &mut self, - audio: &mut AudioFrontend, - state: &State, - client: &Client, - camera: &Camera, - ) { - if audio.sfx_enabled() && !self.ambience.read().tracks.is_empty() { - let focus_off = camera.get_focus_pos().map(f32::trunc); - let cam_pos = camera.dependents().cam_pos + focus_off; - - let terrain_alt = if let Some(chunk) = client.current_chunk() { - chunk.meta().alt() - } else { - 0.0 - }; - - // multipler at end will have to change depending on how intense rain normally - // is - self.rain_intensity = client.current_weather().rain * 5.0; - - let mut volume_multiplier = self.rain_intensity; - - // TODO: make rain diminish with distance above terrain - let target_volume = { - // Checks if the camera is underwater to stop ambient sounds - if state - .terrain() - .get((cam_pos).map(|e| e.floor() as i32)) - .map(|b| b.is_liquid()) - .unwrap_or(false) - { - volume_multiplier *= 0.1; - } - // Is the camera roughly under the terrain? - if cam_pos.z < terrain_alt - 10.0 { - volume_multiplier = 0.0; - } - - volume_multiplier = volume_multiplier.clamped(0.0, 1.0); - - // possibly remove noise - if volume_multiplier < 0.05 { - 0.0 + // remove channel if not playing + if audio.ambient_channels[index].get_multiplier() == 0.0 { + audio.ambient_channels[index].stop(); + audio.ambient_channels.remove(index); + }; + // move on to next tag + break; } else { - volume_multiplier - } - }; - - // Transitions the ambient sounds (more) smoothly - if audio.get_ambient_channel(AmbientChannelTag::Rain).is_none() { - audio.new_ambient_channel(AmbientChannelTag::Rain); - } else { - self.volume = audio.get_ambient_volume(AmbientChannelTag::Rain); - audio.set_ambient_volume( - AmbientChannelTag::Rain, - Lerp::lerp(self.volume, target_volume, 0.01), - ); - } - - if self.began_playing.elapsed().as_secs_f32() > self.next_track_change { - let ambience = self.ambience.read(); - let rain_track = &ambience - .tracks - .iter() - .find(|track| track.tag == AmbientChannelTag::Rain); - self.began_playing = Instant::now(); - if let Some(rain_track) = rain_track { - self.next_track_change = rain_track.length; - audio.play_ambient(AmbientChannelTag::Rain, &rain_track.path, target_volume); + // channel tag and current tag don't match, move on to next channel + continue; } } } } } -fn load_ambience_items() -> AssetHandle { +impl AmbientChannel { + pub fn maintain(&mut self, state: &State, client: &Client, camera: &Camera) -> f32 { + let tag = self.get_tag(); + + let focus_off = camera.get_focus_pos().map(f32::trunc); + let cam_pos = camera.dependents().cam_pos + focus_off; + + let mut target_volume: f32 = match tag { + // Get target volume of wind + AmbientChannelTag::Wind => self.get_wind_volume(client, camera), + // get target volume of rain + AmbientChannelTag::Rain => self.get_rain_volume(client), + }; + + // TODO: make rain diminish with distance above terrain + target_volume = self.check_camera(state, client, cam_pos, target_volume); + + return target_volume; + } + + fn check_camera( + &mut self, + state: &State, + client: &Client, + cam_pos: Vec3, + initial_volume: f32, + ) -> f32 { + let mut volume_multiplier = initial_volume; + let terrain_alt = if let Some(chunk) = client.current_chunk() { + chunk.meta().alt() + } else { + 0.0 + }; + // Checks if the camera is underwater to stop ambient sounds + if state + .terrain() + .get((cam_pos).map(|e| e.floor() as i32)) + .map(|b| b.is_liquid()) + .unwrap_or(false) + { + volume_multiplier *= 0.1; + } + // Is the camera roughly under the terrain? + if cam_pos.z < terrain_alt - 10.0 { + volume_multiplier = 0.0; + } + + volume_multiplier.clamped(0.0, 1.0) + } + + fn get_wind_volume(&mut self, client: &Client, camera: &Camera) -> f32 { + let focus_off = camera.get_focus_pos().map(f32::trunc); + let cam_pos = camera.dependents().cam_pos + focus_off; + + let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() { + (chunk.meta().alt(), chunk.meta().tree_density()) + } else { + (0.0, 0.0) + }; + + // Wind volume increases with altitude + let alt_multiplier = (cam_pos.z / 1200.0).abs(); + + // Tree density factors into wind volume. The more trees, + // the lower wind volume. The trees make more of an impact + // the closer the camera is to the ground. + let tree_multiplier = + ((1.0 - tree_density) + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0); + + return alt_multiplier * tree_multiplier; + } + + fn get_rain_volume(&mut self, client: &Client) -> f32 { + // multipler at end will have to change depending on how intense rain normally + // is + let rain_intensity = client.current_weather().rain * 5.0; + + return rain_intensity; + } +} + +pub fn load_ambience_items() -> AssetHandle { AmbientCollection::load_or_insert_with("voxygen.audio.ambient", |error| { warn!( "Error reading ambience config file, ambience will not be available: {:#?}", diff --git a/voxygen/src/audio/channel.rs b/voxygen/src/audio/channel.rs index ab34564933..62ff086c04 100644 --- a/voxygen/src/audio/channel.rs +++ b/voxygen/src/audio/channel.rs @@ -22,6 +22,8 @@ use crate::audio::{ }; use rodio::{OutputStreamHandle, Sample, Sink, Source, SpatialSink}; use serde::Deserialize; +use strum::EnumIter; +use std::time::Instant; use tracing::warn; use vek::*; @@ -157,7 +159,7 @@ impl MusicChannel { /// AmbientChannelTags are used for non-positional sfx. Currently the only use /// is for wind. -#[derive(Debug, PartialEq, Clone, Copy, Deserialize)] +#[derive(Debug, PartialEq, Clone, Copy, Deserialize, EnumIter)] pub enum AmbientChannelTag { Wind, Rain, @@ -168,6 +170,8 @@ pub struct AmbientChannel { tag: AmbientChannelTag, multiplier: f32, sink: Sink, + began_playing: Instant, + next_track_change: f32, } impl AmbientChannel { @@ -178,6 +182,8 @@ impl AmbientChannel { tag, multiplier, sink, + began_playing: Instant::now(), + next_track_change: 0.0, }, Err(_) => { warn!("Failed to create rodio sink. May not play ambient sounds."); @@ -185,6 +191,8 @@ impl AmbientChannel { tag, multiplier, sink: Sink::new_idle().0, + began_playing: Instant::now(), + next_track_change: 0.0, } }, } @@ -211,6 +219,20 @@ impl AmbientChannel { pub fn get_multiplier(&mut self) -> f32 { self.multiplier } pub fn get_tag(&self) -> AmbientChannelTag { self.tag } + + pub fn set_tag(&mut self, tag: AmbientChannelTag) { self.tag = tag } + + pub fn get_began_playing(&self) -> Instant { self.began_playing } + + pub fn get_next_track_change(&self) -> f32 { self.next_track_change } + + pub fn set_began_playing(&mut self, began_playing: Instant) { + self.began_playing = began_playing + } + + pub fn set_next_track_change(&mut self, next_track_change: f32) { + self.next_track_change = next_track_change + } } /// An SfxChannel uses a positional audio sink, and is designed for short-lived @@ -264,9 +286,8 @@ impl SfxChannel { pub fn update(&mut self, listener: &Listener) { const FALLOFF: f32 = 0.13; - self.sink.set_emitter_position( - ((self.pos - listener.pos) * FALLOFF).into_array(), - ); + self.sink + .set_emitter_position(((self.pos - listener.pos) * FALLOFF).into_array()); self.sink .set_left_ear_position(listener.ear_left_rpos.into_array()); self.sink @@ -281,7 +302,7 @@ pub struct UIChannel { impl UIChannel { pub fn new(stream: &OutputStreamHandle) -> Self { Self { - sink: Sink::try_new(stream).unwrap() + sink: Sink::try_new(stream).unwrap(), } } diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs index 9656f0c4d7..3b2942c71e 100644 --- a/voxygen/src/audio/mod.rs +++ b/voxygen/src/audio/mod.rs @@ -7,7 +7,9 @@ pub mod music; pub mod sfx; pub mod soundcache; -use channel::{AmbientChannel, AmbientChannelTag, MusicChannel, MusicChannelTag, SfxChannel, UIChannel}; +use channel::{ + AmbientChannel, AmbientChannelTag, MusicChannel, MusicChannelTag, SfxChannel, UIChannel, +}; use fader::Fader; use music::MusicTransitionManifest; use sfx::{SfxEvent, SfxTriggerItem}; @@ -38,16 +40,16 @@ pub struct AudioFrontend { //pub device_list: Vec, //pub audio_device: Option, pub stream: Option, - audio_stream: Option, + pub audio_stream: Option, - music_channels: Vec, - ambient_channels: Vec, - sfx_channels: Vec, - ui_channels: Vec, - sfx_volume: f32, - music_volume: f32, - master_volume: f32, - listener: Listener, + pub music_channels: Vec, + pub ambient_channels: Vec, + pub sfx_channels: Vec, + pub ui_channels: Vec, + pub sfx_volume: f32, + pub music_volume: f32, + pub master_volume: f32, + pub listener: Listener, mtm: AssetHandle, } @@ -216,7 +218,11 @@ impl AudioFrontend { /// Function to play sfx from external places. Useful for UI and /// inventory events - pub fn emit_sfx_item(&mut self, trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>, vol: Option) { + pub fn emit_sfx_item( + &mut self, + trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>, + vol: Option, + ) { if let Some((event, item)) = trigger_item { let sfx_file = match item.files.len() { 0 => { @@ -302,7 +308,7 @@ impl AudioFrontend { } else { channel.play(sound); } - } + } } Ok(()) } @@ -371,29 +377,29 @@ impl AudioFrontend { } // sets the volume of the channel with the given tag to the given volume - fn set_ambient_volume(&mut self, channel_tag: AmbientChannelTag, volume_multiplier: f32) { - if self.audio_stream.is_some() { - let sfx_volume = self.get_sfx_volume(); - if let Some(channel) = self.get_ambient_channel(channel_tag) { - channel.set_multiplier(volume_multiplier); - channel.set_volume(sfx_volume); - } - } - } + // fn set_ambient_volume(&mut self, channel_tag: AmbientChannelTag, + // volume_multiplier: f32) { if self.audio_stream.is_some() { + // let sfx_volume = self.get_sfx_volume(); + // if let Some(channel) = self.get_ambient_channel(channel_tag) { + // channel.set_multiplier(volume_multiplier); + // channel.set_volume(sfx_volume); + // } + // } + // } // retrieves volume (pre-sfx-setting) of the channel with a given tag - fn get_ambient_volume(&mut self, channel_tag: AmbientChannelTag) -> f32 { - if self.audio_stream.is_some() { - if let Some(channel) = self.get_ambient_channel(channel_tag) { - let channel_multiplier = channel.get_multiplier(); - channel_multiplier - } else { - 0.0 - } - } else { - 0.0 - } - } + // fn get_ambient_volume(&mut self, channel_tag: AmbientChannelTag) -> f32 { + // if self.audio_stream.is_some() { + // if let Some(channel) = self.get_ambient_channel(channel_tag) { + // let channel_multiplier = channel.get_multiplier(); + // channel_multiplier + // } else { + // 0.0 + // } + // } else { + // 0.0 + // } + // } fn play_music(&mut self, sound: &str, channel_tag: MusicChannelTag) { if self.music_enabled() { diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index 6bc5818270..d33eb68263 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -82,7 +82,7 @@ mod event_mapper; -use specs::{WorldExt}; +use specs::WorldExt; use crate::{ audio::AudioFrontend, diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 4a995f808b..ff811bd9dc 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -223,7 +223,10 @@ fn main() { // Setup audio let mut audio = match settings.audio.output { AudioOutput::Off => AudioFrontend::no_audio(), - AudioOutput::Automatic => AudioFrontend::new(settings.audio.num_sfx_channels, settings.audio.num_ui_channels), + AudioOutput::Automatic => AudioFrontend::new( + settings.audio.num_sfx_channels, + settings.audio.num_ui_channels, + ), // AudioOutput::Device(ref dev) => Some(dev.clone()), }; diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index ece128be16..a27d27ed8d 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -19,12 +19,7 @@ pub use self::{ trail::TrailMgr, }; use crate::{ - audio::{ - ambient::{AmbientRainMgr, AmbientWindMgr}, - music::MusicMgr, - sfx::SfxMgr, - AudioFrontend, - }, + audio::{ambient, ambient::AmbientMgr, music::MusicMgr, sfx::SfxMgr, AudioFrontend}, render::{ create_skybox_mesh, CloudsLocals, Consts, Drawer, GlobalModel, Globals, GlobalsBindGroup, Light, Model, PointLightMatrix, PostProcessLocals, Renderer, Shadow, ShadowLocals, @@ -107,8 +102,9 @@ pub struct Scene { figure_mgr: FigureMgr, pub sfx_mgr: SfxMgr, music_mgr: MusicMgr, - ambient_wind_mgr: AmbientWindMgr, - ambient_rain_mgr: AmbientRainMgr, + ambient_mgr: AmbientMgr, + // ambient_wind_mgr: AmbientWindMgr, + // ambient_rain_mgr: AmbientRainMgr, } pub struct SceneData<'a> { @@ -320,8 +316,11 @@ impl Scene { figure_mgr: FigureMgr::new(renderer), sfx_mgr: SfxMgr::default(), music_mgr: MusicMgr::default(), - ambient_wind_mgr: AmbientWindMgr::default(), - ambient_rain_mgr: AmbientRainMgr::default(), + ambient_mgr: AmbientMgr { + ambience: ambient::load_ambience_items(), + }, + // ambient_wind_mgr: AmbientWindMgr::default(), + // ambient_rain_mgr: AmbientRainMgr::default(), } } @@ -414,10 +413,10 @@ impl Scene { ) { span!(_guard, "handle_outcome", "Scene::handle_outcome"); let underwater = state - .terrain() - .get(cam_pos.map(|e| e.floor() as i32)) - .map(|b| b.is_liquid()) - .unwrap_or(false); + .terrain() + .get(cam_pos.map(|e| e.floor() as i32)) + .map(|b| b.is_liquid()) + .unwrap_or(false); self.particle_mgr.handle_outcome(outcome, scene_data); self.sfx_mgr .handle_outcome(outcome, audio, scene_data.client, state, underwater); @@ -1080,10 +1079,12 @@ impl Scene { client, ); self.music_mgr.maintain(audio, scene_data.state, client); - self.ambient_wind_mgr - .maintain(audio, scene_data.state, client, &self.camera); - self.ambient_rain_mgr + self.ambient_mgr .maintain(audio, scene_data.state, client, &self.camera); + // self.ambient_wind_mgr + // .maintain(audio, scene_data.state, client, &self.camera); + // self.ambient_rain_mgr + // .maintain(audio, scene_data.state, client, &self.camera); } pub fn global_bind_group(&self) -> &GlobalsBindGroup { &self.globals_bind_group } diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index d976d03756..81e06f55c6 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -245,7 +245,9 @@ impl SessionState { let sfx_triggers = self.scene.sfx_mgr.triggers.read(); let sfx_trigger_item = sfx_triggers.get_key_value(&SfxEvent::from(&inv_event)); - global_state.audio.emit_sfx_item(sfx_trigger_item, Some(1.0)); + global_state + .audio + .emit_sfx_item(sfx_trigger_item, Some(1.0)); match inv_event { InventoryUpdateEvent::BlockCollectFailed { pos, reason } => { @@ -358,7 +360,7 @@ impl PlayState for SessionState { let client = self.client.borrow(); (client.presence(), client.registered()) }; - + if client_presence.is_some() { let camera = self.scene.camera_mut(); @@ -1577,12 +1579,16 @@ impl PlayState for SessionState { &scene_data, &client, ); - // Process outcomes from client for outcome in outcomes { - self.scene - .handle_outcome(&outcome, &scene_data, &mut global_state.audio, &client.state(), cam_pos); + self.scene.handle_outcome( + &outcome, + &scene_data, + &mut global_state.audio, + &client.state(), + cam_pos, + ); self.hud .handle_outcome(&outcome, scene_data.client, global_state); }