use_signal_normalize/
lib.rs1#![forbid(unsafe_code)]
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum NormalizeError {
23 EmptySamples,
24 InvalidSample,
25 InvalidRange,
26}
27
28fn validated_samples(samples: &[f64]) -> Result<&[f64], NormalizeError> {
29 if samples.is_empty() {
30 return Err(NormalizeError::EmptySamples);
31 }
32
33 if samples.iter().any(|sample| !sample.is_finite()) {
34 return Err(NormalizeError::InvalidSample);
35 }
36
37 Ok(samples)
38}
39
40fn sample_min_max(samples: &[f64]) -> (f64, f64) {
41 let mut values = samples.iter().copied();
42 let first = values.next().unwrap();
43
44 values.fold((first, first), |(min, max), sample| {
45 (min.min(sample), max.max(sample))
46 })
47}
48
49pub fn normalize_peak(samples: &[f64]) -> Option<Vec<f64>> {
50 let samples = validated_samples(samples).ok()?;
51 let peak = samples
52 .iter()
53 .map(|sample| sample.abs())
54 .fold(0.0, f64::max);
55
56 if peak == 0.0 {
57 return Some(samples.to_vec());
58 }
59
60 Some(samples.iter().map(|sample| sample / peak).collect())
61}
62
63pub fn normalize_range(samples: &[f64], min: f64, max: f64) -> Result<Vec<f64>, NormalizeError> {
64 let samples = validated_samples(samples)?;
65
66 if !min.is_finite() || !max.is_finite() || min >= max {
67 return Err(NormalizeError::InvalidRange);
68 }
69
70 let (source_min, source_max) = sample_min_max(samples);
71 let source_span = source_max - source_min;
72
73 if source_span == 0.0 {
74 let midpoint = (min + max) / 2.0;
75 return Ok(vec![midpoint; samples.len()]);
76 }
77
78 let target_span = max - min;
79
80 Ok(samples
81 .iter()
82 .map(|sample| min + ((sample - source_min) / source_span) * target_span)
83 .collect())
84}
85
86pub fn center_signal(samples: &[f64]) -> Option<Vec<f64>> {
87 let samples = validated_samples(samples).ok()?;
88 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
89
90 Some(samples.iter().map(|sample| sample - mean).collect())
91}
92
93pub fn remove_dc_offset(samples: &[f64]) -> Option<Vec<f64>> {
94 center_signal(samples)
95}
96
97#[cfg(test)]
98mod tests {
99 use super::{NormalizeError, center_signal, normalize_peak, normalize_range, remove_dc_offset};
100
101 #[test]
102 fn normalizes_peak_values() {
103 assert_eq!(
104 normalize_peak(&[-2.0, 0.0, 1.0]),
105 Some(vec![-1.0, 0.0, 0.5])
106 );
107 assert_eq!(normalize_peak(&[0.0, 0.0]), Some(vec![0.0, 0.0]));
108 }
109
110 #[test]
111 fn normalizes_into_target_ranges() {
112 assert_eq!(
113 normalize_range(&[0.0, 1.0], -1.0, 1.0).unwrap(),
114 vec![-1.0, 1.0]
115 );
116 assert_eq!(
117 normalize_range(&[5.0, 5.0], -1.0, 1.0).unwrap(),
118 vec![0.0, 0.0]
119 );
120 }
121
122 #[test]
123 fn centers_signals_and_removes_dc_offset() {
124 assert_eq!(center_signal(&[1.0, 2.0, 3.0]), Some(vec![-1.0, 0.0, 1.0]));
125 assert_eq!(
126 remove_dc_offset(&[1.0, 2.0, 3.0]),
127 Some(vec![-1.0, 0.0, 1.0])
128 );
129 }
130
131 #[test]
132 fn rejects_empty_and_invalid_samples() {
133 assert_eq!(normalize_peak(&[]), None);
134 assert_eq!(center_signal(&[1.0, f64::NAN]), None);
135 assert_eq!(
136 normalize_range(&[], -1.0, 1.0),
137 Err(NormalizeError::EmptySamples)
138 );
139 assert_eq!(
140 normalize_range(&[1.0, f64::INFINITY], -1.0, 1.0),
141 Err(NormalizeError::InvalidSample)
142 );
143 }
144
145 #[test]
146 fn rejects_invalid_target_ranges() {
147 assert_eq!(
148 normalize_range(&[0.0, 1.0], 1.0, 1.0),
149 Err(NormalizeError::InvalidRange)
150 );
151 assert_eq!(
152 normalize_range(&[0.0, 1.0], f64::NAN, 1.0),
153 Err(NormalizeError::InvalidRange)
154 );
155 }
156}