Merge branch 'shandley/audio-channel-types' into 'master'

Use non-spatial audio sinks for music

Closes #463

See merge request veloren/veloren!794
This commit is contained in:
Imbris 2020-02-15 21:30:44 +00:00
commit a08fc287ae
11 changed files with 326 additions and 237 deletions

55
Cargo.lock generated
View File

@ -391,11 +391,6 @@ dependencies = [
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "clipboard-win" name = "clipboard-win"
version = "2.2.0" version = "2.2.0"
@ -1583,14 +1578,6 @@ name = "lzw"
version = "0.10.0" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "malloc_buf" name = "malloc_buf"
version = "0.0.6" version = "0.0.6"
@ -1666,23 +1653,6 @@ dependencies = [
"x11-dl 2.18.4 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "mio" name = "mio"
version = "0.6.21" version = "0.6.21"
@ -2555,15 +2525,13 @@ dependencies = [
[[package]] [[package]]
name = "rodio" name = "rodio"
version = "0.9.0" version = "0.10.0"
source = "git+https://github.com/RustAudio/rodio?rev=e5474a2#e5474a2ef15f2d0a3bae2538de159b6d3e5bdf79" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ 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)", "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)", "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)", "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)", "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]] [[package]]
@ -2816,16 +2784,6 @@ name = "slab"
version = "0.4.2" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "smallvec" name = "smallvec"
version = "0.6.13" version = "0.6.13"
@ -3300,7 +3258,7 @@ dependencies = [
"msgbox 0.4.0 (git+https://github.com/bekker/msgbox-rs.git?rev=68fe39a)", "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)", "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)", "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)", "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)", "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)", "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 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 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 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 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 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" "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 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 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 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 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 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" "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 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 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)" = "<none>" "checksum minifb 0.13.0 (git+https://github.com/emoon/rust_minifb.git)" = "<none>"
"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 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 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" "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-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 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 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)" = "<none>" "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 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 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" "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 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 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 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 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 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" "checksum smithay-client-toolkit 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2ccb8c57049b2a34d2cc2b203fa785020ba0129d31920ef0d317430adaf748fa"

View File

@ -55,8 +55,7 @@ num = "0.2.0"
backtrace = "0.3.40" backtrace = "0.3.40"
rand = "0.7.2" rand = "0.7.2"
treeculler = { git = "https://gitlab.com/yusdacra/treeculler.git" } treeculler = { git = "https://gitlab.com/yusdacra/treeculler.git" }
# context for pinning to commit: https://gitlab.com/veloren/veloren/issues/280 rodio = { version = "0.10", default-features = false, features = ["wav", "vorbis"] }
rodio = { git = "https://github.com/RustAudio/rodio", rev = "e5474a2"}
cpal = "0.10" cpal = "0.10"
crossbeam = "=0.7.2" crossbeam = "=0.7.2"
hashbrown = { version = "0.6.2", features = ["rayon", "serde", "nightly"] } hashbrown = { version = "0.6.2", features = ["rayon", "serde", "nightly"] }

View File

@ -1,51 +1,125 @@
use crate::audio::fader::Fader; use crate::audio::fader::{FadeDirection, Fader};
use rodio::{Device, Sample, Source, SpatialSink}; use rodio::{Device, Sample, Sink, Source, SpatialSink};
use vek::*; use vek::*;
#[derive(PartialEq, Clone, Copy)]
pub enum AudioType {
Sfx,
Music,
None,
}
#[derive(PartialEq, Clone, Copy)] #[derive(PartialEq, Clone, Copy)]
enum ChannelState { enum ChannelState {
// Init,
// ToPlay,
// Loading,
Playing, Playing,
Stopping, Fading,
Stopped, Stopped,
} }
/// Each MusicChannel has a MusicChannelTag which help us determine how we
/// should transition between music types
#[derive(PartialEq, Clone, Copy)] #[derive(PartialEq, Clone, Copy)]
pub enum ChannelTag { pub enum MusicChannelTag {
TitleMusic, TitleMusic,
Soundtrack, Exploration,
} }
pub struct Channel { /// A MusicChannel uses a non-positional audio sink designed to play music which
id: usize, /// is always heard at the player's position
sink: SpatialSink, pub struct MusicChannel {
audio_type: AudioType, tag: MusicChannelTag,
sink: Sink,
state: ChannelState, state: ChannelState,
fader: Fader, fader: Fader,
tag: Option<ChannelTag>, }
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<S>(&mut self, source: S, tag: MusicChannelTag)
where
S: Source + Send + 'static,
S::Item: Sample,
S::Item: Send,
<S as std::iter::Iterator>::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<f32>, pub pos: Vec3<f32>,
} }
// TODO: Implement asynchronous loading impl SfxChannel {
impl Channel {
/// Create an empty channel for future use
pub fn new(device: &Device) -> Self { pub fn new(device: &Device) -> Self {
Self { Self {
id: 0,
sink: SpatialSink::new(device, [0.0; 3], [1.0, 0.0, 0.0], [-1.0, 0.0, 0.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(), pos: Vec3::zero(),
} }
} }
@ -57,31 +131,13 @@ impl Channel {
S::Item: Send, S::Item: Send,
<S as std::iter::Iterator>::Item: std::fmt::Debug, <S as std::iter::Iterator>::Item: std::fmt::Debug,
{ {
self.state = ChannelState::Playing;
self.sink.append(source); 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<ChannelTag>) { self.tag = tag; }
pub fn get_tag(&self) -> Option<ChannelTag> { 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 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_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); } 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]) { pub fn set_right_ear_position(&mut self, pos: [f32; 3]) {
self.sink.set_right_ear_position(pos); 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 => {},
}
}
} }

