diff --git a/Cargo.lock b/Cargo.lock index 98b1f66043..fb7cbde8db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -391,11 +391,6 @@ dependencies = [ "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "claxon" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "clipboard-win" version = "2.2.0" @@ -1583,14 +1578,6 @@ name = "lzw" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "mach" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "malloc_buf" version = "0.0.6" @@ -1666,23 +1653,6 @@ dependencies = [ "x11-dl 2.18.4 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "minimp3" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "minimp3-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "slice-deque 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "minimp3-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "mio" version = "0.6.21" @@ -2555,15 +2525,13 @@ dependencies = [ [[package]] name = "rodio" -version = "0.9.0" -source = "git+https://github.com/RustAudio/rodio?rev=e5474a2#e5474a2ef15f2d0a3bae2538de159b6d3e5bdf79" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "claxon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "cpal 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "hound 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lewton 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", - "minimp3 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2816,16 +2784,6 @@ name = "slab" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "slice-deque" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "smallvec" version = "0.6.13" @@ -3300,7 +3258,7 @@ dependencies = [ "msgbox 0.4.0 (git+https://github.com/bekker/msgbox-rs.git?rev=68fe39a)", "num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rodio 0.9.0 (git+https://github.com/RustAudio/rodio?rev=e5474a2)", + "rodio 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3654,7 +3612,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum chunked_transfer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "498d20a7aaf62625b9bf26e637cf7736417cde1d0c99f1d04d1170229a85cf87" "checksum clang-sys 0.28.1 (registry+https://github.com/rust-lang/crates.io-index)" = "81de550971c976f176130da4b2978d3b524eaa0fd9ac31f3ceb5ae1231fb4853" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" -"checksum claxon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f86c952727a495bda7abaf09bafdee1a939194dd793d9a8e26281df55ac43b00" "checksum clipboard-win 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum cocoa 0.18.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1706996401131526e36b3b49f0c4d912639ce110996f3ca144d78946727bce54" @@ -3782,7 +3739,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" "checksum lz4-compress 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f966533a922a9bba9e95e594c1fdb3b9bf5fdcdb11e37e51ad84cd76e468b91" "checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" -"checksum mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1" "checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" "checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" @@ -3792,8 +3748,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" "checksum mime_guess 1.8.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0d977de9ee851a0b16e932979515c0f3da82403183879811bc97d50bd9cc50f7" "checksum minifb 0.13.0 (git+https://github.com/emoon/rust_minifb.git)" = "" -"checksum minimp3 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "542e9bed56860c5070a09939eee0e2df6f8f73f60304ddf56d620947e7017239" -"checksum minimp3-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c109ae05c00ad6e3a53fab101e2f234545bdd010f0fffd399355efaf70817817" "checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" "checksum mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "46e73a04c2fa6250b8d802134d56d554a9ec2922bf977777c805ea5def61ce40" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" @@ -3888,7 +3842,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "92b73c2a1770c255c240eaa4ee600df1704a38dc3feaa6e949e7fcd4f8dc09f9" "checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" -"checksum rodio 0.9.0 (git+https://github.com/RustAudio/rodio?rev=e5474a2)" = "" +"checksum rodio 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1e0e0dfa7c8b17c6428f6e992a22ea595922cc86f946191b6b59e7ce96b77262" "checksum ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2ece421e0c4129b90e4a35b6f625e472e96c552136f5093a2f4fa2bbb75a62d5" "checksum roots 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e4c67c712ab62be58b24ab8960e2b95dd4ee00aac115c76f2709657821fe376d" "checksum rouille 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "112568052ec17fa26c6c11c40acbb30d3ad244bf3d6da0be181f5e7e42e5004f" @@ -3919,7 +3873,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum shrev 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b5752e017e03af9d735b4b069f53b7a7fd90fefafa04d8bd0c25581b0bff437f" "checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -"checksum slice-deque 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ffddf594f5f597f63533d897427a570dbaa9feabaaa06595b74b71b7014507d7" "checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" "checksum smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecf3b85f68e8abaa7555aa5abdb1153079387e60b718283d732f03897fcfc86" "checksum smithay-client-toolkit 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2ccb8c57049b2a34d2cc2b203fa785020ba0129d31920ef0d317430adaf748fa" diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index db5a64754c..74b11a18c0 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -55,8 +55,7 @@ num = "0.2.0" backtrace = "0.3.40" rand = "0.7.2" treeculler = { git = "https://gitlab.com/yusdacra/treeculler.git" } -# context for pinning to commit: https://gitlab.com/veloren/veloren/issues/280 -rodio = { git = "https://github.com/RustAudio/rodio", rev = "e5474a2"} +rodio = { version = "0.10", default-features = false, features = ["wav", "vorbis"] } cpal = "0.10" crossbeam = "=0.7.2" hashbrown = { version = "0.6.2", features = ["rayon", "serde", "nightly"] } diff --git a/voxygen/src/audio/channel.rs b/voxygen/src/audio/channel.rs index 00c36646c0..8c65e6dc98 100644 --- a/voxygen/src/audio/channel.rs +++ b/voxygen/src/audio/channel.rs @@ -1,51 +1,125 @@ -use crate::audio::fader::Fader; -use rodio::{Device, Sample, Source, SpatialSink}; +use crate::audio::fader::{FadeDirection, Fader}; +use rodio::{Device, Sample, Sink, Source, SpatialSink}; use vek::*; -#[derive(PartialEq, Clone, Copy)] -pub enum AudioType { - Sfx, - Music, - None, -} - #[derive(PartialEq, Clone, Copy)] enum ChannelState { - // Init, - // ToPlay, - // Loading, Playing, - Stopping, + Fading, Stopped, } +/// Each MusicChannel has a MusicChannelTag which help us determine how we +/// should transition between music types #[derive(PartialEq, Clone, Copy)] -pub enum ChannelTag { +pub enum MusicChannelTag { TitleMusic, - Soundtrack, + Exploration, } -pub struct Channel { - id: usize, - sink: SpatialSink, - audio_type: AudioType, +/// A MusicChannel uses a non-positional audio sink designed to play music which +/// is always heard at the player's position +pub struct MusicChannel { + tag: MusicChannelTag, + sink: Sink, state: ChannelState, fader: Fader, - tag: Option, +} + +impl MusicChannel { + pub fn new(device: &Device) -> Self { + Self { + sink: Sink::new(device), + tag: MusicChannelTag::TitleMusic, + state: ChannelState::Stopped, + fader: Fader::default(), + } + } + + // Play a music track item on this channel. If the channel has an existing track + // playing, the new sounds will be appended and played once they complete. + // Otherwise it will begin playing immediately. + pub fn play(&mut self, source: S, tag: MusicChannelTag) + where + S: Source + Send + 'static, + S::Item: Sample, + S::Item: Send, + ::Item: std::fmt::Debug, + { + self.tag = tag; + self.sink.append(source); + + self.state = if !self.fader.is_finished() { + ChannelState::Fading + } else { + ChannelState::Playing + }; + } + + /// Set the volume of the current channel. If the channel is currently + /// fading, the volume of the fader is updated to this value. + pub fn set_volume(&mut self, volume: f32) { + if !self.fader.is_finished() { + self.fader.update_target_volume(volume); + } else { + self.sink.set_volume(volume); + } + } + + /// Set a fader for the channel. If a fader exists already, it is replaced. + /// If the channel has not begun playing, and the fader is set to fade in, + /// we set the volume of the channel to the initial volume of the fader so + /// that the volumes match when playing begins. + pub fn set_fader(&mut self, fader: Fader) { + self.fader = fader; + self.state = ChannelState::Fading; + + if self.state == ChannelState::Stopped && fader.direction() == FadeDirection::In { + self.sink.set_volume(fader.get_volume()); + } + } + + /// Returns true if either the channels sink reports itself as empty (no + /// more sounds in the queue) or we have forcibly set the channels state to + /// the 'Stopped' state + pub fn is_done(&self) -> bool { self.sink.empty() || self.state == ChannelState::Stopped } + + pub fn get_tag(&self) -> MusicChannelTag { self.tag } + + /// Maintain the fader attached to this channel. If the channel is not + /// fading, no action is taken. + pub fn maintain(&mut self, dt: f32) { + if self.state == ChannelState::Fading { + self.fader.update(dt); + self.sink.set_volume(self.fader.get_volume()); + + if self.fader.is_finished() { + match self.fader.direction() { + FadeDirection::Out => { + self.state = ChannelState::Stopped; + self.sink.stop(); + }, + FadeDirection::In => { + self.state = ChannelState::Playing; + }, + } + } + } + } +} + +/// An SfxChannel uses a positional audio sink, and is designed for short-lived +/// audio which can be spatially controlled, but does not need control over +/// playback or fading/transitions +pub struct SfxChannel { + sink: SpatialSink, pub pos: Vec3, } -// TODO: Implement asynchronous loading -impl Channel { - /// Create an empty channel for future use +impl SfxChannel { pub fn new(device: &Device) -> Self { Self { - id: 0, sink: SpatialSink::new(device, [0.0; 3], [1.0, 0.0, 0.0], [-1.0, 0.0, 0.0]), - audio_type: AudioType::None, - state: ChannelState::Stopped, - fader: Fader::fade_in(0.0), - tag: None, pos: Vec3::zero(), } } @@ -57,31 +131,13 @@ impl Channel { S::Item: Send, ::Item: std::fmt::Debug, { - self.state = ChannelState::Playing; self.sink.append(source); } - pub fn is_done(&self) -> bool { self.sink.empty() || self.state == ChannelState::Stopped } - - pub fn set_tag(&mut self, tag: Option) { self.tag = tag; } - - pub fn get_tag(&self) -> Option { self.tag } - - pub fn stop(&mut self, fader: Fader) { - self.state = ChannelState::Stopping; - self.fader = fader; - } - - pub fn get_id(&self) -> usize { self.id } - - pub fn set_id(&mut self, new_id: usize) { self.id = new_id; } - - pub fn get_audio_type(&self) -> AudioType { self.audio_type } - - pub fn set_audio_type(&mut self, audio_type: AudioType) { self.audio_type = audio_type; } - pub fn set_volume(&mut self, volume: f32) { self.sink.set_volume(volume); } + pub fn is_done(&self) -> bool { self.sink.empty() } + pub fn set_emitter_position(&mut self, pos: [f32; 3]) { self.sink.set_emitter_position(pos); } pub fn set_left_ear_position(&mut self, pos: [f32; 3]) { self.sink.set_left_ear_position(pos); } @@ -89,20 +145,4 @@ impl Channel { pub fn set_right_ear_position(&mut self, pos: [f32; 3]) { self.sink.set_right_ear_position(pos); } - - pub fn update(&mut self, dt: f32) { - match self.state { - // ChannelState::Init | ChannelState::ToPlay | ChannelState::Loading => {} - ChannelState::Playing => {}, - ChannelState::Stopping => { - self.fader.update(dt); - self.sink.set_volume(self.fader.get_volume()); - - if self.fader.is_finished() { - self.state = ChannelState::Stopped; - } - }, - ChannelState::Stopped => {}, - } - } } diff --git a/voxygen/src/audio/fader.rs b/voxygen/src/audio/fader.rs index dd835a2045..1af58c8c20 100644 --- a/voxygen/src/audio/fader.rs +++ b/voxygen/src/audio/fader.rs @@ -6,13 +6,18 @@ pub struct Fader { volume_to: f32, is_running: bool, } +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum FadeDirection { + In, + Out, +} fn lerp(t: f32, a: f32, b: f32) -> f32 { (1.0 - t) * a + t * b } impl Fader { - pub fn fade(time: f32, volume_from: f32, volume_to: f32) -> Self { + pub fn fade(length: f32, volume_from: f32, volume_to: f32) -> Self { Self { - length: time, + length, running_time: 0.0, volume_from, volume_to, @@ -20,23 +25,28 @@ impl Fader { } } - pub fn fade_in(time: f32) -> Self { - Self { - length: time, - running_time: 0.0, - volume_from: 0.0, - volume_to: 1.0, - is_running: true, + pub fn fade_in(time: f32, volume_to: f32) -> Self { Self::fade(time, 0.0, volume_to) } + + pub fn fade_out(time: f32, volume_from: f32) -> Self { Self::fade(time, volume_from, 0.0) } + + pub fn update_target_volume(&mut self, volume: f32) { + match self.direction() { + FadeDirection::In => { + self.volume_to = volume; + }, + FadeDirection::Out => { + if self.get_volume() > volume { + self.volume_from = volume; + } + }, } } - pub fn fade_out(time: f32, volume_from: f32) -> Self { - Self { - length: time, - running_time: 0.0, - volume_from, - volume_to: 0.0, - is_running: true, + pub fn direction(&self) -> FadeDirection { + if self.volume_to < self.volume_from { + FadeDirection::Out + } else { + FadeDirection::In } } @@ -60,3 +70,107 @@ impl Fader { pub fn is_finished(&self) -> bool { self.running_time >= self.length || !self.is_running } } + +/// Returns a stopped fader with no running duration +impl Default for Fader { + fn default() -> Self { + Self { + length: 0.0, + running_time: 0.0, + volume_from: 0.0, + volume_to: 1.0, + is_running: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fade_direction_in() { + let fader = Fader::fade_in(10.0, 0.0); + + assert_eq!(fader.direction(), FadeDirection::In); + } + + #[test] + fn fade_direction_out() { + let fader = Fader::fade_out(10.0, 1.0); + + assert_eq!(fader.direction(), FadeDirection::Out); + } + + #[test] + fn fade_out_completes() { + let mut fader = Fader::fade_out(10.0, 1.0); + + // Run for the full duration + fader.update(10.0); + + assert_eq!(fader.get_volume(), 0.0); + assert!(fader.is_finished()); + } + + #[test] + fn update_target_volume_fading_out_when_currently_above() { + let mut fader = Fader::fade_out(20.0, 1.0); + + // After 0.1s, the fader should still be close to 1.0 + fader.update(0.1); + + // Reduce volume to 0.4. We are currently above that. + fader.update_target_volume(0.4); + + // The volume should immediately reduce to < 0.4 on the next update + fader.update(0.1); + + assert!(fader.get_volume() < 0.4) + } + + #[test] + fn update_target_volume_fading_out_when_currently_below() { + let mut fader = Fader::fade_out(10.0, 0.8); + + // After 9s, the fader should be close to 0 + fader.update(9.0); + + // Notify of a volume increase to 1.0. We are already far below that. + fader.update_target_volume(1.0); + + // The fader should be unaffected by the new value, and continue dropping + fader.update(0.1); + + assert!(fader.get_volume() < 0.2); + } + + #[test] + fn update_target_volume_fading_in_when_currently_above() { + let mut fader = Fader::fade_in(10.0, 1.0); + + // After 9s, the fader should be close to 1.0 + fader.update(9.0); + + // Reduce volume to 0.4. We are currently above that. + fader.update_target_volume(0.4); + + // Run out the fader. It's volume should be 0.4 + fader.update(1.0); + + assert_eq!(fader.get_volume(), 0.4); + } + + #[test] + fn update_target_volume_fading_in_when_currently_below() { + let mut fader = Fader::fade_in(20.0, 1.0); + + // After 0.1s, the fader should still be close to 0.0 + fader.update(0.1); + + // Reduce volume to 0.4. The volume_to should be reduced accordingly. + fader.update_target_volume(0.4); + + assert_eq!(fader.volume_to, 0.4); + } +} diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs index 7e73d9a752..53ef2965df 100644 --- a/voxygen/src/audio/mod.rs +++ b/voxygen/src/audio/mod.rs @@ -4,7 +4,7 @@ pub mod music; pub mod sfx; pub mod soundcache; -use channel::{AudioType, Channel, ChannelTag}; +use channel::{MusicChannel, MusicChannelTag, SfxChannel}; use fader::Fader; use soundcache::SoundCache; @@ -21,8 +21,8 @@ pub struct AudioFrontend { audio_device: Option, sound_cache: SoundCache, - channels: Vec, - next_channel_id: usize, + music_channels: Vec, + sfx_channels: Vec, sfx_volume: f32, music_volume: f32, @@ -36,21 +36,23 @@ pub struct AudioFrontend { impl AudioFrontend { /// Construct with given device - pub fn new(device: String, channel_num: usize) -> Self { - let mut channels = Vec::with_capacity(channel_num); + pub fn new(device: String, max_sfx_channels: usize) -> Self { + let mut sfx_channels = Vec::with_capacity(max_sfx_channels); let audio_device = get_device_raw(&device); + if let Some(audio_device) = &audio_device { - for _i in 0..channel_num { - channels.push(Channel::new(&audio_device)); + for _ in 0..max_sfx_channels { + sfx_channels.push(SfxChannel::new(&audio_device)); } } + Self { device: device.clone(), device_list: list_devices(), audio_device, sound_cache: SoundCache::new(), - channels, - next_channel_id: 1, + music_channels: Vec::new(), + sfx_channels, sfx_volume: 1.0, music_volume: 1.0, listener_pos: Vec3::zero(), @@ -67,8 +69,8 @@ impl AudioFrontend { device_list: Vec::new(), audio_device: None, sound_cache: SoundCache::new(), - channels: Vec::new(), - next_channel_id: 1, + music_channels: Vec::new(), + sfx_channels: Vec::new(), sfx_volume: 1.0, music_volume: 1.0, listener_pos: Vec3::zero(), @@ -78,40 +80,62 @@ impl AudioFrontend { } } - /// Maintain audio + /// Drop any unused music channels, and update their faders pub fn maintain(&mut self, dt: f32) { - for channel in self.channels.iter_mut() { - channel.update(dt); + self.music_channels.retain(|c| !c.is_done()); + + for channel in self.music_channels.iter_mut() { + channel.maintain(dt); } } - pub fn get_channel( + fn get_sfx_channel(&mut self) -> Option<&mut SfxChannel> { + if self.audio_device.is_some() { + if let Some(channel) = self.sfx_channels.iter_mut().find(|c| c.is_done()) { + channel.set_volume(self.sfx_volume); + + return Some(channel); + } + } + + None + } + + /// Retrieve a music channel from the channel list. This inspects the + /// MusicChannelTag to determine whether we are transitioning between + /// music types and acts accordingly. For example transitioning between + /// `TitleMusic` and `Exploration` should fade out the title channel and + /// fade in a new `Exploration` channel. + fn get_music_channel( &mut self, - audio_type: AudioType, - channel_tag: Option, - ) -> Option<&mut Channel> { - if let Some(channel) = self.channels.iter_mut().find(|c| c.is_done()) { - let id = self.next_channel_id; - self.next_channel_id += 1; + next_channel_tag: MusicChannelTag, + ) -> Option<&mut MusicChannel> { + if let Some(audio_device) = &self.audio_device { + if self.music_channels.is_empty() { + let mut next_music_channel = MusicChannel::new(&audio_device); + next_music_channel.set_volume(self.music_volume); - let volume = match audio_type { - AudioType::Music => self.music_volume, - _ => self.sfx_volume, - }; + self.music_channels.push(next_music_channel); + } else { + let existing_channel = self.music_channels.last_mut()?; - channel.set_id(id); - channel.set_tag(channel_tag); - channel.set_audio_type(audio_type); - channel.set_volume(volume); + if existing_channel.get_tag() != next_channel_tag { + // Fade the existing channel out. It will be removed when the fade completes. + existing_channel.set_fader(Fader::fade_out(2.0, self.music_volume)); - Some(channel) - } else { - None + let mut next_music_channel = MusicChannel::new(&audio_device); + + next_music_channel.set_fader(Fader::fade_in(12.0, self.music_volume)); + + self.music_channels.push(next_music_channel); + } + } } + + self.music_channels.last_mut() } - /// Play specfied sound file. - pub fn play_sound(&mut self, sound: &str, pos: Vec3) -> Option { + pub fn play_sfx(&mut self, sound: &str, pos: Vec3) { if self.audio_device.is_some() { let calc_pos = ((pos - self.listener_pos) * FALLOFF).into_array(); @@ -120,33 +144,22 @@ impl AudioFrontend { let left_ear = self.listener_ear_left.into_array(); let right_ear = self.listener_ear_right.into_array(); - if let Some(channel) = self.get_channel(AudioType::Sfx, None) { + if let Some(channel) = self.get_sfx_channel() { channel.set_emitter_position(calc_pos); channel.set_left_ear_position(left_ear); channel.set_right_ear_position(right_ear); channel.play(sound); - - return Some(channel.get_id()); } } - - None } - pub fn play_music(&mut self, sound: &str, channel_tag: Option) -> Option { - if self.audio_device.is_some() { - if let Some(channel) = self.get_channel(AudioType::Music, channel_tag) { - let file = assets::load_file(&sound, &["ogg"]).expect("Failed to load sound"); - let sound = Decoder::new(file).expect("Failed to decode sound"); + fn play_music(&mut self, sound: &str, channel_tag: MusicChannelTag) { + if let Some(channel) = self.get_music_channel(channel_tag) { + let file = assets::load_file(&sound, &["ogg"]).expect("Failed to load sound"); + let sound = Decoder::new(file).expect("Failed to decode sound"); - channel.set_emitter_position([0.0; 3]); - channel.play(sound); - - return Some(channel.get_id()); - } + channel.play(sound, channel_tag); } - - None } pub fn set_listener_pos(&mut self, pos: &Vec3, ori: &Vec3) { @@ -161,8 +174,8 @@ impl AudioFrontend { self.listener_ear_left = pos_left; self.listener_ear_right = pos_right; - for channel in self.channels.iter_mut() { - if !channel.is_done() && channel.get_audio_type() == AudioType::Sfx { + for channel in self.sfx_channels.iter_mut() { + if !channel.is_done() { // TODO: Update this to correctly determine the updated relative position of // the SFX emitter when the player (listener) moves // channel.set_emitter_position( @@ -174,32 +187,18 @@ impl AudioFrontend { } } - pub fn play_title_music(&mut self) -> Option { + pub fn play_title_music(&mut self) { if self.music_enabled() { self.play_music( "voxygen.audio.soundtrack.veloren_title_tune", - Some(ChannelTag::TitleMusic), + MusicChannelTag::TitleMusic, ) - } else { - None } } - pub fn stop_title_music(&mut self) { - let index = self.channels.iter().position(|c| { - !c.is_done() && c.get_tag().is_some() && c.get_tag().unwrap() == ChannelTag::TitleMusic - }); - - if let Some(index) = index { - self.channels[index].stop(Fader::fade_out(1.5, self.music_volume)); - } - } - - pub fn stop_channel(&mut self, channel_id: usize, fader: Fader) { - let index = self.channels.iter().position(|c| c.get_id() == channel_id); - - if let Some(index) = index { - self.channels[index].stop(fader); + pub fn play_exploration_music(&mut self, item: &str) { + if self.music_enabled() { + self.play_music(item, MusicChannelTag::Exploration) } } @@ -214,24 +213,16 @@ impl AudioFrontend { pub fn set_sfx_volume(&mut self, sfx_volume: f32) { self.sfx_volume = sfx_volume; - for channel in self.channels.iter_mut() { - if channel.get_audio_type() == AudioType::Sfx { - channel.set_volume(sfx_volume); - } + for channel in self.sfx_channels.iter_mut() { + channel.set_volume(sfx_volume); } } pub fn set_music_volume(&mut self, music_volume: f32) { self.music_volume = music_volume; - for channel in self.channels.iter_mut() { - if channel.get_audio_type() == AudioType::Music { - if music_volume > 0.0 { - channel.set_volume(music_volume); - } else { - channel.stop(Fader::fade_out(0.0, 0.0)); - } - } + for channel in self.music_channels.iter_mut() { + channel.set_volume(music_volume); } } diff --git a/voxygen/src/audio/music.rs b/voxygen/src/audio/music.rs index 8f6ecc0525..7b46abd016 100644 --- a/voxygen/src/audio/music.rs +++ b/voxygen/src/audio/music.rs @@ -1,4 +1,4 @@ -use crate::audio::{channel::ChannelTag, AudioFrontend}; +use crate::audio::AudioFrontend; use client::Client; use common::assets; use rand::{seq::IteratorRandom, thread_rng}; @@ -31,7 +31,6 @@ pub struct MusicMgr { soundtrack: SoundtrackCollection, began_playing: Instant, next_track_change: f64, - current_music: Option, last_track: String, } @@ -41,7 +40,6 @@ impl MusicMgr { soundtrack: Self::load_soundtrack_items(), began_playing: Instant::now(), next_track_change: 0.0, - current_music: None, last_track: String::from("None"), } } @@ -50,15 +48,15 @@ impl MusicMgr { if audio.music_enabled() && self.began_playing.elapsed().as_secs_f64() > self.next_track_change { - self.current_music = self.play_random_track(audio, client); + self.play_random_track(audio, client); } } - fn play_random_track(&mut self, audio: &mut AudioFrontend, client: &Client) -> Option { + fn play_random_track(&mut self, audio: &mut AudioFrontend, client: &Client) { const SILENCE_BETWEEN_TRACKS_SECONDS: f64 = 45.0; let game_time = (client.state().get_time_of_day() as u64 % 86400) as u32; - let current_period_of_day = self.get_current_day_period(game_time); + let current_period_of_day = Self::get_current_day_period(game_time); let mut rng = thread_rng(); let track = self @@ -79,10 +77,10 @@ impl MusicMgr { self.began_playing = Instant::now(); self.next_track_change = track.length + SILENCE_BETWEEN_TRACKS_SECONDS; - audio.play_music(&track.path, Some(ChannelTag::Soundtrack)) + audio.play_exploration_music(&track.path); } - fn get_current_day_period(&self, game_time: u32) -> DayPeriod { + fn get_current_day_period(game_time: u32) -> DayPeriod { if game_time > DAY_START_SECONDS && game_time < DAY_END_SECONDS { DayPeriod::Day } else { diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index cbd6d50f5b..65231f5758 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -91,7 +91,7 @@ impl SfxMgr { }, }; - audio.play_sound(sfx_file, position); + audio.play_sfx(sfx_file, position); } } } diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index ea6f4ee58c..53536055ce 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -121,7 +121,7 @@ fn main() { }; let mut audio = if settings.audio.audio_on { - AudioFrontend::new(audio_device(), 16) + AudioFrontend::new(audio_device(), settings.audio.max_sfx_channels) } else { AudioFrontend::no_audio() }; diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 3c05d71eae..9ce56b48dc 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -18,7 +18,6 @@ use ui::{Event as MainMenuEvent, MainMenuUi}; pub struct MainMenuState { main_menu_ui: MainMenuUi, - title_music_channel: Option, singleplayer: Option, } @@ -27,7 +26,6 @@ impl MainMenuState { pub fn new(global_state: &mut GlobalState) -> Self { Self { main_menu_ui: MainMenuUi::new(global_state), - title_music_channel: None, singleplayer: None, } } @@ -44,11 +42,8 @@ impl PlayState for MainMenuState { let mut client_init: Option = None; // Kick off title music - if self.title_music_channel.is_none() - && global_state.settings.audio.audio_on - && global_state.audio.music_enabled() - { - self.title_music_channel = global_state.audio.play_title_music(); + if global_state.settings.audio.audio_on && global_state.audio.music_enabled() { + global_state.audio.play_title_music(); } // Reset singleplayer server if it was running already diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 83b0a736b1..437784d197 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -126,9 +126,6 @@ impl PlayState for SessionState { let mut clock = Clock::start(); self.client.borrow_mut().clear_terrain(); - // Kill the title music if it is still playing - global_state.audio.stop_title_music(); - // Send startup commands to the server if global_state.settings.send_logon_commands { for cmd in &global_state.settings.logon_commands { diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 87822b87b0..9b4f5f6922 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -221,6 +221,7 @@ pub struct AudioSettings { pub master_volume: f32, pub music_volume: f32, pub sfx_volume: f32, + pub max_sfx_channels: usize, /// Audio Device that Voxygen will use to play audio. pub audio_device: Option, @@ -233,6 +234,7 @@ impl Default for AudioSettings { master_volume: 1.0, music_volume: 0.4, sfx_volume: 0.6, + max_sfx_channels: 10, audio_device: None, audio_on: true, }