Skip to main content

audio_lab/signal/ops/
time.rs

1use crate::signal::{TimeSignal, math::db_to_gain};
2
3impl TimeSignal {
4    pub fn energy_per_channel(&self) -> Vec<f64> {
5        self.channel_iter()
6            .map(|channel| channel.iter().map(|sample| sample * sample).sum())
7            .collect()
8    }
9
10    pub fn power_per_channel(&self) -> Vec<f64> {
11        let num_time_steps = self.num_time_steps();
12        if num_time_steps == 0 {
13            return vec![0.0; self.num_channels()];
14        }
15
16        self.energy_per_channel()
17            .into_iter()
18            .map(|energy| energy / num_time_steps as f64)
19            .collect()
20    }
21
22    pub fn rms_per_channel(&self) -> Vec<f64> {
23        self.power_per_channel()
24            .into_iter()
25            .map(f64::sqrt)
26            .collect()
27    }
28
29    pub fn normalize_peak(&mut self, peak_level: f64) {
30        let max = self.iter().map(|s| s.abs()).fold(0.0_f64, f64::max);
31        if max == 0.0 {
32            return;
33        }
34        let gain = peak_level / max;
35        self.iter_mut().for_each(|s| *s *= gain);
36    }
37
38    pub fn normalize_peak_db(&mut self, peak_db: f64) {
39        self.normalize_peak(db_to_gain(peak_db));
40    }
41}
42
43#[cfg(test)]
44mod tests {
45    use approx::assert_abs_diff_eq;
46    use ndarray::arr2;
47
48    use crate::signal::TimeSignal;
49
50    #[test]
51    fn energy_power_and_rms_are_computed_per_channel() {
52        let signal = TimeSignal::new(arr2(&[[1.0, -1.0, 2.0], [0.0, 3.0, 4.0]]), 48_000.0).unwrap();
53
54        assert_eq!(signal.energy_per_channel().len(), 2);
55        assert_abs_diff_eq!(signal.energy_per_channel()[0], 6.0, epsilon = 1e-12);
56        assert_abs_diff_eq!(signal.energy_per_channel()[1], 25.0, epsilon = 1e-12);
57        assert_abs_diff_eq!(signal.power_per_channel()[0], 2.0, epsilon = 1e-12);
58        assert_abs_diff_eq!(signal.power_per_channel()[1], 25.0 / 3.0, epsilon = 1e-12);
59        assert_abs_diff_eq!(signal.rms_per_channel()[0], 2.0_f64.sqrt(), epsilon = 1e-12);
60        assert_abs_diff_eq!(
61            signal.rms_per_channel()[1],
62            (25.0_f64 / 3.0).sqrt(),
63            epsilon = 1e-12
64        );
65    }
66
67    #[test]
68    fn power_and_rms_return_zero_for_empty_signals() {
69        let signal = TimeSignal::zeros(2, 0, 48_000.0).unwrap();
70
71        assert_eq!(signal.energy_per_channel(), vec![0.0, 0.0]);
72        assert_eq!(signal.power_per_channel(), vec![0.0, 0.0]);
73        assert_eq!(signal.rms_per_channel(), vec![0.0, 0.0]);
74    }
75
76    #[test]
77    fn normalize_scales_to_requested_peak_linear() {
78        let mut signal = TimeSignal::new(arr2(&[[0.25, -0.5, 1.0]]), 48_000.0).unwrap();
79        signal.normalize_peak(0.5);
80
81        assert_abs_diff_eq!(signal.channel(0)[0], 0.125, epsilon = 1e-12);
82        assert_abs_diff_eq!(signal.channel(0)[1], -0.25, epsilon = 1e-12);
83        assert_abs_diff_eq!(signal.channel(0)[2], 0.5, epsilon = 1e-12);
84    }
85
86    #[test]
87    fn normalize_scales_to_requested_peak_db() {
88        let mut signal = TimeSignal::new(arr2(&[[0.25, -0.5, 1.0]]), 48_000.0).unwrap();
89        signal.normalize_peak_db(-6.0);
90
91        let expected_peak = 10.0_f64.powf(-6.0 / 20.0);
92        assert_abs_diff_eq!(signal.channel(0)[0], 0.25 * expected_peak, epsilon = 1e-12);
93        assert_abs_diff_eq!(signal.channel(0)[1], -0.5 * expected_peak, epsilon = 1e-12);
94        assert_abs_diff_eq!(signal.channel(0)[2], expected_peak, epsilon = 1e-12);
95    }
96}