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}