Merge branch 'DaforLynx/thunder-fix' into 'master'

Lightning strikes are less audible; cave music restored; fixed audio permanently stopping on logout

See merge request veloren/veloren!3487
This commit is contained in:
Marcel 2022-07-21 08:27:35 +00:00
commit 7f94580b20
8 changed files with 89 additions and 76 deletions

View File

@ -2,22 +2,22 @@
tracks: [ tracks: [
( (
path: "voxygen.audio.ambience.wind", path: "voxygen.audio.ambience.wind",
length: 14.17, length: 14.2,
tag: Wind, tag: Wind,
), ),
( (
path: "voxygen.audio.ambience.rain", path: "voxygen.audio.ambience.rain",
length: 16.97, length: 17.0,
tag: Rain, tag: Rain,
), ),
( (
path:"voxygen.audio.ambience.thunder", path:"voxygen.audio.ambience.thunder",
length: 31.97, length: 32.0,
tag: Thunder, tag: Thunder,
), ),
( (
path:"voxygen.audio.ambience.leaves", path:"voxygen.audio.ambience.leaves",
length: 25.97, length: 26.0,
tag: Leaves, tag: Leaves,
), ),
] ]

Binary file not shown.

View File

@ -1496,20 +1496,22 @@ impl Client {
if let Some(position) = self.current::<comp::Pos>() { if let Some(position) = self.current::<comp::Pos>() {
player_alt = position.0.z; player_alt = position.0.z;
} }
let mut contains_cave = false; //let mut contains_cave = false;
let mut terrain_alt = 0.0; let mut terrain_alt = 0.0;
let mut contains_dungeon = false; let mut contains_dungeon = false;
let mut contains_settlement = false; let mut contains_settlement = false;
if let Some(chunk) = self.current_chunk() { if let Some(chunk) = self.current_chunk() {
terrain_alt = chunk.meta().alt(); terrain_alt = chunk.meta().alt();
contains_cave = chunk.meta().contains_cave(); //contains_cave = chunk.meta().contains_cave();
contains_dungeon = chunk.meta().contains_dungeon(); contains_dungeon = chunk.meta().contains_dungeon();
contains_settlement = chunk.meta().contains_settlement(); contains_settlement = chunk.meta().contains_settlement();
} }
if player_alt < (terrain_alt - 25.0) && contains_cave { if player_alt < terrain_alt - 40.0 {
SitesKind::Cave if contains_dungeon {
} else if player_alt < (terrain_alt - 25.0) && contains_dungeon {
SitesKind::Dungeon SitesKind::Dungeon
} else {
SitesKind::Cave
}
} else if contains_settlement { } else if contains_settlement {
SitesKind::Settlement SitesKind::Settlement
} else { } else {

View File

@ -89,7 +89,11 @@ impl AmbientMgr {
channel.next_track_change = track.length; channel.next_track_change = track.length;
// Play the file of the current tag at the current multiplier; // Play the file of the current tag at the current multiplier;
let current_multiplier = channel.multiplier; let current_multiplier = channel.multiplier;
audio.play_ambient(tag, &track.path, current_multiplier * ambience_volume); audio.play_ambient(
tag,
&track.path,
Some(current_multiplier * ambience_volume),
);
} }
}; };
@ -103,6 +107,15 @@ impl AmbientMgr {
} }
impl AmbientChannelTag { impl AmbientChannelTag {
pub fn tag_max_volume(tag: AmbientChannelTag) -> f32 {
match tag {
AmbientChannelTag::Wind => 1.15,
AmbientChannelTag::Rain => 0.95,
AmbientChannelTag::Thunder => 1.33,
AmbientChannelTag::Leaves => 1.33,
}
}
// Gets appropriate volume for each tag // Gets appropriate volume for each tag
pub fn get_tag_volume(tag: AmbientChannelTag, client: &Client, camera: &Camera) -> f32 { pub fn get_tag_volume(tag: AmbientChannelTag, client: &Client, camera: &Camera) -> f32 {
match tag { match tag {
@ -134,11 +147,10 @@ impl AmbientChannelTag {
/ 15.0_f32.powi(2)) / 15.0_f32.powi(2))
.min(1.33); .min(1.33);
((alt_multiplier (alt_multiplier
* tree_multiplier * tree_multiplier
* (wind_speed_multiplier + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2))) * (wind_speed_multiplier + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)))
+ ((alt_multiplier * 0.15) * tree_multiplier)) + (alt_multiplier * 0.15) * tree_multiplier
.min(1.15)
}, },
AmbientChannelTag::Rain => { AmbientChannelTag::Rain => {
let focus_off = camera.get_focus_pos().map(f32::trunc); let focus_off = camera.get_focus_pos().map(f32::trunc);
@ -153,10 +165,10 @@ impl AmbientChannelTag {
let camera_multiplier = let camera_multiplier =
1.0 - ((cam_pos.z - terrain_alt).abs() / 75.0).powi(2).min(1.0); 1.0 - ((cam_pos.z - terrain_alt).abs() / 75.0).powi(2).min(1.0);
((client.weather_at_player().rain * 2.5) * camera_multiplier).min(0.95) (client.weather_at_player().rain * 3.0) * camera_multiplier
}, },
AmbientChannelTag::Thunder => { AmbientChannelTag::Thunder => {
let rain_intensity = (client.weather_at_player().rain * 2.5).min(1.33); let rain_intensity = client.weather_at_player().rain * 3.0;
if rain_intensity < 0.7 { if rain_intensity < 0.7 {
0.0 0.0
@ -179,8 +191,8 @@ impl AmbientChannelTag {
// the closer the camera is to the ground // the closer the camera is to the ground
let tree_multiplier = 1.0 let tree_multiplier = 1.0
- (((1.0 - tree_density) - (((1.0 - tree_density)
+ ((cam_pos.z - terrain_alt + 20.0).abs() / 150.0).powi(2)) + ((cam_pos.z - terrain_alt - 20.0).abs() / 150.0).powi(2))
.min(1.0)); .min(1.1));
// Take into account wind speed too, which amplifies tree noise // Take into account wind speed too, which amplifies tree noise
let wind_speed_multiplier = (client.weather_at_player().wind.magnitude_squared() let wind_speed_multiplier = (client.weather_at_player().wind.magnitude_squared()
@ -188,7 +200,7 @@ impl AmbientChannelTag {
.min(1.0); .min(1.0);
if tree_multiplier > 0.1 { if tree_multiplier > 0.1 {
(tree_multiplier * (1.0 + wind_speed_multiplier)).min(1.33) tree_multiplier * (1.0 + wind_speed_multiplier)
} else { } else {
0.0 0.0
} }
@ -225,7 +237,8 @@ fn get_target_volume(
} }
// Is the camera underneath the terrain? Fade out the lower it goes beneath. // Is the camera underneath the terrain? Fade out the lower it goes beneath.
volume_multiplier * ((cam_pos.z - terrain_alt) / 30.0 + 1.0).clamped(0.0, 1.0) (volume_multiplier * ((cam_pos.z - terrain_alt) / 40.0 + 1.0).clamped(0.0, 1.0))
.min(AmbientChannelTag::tag_max_volume(tag))
} }
pub fn load_ambience_items() -> AssetHandle<AmbientCollection> { pub fn load_ambience_items() -> AssetHandle<AmbientCollection> {

View File

@ -282,6 +282,11 @@ impl SfxChannel {
} }
} }
/// An UiChannel uses a non-spatial audio sink, and is designed for short-lived
/// audio which is not spatially controlled, but does not need control over
/// playback or fading/transitions
///
/// See also: [`Rodio::Sink`](https://docs.rs/rodio/0.11.0/rodio/struct.Sink.html)
pub struct UiChannel { pub struct UiChannel {
sink: Sink, sink: Sink,
} }

View File

@ -21,6 +21,11 @@ use common::assets::{AssetExt, AssetHandle};
use rodio::{source::Source, OutputStream, OutputStreamHandle, StreamError}; use rodio::{source::Source, OutputStream, OutputStreamHandle, StreamError};
use vek::*; use vek::*;
/// Prevents sounds that are too low in volume from playing. This may marginally
/// improve performance in certain situations since less audio channels will be
/// used on average.
const MIN_HEARABLE_VOLUME: f32 = 0.003;
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub struct Listener { pub struct Listener {
pos: Vec3<f32>, pos: Vec3<f32>,
@ -216,20 +221,14 @@ impl AudioFrontend {
self.music_channels.last_mut() self.music_channels.last_mut()
} }
/// Play an sfx file given the position, SfxEvent, and whether it is /// Find sound based on given trigger_item
/// underwater or not /// Randomizes if multiple sounds are found
pub fn emit_sfx( /// Errors if no sounds are found
&mut self, fn get_sfx_file<'a>(
trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>, trigger_item: Option<(&'a SfxEvent, &'a SfxTriggerItem)>,
position: Vec3<f32>, ) -> Option<&'a str> {
volume: Option<f32>, trigger_item.map(|(event, item)| {
underwater: bool, match item.files.len() {
) {
if let Some((event, item)) = trigger_item {
// Find sound based on given trigger_item
// Randomizes if multiple sounds are found
// Errors if no sounds are found
let sfx_file = match item.files.len() {
0 => { 0 => {
debug!("Sfx event {:?} is missing audio file.", event); debug!("Sfx event {:?} is missing audio file.", event);
"voxygen.audio.sfx.placeholder" "voxygen.audio.sfx.placeholder"
@ -243,9 +242,22 @@ impl AudioFrontend {
let rand_step = rand::random::<usize>() % item.files.len(); let rand_step = rand::random::<usize>() % item.files.len();
&item.files[rand_step] &item.files[rand_step]
}, },
}; }
})
}
/// Play an sfx file given the position, SfxEvent, and whether it is
/// underwater or not
pub fn emit_sfx(
&mut self,
trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
position: Vec3<f32>,
volume: Option<f32>,
underwater: bool,
) {
if let Some(sfx_file) = Self::get_sfx_file(trigger_item) {
// Play sound in empty channel at given position // Play sound in empty channel at given position
if self.audio_stream.is_some() { if self.audio_stream.is_some() && volume.map_or(true, |v| v > MIN_HEARABLE_VOLUME) {
let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0)); let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0));
let listener = self.listener.clone(); let listener = self.listener.clone();
@ -275,27 +287,9 @@ impl AudioFrontend {
trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>, trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
volume: Option<f32>, volume: Option<f32>,
) { ) {
// Find sound based on given trigger_item if let Some(sfx_file) = Self::get_sfx_file(trigger_item) {
// Randomizes if multiple sounds are found
// Errors if no sounds are found
if let Some((event, item)) = trigger_item {
let sfx_file = match item.files.len() {
0 => {
debug!("Sfx event {:?} is missing audio file.", event);
"voxygen.audio.sfx.placeholder"
},
1 => item
.files
.last()
.expect("Failed to determine sound file for this trigger item."),
_ => {
// If more than one file is listed, choose one at random
let rand_step = rand::random::<usize>() % item.files.len();
&item.files[rand_step]
},
};
// Play sound in empty channel // Play sound in empty channel
if self.audio_stream.is_some() { if self.audio_stream.is_some() && volume.map_or(true, |v| v > MIN_HEARABLE_VOLUME) {
let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0)); let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0));
if let Some(channel) = self.get_ui_channel() { if let Some(channel) = self.get_ui_channel() {
@ -308,10 +302,10 @@ impl AudioFrontend {
} }
/// Plays a file at a given volume in the channel with a given tag /// Plays a file at a given volume in the channel with a given tag
fn play_ambient(&mut self, channel_tag: AmbientChannelTag, sound: &str, volume: f32) { fn play_ambient(&mut self, channel_tag: AmbientChannelTag, sound: &str, volume: Option<f32>) {
if self.audio_stream.is_some() { if self.audio_stream.is_some() {
if let Some(channel) = self.get_ambient_channel(channel_tag) { if let Some(channel) = self.get_ambient_channel(channel_tag) {
channel.set_volume(volume); channel.set_volume(volume.unwrap_or(1.0));
channel.play(load_ogg(sound)); channel.play(load_ogg(sound));
} }
} }
@ -503,19 +497,18 @@ impl AudioFrontend {
} }
} }
pub fn stop_ambient_sounds(&mut self) { pub fn stop_all_ambience(&mut self) { self.ambient_channels.retain(|x| Some(x).is_none()) }
for channel in self.ambient_channels.iter_mut() {
channel.stop()
}
}
// Sfx channels do not repopulate themselves yet
pub fn stop_all_sfx(&mut self) { pub fn stop_all_sfx(&mut self) {
for channel in self.sfx_channels.iter_mut() { if let Some(audio_stream) = &self.audio_stream {
channel.stop() for channel in &mut self.sfx_channels {
*channel = SfxChannel::new(audio_stream);
} }
for channel in self.ui_channels.iter_mut() { for channel in &mut self.ui_channels {
channel.stop() *channel = UiChannel::new(audio_stream);
} }
};
} }
// The following is for the disabled device switcher // The following is for the disabled device switcher

View File

@ -435,13 +435,13 @@ impl SfxMgr {
); );
}, },
Outcome::Lightning { pos } => { Outcome::Lightning { pos } => {
let power = (1.0 - pos.distance(audio.listener.pos) / 6_000.0) let power = (1.0 - pos.distance(audio.listener.pos) / 5_000.0)
.clamped(0.0, 1.0) .max(0.0)
.powf(0.75); .powi(7);
if power > 0.0 { if power > 0.0 {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Lightning); let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Lightning);
// TODO: Don't use UI sfx, add a way to control position falloff // TODO: Don't use UI sfx, add a way to control position falloff
audio.emit_ui_sfx(sfx_trigger_item, Some(power * 3.0)); audio.emit_ui_sfx(sfx_trigger_item, Some((power * 3.0).min(2.9)));
} }
}, },
Outcome::GroundSlam { pos, .. } => { Outcome::GroundSlam { pos, .. } => {

View File

@ -1206,7 +1206,7 @@ impl PlayState for SessionState {
// Stop all sounds // Stop all sounds
// TODO: Abstract this behavior to all instances of PlayStateResult::Pop // TODO: Abstract this behavior to all instances of PlayStateResult::Pop
// somehow // somehow
global_state.audio.stop_ambient_sounds(); global_state.audio.stop_all_ambience();
global_state.audio.stop_all_sfx(); global_state.audio.stop_all_sfx();
return PlayStateResult::Pop; return PlayStateResult::Pop;
}, },