Skip to main content

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}