1use crate::signal::{
2 data::RealData,
3 math::{db_to_gain, gain_to_db},
4};
5
6impl RealData {
7 pub fn max_per_channel(&self) -> Vec<(f64, f64)> {
8 self.y_data()
9 .outer_iter()
10 .map(|channel| {
11 let (max_index, max_value) = channel
12 .iter()
13 .enumerate()
14 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
15 .expect("RealData channels always have consistent lengths");
16
17 (self.x_axis().get(max_index), *max_value)
18 })
19 .collect()
20 }
21
22 pub fn max_overall(&self) -> (f64, f64) {
23 *self
24 .max_per_channel()
25 .iter()
26 .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
27 .expect("RealData must contain at least one sample")
28 }
29
30 pub fn max_abs_per_channel(&self) -> Vec<(f64, f64)> {
31 self.y_data()
32 .outer_iter()
33 .map(|channel| {
34 let (max_index, max_abs_value) = channel
35 .iter()
36 .enumerate()
37 .map(|(idx, &value)| (idx, value.abs()))
38 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
39 .expect("RealData channels always have consistent lengths");
40
41 (self.x_axis().get(max_index), max_abs_value)
42 })
43 .collect()
44 }
45
46 pub fn max_abs_overall(&self) -> (f64, f64) {
47 *self
48 .max_abs_per_channel()
49 .iter()
50 .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
51 .expect("RealData must contain at least one sample")
52 }
53
54 pub fn min_max_per_channel(&self) -> Vec<((f64, f64), (f64, f64))> {
55 self.y_data()
56 .outer_iter()
57 .map(|channel| {
58 let (min_index, min_value) = channel
59 .iter()
60 .enumerate()
61 .min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
62 .expect("RealData channels always have consistent lengths");
63 let (max_index, max_value) = channel
64 .iter()
65 .enumerate()
66 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
67 .expect("RealData channels always have consistent lengths");
68
69 (
70 (self.x_axis().get(min_index), *min_value),
71 (self.x_axis().get(max_index), *max_value),
72 )
73 })
74 .collect()
75 }
76
77 pub fn min_max_overall(&self) -> ((f64, f64), (f64, f64)) {
78 let per_channel = self.min_max_per_channel();
79
80 let min = per_channel
81 .iter()
82 .map(|&(min, _)| min)
83 .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
84 .expect("RealData must contain at least one sample");
85 let max = per_channel
86 .iter()
87 .map(|&(_, max)| max)
88 .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
89 .expect("RealData must contain at least one sample");
90
91 (min, max)
92 }
93
94 pub fn to_decibels(&self) -> RealData {
95 RealData::from_axis(self.x_axis().clone(), self.y_data().mapv(gain_to_db))
96 .expect("x/y data originated from RealData")
97 }
98
99 pub fn to_gain(&self) -> RealData {
100 RealData::from_axis(self.x_axis().clone(), self.y_data().mapv(db_to_gain))
101 .expect("x/y data originated from RealData")
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use approx::assert_abs_diff_eq;
108 use ndarray::{arr2, aview2};
109
110 use crate::signal::data::RealData;
111
112 #[test]
113 fn extrema_helpers() {
114 let data = RealData::new(
115 ndarray::arr1(&[0.0, 1.0, 2.0, 3.0, 4.0]),
116 arr2(&[[0.0, 1.0, 5.0, -11.0, 1.0], [0.0, -10.0, 0.0, 6.0, 1.0]]),
117 )
118 .unwrap();
119
120 assert_eq!(data.max_per_channel(), vec![(2.0, 5.0), (3.0, 6.0)]);
121 assert_eq!(data.max_overall(), (3.0, 6.0));
122 assert_eq!(data.max_abs_per_channel(), vec![(3.0, 11.0), (1.0, 10.0)]);
123 assert_eq!(data.max_abs_overall(), (3.0, 11.0));
124 assert_eq!(
125 data.min_max_per_channel(),
126 vec![((3.0, -11.0), (2.0, 5.0)), ((1.0, -10.0), (3.0, 6.0))]
127 );
128 assert_eq!(data.min_max_overall(), ((3.0, -11.0), (3.0, 6.0)));
129 }
130
131 #[test]
132 fn db_gain_conversion_helpers() {
133 let gain = RealData::new(ndarray::arr1(&[1.0, 2.0]), arr2(&[[1.0, 10.0]])).unwrap();
134 let db = gain.to_decibels();
135 assert_abs_diff_eq!(db.channel(0)[0], 0.0, epsilon = 1e-12);
136 assert_abs_diff_eq!(db.channel(0)[1], 20.0, epsilon = 1e-12);
137
138 let roundtrip = db.to_gain();
139 assert!(
140 roundtrip
141 .y_data()
142 .abs_diff_eq(&aview2(&[[1.0, 10.0]]), 1e-12)
143 );
144 }
145}