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