veloren_voxygen/audio/
fader.rs

1//! Controls volume transitions for Audio Channels
2
3use std::time::Duration;
4
5/// Faders are attached to channels with initial and target volumes as well as a
6/// transition time.
7#[derive(PartialEq, Clone, Copy)]
8pub struct Fader {
9    length: Duration,
10    running_time: Duration,
11    volume_from: f32,
12    volume_to: f32,
13    is_running: bool,
14}
15/// Enables quick lookup of whether a fader is increasing or decreasing the
16/// channel volume
17#[derive(Debug, PartialEq, Eq, Clone, Copy)]
18pub enum FadeDirection {
19    In,
20    Out,
21}
22
23fn lerp(t: f32, a: f32, b: f32) -> f32 { (1.0 - t) * a + t * b }
24
25impl Fader {
26    pub fn fade(length: Duration, volume_from: f32, volume_to: f32) -> Self {
27        Self {
28            length,
29            running_time: Duration::default(),
30            volume_from,
31            volume_to,
32            is_running: true,
33        }
34    }
35
36    pub fn fade_in(time: Duration, volume_to: f32) -> Self { Self::fade(time, 0.0, volume_to) }
37
38    pub fn fade_out(time: Duration, volume_from: f32) -> Self { Self::fade(time, volume_from, 0.0) }
39
40    /// Used to update the `target` volume of the fader when the max or min
41    /// volume changes. This occurs when the player changes their in-game
42    /// volume setting during a fade. Updating the target in this case prevents
43    /// the final fade volume from falling outside of the newly configured
44    /// volume range.
45    pub fn update_target_volume(&mut self, volume: f32) {
46        match self.direction() {
47            FadeDirection::In => {
48                self.volume_to = volume;
49            },
50            FadeDirection::Out => {
51                if self.get_volume() > volume {
52                    self.volume_from = volume;
53                }
54            },
55        }
56    }
57
58    pub fn direction(&self) -> FadeDirection {
59        if self.volume_to < self.volume_from {
60            FadeDirection::Out
61        } else {
62            FadeDirection::In
63        }
64    }
65
66    /// Called each tick to update the volume and state
67    pub fn update(&mut self, dt: Duration) {
68        if self.is_running {
69            self.running_time += dt;
70            if self.running_time >= self.length {
71                self.running_time = self.length;
72                self.is_running = false;
73            }
74        }
75    }
76
77    pub fn get_volume(&self) -> f32 {
78        lerp(
79            self.running_time.as_nanos() as f32 / self.length.as_nanos() as f32,
80            self.volume_from,
81            self.volume_to,
82        )
83    }
84
85    pub fn is_finished(&self) -> bool { self.running_time >= self.length || !self.is_running }
86}
87
88/// Returns a stopped fader with no running duration
89impl Default for Fader {
90    fn default() -> Self {
91        Self {
92            length: Duration::default(),
93            running_time: Duration::default(),
94            volume_from: 0.0,
95            volume_to: 1.0,
96            is_running: false,
97        }
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn fade_direction_in() {
107        let fader = Fader::fade_in(Duration::from_secs(10), 0.0);
108
109        assert_eq!(fader.direction(), FadeDirection::In);
110    }
111
112    #[test]
113    fn fade_direction_out() {
114        let fader = Fader::fade_out(Duration::from_secs(10), 1.0);
115
116        assert_eq!(fader.direction(), FadeDirection::Out);
117    }
118
119    #[test]
120    fn fade_out_completes() {
121        let mut fader = Fader::fade_out(Duration::from_secs(10), 1.0);
122
123        // Run for the full duration
124        fader.update(Duration::from_secs(10));
125
126        assert_eq!(fader.get_volume(), 0.0);
127        assert!(fader.is_finished());
128    }
129
130    #[test]
131    fn update_target_volume_fading_out_when_currently_above() {
132        let mut fader = Fader::fade_out(Duration::from_secs(20), 1.0);
133
134        // After 0.1s, the fader should still be close to 1.0
135        fader.update(Duration::from_millis(100));
136
137        // Reduce volume to 0.4. We are currently above that.
138        fader.update_target_volume(0.4);
139
140        // The volume should immediately reduce to < 0.4 on the next update
141        fader.update(Duration::from_millis(100));
142
143        assert!(fader.get_volume() < 0.4)
144    }
145
146    #[test]
147    fn update_target_volume_fading_out_when_currently_below() {
148        let mut fader = Fader::fade_out(Duration::from_secs(10), 0.8);
149
150        // After 9s, the fader should be close to 0
151        fader.update(Duration::from_secs(9));
152
153        // Notify of a volume increase to 1.0. We are already far below that.
154        fader.update_target_volume(1.0);
155
156        // The fader should be unaffected by the new value, and continue dropping
157        fader.update(Duration::from_millis(100));
158
159        assert!(fader.get_volume() < 0.2);
160    }
161
162    #[test]
163    fn update_target_volume_fading_in_when_currently_above() {
164        let mut fader = Fader::fade_in(Duration::from_secs(10), 1.0);
165
166        // After 9s, the fader should be close to 1.0
167        fader.update(Duration::from_secs(9));
168
169        // Reduce volume to 0.4. We are currently above that.
170        fader.update_target_volume(0.4);
171
172        // Run out the fader. It's volume should be 0.4
173        fader.update(Duration::from_secs(1));
174
175        assert_eq!(fader.get_volume(), 0.4);
176    }
177
178    #[test]
179    fn update_target_volume_fading_in_when_currently_below() {
180        let mut fader = Fader::fade_in(Duration::from_secs(20), 1.0);
181
182        // After 0.1s, the fader should still be close to 0.0
183        fader.update(Duration::from_millis(100));
184
185        // Reduce volume to 0.4. The volume_to should be reduced accordingly.
186        fader.update_target_volume(0.4);
187
188        assert_eq!(fader.volume_to, 0.4);
189    }
190}