View File

@ -6,13 +6,18 @@ pub struct Fader {
volume_to: f32, volume_to: f32,
is_running: bool, 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 } fn lerp(t: f32, a: f32, b: f32) -> f32 { (1.0 - t) * a + t * b }
impl Fader { 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 { Self {
length: time, length,
running_time: 0.0, running_time: 0.0,
volume_from, volume_from,
volume_to, volume_to,
@ -20,23 +25,28 @@ impl Fader {
} }
} }
pub fn fade_in(time: f32) -> Self { pub fn fade_in(time: f32, volume_to: f32) -> Self { Self::fade(time, 0.0, volume_to) }
Self {
length: time, pub fn fade_out(time: f32, volume_from: f32) -> Self { Self::fade(time, volume_from, 0.0) }
running_time: 0.0,
volume_from: 0.0, pub fn update_target_volume(&mut self, volume: f32) {
volume_to: 1.0, match self.direction() {
is_running: true, 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 { pub fn direction(&self) -> FadeDirection {
Self { if self.volume_to < self.volume_from {
length: time, FadeDirection::Out
running_time: 0.0, } else {
volume_from, FadeDirection::In
volume_to: 0.0,
is_running: true,
} }
} }
@ -60,3 +70,107 @@ impl Fader {
pub fn is_finished(&self) -> bool { self.running_time >= self.length || !self.is_running } 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);
}
}

View File

@ -4,7 +4,7 @@ pub mod music;
pub mod sfx; pub mod sfx;
pub mod soundcache; pub mod soundcache;
use channel::{AudioType, Channel, ChannelTag}; use channel::{MusicChannel, MusicChannelTag, SfxChannel};
use fader::Fader; use fader::Fader;
use soundcache::SoundCache; use soundcache::SoundCache;
@ -21,8 +21,8 @@ pub struct AudioFrontend {
audio_device: Option<Device>, audio_device: Option<Device>,
sound_cache: SoundCache, sound_cache: SoundCache,
channels: Vec<Channel>, music_channels: Vec<MusicChannel>,
next_channel_id: usize, sfx_channels: Vec<SfxChannel>,
sfx_volume: f32, sfx_volume: f32,
music_volume: f32, music_volume: f32,
@ -36,21 +36,23 @@ pub struct AudioFrontend {
impl AudioFrontend { impl AudioFrontend {
/// Construct with given device /// Construct with given device
pub fn new(device: String, channel_num: usize) -> Self { pub fn new(device: String, max_sfx_channels: usize) -> Self {
let mut channels = Vec::with_capacity(channel_num); let mut sfx_channels = Vec::with_capacity(max_sfx_channels);
let audio_device = get_device_raw(&device); let audio_device = get_device_raw(&device);
if let Some(audio_device) = &audio_device { if let Some(audio_device) = &audio_device {
for _i in 0..channel_num { for _ in 0..max_sfx_channels {
channels.push(Channel::new(&audio_device)); sfx_channels.push(SfxChannel::new(&audio_device));
} }
} }
Self { Self {
device: device.clone(), device: device.clone(),
device_list: list_devices(), device_list: list_devices(),
audio_device, audio_device,
sound_cache: SoundCache::new(), sound_cache: SoundCache::new(),
channels, music_channels: Vec::new(),
next_channel_id: 1, sfx_channels,
sfx_volume: 1.0, sfx_volume: 1.0,
music_volume: 1.0, music_volume: 1.0,
listener_pos: Vec3::zero(), listener_pos: Vec3::zero(),
@ -67,8 +69,8 @@ impl AudioFrontend {
device_list: Vec::new(), device_list: Vec::new(),
audio_device: None, audio_device: None,
sound_cache: SoundCache::new(), sound_cache: SoundCache::new(),
channels: Vec::new(), music_channels: Vec::new(),
next_channel_id: 1, sfx_channels: Vec::new(),
sfx_volume: 1.0, sfx_volume: 1.0,
music_volume: 1.0, music_volume: 1.0,
listener_pos: Vec3::zero(), 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) { pub fn maintain(&mut self, dt: f32) {
for channel in self.channels.iter_mut() { self.music_channels.retain(|c| !c.is_done());
channel.update(dt);
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, &mut self,
audio_type: AudioType, next_channel_tag: MusicChannelTag,
channel_tag: Option<ChannelTag>, ) -> Option<&mut MusicChannel> {
) -> Option<&mut Channel> { if let Some(audio_device) = &self.audio_device {
if let Some(channel) = self.channels.iter_mut().find(|c| c.is_done()) { if self.music_channels.is_empty() {
let id = self.next_channel_id; let mut next_music_channel = MusicChannel::new(&audio_device);
self.next_channel_id += 1; next_music_channel.set_volume(self.music_volume);
let volume = match audio_type { self.music_channels.push(next_music_channel);
AudioType::Music => self.music_volume, } else {
_ => self.sfx_volume, let existing_channel = self.music_channels.last_mut()?;
};
channel.set_id(id); if existing_channel.get_tag() != next_channel_tag {
channel.set_tag(channel_tag); // Fade the existing channel out. It will be removed when the fade completes.
channel.set_audio_type(audio_type); existing_channel.set_fader(Fader::fade_out(2.0, self.music_volume));
channel.set_volume(volume);
Some(channel) let mut next_music_channel = MusicChannel::new(&audio_device);
} else {
None 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_sfx(&mut self, sound: &str, pos: Vec3<f32>) {
pub fn play_sound(&mut self, sound: &str, pos: Vec3<f32>) -> Option<usize> {
if self.audio_device.is_some() { if self.audio_device.is_some() {
let calc_pos = ((pos - self.listener_pos) * FALLOFF).into_array(); 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 left_ear = self.listener_ear_left.into_array();
let right_ear = self.listener_ear_right.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_emitter_position(calc_pos);
channel.set_left_ear_position(left_ear); channel.set_left_ear_position(left_ear);
channel.set_right_ear_position(right_ear); channel.set_right_ear_position(right_ear);
channel.play(sound); channel.play(sound);
return Some(channel.get_id());
} }
} }
None
} }
pub fn play_music(&mut self, sound: &str, channel_tag: Option<ChannelTag>) -> Option<usize> { fn play_music(&mut self, sound: &str, channel_tag: MusicChannelTag) {
if self.audio_device.is_some() { if let Some(channel) = self.get_music_channel(channel_tag) {
if let Some(channel) = self.get_channel(AudioType::Music, channel_tag) { let file = assets::load_file(&sound, &["ogg"]).expect("Failed to load sound");
let file = assets::load_file(&sound, &["ogg"]).expect("Failed to load sound"); let sound = Decoder::new(file).expect("Failed to decode sound");
let sound = Decoder::new(file).expect("Failed to decode sound");
channel.set_emitter_position([0.0; 3]); channel.play(sound, channel_tag);
channel.play(sound);
return Some(channel.get_id());
}
} }
None
} }
pub fn set_listener_pos(&mut self, pos: &Vec3<f32>, ori: &Vec3<f32>) { pub fn set_listener_pos(&mut self, pos: &Vec3<f32>, ori: &Vec3<f32>) {
@ -161,8 +174,8 @@ impl AudioFrontend {
self.listener_ear_left = pos_left; self.listener_ear_left = pos_left;
self.listener_ear_right = pos_right; self.listener_ear_right = pos_right;
for channel in self.channels.iter_mut() { for channel in self.sfx_channels.iter_mut() {
if !channel.is_done() && channel.get_audio_type() == AudioType::Sfx { if !channel.is_done() {
// TODO: Update this to correctly determine the updated relative position of // TODO: Update this to correctly determine the updated relative position of
// the SFX emitter when the player (listener) moves // the SFX emitter when the player (listener) moves
// channel.set_emitter_position( // channel.set_emitter_position(
@ -174,32 +187,18 @@ impl AudioFrontend {
} }
} }
pub fn play_title_music(&mut self) -> Option<usize> { pub fn play_title_music(&mut self) {
if self.music_enabled() { if self.music_enabled() {
self.play_music( self.play_music(
"voxygen.audio.soundtrack.veloren_title_tune", "voxygen.audio.soundtrack.veloren_title_tune",
Some(ChannelTag::TitleMusic), MusicChannelTag::TitleMusic,
) )
} else {
None
} }
} }
pub fn stop_title_music(&mut self) { pub fn play_exploration_music(&mut self, item: &str) {
let index = self.channels.iter().position(|c| { if self.music_enabled() {
!c.is_done() && c.get_tag().is_some() && c.get_tag().unwrap() == ChannelTag::TitleMusic self.play_music(item, MusicChannelTag::Exploration)
});
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);
} }
} }
@ -214,24 +213,16 @@ impl AudioFrontend {
pub fn set_sfx_volume(&mut self, sfx_volume: f32) { pub fn set_sfx_volume(&mut self, sfx_volume: f32) {
self.sfx_volume = sfx_volume; self.sfx_volume = sfx_volume;
for channel in self.channels.iter_mut() { for channel in self.sfx_channels.iter_mut() {
if channel.get_audio_type() == AudioType::Sfx { channel.set_volume(sfx_volume);
channel.set_volume(sfx_volume);
}
} }
} }
pub fn set_music_volume(&mut self, music_volume: f32) { pub fn set_music_volume(&mut self, music_volume: f32) {
self.music_volume = music_volume; self.music_volume = music_volume;
for channel in self.channels.iter_mut() { for channel in self.music_channels.iter_mut() {
if channel.get_audio_type() == AudioType::Music { channel.set_volume(music_volume);
if music_volume > 0.0 {
channel.set_volume(music_volume);
} else {
channel.stop(Fader::fade_out(0.0, 0.0));
}
}
} }
} }

View File

@ -1,4 +1,4 @@
use crate::audio::{channel::ChannelTag, AudioFrontend}; use crate::audio::AudioFrontend;
use client::Client; use client::Client;
use common::assets; use common::assets;
use rand::{seq::IteratorRandom, thread_rng}; use rand::{seq::IteratorRandom, thread_rng};
@ -31,7 +31,6 @@ pub struct MusicMgr {
soundtrack: SoundtrackCollection, soundtrack: SoundtrackCollection,
began_playing: Instant, began_playing: Instant,
next_track_change: f64, next_track_change: f64,
current_music: Option<usize>,
last_track: String, last_track: String,
} }
@ -41,7 +40,6 @@ impl MusicMgr {
soundtrack: Self::load_soundtrack_items(), soundtrack: Self::load_soundtrack_items(),
began_playing: Instant::now(), began_playing: Instant::now(),
next_track_change: 0.0, next_track_change: 0.0,
current_music: None,
last_track: String::from("None"), last_track: String::from("None"),
} }
} }
@ -50,15 +48,15 @@ impl MusicMgr {
if audio.music_enabled() if audio.music_enabled()
&& self.began_playing.elapsed().as_secs_f64() > self.next_track_change && 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<usize> { fn play_random_track(&mut self, audio: &mut AudioFrontend, client: &Client) {
const SILENCE_BETWEEN_TRACKS_SECONDS: f64 = 45.0; const SILENCE_BETWEEN_TRACKS_SECONDS: f64 = 45.0;
let game_time = (client.state().get_time_of_day() as u64 % 86400) as u32; 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 mut rng = thread_rng();
let track = self let track = self
@ -79,10 +77,10 @@ impl MusicMgr {
self.began_playing = Instant::now(); self.began_playing = Instant::now();
self.next_track_change = track.length + SILENCE_BETWEEN_TRACKS_SECONDS; 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 { if game_time > DAY_START_SECONDS && game_time < DAY_END_SECONDS {
DayPeriod::Day DayPeriod::Day
} else { } else {

View File

@ -91,7 +91,7 @@ impl SfxMgr {
}, },
}; };
audio.play_sound(sfx_file, position); audio.play_sfx(sfx_file, position);
} }
} }
} }

View File

@ -121,7 +121,7 @@ fn main() {
}; };
let mut audio = if settings.audio.audio_on { let mut audio = if settings.audio.audio_on {
AudioFrontend::new(audio_device(), 16) AudioFrontend::new(audio_device(), settings.audio.max_sfx_channels)
} else { } else {
AudioFrontend::no_audio() AudioFrontend::no_audio()
}; };

View File

@ -18,7 +18,6 @@ use ui::{Event as MainMenuEvent, MainMenuUi};
pub struct MainMenuState { pub struct MainMenuState {
main_menu_ui: MainMenuUi, main_menu_ui: MainMenuUi,
title_music_channel: Option<usize>,
singleplayer: Option<Singleplayer>, singleplayer: Option<Singleplayer>,
} }
@ -27,7 +26,6 @@ impl MainMenuState {
pub fn new(global_state: &mut GlobalState) -> Self { pub fn new(global_state: &mut GlobalState) -> Self {
Self { Self {
main_menu_ui: MainMenuUi::new(global_state), main_menu_ui: MainMenuUi::new(global_state),
title_music_channel: None,
singleplayer: None, singleplayer: None,
} }
} }
@ -44,11 +42,8 @@ impl PlayState for MainMenuState {
let mut client_init: Option<ClientInit> = None; let mut client_init: Option<ClientInit> = None;
// Kick off title music // Kick off title music
if self.title_music_channel.is_none() if global_state.settings.audio.audio_on && global_state.audio.music_enabled() {
&& global_state.settings.audio.audio_on global_state.audio.play_title_music();
&& global_state.audio.music_enabled()
{
self.title_music_channel = global_state.audio.play_title_music();
} }
// Reset singleplayer server if it was running already // Reset singleplayer server if it was running already

View File

@ -126,9 +126,6 @@ impl PlayState for SessionState {
let mut clock = Clock::start(); let mut clock = Clock::start();
self.client.borrow_mut().clear_terrain(); 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 // Send startup commands to the server
if global_state.settings.send_logon_commands { if global_state.settings.send_logon_commands {
for cmd in &global_state.settings.logon_commands { for cmd in &global_state.settings.logon_commands {

View File

@ -221,6 +221,7 @@ pub struct AudioSettings {
pub master_volume: f32, pub master_volume: f32,
pub music_volume: f32, pub music_volume: f32,
pub sfx_volume: f32, pub sfx_volume: f32,
pub max_sfx_channels: usize,
/// Audio Device that Voxygen will use to play audio. /// Audio Device that Voxygen will use to play audio.
pub audio_device: Option<String>, pub audio_device: Option<String>,
@ -233,6 +234,7 @@ impl Default for AudioSettings {
master_volume: 1.0, master_volume: 1.0,
music_volume: 0.4, music_volume: 0.4,
sfx_volume: 0.6, sfx_volume: 0.6,
max_sfx_channels: 10,
audio_device: None, audio_device: None,
audio_on: true, audio_on: true,
} }