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 [`FrequencySpectrum::max().1`] - [`FrequencySpectrum::min().1`],
201    /// i.e. the range of the frequency values (not the frequencies itself,
202    /// but their amplitudes/values).
203    #[inline]
204    #[must_use]
205    pub fn range(&self) -> FrequencyValue {
206        self.max().1 - self.min().1
207    }
208
209    /// Returns the underlying data.
210    #[inline]
211    #[must_use]
212    #[allow(clippy::missing_const_for_fn)] // false positive
213    pub fn data(&self) -> &[(Frequency, FrequencyValue)] {
214        &self.data
215    }
216
217    /// Returns the frequency resolution of this spectrum.
218    #[inline]
219    #[must_use]
220    pub const fn frequency_resolution(&self) -> f32 {
221        self.frequency_resolution
222    }
223
224    /// Returns the number of samples used to obtain this spectrum.
225    #[inline]
226    #[must_use]
227    pub const fn samples_len(&self) -> u32 {
228        self.samples_len
229    }
230
231    /// Getter for the highest frequency that is captured inside this spectrum.
232    /// Shortcut for `spectrum.data()[spectrum.data().len() - 1].0`.
233    /// This corresponds to the [`crate::limit::FrequencyLimit`] of the spectrum.
234    ///
235    /// This method could return the Nyquist frequency, if there was no Frequency
236    /// limit while obtaining the spectrum.
237    #[inline]
238    #[must_use]
239    pub fn max_fr(&self) -> Frequency {
240        self.data[self.data.len() - 1].0
241    }
242
243    /// Getter for the lowest frequency that is captured inside this spectrum.
244    /// Shortcut for `spectrum.data()[0].0`.
245    /// This corresponds to the [`crate::limit::FrequencyLimit`] of the spectrum.
246    ///
247    /// This method could return the DC component, see [`Self::dc_component`].
248    #[inline]
249    #[must_use]
250    pub fn min_fr(&self) -> Frequency {
251        self.data[0].0
252    }
253
254    /// Returns the *DC Component* or also called *DC bias* which corresponds
255    /// to the FFT result at index 0 which corresponds to `0Hz`. This is only
256    /// present if the frequencies were not limited to for example `100 <= f <= 10000`
257    /// when the libraries main function was called.
258    ///
259    /// More information:
260    /// <https://dsp.stackexchange.com/questions/12972/discrete-fourier-transform-what-is-the-dc-term-really>
261    ///
262    /// Excerpt:
263    /// *As far as practical applications go, the DC or 0 Hz term is not particularly useful.
264    /// In many cases it will be close to zero, as most signal processing applications will
265    /// tend to filter out any DC component at the analogue level. In cases where you might
266    /// be interested it can be calculated directly as an average in the usual way, without
267    /// resorting to a DFT/FFT.* - Paul R.
268    #[inline]
269    #[must_use]
270    pub fn dc_component(&self) -> Option<FrequencyValue> {
271        let (maybe_dc_component, dc_value) = &self.data[0];
272        if maybe_dc_component.val() == 0.0 {
273            Some(*dc_value)
274        } else {
275            None
276        }
277    }
278
279    /// Returns the value of the given frequency from the spectrum either exactly or approximated.
280    /// If `search_fr` is not exactly given in the spectrum, i.e. due to the
281    /// [`Self::frequency_resolution`], this function takes the two closest
282    /// neighbors/points (A, B), put a linear function through them and calculates
283    /// the point C in the middle. This is done by the private function
284    /// `calculate_y_coord_between_points`.
285    ///
286    /// ## Panics
287    /// If parameter `search_fr` (frequency) is below the lowest or the maximum
288    /// frequency, this function panics! This is because the user provide
289    /// the min/max frequency when the spectrum is created and knows about it.
290    /// This is similar to an intended "out of bounds"-access.
291    ///
292    /// ## Parameters
293    /// - `search_fr` The frequency of that you want the amplitude/value in the spectrum.
294    ///
295    /// ## Return
296    /// Either exact value of approximated value, determined by [`Self::frequency_resolution`].
297    #[inline]
298    #[must_use]
299    pub fn freq_val_exact(&self, search_fr: f32) -> FrequencyValue {
300        // lowest frequency in the spectrum
301        let (min_fr, min_fr_val) = self.data[0];
302        // highest frequency in the spectrum
303        let (max_fr, max_fr_val) = self.data[self.data.len() - 1];
304
305        // https://docs.rs/float-cmp/0.8.0/float_cmp/
306        let equals_min_fr = float_cmp::approx_eq!(f32, min_fr.val(), search_fr, ulps = 3);
307        let equals_max_fr = float_cmp::approx_eq!(f32, max_fr.val(), search_fr, ulps = 3);
308
309        // Fast return if possible
310        if equals_min_fr {
311            return min_fr_val;
312        }
313        if equals_max_fr {
314            return max_fr_val;
315        }
316        // bounds check
317        if search_fr < min_fr.val() || search_fr > max_fr.val() {
318            panic!(
319                "Frequency {}Hz is out of bounds [{}; {}]!",
320                search_fr,
321                min_fr.val(),
322                max_fr.val()
323            );
324        }
325
326        // We search for Point C (x=search_fr, y=???) between Point A and Point B iteratively.
327        // Point B is always the successor of A.
328
329        for two_points in self.data.iter().as_slice().windows(2) {
330            let point_a = two_points[0];
331            let point_b = two_points[1];
332            let point_a_x = point_a.0.val();
333            let point_a_y = point_a.1;
334            let point_b_x = point_b.0.val();
335            let point_b_y = point_b.1.val();
336
337            // check if we are in the correct window; we are in the correct window
338            // iff point_a_x <= search_fr <= point_b_x
339            if search_fr > point_b_x {
340                continue;
341            }
342
343            return if float_cmp::approx_eq!(f32, point_a_x, search_fr, ulps = 3) {
344                // directly return if possible
345                point_a_y
346            } else {
347                calculate_y_coord_between_points(
348                    (point_a_x, point_a_y.val()),
349                    (point_b_x, point_b_y),
350                    search_fr,
351                )
352                .into()
353            };
354        }
355
356        panic!("Here be dragons");
357    }
358
359    /// Returns the frequency closest to parameter `search_fr` in the spectrum. For example
360    /// if the spectrum looks like this:
361    /// ```text
362    /// Vector:    [0]      [1]      [2]      [3]
363    /// Frequency  100 Hz   200 Hz   300 Hz   400 Hz
364    /// Fr Value   0.0      1.0      0.5      0.1
365    /// ```
366    /// then `get_frequency_value_closest(320)` will return `(300.0, 0.5)`.
367    ///
368    /// ## Panics
369    /// If parameter `search_fre` (frequency) is below the lowest or the maximum
370    /// frequency, this function panics!
371    ///
372    /// ## Parameters
373    /// - `search_fr` The frequency of that you want the amplitude/value in the spectrum.
374    ///
375    /// ## Return
376    /// Closest matching point in spectrum, determined by [`Self::frequency_resolution`].
377    #[inline]
378    #[must_use]
379    pub fn freq_val_closest(&self, search_fr: f32) -> (Frequency, FrequencyValue) {
380        // lowest frequency in the spectrum
381        let (min_fr, min_fr_val) = self.data[0];
382        // highest frequency in the spectrum
383        let (max_fr, max_fr_val) = self.data[self.data.len() - 1];
384
385        // https://docs.rs/float-cmp/0.8.0/float_cmp/
386        let equals_min_fr = float_cmp::approx_eq!(f32, min_fr.val(), search_fr, ulps = 3);
387        let equals_max_fr = float_cmp::approx_eq!(f32, max_fr.val(), search_fr, ulps = 3);
388
389        // Fast return if possible
390        if equals_min_fr {
391            return (min_fr, min_fr_val);
392        }
393        if equals_max_fr {
394            return (max_fr, max_fr_val);
395        }
396
397        // bounds check
398        if search_fr < min_fr.val() || search_fr > max_fr.val() {
399            panic!(
400                "Frequency {}Hz is out of bounds [{}; {}]!",
401                search_fr,
402                min_fr.val(),
403                max_fr.val()
404            );
405        }
406
407        for two_points in self.data.iter().as_slice().windows(2) {
408            let point_a = two_points[0];
409            let point_b = two_points[1];
410            let point_a_x = point_a.0;
411            let point_a_y = point_a.1;
412            let point_b_x = point_b.0;
413            let point_b_y = point_b.1;
414
415            // check if we are in the correct window; we are in the correct window
416            // iff point_a_x <= search_fr <= point_b_x
417            if search_fr > point_b_x.val() {
418                continue;
419            }
420
421            return if float_cmp::approx_eq!(f32, point_a_x.val(), search_fr, ulps = 3) {
422                // directly return if possible
423                (point_a_x, point_a_y)
424            } else {
425                // absolute difference
426                let delta_to_a = search_fr - point_a_x.val();
427                // let delta_to_b = point_b_x.val() - search_fr;
428                if delta_to_a / self.frequency_resolution < 0.5 {
429                    (point_a_x, point_a_y)
430                } else {
431                    (point_b_x, point_b_y)
432                }
433            };
434        }
435
436        panic!("Here be dragons");
437    }
438
439    /// Wrapper around [`Self::freq_val_exact`] that consumes [mel].
440    ///
441    /// [mel]: https://en.wikipedia.org/wiki/Mel_scale
442    #[inline]
443    #[must_use]
444    pub fn mel_val(&self, mel_val: f32) -> FrequencyValue {
445        let hz = mel_to_hertz(mel_val);
446        self.freq_val_exact(hz)
447    }
448
449    /// Returns a [`BTreeMap`] with all value pairs. The key is of type [`u32`]
450    /// because [`f32`] is not [`Ord`].
451    #[inline]
452    #[must_use]
453    pub fn to_map(&self) -> BTreeMap<u32, f32> {
454        self.data
455            .iter()
456            .map(|(fr, fr_val)| (fr.val() as u32, fr_val.val()))
457            .collect()
458    }
459
460    /// Like [`Self::to_map`] but converts the frequency (x-axis) to [mels]. The
461    /// resulting map contains more results in a higher density the higher the
462    /// mel value gets. This comes from the logarithmic transformation from
463    /// hertz to mels.
464    ///
465    /// [mels]: https://en.wikipedia.org/wiki/Mel_scale
466    #[inline]
467    #[must_use]
468    pub fn to_mel_map(&self) -> BTreeMap<u32, f32> {
469        self.data
470            .iter()
471            .map(|(fr, fr_val)| (hertz_to_mel(fr.val()) as u32, fr_val.val()))
472            .collect()
473    }
474
475    /// Calculates the `min`, `max`, `median`, and `average` of the frequency values/magnitudes/
476    /// amplitudes.
477    ///
478    /// To do so, it needs to create a sorted copy of the data.
479    #[inline]
480    fn calc_statistics(&mut self, working_buffer: &mut [(Frequency, FrequencyValue)]) {
481        // We create a copy with all data from `self.data` but we sort it by the
482        // frequency value and not the frequency. This way, we can easily find the
483        // median.
484
485        let data_sorted_by_val = {
486            assert_eq!(
487                self.data.len(),
488                working_buffer.len(),
489                "The working buffer must have the same length as `self.data`!"
490            );
491
492            for (i, pair) in self.data.iter().enumerate() {
493                working_buffer[i] = *pair;
494            }
495            working_buffer.sort_by(|(_l_fr, l_fr_val), (_r_fr, r_fr_val)| {
496                // compare by frequency value, from min to max
497                l_fr_val.cmp(r_fr_val)
498            });
499
500            working_buffer
501        };
502
503        // sum of all frequency values
504        let sum: f32 = data_sorted_by_val
505            .iter()
506            .map(|fr_val| fr_val.1.val())
507            .fold(0.0, |a, b| a + b);
508
509        // average of all frequency values
510        let avg = sum / data_sorted_by_val.len() as f32;
511        let average: FrequencyValue = avg.into();
512
513        // median of all frequency values
514        let median = {
515            // we assume that data_sorted_by_val.length() is always even, because
516            // it must be a power of 2 (for FFT)
517            let a = data_sorted_by_val[data_sorted_by_val.len() / 2 - 1].1;
518            let b = data_sorted_by_val[data_sorted_by_val.len() / 2].1;
519            (a + b) / 2.0.into()
520        };
521
522        // Because we sorted the vector from lowest to highest value, the
523        // following lines are correct, i.e., we get min/max value with
524        // the corresponding frequency.
525        let min = data_sorted_by_val[0];
526        let max = data_sorted_by_val[data_sorted_by_val.len() - 1];
527
528        // check that I get the comparison right (and not from max to min)
529        debug_assert!(min.1 <= max.1, "min must be <= max");
530
531        self.min = min;
532        self.max = max;
533        self.average = average;
534        self.median = median;
535    }
536}
537
538/*impl FromIterator<(Frequency, FrequencyValue)> for FrequencySpectrum {
539
540    #[inline]
541    fn from_iter<T: IntoIterator<Item=(Frequency, FrequencyValue)>>(iter: T) -> Self {
542        // 1024 is just a guess: most likely 2048 is a common FFT length,
543        // i.e. 1024 results for the frequency spectrum.
544        let mut vec = Vec::with_capacity(1024);
545        for (fr, val) in iter {
546            vec.push((fr, val))
547        }
548
549        FrequencySpectrum::new(vec)
550    }
551}*/
552
553mod math {
554    // use super::*;
555
556    /// Calculates the y coordinate of Point C between two given points A and B
557    /// if the x-coordinate of C is known. It does that by putting a linear function
558    /// through the two given points.
559    ///
560    /// ## Parameters
561    /// - `(x1, y1)` x and y of point A
562    /// - `(x2, y2)` x and y of point B
563    /// - `x_coord` x coordinate of searched point C
564    ///
565    /// ## Return Value
566    /// y coordinate of searched point C
567    #[inline]
568    pub fn calculate_y_coord_between_points(
569        (x1, y1): (f32, f32),
570        (x2, y2): (f32, f32),
571        x_coord: f32,
572    ) -> f32 {
573        // e.g. Points (100, 1.0) and (200, 0.0)
574        // y=f(x)=-0.01x + c
575        // 1.0 = f(100) = -0.01x + c
576        // c = 1.0 + 0.01*100 = 2.0
577        // y=f(180)=-0.01*180 + 2.0
578
579        // gradient, anstieg
580        let slope = (y2 - y1) / (x2 - x1);
581        // calculate c in y=f(x)=slope * x + c
582        let c = y1 - slope * x1;
583
584        slope * x_coord + c
585    }
586
587    /// Converts hertz to [mel](https://en.wikipedia.org/wiki/Mel_scale).
588    pub fn hertz_to_mel(hz: f32) -> f32 {
589        assert!(hz >= 0.0);
590        2595.0 * libm::log10f(1.0 + (hz / 700.0))
591    }
592
593    /// Converts [mel](https://en.wikipedia.org/wiki/Mel_scale) to hertz.
594    pub fn mel_to_hertz(mel: f32) -> f32 {
595        assert!(mel >= 0.0);
596        700.0 * (libm::powf(10.0, mel / 2595.0) - 1.0)
597    }
598
599    #[cfg(test)]
600    mod tests {
601        use super::*;
602
603        #[test]
604        fn test_calculate_y_coord_between_points() {
605            assert_eq!(
606                // expected y coordinate
607                0.5,
608                calculate_y_coord_between_points(
609                    (100.0, 1.0),
610                    (200.0, 0.0),
611                    150.0,
612                ),
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!(
754                (50 + 100) as f32 / 2.0,
755                spectrum.median().val(),
756                "median() must work"
757            );
758            assert_eq!(
759                50.0,
760                spectrum.frequency_resolution(),
761                "frequency resolution must be returned"
762            );
763        }
764
765        // test get frequency exact
766        {
767            assert_eq!(5.0, spectrum.freq_val_exact(0.0).val(),);
768            assert_eq!(50.0, spectrum.freq_val_exact(50.0).val(),);
769            assert_eq!(150.0, spectrum.freq_val_exact(150.0).val(),);
770            assert_eq!(100.0, spectrum.freq_val_exact(200.0).val(),);
771            assert_eq!(20.0, spectrum.freq_val_exact(250.0).val(),);
772            assert_eq!(0.0, spectrum.freq_val_exact(300.0).val(),);
773            assert_eq!(100.0, spectrum.freq_val_exact(375.0).val(),);
774            assert_eq!(200.0, spectrum.freq_val_exact(450.0).val(),);
775        }
776
777        // test get frequency closest
778        {
779            assert_eq!((0.0.into(), 5.0.into()), spectrum.freq_val_closest(0.0),);
780            assert_eq!((50.0.into(), 50.0.into()), spectrum.freq_val_closest(50.0),);
781            assert_eq!(
782                (450.0.into(), 200.0.into()),
783                spectrum.freq_val_closest(450.0),
784            );
785            assert_eq!(
786                (450.0.into(), 200.0.into()),
787                spectrum.freq_val_closest(448.0),
788            );
789            assert_eq!(
790                (450.0.into(), 200.0.into()),
791                spectrum.freq_val_closest(400.0),
792            );
793            assert_eq!((50.0.into(), 50.0.into()), spectrum.freq_val_closest(47.3),);
794            assert_eq!((50.0.into(), 50.0.into()), spectrum.freq_val_closest(51.3),);
795        }
796    }
797
798    #[test]
799    #[should_panic]
800    fn test_spectrum_get_frequency_value_exact_panic_below_min() {
801        let mut spectrum_vector = vec![
802            (0.0_f32.into(), 5.0_f32.into()),
803            (450.0.into(), 200.0.into()),
804        ];
805
806        let spectrum = FrequencySpectrum::new(
807            spectrum_vector.clone(),
808            50.0,
809            spectrum_vector.len() as _,
810            &mut spectrum_vector,
811        );
812
813        // -1 not included, expect panic
814        spectrum.freq_val_exact(-1.0).val();
815    }
816
817    #[test]
818    #[should_panic]
819    fn test_spectrum_get_frequency_value_exact_panic_below_max() {
820        let mut spectrum_vector = vec![
821            (0.0_f32.into(), 5.0_f32.into()),
822            (450.0.into(), 200.0.into()),
823        ];
824
825        let spectrum = FrequencySpectrum::new(
826            spectrum_vector.clone(),
827            50.0,
828            spectrum_vector.len() as _,
829            &mut spectrum_vector,
830        );
831
832        // 451 not included, expect panic
833        spectrum.freq_val_exact(451.0).val();
834    }
835
836    #[test]
837    #[should_panic]
838    fn test_spectrum_get_frequency_value_closest_panic_below_min() {
839        let mut spectrum_vector = vec![
840            (0.0_f32.into(), 5.0_f32.into()),
841            (450.0.into(), 200.0.into()),
842        ];
843
844        let spectrum = FrequencySpectrum::new(
845            spectrum_vector.clone(),
846            50.0,
847            spectrum_vector.len() as _,
848            &mut spectrum_vector,
849        );
850        // -1 not included, expect panic
851        let _ = spectrum.freq_val_closest(-1.0);
852    }
853
854    #[test]
855    #[should_panic]
856    fn test_spectrum_get_frequency_value_closest_panic_below_max() {
857        let mut spectrum_vector = vec![
858            (0.0_f32.into(), 5.0_f32.into()),
859            (450.0.into(), 200.0.into()),
860        ];
861
862        let spectrum = FrequencySpectrum::new(
863            spectrum_vector.clone(),
864            50.0,
865            spectrum_vector.len() as _,
866            &mut spectrum_vector,
867        );
868
869        // 451 not included, expect panic
870        let _ = spectrum.freq_val_closest(451.0);
871    }
872
873    #[test]
874    fn test_nan_safety() {
875        let mut spectrum_vector: Vec<(Frequency, FrequencyValue)> =
876            vec![(0.0.into(), 0.0.into()); 8];
877
878        let spectrum = FrequencySpectrum::new(
879            spectrum_vector.clone(),
880            // not important here, any value
881            50.0,
882            spectrum_vector.len() as _,
883            &mut spectrum_vector,
884        );
885
886        assert_ne!(
887            f32::NAN,
888            spectrum.min().1.val(),
889            "NaN is not valid, must be 0.0!"
890        );
891        assert_ne!(
892            f32::NAN,
893            spectrum.max().1.val(),
894            "NaN is not valid, must be 0.0!"
895        );
896        assert_ne!(
897            f32::NAN,
898            spectrum.average().val(),
899            "NaN is not valid, must be 0.0!"
900        );
901        assert_ne!(
902            f32::NAN,
903            spectrum.median().val(),
904            "NaN is not valid, must be 0.0!"
905        );
906
907        assert_ne!(
908            f32::INFINITY,
909            spectrum.min().1.val(),
910            "INFINITY is not valid, must be 0.0!"
911        );
912        assert_ne!(
913            f32::INFINITY,
914            spectrum.max().1.val(),
915            "INFINITY is not valid, must be 0.0!"
916        );
917        assert_ne!(
918            f32::INFINITY,
919            spectrum.average().val(),
920            "INFINITY is not valid, must be 0.0!"
921        );
922        assert_ne!(
923            f32::INFINITY,
924            spectrum.median().val(),
925            "INFINITY is not valid, must be 0.0!"
926        );
927    }
928
929    #[test]
930    fn test_no_dc_component() {
931        let mut spectrum_vector: Vec<(Frequency, FrequencyValue)> =
932            vec![(150.0.into(), 150.0.into()), (200.0.into(), 100.0.into())];
933
934        let spectrum = FrequencySpectrum::new(
935            spectrum_vector.clone(),
936            50.0,
937            spectrum_vector.len() as _,
938            &mut spectrum_vector,
939        );
940
941        assert!(
942            spectrum.dc_component().is_none(),
943            "This spectrum should not contain a DC component!"
944        )
945    }
946
947    #[test]
948    fn test_max() {
949        let maximum: (Frequency, FrequencyValue) = (34.991455.into(), 86.791145.into());
950        let mut spectrum_vector: Vec<(Frequency, FrequencyValue)> = vec![
951            (2.6916504.into(), 22.81816.into()),
952            (5.383301.into(), 2.1004658.into()),
953            (8.074951.into(), 8.704016.into()),
954            (10.766602.into(), 3.4043686.into()),
955            (13.458252.into(), 8.649045.into()),
956            (16.149902.into(), 9.210494.into()),
957            (18.841553.into(), 14.937911.into()),
958            (21.533203.into(), 5.1524887.into()),
959            (24.224854.into(), 20.706167.into()),
960            (26.916504.into(), 8.359295.into()),
961            (29.608154.into(), 3.7514696.into()),
962            (32.299805.into(), 15.109907.into()),
963            maximum,
964            (37.683105.into(), 52.140736.into()),
965            (40.374756.into(), 24.108875.into()),
966            (43.066406.into(), 11.070151.into()),
967            (45.758057.into(), 10.569871.into()),
968            (48.449707.into(), 6.1969466.into()),
969            (51.141357.into(), 16.722788.into()),
970            (53.833008.into(), 8.93011.into()),
971        ];
972
973        let spectrum = FrequencySpectrum::new(
974            spectrum_vector.clone(),
975            44100.0,
976            spectrum_vector.len() as _,
977            &mut spectrum_vector,
978        );
979
980        assert_eq!(
981            spectrum.max(),
982            maximum,
983            "Should return the maximum frequency value!"
984        )
985    }
986
987    #[test]
988    fn test_mel_getter() {
989        let mut spectrum_vector = vec![
990            (0.0_f32.into(), 5.0_f32.into()),
991            (450.0.into(), 200.0.into()),
992        ];
993
994        let spectrum = FrequencySpectrum::new(
995            spectrum_vector.clone(),
996            50.0,
997            spectrum_vector.len() as _,
998            &mut spectrum_vector,
999        );
1000        let _ = spectrum.mel_val(450.0);
1001    }
1002}