1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
//! 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, Eq, 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: 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]
    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]
    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]
    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);
    }
}