veloren/voxygen/src/audio/fader.rs
Marcel Märtens e4e5c6e55b massivly switch clock algorithm.
- before we had a Clock that tried to average multiple ticks and predict the next sleep.
   This system is massivly bugged.
   a) We know exactly how long the busy time took, so we dont need to predict anything in the first place
   b) Preduction was totally unrealistic after a single lag spike
   c) When a very slow tick happens, we dont benefit from 10 fast ticks.
 - Instead we just try to keep the tick time exact what we expect.
   If we can't manage a constant tick time because we are to slow, the systems have to "catch" this via the `dt` anyway.
2020-11-10 18:31:42 +01:00

194 lines
5.7 KiB
Rust

//! Controls volume transitions for Audio Channels
use std::time::Duration;
/// Faders are attached to channels with initial and target volumes as well as a
/// transition time.
#[derive(PartialEq, Clone, Copy)]
pub struct Fader {
length: Duration,
running_time: Duration,
volume_from: f32,
volume_to: f32,
is_running: bool,
}
/// Enables quick lookup of whether a fader is increasing or decreasing the
/// channel volume
#[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(length: Duration, volume_from: f32, volume_to: f32) -> Self {
Self {
length,
running_time: Duration::default(),
volume_from,
volume_to,
is_running: true,
}
}
pub fn fade_in(time: Duration, volume_to: f32) -> Self { Self::fade(time, 0.0, volume_to) }
pub fn fade_out(time: Duration, volume_from: f32) -> Self { Self::fade(time, volume_from, 0.0) }
/// Used to update the `target` volume of the fader when the max or min
/// volume changes. This occurs when the player changes their in-game
/// volume setting during a fade. Updating the target in this case prevents
/// the final fade volume from falling outside of the newly configured
/// volume range.
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 direction(&self) -> FadeDirection {
if self.volume_to < self.volume_from {
FadeDirection::Out
} else {
FadeDirection::In
}
}
/// Called each tick to update the volume and state
pub fn update(&mut self, dt: std::time::Duration) {
if self.is_running {
self.running_time += dt;
if self.running_time >= self.length {
self.running_time = self.length;
self.is_running = false;
}
}
}
pub fn get_volume(&self) -> f32 {
lerp(
self.running_time.as_nanos() as f32 / self.length.as_nanos() as f32,
self.volume_from,
self.volume_to,
)
}
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: Duration::default(),
running_time: Duration::default(),
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(Duration::from_secs(10), 0.0);
assert_eq!(fader.direction(), FadeDirection::In);
}
#[test]
fn fade_direction_out() {
let fader = Fader::fade_out(Duration::from_secs(10), 1.0);
assert_eq!(fader.direction(), FadeDirection::Out);
}
#[test]
#[allow(clippy::float_cmp)] // TODO: Pending review in #587
fn fade_out_completes() {
let mut fader = Fader::fade_out(Duration::from_secs(10), 1.0);
// Run for the full duration
fader.update(Duration::from_secs(10));
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(Duration::from_secs(20), 1.0);
// After 0.1s, the fader should still be close to 1.0
fader.update(Duration::from_millis(100));
// 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(Duration::from_millis(100));
assert!(fader.get_volume() < 0.4)
}
#[test]
fn update_target_volume_fading_out_when_currently_below() {
let mut fader = Fader::fade_out(Duration::from_secs(10), 0.8);
// After 9s, the fader should be close to 0
fader.update(Duration::from_secs(9));
// 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(Duration::from_millis(100));
assert!(fader.get_volume() < 0.2);
}
#[test]
#[allow(clippy::float_cmp)] // TODO: Pending review in #587
fn update_target_volume_fading_in_when_currently_above() {
let mut fader = Fader::fade_in(Duration::from_secs(10), 1.0);
// After 9s, the fader should be close to 1.0
fader.update(Duration::from_secs(9));
// 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(Duration::from_secs(1));
assert_eq!(fader.get_volume(), 0.4);
}
#[test]
#[allow(clippy::float_cmp)] // TODO: Pending review in #587
fn update_target_volume_fading_in_when_currently_below() {
let mut fader = Fader::fade_in(Duration::from_secs(20), 1.0);
// After 0.1s, the fader should still be close to 0.0
fader.update(Duration::from_millis(100));
// 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);
}
}