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::vec::Vec;
31
32/// Convenient wrapper around the processed FFT result.
33///
34/// This is the result produced by [`samples_fft_to_spectrum`]. It describes
35/// each frequency and its corresponding value (magnitude) from the analyzed
36/// samples, according to the provided input parameters. The data is
37/// scaled/normalized according to the optionally applied scaling function.
38///
39/// Unless frequencies were explicitly excluded, the spectrum covers the full
40/// range from the DC component (0 Hz) up to the Nyquist frequency with the
41/// frequency resolution derived from the input data.
42///
43/// [`samples_fft_to_spectrum`]: crate::samples_fft_to_spectrum
44#[derive(Debug)]
45pub struct FrequencySpectrum {
46 /// All (Frequency, FrequencyValue) data pairs sorted from lowest to highest
47 /// frequency (in Hz).
48 ///
49 /// The frequency bin refers to the original frequency bin and not
50 /// necessarily to the element in the vector, if there was a
51 /// [`FrequencyLimit`].
52 ///
53 /// The data is normalized/scaled according to all applied scaling
54 /// functions.
55 ///
56 /// [`FrequencyLimit`]: crate::limit::FrequencyLimit
57 data: Vec<(Frequency, FrequencyValue)>,
58 /// Frequency resolution of the examined samples in Hertz, i.e. the
59 /// frequency steps between elements in [`Self::data()`].
60 frequency_resolution: Frequency,
61 /// Number of samples that were analyzed. Might be higher than the length
62 /// of `data`, if the spectrum was created with a [`FrequencyLimit`].
63 ///
64 /// [`FrequencyLimit`]: crate::limit::FrequencyLimit
65 samples_len: u32,
66 /// Average frequency value corresponding to data in
67 /// [`FrequencySpectrum::data()`].
68 average: FrequencyValue,
69 /// Minimal element in [`FrequencySpectrum::data()`] regarding the
70 /// frequency value.
71 min: (Frequency, FrequencyValue),
72 /// Maximal element in [`FrequencySpectrum::data()`] regarding the
73 /// frequency value.
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 #[inline]
88 #[must_use]
89 pub(crate) fn new(
90 data: Vec<(Frequency, FrequencyValue)>,
91 frequency_resolution: Frequency,
92 samples_len: u32,
93 ) -> Self {
94 debug_assert!(
95 data.len() >= 2,
96 "Input data of length={} for spectrum makes no sense!",
97 data.len()
98 );
99
100 let mut obj = Self {
101 data,
102 frequency_resolution,
103 samples_len,
104 // placeholders; calc_statistics() below fills them in
105 average: FrequencyValue::default(),
106 min: (Frequency::default(), FrequencyValue::default()),
107 max: (Frequency::default(), FrequencyValue::default()),
108 };
109
110 // Important to call this once initially.
111 obj.calc_statistics();
112 obj
113 }
114
115 /// Applies the function `scaling_fn` to each element and updates several
116 /// metrics about the spectrum, such as `min` and `max`, afterwards
117 /// accordingly. It ensures that no value is `NaN` or `Infinity`
118 /// (regarding IEEE-754) after `scaling_fn` was applied. Otherwise,
119 /// `SpectrumAnalyzerError::ScalingError` is returned.
120 ///
121 /// ## Parameters
122 /// * `scaling_fn` See [`SpectrumScalingFunction`].
123 #[inline]
124 pub fn apply_scaling_fn(
125 &mut self,
126 scaling_fn: &SpectrumScalingFunction,
127 ) -> Result<(), SpectrumAnalyzerError> {
128 // This represents statistics about the spectrum in its current state
129 // which a scaling function may use to scale values.
130 //
131 // On the first invocation of this function, these values represent the
132 // statistics for the unscaled, hence initial, spectrum.
133 let stats = SpectrumDataStats {
134 min: self.min.1,
135 max: self.max.1,
136 average: self.average,
137 // attention! not necessarily `data.len()`!
138 n: self.samples_len as f32,
139 };
140
141 for (_fr, fr_val) in &mut self.data {
142 // scale value
143 let scaled_val: f32 = scaling_fn(fr_val.val(), &stats);
144
145 // sanity check
146 if scaled_val.is_nan() || scaled_val.is_infinite() {
147 return Err(SpectrumAnalyzerError::ScalingError(
148 fr_val.val(),
149 scaled_val,
150 ));
151 }
152
153 // Update value in spectrum
154 *fr_val = scaled_val.into()
155 }
156
157 self.calc_statistics();
158 Ok(())
159 }
160
161 /// Returns the average frequency value of the spectrum.
162 #[inline]
163 #[must_use]
164 pub const fn average(&self) -> FrequencyValue {
165 self.average
166 }
167
168 /// Returns the maximum (frequency, frequency value)-pair of the spectrum
169 /// **regarding the frequency value**.
170 #[inline]
171 #[must_use]
172 pub const fn max(&self) -> (Frequency, FrequencyValue) {
173 self.max
174 }
175
176 /// Returns the minimum (frequency, frequency value)-pair of the spectrum
177 /// **regarding the frequency value**.
178 #[inline]
179 #[must_use]
180 pub const fn min(&self) -> (Frequency, FrequencyValue) {
181 self.min
182 }
183
184 /// Returns <code>[FrequencySpectrum::max()].1</code> subtracted by
185 /// <code>[FrequencySpectrum::min()].1</code>, i.e. the range of the
186 /// frequency values (not the frequencies itself, but their values).
187 #[inline]
188 #[must_use]
189 pub fn range(&self) -> FrequencyValue {
190 self.max().1 - self.min().1
191 }
192
193 /// Returns the underlying sorted data.
194 #[inline]
195 #[must_use]
196 #[allow(clippy::missing_const_for_fn)] // false positive
197 pub fn data(&self) -> &[(Frequency, FrequencyValue)] {
198 debug_assert!(self.data.is_sorted());
199 &self.data
200 }
201
202 /// Returns the frequency resolution of this spectrum.
203 #[inline]
204 #[must_use]
205 pub const fn frequency_resolution(&self) -> Frequency {
206 self.frequency_resolution
207 }
208
209 /// Returns the number of samples used to obtain this spectrum.
210 #[inline]
211 #[must_use]
212 pub const fn samples_len(&self) -> u32 {
213 self.samples_len
214 }
215
216 /// Getter for the highest frequency that is captured inside this spectrum.
217 /// Shortcut for `spectrum.data()[spectrum.data().len() - 1].0`.
218 /// This corresponds to the [`crate::limit::FrequencyLimit`] of the spectrum.
219 ///
220 /// This method could return the Nyquist frequency, if there was no Frequency
221 /// limit while obtaining the spectrum.
222 #[inline]
223 #[must_use]
224 pub fn max_fr(&self) -> Frequency {
225 self.data[self.data.len() - 1].0
226 }
227
228 /// Getter for the lowest frequency that is captured inside this spectrum.
229 /// Shortcut for `spectrum.data()[0].0`.
230 /// This corresponds to the [`crate::limit::FrequencyLimit`] of the spectrum.
231 ///
232 /// This method could return the DC component, see [`Self::dc_component`].
233 #[inline]
234 #[must_use]
235 pub fn min_fr(&self) -> Frequency {
236 self.data[0].0
237 }
238
239 /// Returns the *DC Component* or also called *DC bias* which corresponds
240 /// to the FFT result at index 0 which corresponds to `0Hz`. This is only
241 /// present if the frequencies were not limited to for example `100 <= f <= 10000`
242 /// when the libraries main function was called.
243 ///
244 /// Note that the unscaled value is `N` times the mean of the (windowed)
245 /// samples, not the mean itself. See [`crate::samples_fft_to_spectrum`].
246 ///
247 /// More information:
248 /// <https://dsp.stackexchange.com/questions/12972/discrete-fourier-transform-what-is-the-dc-term-really>
249 ///
250 /// Excerpt:
251 /// *As far as practical applications go, the DC or 0 Hz term is not particularly useful.
252 /// In many cases it will be close to zero, as most signal processing applications will
253 /// tend to filter out any DC component at the analogue level. In cases where you might
254 /// be interested it can be calculated directly as an average in the usual way, without
255 /// resorting to a DFT/FFT.* - Paul R.
256 #[inline]
257 #[must_use]
258 pub fn dc_component(&self) -> Option<FrequencyValue> {
259 let (maybe_dc_component, dc_value) = &self.data[0];
260 if *maybe_dc_component == 0.0 {
261 Some(*dc_value)
262 } else {
263 None
264 }
265 }
266
267 /// Returns the value of the given frequency from the spectrum either
268 /// exactly or approximated.
269 ///
270 /// If the value is out of bounds, the function returns `None`.
271 ///
272 /// If `search_fr` is not exactly given in the spectrum, i.e. due to the
273 /// [`Self::frequency_resolution`], this function takes the two closest
274 /// neighbors/points (A, B), put a linear function through them and calculates
275 /// the point C in the middle. This is done by the private function
276 /// `calculate_y_coord_between_points`.
277 ///
278 /// The interpolated value only follows the shape of the spectrum. It is
279 /// not the value a sine wave of exactly `search_fr` would have, because
280 /// such a sine wave leaks into the neighboring bins.
281 ///
282 /// ## Parameters
283 /// - `search_fr` The frequency of that you want the value in the spectrum.
284 #[inline]
285 #[must_use]
286 pub fn freq_val_exact(&self, search_fr: f32) -> Option<FrequencyValue> {
287 // lowest frequency in the spectrum
288 let (min_fr, min_fr_val) = self.data[0];
289 // highest frequency in the spectrum
290 let (max_fr, max_fr_val) = self.data[self.data.len() - 1];
291
292 // https://docs.rs/float-cmp/0.8.0/float_cmp/
293 let equals_min_fr = float_cmp::approx_eq!(f32, min_fr.val(), search_fr, ulps = 3);
294 let equals_max_fr = float_cmp::approx_eq!(f32, max_fr.val(), search_fr, ulps = 3);
295
296 // Fast return if possible
297 if equals_min_fr {
298 return Some(min_fr_val);
299 }
300 if equals_max_fr {
301 return Some(max_fr_val);
302 }
303 // bounds check; a NaN search frequency fails every comparison and
304 // therefore lands here as well
305 let in_bounds = search_fr >= min_fr && search_fr <= max_fr;
306 if !in_bounds {
307 return None;
308 }
309
310 // We search for Point C (x=search_fr, y=???) between Point A and Point B iteratively.
311 // Point B is always the successor of A.
312
313 for two_points in self.data.iter().as_slice().windows(2) {
314 let point_a = two_points[0];
315 let point_b = two_points[1];
316 let point_a_x = point_a.0.val();
317 let point_a_y = point_a.1;
318 let point_b_x = point_b.0.val();
319 let point_b_y = point_b.1.val();
320
321 // check if we are in the correct window; we are in the correct window
322 // iff point_a_x <= search_fr <= point_b_x
323 if search_fr > point_b_x {
324 continue;
325 }
326
327 let fr_val = if float_cmp::approx_eq!(f32, point_a_x, search_fr, ulps = 3) {
328 // directly return if possible
329 point_a_y
330 } else {
331 calculate_y_coord_between_points(
332 (point_a_x, point_a_y.val()),
333 (point_b_x, point_b_y),
334 search_fr,
335 )
336 .into()
337 };
338 return Some(fr_val);
339 }
340
341 unreachable!("the loop always terminates");
342 }
343
344 /// Returns the frequency closest to parameter `search_fr` in the spectrum.
345 ///
346 /// If the value is out of bounds, the function returns `None`.
347 ///
348 /// For example, if the spectrum looks like this:
349 /// ```text
350 /// Vector: [0] [1] [2] [3]
351 /// Frequency 100 Hz 200 Hz 300 Hz 400 Hz
352 /// Fr Value 0.0 1.0 0.5 0.1
353 /// ```
354 /// then `get_frequency_value_closest(320)` will return `(300.0, 0.5)`.
355 ///
356 /// ## Parameters
357 /// - `search_fr` The frequency of that you want the value in the spectrum.
358 #[inline]
359 #[must_use]
360 pub fn freq_val_closest(&self, search_fr: f32) -> Option<(Frequency, FrequencyValue)> {
361 // lowest frequency in the spectrum
362 let (min_fr, min_fr_val) = self.data[0];
363 // highest frequency in the spectrum
364 let (max_fr, max_fr_val) = self.data[self.data.len() - 1];
365
366 // https://docs.rs/float-cmp/0.8.0/float_cmp/
367 let equals_min_fr = float_cmp::approx_eq!(f32, min_fr.val(), search_fr, ulps = 3);
368 let equals_max_fr = float_cmp::approx_eq!(f32, max_fr.val(), search_fr, ulps = 3);
369
370 // Fast return if possible
371 if equals_min_fr {
372 return Some((min_fr, min_fr_val));
373 }
374 if equals_max_fr {
375 return Some((max_fr, max_fr_val));
376 }
377
378 // bounds check; a NaN search frequency fails every comparison and
379 // therefore lands here as well
380 let in_bounds = search_fr >= min_fr && search_fr <= max_fr;
381 if !in_bounds {
382 return None;
383 }
384
385 for two_points in self.data.iter().as_slice().windows(2) {
386 let point_a = two_points[0];
387 let point_b = two_points[1];
388 let point_a_x = point_a.0;
389 let point_a_y = point_a.1;
390 let point_b_x = point_b.0;
391 let point_b_y = point_b.1;
392
393 // check if we are in the correct window; we are in the correct window
394 // iff point_a_x <= search_fr <= point_b_x
395 if search_fr > point_b_x {
396 continue;
397 }
398
399 let pair = if float_cmp::approx_eq!(f32, point_a_x.val(), search_fr, ulps = 3) {
400 // directly return if possible
401 (point_a_x, point_a_y)
402 } else {
403 // absolute difference
404 let delta_to_a = search_fr - point_a_x;
405 if delta_to_a / self.frequency_resolution < 0.5 {
406 (point_a_x, point_a_y)
407 } else {
408 (point_b_x, point_b_y)
409 }
410 };
411 return Some(pair);
412 }
413
414 unreachable!("the loop always terminates");
415 }
416
417 /// Returns a sorted [`Vec`] with all value pairs as `f32`.
418 #[inline]
419 #[must_use]
420 pub fn to_vec(&self) -> Vec<(f32, f32)> {
421 debug_assert!(self.data.is_sorted());
422 self.data
423 .iter()
424 .map(|(fr, fr_val)| (fr.val(), fr_val.val()))
425 .collect()
426 }
427
428 /// Calculates the `min`, `max`, and `average` of the frequency values.
429 #[inline]
430 fn calc_statistics(&mut self) {
431 // Single pass over the data: min, max, and sum (for the average).
432 //
433 // On equal frequency values, min keeps the first and max the last
434 // occurrence, so results are deterministic.
435 let mut min = self.data[0];
436 let mut max = self.data[0];
437 let mut sum = 0.0;
438 for pair in &self.data {
439 if pair.1 < min.1 {
440 min = *pair;
441 }
442 if pair.1 >= max.1 {
443 max = *pair;
444 }
445 sum += pair.1.val();
446 }
447
448 // average of all frequency values
449 let average: FrequencyValue = (sum / self.data.len() as f32).into();
450
451 // check that I get the comparison right (and not from max to min)
452 debug_assert!(min.1 <= max.1, "min must be <= max");
453
454 self.min = min;
455 self.max = max;
456 self.average = average;
457 }
458}
459
460/*impl FromIterator<(Frequency, FrequencyValue)> for FrequencySpectrum {
461
462 #[inline]
463 fn from_iter<T: IntoIterator<Item=(Frequency, FrequencyValue)>>(iter: T) -> Self {
464 // 1024 is just a guess: most likely 2048 is a common FFT length,
465 // i.e. 1024 results for the frequency spectrum.
466 let mut vec = Vec::with_capacity(1024);
467 for (fr, val) in iter {
468 vec.push((fr, val))
469 }
470
471 FrequencySpectrum::new(vec)
472 }
473}*/
474
475mod math {
476 // use super::*;
477
478 /// Calculates the y coordinate of Point C between two given points A and B
479 /// if the x-coordinate of C is known. It does that by putting a linear function
480 /// through the two given points.
481 ///
482 /// ## Parameters
483 /// - `(x1, y1)` x and y of point A
484 /// - `(x2, y2)` x and y of point B
485 /// - `x_coord` x coordinate of searched point C
486 ///
487 /// ## Return Value
488 /// y coordinate of searched point C
489 #[inline]
490 pub fn calculate_y_coord_between_points(
491 (x1, y1): (f32, f32),
492 (x2, y2): (f32, f32),
493 x_coord: f32,
494 ) -> f32 {
495 // e.g. Points (100, 1.0) and (200, 0.0)
496 // y=f(x)=-0.01x + c
497 // 1.0 = f(100) = -0.01x + c
498 // c = 1.0 + 0.01*100 = 2.0
499 // y=f(180)=-0.01*180 + 2.0
500
501 // gradient, anstieg
502 let slope = (y2 - y1) / (x2 - x1);
503 // calculate c in y=f(x)=slope * x + c
504 let c = y1 - slope * x1;
505
506 slope * x_coord + c
507 }
508
509 #[cfg(test)]
510 mod tests {
511 use super::*;
512
513 #[test]
514 fn test_calculate_y_coord_between_points() {
515 assert_eq!(
516 // expected y coordinate
517 0.5,
518 calculate_y_coord_between_points((100.0, 1.0), (200.0, 0.0), 150.0,),
519 "Must calculate middle point between points by laying a linear function through the two points"
520 );
521 // Must calculate arbitrary point between points by laying a linear function through the
522 // two points.
523 float_cmp::assert_approx_eq!(
524 f32,
525 0.2,
526 calculate_y_coord_between_points((100.0, 1.0), (200.0, 0.0), 180.0,),
527 ulps = 3
528 );
529 }
530 }
531}
532
533#[cfg(test)]
534mod tests {
535 use super::*;
536
537 /// Test if a frequency spectrum can be sent to other threads.
538 #[test]
539 const fn test_impl_send() {
540 #[allow(unused)]
541 // test if this compiles
542 fn consume(s: FrequencySpectrum) {
543 let _: &dyn Send = &s;
544 }
545 }
546
547 #[test]
548 fn test_freq_val_invalid_search_frequency() {
549 let spectrum_vector = vec![
550 (0.0_f32.into(), 5.0_f32.into()),
551 (450.0.into(), 200.0.into()),
552 ];
553 let spectrum = FrequencySpectrum::new(
554 spectrum_vector.clone(),
555 50.0.into(),
556 spectrum_vector.len() as _,
557 );
558
559 for search_fr in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY, -1.0, 451.0] {
560 assert_eq!(None, spectrum.freq_val_exact(search_fr));
561 assert_eq!(None, spectrum.freq_val_closest(search_fr));
562 }
563 }
564
565 #[test]
566 #[allow(clippy::cognitive_complexity)]
567 fn test_spectrum_basic() {
568 let spectrum = vec![
569 (0.0_f32, 5.0_f32),
570 (50.0, 50.0),
571 (100.0, 100.0),
572 (150.0, 150.0),
573 (200.0, 100.0),
574 (250.0, 20.0),
575 (300.0, 0.0),
576 (450.0, 200.0),
577 (500.0, 100.0),
578 ];
579
580 let spectrum_vector = spectrum
581 .into_iter()
582 .map(|(fr, val)| (fr.into(), val.into()))
583 .collect::<Vec<(Frequency, FrequencyValue)>>();
584
585 let spectrum = FrequencySpectrum::new(
586 spectrum_vector.clone(),
587 50.0.into(),
588 spectrum_vector.len() as _,
589 );
590
591 // test inner vector is ordered
592 {
593 assert_eq!(
594 (0.0.into(), 5.0.into()),
595 spectrum.data()[0],
596 "Vector must be ordered"
597 );
598 assert_eq!(
599 (50.0.into(), 50.0.into()),
600 spectrum.data()[1],
601 "Vector must be ordered"
602 );
603 assert_eq!(
604 (100.0.into(), 100.0.into()),
605 spectrum.data()[2],
606 "Vector must be ordered"
607 );
608 assert_eq!(
609 (150.0.into(), 150.0.into()),
610 spectrum.data()[3],
611 "Vector must be ordered"
612 );
613 assert_eq!(
614 (200.0.into(), 100.0.into()),
615 spectrum.data()[4],
616 "Vector must be ordered"
617 );
618 assert_eq!(
619 (250.0.into(), 20.0.into()),
620 spectrum.data()[5],
621 "Vector must be ordered"
622 );
623 assert_eq!(
624 (300.0.into(), 0.0.into()),
625 spectrum.data()[6],
626 "Vector must be ordered"
627 );
628 assert_eq!(
629 (450.0.into(), 200.0.into()),
630 spectrum.data()[7],
631 "Vector must be ordered"
632 );
633 assert_eq!(
634 (500.0.into(), 100.0.into()),
635 spectrum.data()[8],
636 "Vector must be ordered"
637 );
638 }
639
640 // test DC component getter
641 assert_eq!(
642 Some(5.0.into()),
643 spectrum.dc_component(),
644 "Spectrum must contain DC component"
645 );
646
647 // test getters
648 {
649 assert_eq!(0.0, spectrum.min_fr(), "min_fr() must work");
650 assert_eq!(500.0, spectrum.max_fr(), "max_fr() must work");
651 assert_eq!(
652 (300.0.into(), 0.0.into()),
653 spectrum.min(),
654 "min() must work"
655 );
656 assert_eq!(
657 (450.0.into(), 200.0.into()),
658 spectrum.max(),
659 "max() must work"
660 );
661 assert_eq!(200.0 - 0.0, spectrum.range(), "range() must work");
662 assert_eq!(80.55556, spectrum.average(), "average() must work");
663 assert_eq!(
664 50.0,
665 spectrum.frequency_resolution(),
666 "frequency resolution must be returned"
667 );
668 }
669
670 // test get frequency exact
671 {
672 assert_eq!(5.0, spectrum.freq_val_exact(0.0).unwrap(),);
673 assert_eq!(50.0, spectrum.freq_val_exact(50.0).unwrap(),);
674 assert_eq!(150.0, spectrum.freq_val_exact(150.0).unwrap(),);
675 assert_eq!(100.0, spectrum.freq_val_exact(200.0).unwrap(),);
676 assert_eq!(20.0, spectrum.freq_val_exact(250.0).unwrap(),);
677 assert_eq!(0.0, spectrum.freq_val_exact(300.0).unwrap(),);
678 assert_eq!(100.0, spectrum.freq_val_exact(375.0).unwrap(),);
679 assert_eq!(200.0, spectrum.freq_val_exact(450.0).unwrap(),);
680 assert_eq!(None, spectrum.freq_val_exact(2000.0));
681 }
682
683 // test get frequency closest
684 {
685 assert_eq!(
686 (0.0.into(), 5.0.into()),
687 spectrum.freq_val_closest(0.0).unwrap()
688 );
689 assert_eq!(
690 (50.0.into(), 50.0.into()),
691 spectrum.freq_val_closest(50.0).unwrap()
692 );
693 assert_eq!(
694 (450.0.into(), 200.0.into()),
695 spectrum.freq_val_closest(450.0).unwrap()
696 );
697 assert_eq!(
698 (450.0.into(), 200.0.into()),
699 spectrum.freq_val_closest(448.0).unwrap()
700 );
701 assert_eq!(
702 (450.0.into(), 200.0.into()),
703 spectrum.freq_val_closest(400.0).unwrap()
704 );
705 assert_eq!(
706 (50.0.into(), 50.0.into()),
707 spectrum.freq_val_closest(47.3).unwrap()
708 );
709 assert_eq!(
710 (50.0.into(), 50.0.into()),
711 spectrum.freq_val_closest(51.3).unwrap()
712 );
713 }
714 }
715
716 #[test]
717 fn test_spectrum_get_frequency_value_exact_below_min_return_none() {
718 let spectrum_vector = vec![
719 (0.0_f32.into(), 5.0_f32.into()),
720 (450.0.into(), 200.0.into()),
721 ];
722
723 let spectrum = FrequencySpectrum::new(
724 spectrum_vector.clone(),
725 50.0.into(),
726 spectrum_vector.len() as _,
727 );
728
729 // -1 not included
730 assert!(spectrum.freq_val_exact(-1.0).is_none());
731 }
732
733 #[test]
734 fn test_spectrum_get_frequency_value_exact_below_max_return_none() {
735 let spectrum_vector = vec![
736 (0.0_f32.into(), 5.0_f32.into()),
737 (450.0.into(), 200.0.into()),
738 ];
739
740 let spectrum = FrequencySpectrum::new(
741 spectrum_vector.clone(),
742 50.0.into(),
743 spectrum_vector.len() as _,
744 );
745
746 // 451 not included
747 assert!(spectrum.freq_val_exact(451.0).is_none());
748 }
749
750 #[test]
751 fn test_nan_safety() {
752 let spectrum_vector: Vec<(Frequency, FrequencyValue)> = vec![(0.0.into(), 0.0.into()); 8];
753
754 let spectrum = FrequencySpectrum::new(
755 spectrum_vector.clone(),
756 // not important here, any value
757 50.0.into(),
758 spectrum_vector.len() as _,
759 );
760
761 assert_ne!(f32::NAN, spectrum.min().1, "NaN is not valid, must be 0.0!");
762 assert_ne!(f32::NAN, spectrum.max().1, "NaN is not valid, must be 0.0!");
763 assert_ne!(
764 f32::NAN,
765 spectrum.average(),
766 "NaN is not valid, must be 0.0!"
767 );
768
769 assert_ne!(
770 f32::INFINITY,
771 spectrum.min().1,
772 "INFINITY is not valid, must be 0.0!"
773 );
774 assert_ne!(
775 f32::INFINITY,
776 spectrum.max().1,
777 "INFINITY is not valid, must be 0.0!"
778 );
779 assert_ne!(
780 f32::INFINITY,
781 spectrum.average(),
782 "INFINITY is not valid, must be 0.0!"
783 );
784 }
785
786 #[test]
787 fn test_no_dc_component() {
788 let spectrum_vector: Vec<(Frequency, FrequencyValue)> =
789 vec![(150.0.into(), 150.0.into()), (200.0.into(), 100.0.into())];
790
791 let spectrum = FrequencySpectrum::new(
792 spectrum_vector.clone(),
793 50.0.into(),
794 spectrum_vector.len() as _,
795 );
796
797 assert!(
798 spectrum.dc_component().is_none(),
799 "This spectrum should not contain a DC component!"
800 )
801 }
802
803 #[test]
804 fn test_max() {
805 let maximum: (Frequency, FrequencyValue) = (34.991455.into(), 86.791145.into());
806 let spectrum_vector: Vec<(Frequency, FrequencyValue)> = vec![
807 (2.6916504.into(), 22.81816.into()),
808 (5.383301.into(), 2.1004658.into()),
809 (8.074951.into(), 8.704016.into()),
810 (10.766602.into(), 3.4043686.into()),
811 (13.458252.into(), 8.649045.into()),
812 (16.149902.into(), 9.210494.into()),
813 (18.841553.into(), 14.937911.into()),
814 (21.533203.into(), 5.1524887.into()),
815 (24.224854.into(), 20.706167.into()),
816 (26.916504.into(), 8.359295.into()),
817 (29.608154.into(), 3.7514696.into()),
818 (32.299805.into(), 15.109907.into()),
819 maximum,
820 (37.683105.into(), 52.140736.into()),
821 (40.374756.into(), 24.108875.into()),
822 (43.066406.into(), 11.070151.into()),
823 (45.758057.into(), 10.569871.into()),
824 (48.449707.into(), 6.1969466.into()),
825 (51.141357.into(), 16.722788.into()),
826 (53.833008.into(), 8.93011.into()),
827 ];
828
829 let spectrum = FrequencySpectrum::new(
830 spectrum_vector.clone(),
831 44100.0.into(),
832 spectrum_vector.len() as _,
833 );
834
835 assert_eq!(
836 spectrum.max(),
837 maximum,
838 "Should return the maximum frequency value!"
839 )
840 }
841}