use crate::error::SpectrumAnalyzerError;
use crate::frequency::{Frequency, FrequencyValue};
use crate::scaling::{SpectrumDataStats, SpectrumScalingFunction};
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use core::cell::{Cell, Ref, RefCell};
#[derive(Debug, Default)]
pub struct FrequencySpectrum {
data: RefCell<Vec<(Frequency, FrequencyValue)>>,
frequency_resolution: f32,
samples_len: u32,
average: Cell<FrequencyValue>,
median: Cell<FrequencyValue>,
min: Cell<(Frequency, FrequencyValue)>,
max: Cell<(Frequency, FrequencyValue)>,
}
impl FrequencySpectrum {
#[inline(always)]
pub fn new(
data: Vec<(Frequency, FrequencyValue)>,
frequency_resolution: f32,
samples_len: u32,
) -> Self {
debug_assert!(
data.len() >= 2,
"Input data of length={} for spectrum makes no sense!",
data.len()
);
let obj = Self {
data: RefCell::new(data),
frequency_resolution,
samples_len,
average: Cell::new(FrequencyValue::from(-1.0)),
median: Cell::new(FrequencyValue::from(-1.0)),
min: Cell::new((Frequency::from(-1.0), FrequencyValue::from(-1.0))),
max: Cell::new((Frequency::from(-1.0), FrequencyValue::from(-1.0))),
};
obj.calc_statistics();
obj
}
#[inline(always)]
pub fn apply_scaling_fn(
&self,
scaling_fn: SpectrumScalingFunction,
) -> Result<(), SpectrumAnalyzerError> {
{
let mut data = self.data.borrow_mut();
let stats = SpectrumDataStats {
min: self.min.get().1.val(),
max: self.max.get().1.val(),
average: self.average.get().val(),
median: self.median.get().val(),
n: self.samples_len as f32,
};
for (_fr, fr_val) in &mut *data {
let scaled_val: f32 = scaling_fn(fr_val.val(), &stats);
if scaled_val.is_nan() || scaled_val.is_infinite() {
return Err(SpectrumAnalyzerError::ScalingError(
fr_val.val(),
scaled_val,
));
}
*fr_val = scaled_val.into()
}
}
self.calc_statistics();
Ok(())
}
#[inline(always)]
pub fn average(&self) -> FrequencyValue {
self.average.get()
}
#[inline(always)]
pub fn median(&self) -> FrequencyValue {
self.median.get()
}
#[inline(always)]
pub fn max(&self) -> (Frequency, FrequencyValue) {
self.max.get()
}
#[inline(always)]
pub fn min(&self) -> (Frequency, FrequencyValue) {
self.min.get()
}
#[inline(always)]
pub fn range(&self) -> FrequencyValue {
self.max().1 - self.min().1
}
#[inline(always)]
pub fn data(&self) -> Ref<Vec<(Frequency, FrequencyValue)>> {
self.data.borrow()
}
#[inline(always)]
pub const fn frequency_resolution(&self) -> f32 {
self.frequency_resolution
}
#[inline(always)]
pub const fn samples_len(&self) -> u32 {
self.samples_len
}
#[inline(always)]
pub fn max_fr(&self) -> Frequency {
let data = self.data.borrow();
data[data.len() - 1].0
}
#[inline(always)]
pub fn min_fr(&self) -> Frequency {
let data = self.data.borrow();
data[0].0
}
#[inline(always)]
pub fn dc_component(&self) -> Option<FrequencyValue> {
let data = self.data.borrow();
let (maybe_dc_component, dc_value) = &data[0];
if maybe_dc_component.val() == 0.0 {
Some(*dc_value)
} else {
None
}
}
#[inline(always)]
pub fn freq_val_exact(&self, search_fr: f32) -> FrequencyValue {
let data = self.data.borrow();
let (min_fr, min_fr_val) = data[0];
let (max_fr, max_fr_val) = data[data.len() - 1];
let equals_min_fr = float_cmp::approx_eq!(f32, min_fr.val(), search_fr, ulps = 3);
let equals_max_fr = float_cmp::approx_eq!(f32, max_fr.val(), search_fr, ulps = 3);
if equals_min_fr {
return min_fr_val;
}
if equals_max_fr {
return max_fr_val;
}
if search_fr < min_fr.val() || search_fr > max_fr.val() {
panic!(
"Frequency {}Hz is out of bounds [{}; {}]!",
search_fr,
min_fr.val(),
max_fr.val()
);
}
for two_points in data.iter().as_slice().windows(2) {
let point_a = two_points[0];
let point_b = two_points[1];
let point_a_x = point_a.0.val();
let point_a_y = point_a.1;
let point_b_x = point_b.0.val();
let point_b_y = point_b.1.val();
if search_fr > point_b_x {
continue;
}
return if float_cmp::approx_eq!(f32, point_a_x, search_fr, ulps = 3) {
point_a_y
} else {
calculate_y_coord_between_points(
(point_a_x, point_a_y.val()),
(point_b_x, point_b_y),
search_fr,
)
.into()
};
}
panic!("Here be dragons");
}
#[inline(always)]
pub fn freq_val_closest(&self, search_fr: f32) -> (Frequency, FrequencyValue) {
let data = self.data.borrow();
let (min_fr, min_fr_val) = data[0];
let (max_fr, max_fr_val) = data[data.len() - 1];
let equals_min_fr = float_cmp::approx_eq!(f32, min_fr.val(), search_fr, ulps = 3);
let equals_max_fr = float_cmp::approx_eq!(f32, max_fr.val(), search_fr, ulps = 3);
if equals_min_fr {
return (min_fr, min_fr_val);
}
if equals_max_fr {
return (max_fr, max_fr_val);
}
if search_fr < min_fr.val() || search_fr > max_fr.val() {
panic!(
"Frequency {}Hz is out of bounds [{}; {}]!",
search_fr,
min_fr.val(),
max_fr.val()
);
}
for two_points in data.iter().as_slice().windows(2) {
let point_a = two_points[0];
let point_b = two_points[1];
let point_a_x = point_a.0;
let point_a_y = point_a.1;
let point_b_x = point_b.0;
let point_b_y = point_b.1;
if search_fr > point_b_x.val() {
continue;
}
return if float_cmp::approx_eq!(f32, point_a_x.val(), search_fr, ulps = 3) {
(point_a_x, point_a_y)
} else {
let delta_to_a = search_fr - point_a_x.val();
if delta_to_a / self.frequency_resolution < 0.5 {
(point_a_x, point_a_y)
} else {
(point_b_x, point_b_y)
}
};
}
panic!("Here be dragons");
}
#[inline(always)]
pub fn to_map(&self, scale_fn: Option<&dyn Fn(f32) -> u32>) -> BTreeMap<u32, f32> {
self.data
.borrow()
.iter()
.map(|(fr, fr_val)| (fr.val(), fr_val.val()))
.map(|(fr, fr_val)| {
(
if let Some(fnc) = scale_fn {
(fnc)(fr)
} else {
fr as u32
},
fr_val,
)
})
.collect()
}
#[inline(always)]
fn calc_statistics(&self) {
let mut data_sorted = self.data.borrow().clone();
data_sorted.sort_by(|(_l_fr, l_fr_val), (_r_fr, r_fr_val)| {
l_fr_val.cmp(r_fr_val)
});
let sum: f32 = data_sorted
.iter()
.map(|fr_val| fr_val.1.val())
.fold(0.0, |a, b| a + b);
let avg = sum / data_sorted.len() as f32;
let average: FrequencyValue = avg.into();
let median = {
let a = data_sorted[data_sorted.len() / 2 - 1].1;
let b = data_sorted[data_sorted.len() / 2].1;
(a + b) / 2.0.into()
};
let min = data_sorted[0];
let max = data_sorted[data_sorted.len() - 1];
debug_assert!(min.1 <= max.1, "min must be <= max");
self.min.replace(min);
self.max.replace(max);
self.average.replace(average);
self.median.replace(median);
}
}
#[inline(always)]
fn calculate_y_coord_between_points(
(x1, y1): (f32, f32),
(x2, y2): (f32, f32),
x_coord: f32,
) -> f32 {
let slope = (y2 - y1) / (x2 - x1);
let c = y1 - slope * x1;
slope * x_coord + c
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculate_y_coord_between_points() {
assert_eq!(
0.5,
calculate_y_coord_between_points(
(100.0, 1.0),
(200.0, 0.0),
150.0,
),
"Must calculate middle point between points by laying a linear function through the two points"
);
assert!(
float_cmp::approx_eq!(
f32,
0.2,
calculate_y_coord_between_points(
(100.0, 1.0),
(200.0, 0.0),
180.0,
),
ulps = 3
),
"Must calculate arbitrary point between points by laying a linear function through the two points"
);
}
#[test]
#[allow(clippy::cognitive_complexity)]
fn test_spectrum_basic() {
let spectrum = vec![
(0.0_f32, 5.0_f32),
(50.0, 50.0),
(100.0, 100.0),
(150.0, 150.0),
(200.0, 100.0),
(250.0, 20.0),
(300.0, 0.0),
(450.0, 200.0),
];
let spectrum = spectrum
.into_iter()
.map(|(fr, val)| (fr.into(), val.into()))
.collect::<Vec<(Frequency, FrequencyValue)>>();
let spectrum_len = spectrum.len() as u32;
let spectrum = FrequencySpectrum::new(spectrum, 50.0, spectrum_len);
{
assert_eq!(
(0.0.into(), 5.0.into()),
spectrum.data()[0],
"Vector must be ordered"
);
assert_eq!(
(50.0.into(), 50.0.into()),
spectrum.data()[1],
"Vector must be ordered"
);
assert_eq!(
(100.0.into(), 100.0.into()),
spectrum.data()[2],
"Vector must be ordered"
);
assert_eq!(
(150.0.into(), 150.0.into()),
spectrum.data()[3],
"Vector must be ordered"
);
assert_eq!(
(200.0.into(), 100.0.into()),
spectrum.data()[4],
"Vector must be ordered"
);
assert_eq!(
(250.0.into(), 20.0.into()),
spectrum.data()[5],
"Vector must be ordered"
);
assert_eq!(
(300.0.into(), 0.0.into()),
spectrum.data()[6],
"Vector must be ordered"
);
assert_eq!(
(450.0.into(), 200.0.into()),
spectrum.data()[7],
"Vector must be ordered"
);
}
assert!(
spectrum.dc_component().is_some(),
"Spectrum must contain DC component"
);
assert_eq!(
5.0,
spectrum.dc_component().unwrap().val(),
"Spectrum must contain DC component"
);
{
assert_eq!(0.0, spectrum.min_fr().val(), "min_fr() must work");
assert_eq!(450.0, spectrum.max_fr().val(), "max_fr() must work");
assert_eq!(
(300.0.into(), 0.0.into()),
spectrum.min(),
"min() must work"
);
assert_eq!(
(450.0.into(), 200.0.into()),
spectrum.max(),
"max() must work"
);
assert_eq!(200.0 - 0.0, spectrum.range().val(), "range() must work");
assert_eq!(78.125, spectrum.average().val(), "average() must work");
assert_eq!(
(50 + 100) as f32 / 2.0,
spectrum.median().val(),
"median() must work"
);
assert_eq!(
50.0,
spectrum.frequency_resolution(),
"frequency resolution must be returned"
);
}
{
assert_eq!(5.0, spectrum.freq_val_exact(0.0).val(),);
assert_eq!(50.0, spectrum.freq_val_exact(50.0).val(),);
assert_eq!(150.0, spectrum.freq_val_exact(150.0).val(),);
assert_eq!(100.0, spectrum.freq_val_exact(200.0).val(),);
assert_eq!(20.0, spectrum.freq_val_exact(250.0).val(),);
assert_eq!(0.0, spectrum.freq_val_exact(300.0).val(),);
assert_eq!(100.0, spectrum.freq_val_exact(375.0).val(),);
assert_eq!(200.0, spectrum.freq_val_exact(450.0).val(),);
}
{
assert_eq!((0.0.into(), 5.0.into()), spectrum.freq_val_closest(0.0),);
assert_eq!((50.0.into(), 50.0.into()), spectrum.freq_val_closest(50.0),);
assert_eq!(
(450.0.into(), 200.0.into()),
spectrum.freq_val_closest(450.0),
);
assert_eq!(
(450.0.into(), 200.0.into()),
spectrum.freq_val_closest(448.0),
);
assert_eq!(
(450.0.into(), 200.0.into()),
spectrum.freq_val_closest(400.0),
);
assert_eq!((50.0.into(), 50.0.into()), spectrum.freq_val_closest(47.3),);
assert_eq!((50.0.into(), 50.0.into()), spectrum.freq_val_closest(51.3),);
}
}
#[test]
#[should_panic]
fn test_spectrum_get_frequency_value_exact_panic_below_min() {
let spectrum_vector = vec![(0.0_f32, 5.0_f32), (450.0, 200.0)];
let spectrum = spectrum_vector
.into_iter()
.map(|(fr, val)| (fr.into(), val.into()))
.collect::<Vec<(Frequency, FrequencyValue)>>();
let spectrum_len = spectrum.len() as u32;
let spectrum = FrequencySpectrum::new(spectrum, 50.0, spectrum_len);
spectrum.freq_val_exact(-1.0).val();
}
#[test]
#[should_panic]
fn test_spectrum_get_frequency_value_exact_panic_below_max() {
let spectrum_vector = vec![(0.0_f32, 5.0_f32), (450.0, 200.0)];
let spectrum = spectrum_vector
.into_iter()
.map(|(fr, val)| (fr.into(), val.into()))
.collect::<Vec<(Frequency, FrequencyValue)>>();
let spectrum_len = spectrum.len() as u32;
let spectrum = FrequencySpectrum::new(spectrum, 50.0, spectrum_len);
spectrum.freq_val_exact(451.0).val();
}
#[test]
#[should_panic]
fn test_spectrum_get_frequency_value_closest_panic_below_min() {
let spectrum_vector = vec![(0.0_f32, 5.0_f32), (450.0, 200.0)];
let spectrum = spectrum_vector
.into_iter()
.map(|(fr, val)| (fr.into(), val.into()))
.collect::<Vec<(Frequency, FrequencyValue)>>();
let spectrum_len = spectrum.len() as u32;
let spectrum = FrequencySpectrum::new(spectrum, 50.0, spectrum_len);
spectrum.freq_val_closest(-1.0);
}
#[test]
#[should_panic]
fn test_spectrum_get_frequency_value_closest_panic_below_max() {
let spectrum_vector = vec![(0.0_f32, 5.0_f32), (450.0, 200.0)];
let spectrum = spectrum_vector
.into_iter()
.map(|(fr, val)| (fr.into(), val.into()))
.collect::<Vec<(Frequency, FrequencyValue)>>();
let spectrum_len = spectrum.len() as u32;
let spectrum = FrequencySpectrum::new(spectrum, 50.0, spectrum_len);
spectrum.freq_val_closest(451.0);
}
#[test]
fn test_nan_safety() {
let spectrum_vector: Vec<(Frequency, FrequencyValue)> = vec![(0.0.into(), 0.0.into()); 8];
let spectrum_len = spectrum_vector.len() as u32;
let spectrum = FrequencySpectrum::new(
spectrum_vector,
50.0,
spectrum_len,
);
assert_ne!(
f32::NAN,
spectrum.min().1.val(),
"NaN is not valid, must be 0.0!"
);
assert_ne!(
f32::NAN,
spectrum.max().1.val(),
"NaN is not valid, must be 0.0!"
);
assert_ne!(
f32::NAN,
spectrum.average().val(),
"NaN is not valid, must be 0.0!"
);
assert_ne!(
f32::NAN,
spectrum.median().val(),
"NaN is not valid, must be 0.0!"
);
assert_ne!(
f32::INFINITY,
spectrum.min().1.val(),
"INFINITY is not valid, must be 0.0!"
);
assert_ne!(
f32::INFINITY,
spectrum.max().1.val(),
"INFINITY is not valid, must be 0.0!"
);
assert_ne!(
f32::INFINITY,
spectrum.average().val(),
"INFINITY is not valid, must be 0.0!"
);
assert_ne!(
f32::INFINITY,
spectrum.median().val(),
"INFINITY is not valid, must be 0.0!"
);
}
#[test]
fn test_no_dc_component() {
let spectrum: Vec<(Frequency, FrequencyValue)> =
vec![(150.0.into(), 150.0.into()), (200.0.into(), 100.0.into())];
let spectrum_len = spectrum.len() as u32;
let spectrum = FrequencySpectrum::new(spectrum, 50.0, spectrum_len);
assert!(
spectrum.dc_component().is_none(),
"This spectrum should not contain a DC component!"
)
}
}