use self::math::*;
use crate::error::SpectrumAnalyzerError;
use crate::frequency::{Frequency, FrequencyValue};
use crate::scaling::{SpectrumDataStats, SpectrumScalingFunction};
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
#[derive(Debug, Default)]
pub struct FrequencySpectrum {
data: Vec<(Frequency, FrequencyValue)>,
frequency_resolution: f32,
samples_len: u32,
average: FrequencyValue,
median: FrequencyValue,
min: (Frequency, FrequencyValue),
max: (Frequency, FrequencyValue),
}
impl FrequencySpectrum {
#[inline]
#[must_use]
pub fn new(
data: Vec<(Frequency, FrequencyValue)>,
frequency_resolution: f32,
samples_len: u32,
working_buffer: &mut [(Frequency, FrequencyValue)],
) -> Self {
debug_assert!(
data.len() >= 2,
"Input data of length={} for spectrum makes no sense!",
data.len()
);
let mut obj = Self {
data,
frequency_resolution,
samples_len,
average: FrequencyValue::from(-1.0),
median: FrequencyValue::from(-1.0),
min: (Frequency::from(-1.0), FrequencyValue::from(-1.0)),
max: (Frequency::from(-1.0), FrequencyValue::from(-1.0)),
};
obj.calc_statistics(working_buffer);
obj
}
#[inline]
pub fn apply_scaling_fn(
&mut self,
scaling_fn: &SpectrumScalingFunction,
working_buffer: &mut [(Frequency, FrequencyValue)],
) -> Result<(), SpectrumAnalyzerError> {
let stats = SpectrumDataStats {
min: self.min.1.val(),
max: self.max.1.val(),
average: self.average.val(),
median: self.median.val(),
n: self.samples_len as f32,
};
for (_fr, fr_val) in &mut self.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(working_buffer);
Ok(())
}
#[inline]
#[must_use]
pub const fn average(&self) -> FrequencyValue {
self.average
}
#[inline]
#[must_use]
pub const fn median(&self) -> FrequencyValue {
self.median
}
#[inline]
#[must_use]
pub const fn max(&self) -> (Frequency, FrequencyValue) {
self.max
}
#[inline]
#[must_use]
pub const fn min(&self) -> (Frequency, FrequencyValue) {
self.min
}
#[inline]
#[must_use]
pub fn range(&self) -> FrequencyValue {
self.max().1 - self.min().1
}
#[inline]
#[must_use]
#[allow(clippy::missing_const_for_fn)] pub fn data(&self) -> &[(Frequency, FrequencyValue)] {
&self.data
}
#[inline]
#[must_use]
pub const fn frequency_resolution(&self) -> f32 {
self.frequency_resolution
}
#[inline]
#[must_use]
pub const fn samples_len(&self) -> u32 {
self.samples_len
}
#[inline]
#[must_use]
pub fn max_fr(&self) -> Frequency {
self.data[self.data.len() - 1].0
}
#[inline]
#[must_use]
pub fn min_fr(&self) -> Frequency {
self.data[0].0
}
#[inline]
#[must_use]
pub fn dc_component(&self) -> Option<FrequencyValue> {
let (maybe_dc_component, dc_value) = &self.data[0];
if maybe_dc_component.val() == 0.0 {
Some(*dc_value)
} else {
None
}
}
#[inline]
#[must_use]
pub fn freq_val_exact(&self, search_fr: f32) -> FrequencyValue {
let (min_fr, min_fr_val) = self.data[0];
let (max_fr, max_fr_val) = self.data[self.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 self.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]
#[must_use]
pub fn freq_val_closest(&self, search_fr: f32) -> (Frequency, FrequencyValue) {
let (min_fr, min_fr_val) = self.data[0];
let (max_fr, max_fr_val) = self.data[self.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 self.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]
#[must_use]
pub fn mel_val(&self, mel_val: f32) -> FrequencyValue {
let hz = mel_to_hertz(mel_val);
self.freq_val_exact(hz)
}
#[inline]
#[must_use]
pub fn to_map(&self) -> BTreeMap<u32, f32> {
self.data
.iter()
.map(|(fr, fr_val)| (fr.val() as u32, fr_val.val()))
.collect()
}
#[inline]
#[must_use]
pub fn to_mel_map(&self) -> BTreeMap<u32, f32> {
self.data
.iter()
.map(|(fr, fr_val)| (hertz_to_mel(fr.val()) as u32, fr_val.val()))
.collect()
}
#[inline]
fn calc_statistics(&mut self, working_buffer: &mut [(Frequency, FrequencyValue)]) {
let data_sorted_by_val = {
assert_eq!(
self.data.len(),
working_buffer.len(),
"The working buffer must have the same length as `self.data`!"
);
for (i, pair) in self.data.iter().enumerate() {
working_buffer[i] = *pair;
}
working_buffer.sort_by(|(_l_fr, l_fr_val), (_r_fr, r_fr_val)| {
l_fr_val.cmp(r_fr_val)
});
working_buffer
};
let sum: f32 = data_sorted_by_val
.iter()
.map(|fr_val| fr_val.1.val())
.fold(0.0, |a, b| a + b);
let avg = sum / data_sorted_by_val.len() as f32;
let average: FrequencyValue = avg.into();
let median = {
let a = data_sorted_by_val[data_sorted_by_val.len() / 2 - 1].1;
let b = data_sorted_by_val[data_sorted_by_val.len() / 2].1;
(a + b) / 2.0.into()
};
let min = data_sorted_by_val[0];
let max = data_sorted_by_val[data_sorted_by_val.len() - 1];
debug_assert!(min.1 <= max.1, "min must be <= max");
self.min = min;
self.max = max;
self.average = average;
self.median = median;
}
}
mod math {
#[inline]
pub 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
}
pub fn hertz_to_mel(hz: f32) -> f32 {
assert!(hz >= 0.0);
2595.0 * libm::log10f(1.0 + (hz / 700.0))
}
pub fn mel_to_hertz(mel: f32) -> f32 {
assert!(mel >= 0.0);
700.0 * (libm::powf(10.0, mel / 2595.0) - 1.0)
}
#[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"
);
float_cmp::assert_approx_eq!(
f32,
0.2,
calculate_y_coord_between_points((100.0, 1.0), (200.0, 0.0), 180.0,),
ulps = 3
);
}
#[test]
fn test_mel() {
float_cmp::assert_approx_eq!(f32, hertz_to_mel(0.0), 0.0, epsilon = 0.1);
float_cmp::assert_approx_eq!(f32, hertz_to_mel(500.0), 607.4, epsilon = 0.1);
float_cmp::assert_approx_eq!(f32, hertz_to_mel(5000.0), 2363.5, epsilon = 0.1);
let conv = |hz: f32| mel_to_hertz(hertz_to_mel(hz));
float_cmp::assert_approx_eq!(f32, conv(0.0), 0.0, epsilon = 0.1);
float_cmp::assert_approx_eq!(f32, conv(1000.0), 1000.0, epsilon = 0.1);
float_cmp::assert_approx_eq!(f32, conv(10000.0), 10000.0, epsilon = 0.1);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
const fn test_impl_send() {
#[allow(unused)]
fn consume(s: FrequencySpectrum) {
let _: &dyn Send = &s;
}
}
#[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),
(500.0, 100.0),
];
let mut spectrum_vector = spectrum
.into_iter()
.map(|(fr, val)| (fr.into(), val.into()))
.collect::<Vec<(Frequency, FrequencyValue)>>();
let spectrum = FrequencySpectrum::new(
spectrum_vector.clone(),
50.0,
spectrum_vector.len() as _,
&mut spectrum_vector,
);
{
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_eq!(
(500.0.into(), 100.0.into()),
spectrum.data()[8],
"Vector must be ordered"
);
}
assert_eq!(
Some(5.0.into()),
spectrum.dc_component(),
"Spectrum must contain DC component"
);
{
assert_eq!(0.0, spectrum.min_fr().val(), "min_fr() must work");
assert_eq!(500.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!(80.55556, 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 mut spectrum_vector = vec![
(0.0_f32.into(), 5.0_f32.into()),
(450.0.into(), 200.0.into()),
];
let spectrum = FrequencySpectrum::new(
spectrum_vector.clone(),
50.0,
spectrum_vector.len() as _,
&mut spectrum_vector,
);
spectrum.freq_val_exact(-1.0).val();
}
#[test]
#[should_panic]
fn test_spectrum_get_frequency_value_exact_panic_below_max() {
let mut spectrum_vector = vec![
(0.0_f32.into(), 5.0_f32.into()),
(450.0.into(), 200.0.into()),
];
let spectrum = FrequencySpectrum::new(
spectrum_vector.clone(),
50.0,
spectrum_vector.len() as _,
&mut spectrum_vector,
);
spectrum.freq_val_exact(451.0).val();
}
#[test]
#[should_panic]
fn test_spectrum_get_frequency_value_closest_panic_below_min() {
let mut spectrum_vector = vec![
(0.0_f32.into(), 5.0_f32.into()),
(450.0.into(), 200.0.into()),
];
let spectrum = FrequencySpectrum::new(
spectrum_vector.clone(),
50.0,
spectrum_vector.len() as _,
&mut spectrum_vector,
);
let _ = spectrum.freq_val_closest(-1.0);
}
#[test]
#[should_panic]
fn test_spectrum_get_frequency_value_closest_panic_below_max() {
let mut spectrum_vector = vec![
(0.0_f32.into(), 5.0_f32.into()),
(450.0.into(), 200.0.into()),
];
let spectrum = FrequencySpectrum::new(
spectrum_vector.clone(),
50.0,
spectrum_vector.len() as _,
&mut spectrum_vector,
);
let _ = spectrum.freq_val_closest(451.0);
}
#[test]
fn test_nan_safety() {
let mut spectrum_vector: Vec<(Frequency, FrequencyValue)> =
vec![(0.0.into(), 0.0.into()); 8];
let spectrum = FrequencySpectrum::new(
spectrum_vector.clone(),
50.0,
spectrum_vector.len() as _,
&mut spectrum_vector,
);
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 mut spectrum_vector: Vec<(Frequency, FrequencyValue)> =
vec![(150.0.into(), 150.0.into()), (200.0.into(), 100.0.into())];
let spectrum = FrequencySpectrum::new(
spectrum_vector.clone(),
50.0,
spectrum_vector.len() as _,
&mut spectrum_vector,
);
assert!(
spectrum.dc_component().is_none(),
"This spectrum should not contain a DC component!"
)
}
#[test]
fn test_max() {
let maximum: (Frequency, FrequencyValue) = (34.991455.into(), 86.791145.into());
let mut spectrum_vector: Vec<(Frequency, FrequencyValue)> = vec![
(2.6916504.into(), 22.81816.into()),
(5.383301.into(), 2.1004658.into()),
(8.074951.into(), 8.704016.into()),
(10.766602.into(), 3.4043686.into()),
(13.458252.into(), 8.649045.into()),
(16.149902.into(), 9.210494.into()),
(18.841553.into(), 14.937911.into()),
(21.533203.into(), 5.1524887.into()),
(24.224854.into(), 20.706167.into()),
(26.916504.into(), 8.359295.into()),
(29.608154.into(), 3.7514696.into()),
(32.299805.into(), 15.109907.into()),
maximum,
(37.683105.into(), 52.140736.into()),
(40.374756.into(), 24.108875.into()),
(43.066406.into(), 11.070151.into()),
(45.758057.into(), 10.569871.into()),
(48.449707.into(), 6.1969466.into()),
(51.141357.into(), 16.722788.into()),
(53.833008.into(), 8.93011.into()),
];
let spectrum = FrequencySpectrum::new(
spectrum_vector.clone(),
44100.0,
spectrum_vector.len() as _,
&mut spectrum_vector,
);
assert_eq!(
spectrum.max(),
maximum,
"Should return the maximum frequency value!"
)
}
#[test]
fn test_mel_getter() {
let mut spectrum_vector = vec![
(0.0_f32.into(), 5.0_f32.into()),
(450.0.into(), 200.0.into()),
];
let spectrum = FrequencySpectrum::new(
spectrum_vector.clone(),
50.0,
spectrum_vector.len() as _,
&mut spectrum_vector,
);
let _ = spectrum.mel_val(450.0);
}
}