spectrum_analyzer/spectrum.rs
1/*
2MIT License
3
4Copyright (c) 2023 Philipp Schuster
5
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in all
14copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22SOFTWARE.
23*/
24//! Module for the struct [`FrequencySpectrum`].
25
26use self::math::*;
27use crate::error::SpectrumAnalyzerError;
28use crate::frequency::{Frequency, FrequencyValue};
29use crate::scaling::{SpectrumDataStats, SpectrumScalingFunction};
30use alloc::collections::BTreeMap;
31use alloc::vec::Vec;
32
33/// Convenient wrapper around the processed FFT result which describes each
34/// frequency and its value/amplitude from the analyzed samples.
35///
36/// It only contains the frequencies that were desired, e.g., specified via
37/// [`crate::limit::FrequencyLimit`] when [`crate::samples_fft_to_spectrum`]
38/// was called.
39///
40/// This means, the spectrum can cover all data from the DC component (0Hz) to
41/// the Nyquist frequency.
42///
43/// All results are related to the sampling rate provided to the library
44/// function which creates objects of this struct!
45///
46/// This struct can be shared across thread boundaries.
47#[derive(Debug, Default)]
48pub struct FrequencySpectrum {
49 /// All (Frequency, FrequencyValue) data pairs sorted by lowest frequency
50 /// to the highest frequency.Vector is sorted from lowest
51 /// frequency to highest and data is normalized/scaled
52 /// according to all applied scaling functions.
53 data: Vec<(Frequency, FrequencyValue)>,
54 /// Frequency resolution of the examined samples in Hertz,
55 /// i.e the frequency steps between elements in the vector
56 /// inside field [`Self::data`].
57 frequency_resolution: f32,
58 /// Number of samples that were analyzed. Might be bigger than the length
59 /// of `data`, if the spectrum was created with a [`crate::limit::FrequencyLimit`] .
60 samples_len: u32,
61 /// Average value of frequency value/magnitude/amplitude
62 /// corresponding to data in [`FrequencySpectrum::data`].
63 average: FrequencyValue,
64 /// Median value of frequency value/magnitude/amplitude
65 /// corresponding to data in [`FrequencySpectrum::data`].
66 median: FrequencyValue,
67 /// Pair of (frequency, frequency value/magnitude/amplitude) where
68 /// frequency value is **minimal** inside the spectrum.
69 /// Corresponding to data in [`FrequencySpectrum::data`].
70 min: (Frequency, FrequencyValue),
71 /// Pair of (frequency, frequency value/magnitude/amplitude) where
72 /// frequency value is **maximum** inside the spectrum.
73 /// Corresponding to data in [`FrequencySpectrum::data`].
74 max: (Frequency, FrequencyValue),
75}
76
77impl FrequencySpectrum {
78 /// Creates a new object. Calculates several metrics from the data
79 /// in the given vector.
80 ///
81 /// ## Parameters
82 /// * `data` Vector with all ([`Frequency`], [`FrequencyValue`])-tuples
83 /// * `frequency_resolution` Resolution in Hertz. This equals to
84 /// `data[1].0 - data[0].0`.
85 /// * `samples_len` Number of samples. Might be bigger than `data.len()`
86 /// if the spectrum is obtained with a frequency limit.
87 /// * `working_buffer` Mutable buffer with the same length as `data`
88 /// required to calculate certain metrics.
89 #[inline]
90 #[must_use]
91 pub fn new(
92 data: Vec<(Frequency, FrequencyValue)>,
93 frequency_resolution: f32,
94 samples_len: u32,
95 working_buffer: &mut [(Frequency, FrequencyValue)],
96 ) -> Self {
97 debug_assert!(
98 data.len() >= 2,
99 "Input data of length={} for spectrum makes no sense!",
100 data.len()
101 );
102
103 let mut obj = Self {
104 data,
105 frequency_resolution,
106 samples_len,
107 // default/placeholder values
108 average: FrequencyValue::from(-1.0),
109 median: FrequencyValue::from(-1.0),
110 min: (Frequency::from(-1.0), FrequencyValue::from(-1.0)),
111 max: (Frequency::from(-1.0), FrequencyValue::from(-1.0)),
112 };
113
114 // Important to call this once initially.
115 obj.calc_statistics(working_buffer);
116 obj
117 }
118
119 /// Applies the function `scaling_fn` to each element and updates several
120 /// metrics about the spectrum, such as `min` and `max`, afterwards
121 /// accordingly. It ensures that no value is `NaN` or `Infinity`
122 /// (regarding IEEE-754) after `scaling_fn` was applied. Otherwise,
123 /// `SpectrumAnalyzerError::ScalingError` is returned.
124 ///
125 /// ## Parameters
126 /// * `scaling_fn` See [`crate::scaling::SpectrumScalingFunction`].
127 #[inline]
128 pub fn apply_scaling_fn(
129 &mut self,
130 scaling_fn: &SpectrumScalingFunction,
131 working_buffer: &mut [(Frequency, FrequencyValue)],
132 ) -> Result<(), SpectrumAnalyzerError> {
133 // This represents statistics about the spectrum in its current state
134 // which a scaling function may use to scale values.
135 //
136 // On the first invocation of this function, these values represent the
137 // statistics for the unscaled, hence initial, spectrum.
138 let stats = SpectrumDataStats {
139 min: self.min.1.val(),
140 max: self.max.1.val(),
141 average: self.average.val(),
142 median: self.median.val(),
143 // attention! not necessarily `data.len()`!
144 n: self.samples_len as f32,
145 };
146
147 // Iterate over the whole spectrum and scale each frequency value.
148 // I use a regular for loop instead of for_each(), so that I can
149 // early return a result here
150 for (_fr, fr_val) in &mut self.data {
151 // scale value
152 let scaled_val: f32 = scaling_fn(fr_val.val(), &stats);
153
154 // sanity check
155 if scaled_val.is_nan() || scaled_val.is_infinite() {
156 return Err(SpectrumAnalyzerError::ScalingError(
157 fr_val.val(),
158 scaled_val,
159 ));
160 }
161
162 // Update value in spectrum
163 *fr_val = scaled_val.into()
164 }
165
166 self.calc_statistics(working_buffer);
167 Ok(())
168 }
169
170 /// Returns the average frequency value of the spectrum.
171 #[inline]
172 #[must_use]
173 pub const fn average(&self) -> FrequencyValue {
174 self.average
175 }
176
177 /// Returns the median frequency value of the spectrum.
178 #[inline]
179 #[must_use]
180 pub const fn median(&self) -> FrequencyValue {
181 self.median
182 }
183
184 /// Returns the maximum (frequency, frequency value)-pair of the spectrum
185 /// **regarding the frequency value**.
186 #[inline]
187 #[must_use]
188 pub const fn max(&self) -> (Frequency, FrequencyValue) {
189 self.max
190 }
191
192 /// Returns the minimum (frequency, frequency value)-pair of the spectrum
193 /// **regarding the frequency value**.
194 #[inline]
195 #[must_use]
196 pub const fn min(&self) -> (Frequency, FrequencyValue) {
197 self.min
198 }
199
200 /// Returns <code>[FrequencySpectrum::max()].1</code> subtracted by
201 /// <code>[FrequencySpectrum::min()].1</code>, i.e. the range of the
202 /// frequency values (not the frequencies itself, but their
203 /// amplitudes/values).
204 #[inline]
205 #[must_use]
206 pub fn range(&self) -> FrequencyValue {
207 self.max().1 - self.min().1
208 }
209
210 /// Returns the underlying data.
211 #[inline]
212 #[must_use]
213 #[allow(clippy::missing_const_for_fn)] // false positive
214 pub fn data(&self) -> &[(Frequency, FrequencyValue)] {
215 &self.data
216 }
217
218 /// Returns the frequency resolution of this spectrum.
219 #[inline]
220 #[must_use]
221 pub const fn frequency_resolution(&self) -> f32 {
222 self.frequency_resolution
223 }
224
225 /// Returns the number of samples used to obtain this spectrum.
226 #[inline]
227 #[must_use]
228 pub const fn samples_len(&self) -> u32 {
229 self.samples_len
230 }
231
232 /// Getter for the highest frequency that is captured inside this spectrum.
233 /// Shortcut for `spectrum.data()[spectrum.data().len() - 1].0`.
234 /// This corresponds to the [`crate::limit::FrequencyLimit`] of the spectrum.
235 ///
236 /// This method could return the Nyquist frequency, if there was no Frequency
237 /// limit while obtaining the spectrum.
238 #[inline]
239 #[must_use]
240 pub fn max_fr(&self) -> Frequency {
241 self.data[self.data.len() - 1].0
242 }
243
244 /// Getter for the lowest frequency that is captured inside this spectrum.
245 /// Shortcut for `spectrum.data()[0].0`.
246 /// This corresponds to the [`crate::limit::FrequencyLimit`] of the spectrum.
247 ///
248 /// This method could return the DC component, see [`Self::dc_component`].
249 #[inline]
250 #[must_use]
251 pub fn min_fr(&self) -> Frequency {
252 self.data[0].0
253 }
254
255 /// Returns the *DC Component* or also called *DC bias* which corresponds
256 /// to the FFT result at index 0 which corresponds to `0Hz`. This is only
257 /// present if the frequencies were not limited to for example `100 <= f <= 10000`
258 /// when the libraries main function was called.
259 ///
260 /// More information:
261 /// <https://dsp.stackexchange.com/questions/12972/discrete-fourier-transform-what-is-the-dc-term-really>
262 ///
263 /// Excerpt:
264 /// *As far as practical applications go, the DC or 0 Hz term is not particularly useful.
265 /// In many cases it will be close to zero, as most signal processing applications will
266 /// tend to filter out any DC component at the analogue level. In cases where you might
267 /// be interested it can be calculated directly as an average in the usual way, without
268 /// resorting to a DFT/FFT.* - Paul R.
269 #[inline]
270 #[must_use]
271 pub fn dc_component(&self) -> Option<FrequencyValue> {
272 let (maybe_dc_component, dc_value) = &self.data[0];
273 if maybe_dc_component.val() == 0.0 {
274 Some(*dc_value)
275 } else {
276 None
277 }
278 }
279
280 /// Returns the value of the given frequency from the spectrum either exactly or approximated.
281 /// If `search_fr` is not exactly given in the spectrum, i.e. due to the
282 /// [`Self::frequency_resolution`], this function takes the two closest
283 /// neighbors/points (A, B), put a linear function through them and calculates
284 /// the point C in the middle. This is done by the private function
285 /// `calculate_y_coord_between_points`.
286 ///
287 /// ## Panics
288 /// If parameter `search_fr` (frequency) is below the lowest or the maximum
289 /// frequency, this function panics! This is because the user provide
290 /// the min/max frequency when the spectrum is created and knows about it.
291 /// This is similar to an intended "out of bounds"-access.
292 ///
293 /// ## Parameters
294 /// - `search_fr` The frequency of that you want the amplitude/value in the spectrum.
295 ///
296 /// ## Return
297 /// Either exact value of approximated value, determined by [`Self::frequency_resolution`].
298 #[inline]
299 #[must_use]
300 pub fn freq_val_exact(&self, search_fr: f32) -> FrequencyValue {
301 // lowest frequency in the spectrum
302 let (min_fr, min_fr_val) = self.data[0];
303 // highest frequency in the spectrum
304 let (max_fr, max_fr_val) = self.data[self.data.len() - 1];
305
306 // https://docs.rs/float-cmp/0.8.0/float_cmp/
307 let equals_min_fr = float_cmp::approx_eq!(f32, min_fr.val(), search_fr, ulps = 3);
308 let equals_max_fr = float_cmp::approx_eq!(f32, max_fr.val(), search_fr, ulps = 3);
309
310 // Fast return if possible
311 if equals_min_fr {
312 return min_fr_val;
313 }
314 if equals_max_fr {
315 return max_fr_val;
316 }
317 // bounds check
318 if search_fr < min_fr.val() || search_fr > max_fr.val() {
319 panic!(
320 "Frequency {}Hz is out of bounds [{}; {}]!",
321 search_fr,
322 min_fr.val(),
323 max_fr.val()
324 );
325 }
326
327 // We search for Point C (x=search_fr, y=???) between Point A and Point B iteratively.
328 // Point B is always the successor of A.
329
330 for two_points in self.data.iter().as_slice().windows(2) {
331 let point_a = two_points[0];
332 let point_b = two_points[1];
333 let point_a_x = point_a.0.val();
334 let point_a_y = point_a.1;
335 let point_b_x = point_b.0.val();
336 let point_b_y = point_b.1.val();
337
338 // check if we are in the correct window; we are in the correct window
339 // iff point_a_x <= search_fr <= point_b_x
340 if search_fr > point_b_x {
341 continue;
342 }
343
344 return if float_cmp::approx_eq!(f32, point_a_x, search_fr, ulps = 3) {
345 // directly return if possible
346 point_a_y
347 } else {
348 calculate_y_coord_between_points(
349 (point_a_x, point_a_y.val()),
350 (point_b_x, point_b_y),
351 search_fr,
352 )
353 .into()
354 };
355 }
356
357 panic!("Here be dragons");
358 }
359
360 /// Returns the frequency closest to parameter `search_fr` in the spectrum. For example
361 /// if the spectrum looks like this:
362 /// ```text
363 /// Vector: [0] [1] [2] [3]
364 /// Frequency 100 Hz 200 Hz 300 Hz 400 Hz
365 /// Fr Value 0.0 1.0 0.5 0.1
366 /// ```
367 /// then `get_frequency_value_closest(320)` will return `(300.0, 0.5)`.
368 ///
369 /// ## Panics
370 /// If parameter `search_fr` (frequency) is below the lowest or the maximum
371 /// frequency, this function panics!
372 ///
373 /// ## Parameters
374 /// - `search_fr` The frequency of that you want the amplitude/value in the spectrum.
375 ///
376 /// ## Return
377 /// Closest matching point in spectrum, determined by [`Self::frequency_resolution`].
378 #[inline]
379 #[must_use]
380 pub fn freq_val_closest(&self, search_fr: f32) -> (Frequency, FrequencyValue) {
381 // lowest frequency in the spectrum
382 let (min_fr, min_fr_val) = self.data[0];
383 // highest frequency in the spectrum
384 let (max_fr, max_fr_val) = self.data[self.data.len() - 1];
385
386 // https://docs.rs/float-cmp/0.8.0/float_cmp/
387 let equals_min_fr = float_cmp::approx_eq!(f32, min_fr.val(), search_fr, ulps = 3);
388 let equals_max_fr = float_cmp::approx_eq!(f32, max_fr.val(), search_fr, ulps = 3);
389
390 // Fast return if possible
391 if equals_min_fr {
392 return (min_fr, min_fr_val);
393 }
394 if equals_max_fr {
395 return (max_fr, max_fr_val);
396 }
397
398 // bounds check
399 if search_fr < min_fr.val() || search_fr > max_fr.val() {
400 panic!(
401 "Frequency {}Hz is out of bounds [{}; {}]!",
402 search_fr,
403 min_fr.val(),
404 max_fr.val()
405 );
406 }
407
408 for two_points in self.data.iter().as_slice().windows(2) {
409 let point_a = two_points[0];
410 let point_b = two_points[1];
411 let point_a_x = point_a.0;
412 let point_a_y = point_a.1;
413 let point_b_x = point_b.0;
414 let point_b_y = point_b.1;
415
416 // check if we are in the correct window; we are in the correct window
417 // iff point_a_x <= search_fr <= point_b_x
418 if search_fr > point_b_x.val() {
419 continue;
420 }
421
422 return if float_cmp::approx_eq!(f32, point_a_x.val(), search_fr, ulps = 3) {
423 // directly return if possible
424 (point_a_x, point_a_y)
425 } else {
426 // absolute difference
427 let delta_to_a = search_fr - point_a_x.val();
428 // let delta_to_b = point_b_x.val() - search_fr;
429 if delta_to_a / self.frequency_resolution < 0.5 {
430 (point_a_x, point_a_y)
431 } else {
432 (point_b_x, point_b_y)
433 }
434 };
435 }
436
437 panic!("Here be dragons");
438 }
439
440 /// Wrapper around [`Self::freq_val_exact`] that consumes [mel].
441 ///
442 /// [mel]: https://en.wikipedia.org/wiki/Mel_scale
443 #[inline]
444 #[must_use]
445 pub fn mel_val(&self, mel_val: f32) -> FrequencyValue {
446 let hz = mel_to_hertz(mel_val);
447 self.freq_val_exact(hz)
448 }
449
450 /// Returns a [`BTreeMap`] with all value pairs. The key is of type [`u32`]
451 /// because [`f32`] is not [`Ord`].
452 #[inline]
453 #[must_use]
454 pub fn to_map(&self) -> BTreeMap<u32, f32> {
455 self.data
456 .iter()
457 .map(|(fr, fr_val)| (fr.val() as u32, fr_val.val()))
458 .collect()
459 }
460
461 /// Like [`Self::to_map`] but converts the frequency (x-axis) to [mels]. The
462 /// resulting map contains more results in a higher density the higher the
463 /// mel value gets. This comes from the logarithmic transformation from
464 /// hertz to mels.
465 ///
466 /// [mels]: https://en.wikipedia.org/wiki/Mel_scale
467 #[inline]
468 #[must_use]
469 pub fn to_mel_map(&self) -> BTreeMap<u32, f32> {
470 self.data
471 .iter()
472 .map(|(fr, fr_val)| (hertz_to_mel(fr.val()) as u32, fr_val.val()))
473 .collect()
474 }
475
476 /// Calculates the `min`, `max`, `median`, and `average` of the frequency values/magnitudes/
477 /// amplitudes.
478 ///
479 /// To do so, it needs to create a sorted copy of the data.
480 #[inline]
481 fn calc_statistics(&mut self, working_buffer: &mut [(Frequency, FrequencyValue)]) {
482 // We create a copy with all data from `self.data` but we sort it by the
483 // frequency value and not the frequency. This way, we can easily find the
484 // median.
485
486 let data_sorted_by_val = {
487 assert_eq!(
488 self.data.len(),
489 working_buffer.len(),
490 "The working buffer must have the same length as `self.data`!"
491 );
492
493 for (i, pair) in self.data.iter().enumerate() {
494 working_buffer[i] = *pair;
495 }
496 working_buffer.sort_by(|(_l_fr, l_fr_val), (_r_fr, r_fr_val)| {
497 // compare by frequency value, from min to max
498 l_fr_val.cmp(r_fr_val)
499 });
500
501 working_buffer
502 };
503
504 // sum of all frequency values
505 let sum: f32 = data_sorted_by_val
506 .iter()
507 .map(|fr_val| fr_val.1.val())
508 .fold(0.0, |a, b| a + b);
509
510 // average of all frequency values
511 let avg = sum / data_sorted_by_val.len() as f32;
512 let average: FrequencyValue = avg.into();
513
514 // median of all frequency values
515 let median = {
516 let mid = data_sorted_by_val.len() / 2;
517 if data_sorted_by_val.len() % 2 == 0 {
518 let a = data_sorted_by_val[mid - 1].1;
519 let b = data_sorted_by_val[mid].1;
520 (a + b) / 2.0.into()
521 } else {
522 data_sorted_by_val[mid].1
523 }
524 };
525
526 // Because we sorted the vector from lowest to highest value, the
527 // following lines are correct, i.e., we get min/max value with
528 // the corresponding frequency.
529 let min = data_sorted_by_val[0];
530 let max = data_sorted_by_val[data_sorted_by_val.len() - 1];
531
532 // check that I get the comparison right (and not from max to min)
533 debug_assert!(min.1 <= max.1, "min must be <= max");
534
535 self.min = min;
536 self.max = max;
537 self.average = average;
538 self.median = median;
539 }
540}
541
542/*impl FromIterator<(Frequency, FrequencyValue)> for FrequencySpectrum {
543
544 #[inline]
545 fn from_iter<T: IntoIterator<Item=(Frequency, FrequencyValue)>>(iter: T) -> Self {
546 // 1024 is just a guess: most likely 2048 is a common FFT length,
547 // i.e. 1024 results for the frequency spectrum.
548 let mut vec = Vec::with_capacity(1024);
549 for (fr, val) in iter {
550 vec.push((fr, val))
551 }
552
553 FrequencySpectrum::new(vec)
554 }
555}*/
556
557mod math {
558 // use super::*;
559
560 /// Calculates the y coordinate of Point C between two given points A and B
561 /// if the x-coordinate of C is known. It does that by putting a linear function
562 /// through the two given points.
563 ///
564 /// ## Parameters
565 /// - `(x1, y1)` x and y of point A
566 /// - `(x2, y2)` x and y of point B
567 /// - `x_coord` x coordinate of searched point C
568 ///
569 /// ## Return Value
570 /// y coordinate of searched point C
571 #[inline]
572 pub fn calculate_y_coord_between_points(
573 (x1, y1): (f32, f32),
574 (x2, y2): (f32, f32),
575 x_coord: f32,
576 ) -> f32 {
577 // e.g. Points (100, 1.0) and (200, 0.0)
578 // y=f(x)=-0.01x + c
579 // 1.0 = f(100) = -0.01x + c
580 // c = 1.0 + 0.01*100 = 2.0
581 // y=f(180)=-0.01*180 + 2.0
582
583 // gradient, anstieg
584 let slope = (y2 - y1) / (x2 - x1);
585 // calculate c in y=f(x)=slope * x + c
586 let c = y1 - slope * x1;
587
588 slope * x_coord + c
589 }
590
591 /// Converts hertz to [mel](https://en.wikipedia.org/wiki/Mel_scale).
592 pub fn hertz_to_mel(hz: f32) -> f32 {
593 assert!(hz >= 0.0);
594 2595.0 * libm::log10f(1.0 + (hz / 700.0))
595 }
596
597 /// Converts [mel](https://en.wikipedia.org/wiki/Mel_scale) to hertz.
598 pub fn mel_to_hertz(mel: f32) -> f32 {
599 assert!(mel >= 0.0);
600 700.0 * (libm::powf(10.0, mel / 2595.0) - 1.0)
601 }
602
603 #[cfg(test)]
604 mod tests {
605 use super::*;
606
607 #[test]
608 fn test_calculate_y_coord_between_points() {
609 assert_eq!(
610 // expected y coordinate
611 0.5,
612 calculate_y_coord_between_points((100.0, 1.0), (200.0, 0.0), 150.0,),
613 "Must calculate middle point between points by laying a linear function through the two points"
614 );
615 // Must calculate arbitrary point between points by laying a linear function through the
616 // two points.
617 float_cmp::assert_approx_eq!(
618 f32,
619 0.2,
620 calculate_y_coord_between_points((100.0, 1.0), (200.0, 0.0), 180.0,),
621 ulps = 3
622 );
623 }
624
625 #[test]
626 fn test_mel() {
627 float_cmp::assert_approx_eq!(f32, hertz_to_mel(0.0), 0.0, epsilon = 0.1);
628 float_cmp::assert_approx_eq!(f32, hertz_to_mel(500.0), 607.4, epsilon = 0.1);
629 float_cmp::assert_approx_eq!(f32, hertz_to_mel(5000.0), 2363.5, epsilon = 0.1);
630
631 let conv = |hz: f32| mel_to_hertz(hertz_to_mel(hz));
632
633 float_cmp::assert_approx_eq!(f32, conv(0.0), 0.0, epsilon = 0.1);
634 float_cmp::assert_approx_eq!(f32, conv(1000.0), 1000.0, epsilon = 0.1);
635 float_cmp::assert_approx_eq!(f32, conv(10000.0), 10000.0, epsilon = 0.1);
636 }
637 }
638}
639
640#[cfg(test)]
641mod tests {
642 use super::*;
643
644 /// Test if a frequency spectrum can be sent to other threads.
645 #[test]
646 const fn test_impl_send() {
647 #[allow(unused)]
648 // test if this compiles
649 fn consume(s: FrequencySpectrum) {
650 let _: &dyn Send = &s;
651 }
652 }
653
654 #[test]
655 #[allow(clippy::cognitive_complexity)]
656 fn test_spectrum_basic() {
657 let spectrum = vec![
658 (0.0_f32, 5.0_f32),
659 (50.0, 50.0),
660 (100.0, 100.0),
661 (150.0, 150.0),
662 (200.0, 100.0),
663 (250.0, 20.0),
664 (300.0, 0.0),
665 (450.0, 200.0),
666 (500.0, 100.0),
667 ];
668
669 let mut spectrum_vector = spectrum
670 .into_iter()
671 .map(|(fr, val)| (fr.into(), val.into()))
672 .collect::<Vec<(Frequency, FrequencyValue)>>();
673
674 let spectrum = FrequencySpectrum::new(
675 spectrum_vector.clone(),
676 50.0,
677 spectrum_vector.len() as _,
678 &mut spectrum_vector,
679 );
680
681 // test inner vector is ordered
682 {
683 assert_eq!(
684 (0.0.into(), 5.0.into()),
685 spectrum.data()[0],
686 "Vector must be ordered"
687 );
688 assert_eq!(
689 (50.0.into(), 50.0.into()),
690 spectrum.data()[1],
691 "Vector must be ordered"
692 );
693 assert_eq!(
694 (100.0.into(), 100.0.into()),
695 spectrum.data()[2],
696 "Vector must be ordered"
697 );
698 assert_eq!(
699 (150.0.into(), 150.0.into()),
700 spectrum.data()[3],
701 "Vector must be ordered"
702 );
703 assert_eq!(
704 (200.0.into(), 100.0.into()),
705 spectrum.data()[4],
706 "Vector must be ordered"
707 );
708 assert_eq!(
709 (250.0.into(), 20.0.into()),
710 spectrum.data()[5],
711 "Vector must be ordered"
712 );
713 assert_eq!(
714 (300.0.into(), 0.0.into()),
715 spectrum.data()[6],
716 "Vector must be ordered"
717 );
718 assert_eq!(
719 (450.0.into(), 200.0.into()),
720 spectrum.data()[7],
721 "Vector must be ordered"
722 );
723 assert_eq!(
724 (500.0.into(), 100.0.into()),
725 spectrum.data()[8],
726 "Vector must be ordered"
727 );
728 }
729
730 // test DC component getter
731 assert_eq!(
732 Some(5.0.into()),
733 spectrum.dc_component(),
734 "Spectrum must contain DC component"
735 );
736
737 // test getters
738 {
739 assert_eq!(0.0, spectrum.min_fr().val(), "min_fr() must work");
740 assert_eq!(500.0, spectrum.max_fr().val(), "max_fr() must work");
741 assert_eq!(
742 (300.0.into(), 0.0.into()),
743 spectrum.min(),
744 "min() must work"
745 );
746 assert_eq!(
747 (450.0.into(), 200.0.into()),
748 spectrum.max(),
749 "max() must work"
750 );
751 assert_eq!(200.0 - 0.0, spectrum.range().val(), "range() must work");
752 assert_eq!(80.55556, spectrum.average().val(), "average() must work");
753 assert_eq!(100.0, spectrum.median().val(), "median() must work");
754 assert_eq!(
755 50.0,
756 spectrum.frequency_resolution(),
757 "frequency resolution must be returned"
758 );
759 }
760
761 // test get frequency exact
762 {
763 assert_eq!(5.0, spectrum.freq_val_exact(0.0).val(),);
764 assert_eq!(50.0, spectrum.freq_val_exact(50.0).val(),);
765 assert_eq!(150.0, spectrum.freq_val_exact(150.0).val(),);
766 assert_eq!(100.0, spectrum.freq_val_exact(200.0).val(),);
767 assert_eq!(20.0, spectrum.freq_val_exact(250.0).val(),);
768 assert_eq!(0.0, spectrum.freq_val_exact(300.0).val(),);
769 assert_eq!(100.0, spectrum.freq_val_exact(375.0).val(),);
770 assert_eq!(200.0, spectrum.freq_val_exact(450.0).val(),);
771 }
772
773 // test get frequency closest
774 {
775 assert_eq!((0.0.into(), 5.0.into()), spectrum.freq_val_closest(0.0),);
776 assert_eq!((50.0.into(), 50.0.into()), spectrum.freq_val_closest(50.0),);
777 assert_eq!(
778 (450.0.into(), 200.0.into()),
779 spectrum.freq_val_closest(450.0),
780 );
781 assert_eq!(
782 (450.0.into(), 200.0.into()),
783 spectrum.freq_val_closest(448.0),
784 );
785 assert_eq!(
786 (450.0.into(), 200.0.into()),
787 spectrum.freq_val_closest(400.0),
788 );
789 assert_eq!((50.0.into(), 50.0.into()), spectrum.freq_val_closest(47.3),);
790 assert_eq!((50.0.into(), 50.0.into()), spectrum.freq_val_closest(51.3),);
791 }
792 }
793
794 #[test]
795 #[should_panic]
796 fn test_spectrum_get_frequency_value_exact_panic_below_min() {
797 let mut spectrum_vector = vec![
798 (0.0_f32.into(), 5.0_f32.into()),
799 (450.0.into(), 200.0.into()),
800 ];
801
802 let spectrum = FrequencySpectrum::new(
803 spectrum_vector.clone(),
804 50.0,
805 spectrum_vector.len() as _,
806 &mut spectrum_vector,
807 );
808
809 // -1 not included, expect panic
810 spectrum.freq_val_exact(-1.0).val();
811 }
812
813 #[test]
814 #[should_panic]
815 fn test_spectrum_get_frequency_value_exact_panic_below_max() {
816 let mut spectrum_vector = vec![
817 (0.0_f32.into(), 5.0_f32.into()),
818 (450.0.into(), 200.0.into()),
819 ];
820
821 let spectrum = FrequencySpectrum::new(
822 spectrum_vector.clone(),
823 50.0,
824 spectrum_vector.len() as _,
825 &mut spectrum_vector,
826 );
827
828 // 451 not included, expect panic
829 spectrum.freq_val_exact(451.0).val();
830 }
831
832 #[test]
833 #[should_panic]
834 fn test_spectrum_get_frequency_value_closest_panic_below_min() {
835 let mut spectrum_vector = vec![
836 (0.0_f32.into(), 5.0_f32.into()),
837 (450.0.into(), 200.0.into()),
838 ];
839
840 let spectrum = FrequencySpectrum::new(
841 spectrum_vector.clone(),
842 50.0,
843 spectrum_vector.len() as _,
844 &mut spectrum_vector,
845 );
846 // -1 not included, expect panic
847 let _ = spectrum.freq_val_closest(-1.0);
848 }
849
850 #[test]
851 #[should_panic]
852 fn test_spectrum_get_frequency_value_closest_panic_below_max() {
853 let mut spectrum_vector = vec![
854 (0.0_f32.into(), 5.0_f32.into()),
855 (450.0.into(), 200.0.into()),
856 ];
857
858 let spectrum = FrequencySpectrum::new(
859 spectrum_vector.clone(),
860 50.0,
861 spectrum_vector.len() as _,
862 &mut spectrum_vector,
863 );
864
865 // 451 not included, expect panic
866 let _ = spectrum.freq_val_closest(451.0);
867 }
868
869 #[test]
870 fn test_nan_safety() {
871 let mut spectrum_vector: Vec<(Frequency, FrequencyValue)> =
872 vec![(0.0.into(), 0.0.into()); 8];
873
874 let spectrum = FrequencySpectrum::new(
875 spectrum_vector.clone(),
876 // not important here, any value
877 50.0,
878 spectrum_vector.len() as _,
879 &mut spectrum_vector,
880 );
881
882 assert_ne!(
883 f32::NAN,
884 spectrum.min().1.val(),
885 "NaN is not valid, must be 0.0!"
886 );
887 assert_ne!(
888 f32::NAN,
889 spectrum.max().1.val(),
890 "NaN is not valid, must be 0.0!"
891 );
892 assert_ne!(
893 f32::NAN,
894 spectrum.average().val(),
895 "NaN is not valid, must be 0.0!"
896 );
897 assert_ne!(
898 f32::NAN,
899 spectrum.median().val(),
900 "NaN is not valid, must be 0.0!"
901 );
902
903 assert_ne!(
904 f32::INFINITY,
905 spectrum.min().1.val(),
906 "INFINITY is not valid, must be 0.0!"
907 );
908 assert_ne!(
909 f32::INFINITY,
910 spectrum.max().1.val(),
911 "INFINITY is not valid, must be 0.0!"
912 );
913 assert_ne!(
914 f32::INFINITY,
915 spectrum.average().val(),
916 "INFINITY is not valid, must be 0.0!"
917 );
918 assert_ne!(
919 f32::INFINITY,
920 spectrum.median().val(),
921 "INFINITY is not valid, must be 0.0!"
922 );
923 }
924
925 #[test]
926 fn test_no_dc_component() {
927 let mut spectrum_vector: Vec<(Frequency, FrequencyValue)> =
928 vec![(150.0.into(), 150.0.into()), (200.0.into(), 100.0.into())];
929
930 let spectrum = FrequencySpectrum::new(
931 spectrum_vector.clone(),
932 50.0,
933 spectrum_vector.len() as _,
934 &mut spectrum_vector,
935 );
936
937 assert!(
938 spectrum.dc_component().is_none(),
939 "This spectrum should not contain a DC component!"
940 )
941 }
942
943 #[test]
944 fn test_max() {
945 let maximum: (Frequency, FrequencyValue) = (34.991455.into(), 86.791145.into());
946 let mut spectrum_vector: Vec<(Frequency, FrequencyValue)> = vec![
947 (2.6916504.into(), 22.81816.into()),
948 (5.383301.into(), 2.1004658.into()),
949 (8.074951.into(), 8.704016.into()),
950 (10.766602.into(), 3.4043686.into()),
951 (13.458252.into(), 8.649045.into()),
952 (16.149902.into(), 9.210494.into()),
953 (18.841553.into(), 14.937911.into()),
954 (21.533203.into(), 5.1524887.into()),
955 (24.224854.into(), 20.706167.into()),
956 (26.916504.into(), 8.359295.into()),
957 (29.608154.into(), 3.7514696.into()),
958 (32.299805.into(), 15.109907.into()),
959 maximum,
960 (37.683105.into(), 52.140736.into()),
961 (40.374756.into(), 24.108875.into()),
962 (43.066406.into(), 11.070151.into()),
963 (45.758057.into(), 10.569871.into()),
964 (48.449707.into(), 6.1969466.into()),
965 (51.141357.into(), 16.722788.into()),
966 (53.833008.into(), 8.93011.into()),
967 ];
968
969 let spectrum = FrequencySpectrum::new(
970 spectrum_vector.clone(),
971 44100.0,
972 spectrum_vector.len() as _,
973 &mut spectrum_vector,
974 );
975
976 assert_eq!(
977 spectrum.max(),
978 maximum,
979 "Should return the maximum frequency value!"
980 )
981 }
982
983 #[test]
984 fn test_mel_getter() {
985 let mut spectrum_vector = vec![
986 (0.0_f32.into(), 5.0_f32.into()),
987 (450.0.into(), 200.0.into()),
988 ];
989
990 let spectrum = FrequencySpectrum::new(
991 spectrum_vector.clone(),
992 50.0,
993 spectrum_vector.len() as _,
994 &mut spectrum_vector,
995 );
996 let _ = spectrum.mel_val(450.0);
997 }
998}