Skip to main content

spectrum_analyzer/
limit.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 [`FrequencyLimit`].
25
26use crate::NonNegF32;
27use core::error::Error;
28use core::fmt::{Display, Formatter};
29
30/// Can be used to specify a desired frequency limit.
31///
32/// If you know that you only need frequencies `f <= 1000Hz`,
33/// `1000 <= f <= 6777`, or `10000 <= f`, then this can help you to accelerate
34/// overall computation speed and memory usage.
35///
36/// Please note that due to frequency inaccuracies the FFT result may not contain
37/// a value for `1000Hz` but for `998.76Hz`!
38#[derive(Debug, Copy, Clone)]
39pub enum FrequencyLimit {
40    /// Interested in all frequencies, including the DC component up to the
41    /// Nyquist frequency. In other words, no limit at all.
42    All,
43    /// Lower bound: only interested in frequencies `>= x`. Limit is
44    /// inclusive. Supported values are `0 <= x <= Nyquist-Frequency`.
45    Min(NonNegF32),
46    /// Upper bound: only interested in frequencies `<= x`. Limit is
47    /// inclusive. Supported values are `0 <= x <= Nyquist-Frequency`.
48    Max(NonNegF32),
49    /// Only interested in frequencies `1000 <= f <= 6777` for example. Both values are inclusive.
50    /// The first value of the tuple is equivalent to [`FrequencyLimit::Min`] and the latter
51    /// equivalent to [`FrequencyLimit::Max`]. Furthermore, the first value must not be
52    /// bigger than the second value.
53    Range(NonNegF32, NonNegF32),
54}
55
56impl FrequencyLimit {
57    /// Creates a [`Self::Min`] limit.
58    ///
59    /// # Panics
60    /// If `min` is negative or not finite.
61    #[inline]
62    #[must_use]
63    pub fn min(min: impl Into<NonNegF32>) -> Self {
64        Self::Min(min.into())
65    }
66
67    /// Creates a [`Self::Max`] limit.
68    ///
69    /// # Panics
70    /// If `max` is negative or not finite.
71    #[inline]
72    #[must_use]
73    pub fn max(max: impl Into<NonNegF32>) -> Self {
74        Self::Max(max.into())
75    }
76
77    /// Creates a [`Self::Range`] limit.
78    ///
79    /// # Panics
80    /// If one of the values is negative or not finite or if `min > max`.
81    #[inline]
82    #[must_use]
83    pub fn range(min: impl Into<NonNegF32>, max: impl Into<NonNegF32>) -> Self {
84        let min = min.into();
85        let max = max.into();
86        assert!(min <= max, "min should not be bigger than max");
87        Self::Range(min, max)
88    }
89
90    /// Returns the minimum value, if any.
91    #[inline]
92    #[must_use]
93    pub const fn maybe_min(&self) -> Option<NonNegF32> {
94        match self {
95            Self::Min(min) => Some(*min),
96            Self::Range(min, _) => Some(*min),
97            _ => None,
98        }
99    }
100
101    /// Returns the maximum value, if any.
102    #[inline]
103    #[must_use]
104    pub const fn maybe_max(&self) -> Option<NonNegF32> {
105        match self {
106            Self::Max(max) => Some(*max),
107            Self::Range(_, max) => Some(*max),
108            _ => None,
109        }
110    }
111
112    /// Verifies that the frequency limit has sane values and takes the maximum possible
113    /// frequency into account.
114    pub fn verify(&self, max_detectable_frequency: f32) -> Result<(), FrequencyLimitError> {
115        match self {
116            Self::All => Ok(()),
117            Self::Min(x) | Self::Max(x) => {
118                if *x > max_detectable_frequency {
119                    Err(FrequencyLimitError::ValueAboveNyquist(*x))
120                } else {
121                    Ok(())
122                }
123            }
124            Self::Range(min, max) => {
125                Self::Min(*min).verify(max_detectable_frequency)?;
126                Self::Max(*max).verify(max_detectable_frequency)?;
127                if min > max {
128                    Err(FrequencyLimitError::InvalidRange(*min, *max))
129                } else {
130                    Ok(())
131                }
132            }
133        }
134    }
135}
136
137/// Possible errors when creating a [`FrequencyLimit`]-object.
138#[derive(Debug)]
139pub enum FrequencyLimitError {
140    /// If the maximum value is above Nyquist frequency. Nyquist-Frequency is the maximum
141    /// detectable frequency.
142    ValueAboveNyquist(NonNegF32),
143    /// The first member of the tuple is bigger than the second. A value above
144    /// the Nyquist frequency is reported as [`Self::ValueAboveNyquist`], even
145    /// inside a range.
146    InvalidRange(NonNegF32, NonNegF32),
147}
148
149impl Display for FrequencyLimitError {
150    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
151        match self {
152            Self::ValueAboveNyquist(x) => write!(f, "Value above Nyquist: {x}"),
153            Self::InvalidRange(min, max) => write!(f, "Invalid range: {min} <= x <= {max}"),
154        }
155    }
156}
157
158impl Error for FrequencyLimitError {}
159
160#[cfg(test)]
161mod tests {
162    use crate::limit::FrequencyLimitError;
163    use crate::{FrequencyLimit, NonNegF32};
164
165    #[test]
166    #[should_panic(expected = "value should be finite and not negative")]
167    fn test_construction_rejects_not_a_number() {
168        let _ = FrequencyLimit::min(f32::NAN);
169    }
170
171    #[test]
172    #[should_panic(expected = "value should be finite and not negative")]
173    fn test_construction_rejects_negative() {
174        let _ = FrequencyLimit::max(-1.0);
175    }
176
177    #[test]
178    fn test_min_above_nyquist() {
179        let _ = FrequencyLimit::min(1.0).verify(0.0).unwrap_err();
180    }
181
182    #[test]
183    fn test_max_above_nyquist() {
184        let _ = FrequencyLimit::max(1.0).verify(0.0).unwrap_err();
185    }
186
187    #[test]
188    fn test_range_above_nyquist() {
189        let _ = FrequencyLimit::range(0.0, 1.0).verify(0.0).unwrap_err();
190    }
191
192    #[test]
193    #[should_panic(expected = "min should not be bigger than max")]
194    fn test_range_rejects_wrong_order() {
195        let _ = FrequencyLimit::range(1.0, 0.0);
196    }
197
198    #[test]
199    fn test_range_allows_equal_bounds() {
200        let limit = FrequencyLimit::range(50.0, 50.0);
201
202        assert_eq!(50.0, limit.maybe_min().unwrap());
203        assert_eq!(50.0, limit.maybe_max().unwrap());
204    }
205
206    /// The constructor rejects a wrong order, but the variant itself is
207    /// public, so `verify()` still has to.
208    #[test]
209    fn test_verify_catches_a_wrong_range() {
210        let limit = FrequencyLimit::Range(NonNegF32::from(1.0), NonNegF32::from(0.0));
211
212        assert!(matches!(
213            limit.verify(1.0),
214            Err(FrequencyLimitError::InvalidRange(_, _))
215        ));
216    }
217
218    #[test]
219    fn test_constructors_fill_the_right_bound() {
220        let min = FrequencyLimit::min(50.0);
221        assert_eq!(50.0, min.maybe_min().unwrap());
222        assert_eq!(None, min.maybe_max());
223
224        let max = FrequencyLimit::max(70.0);
225        assert_eq!(None, max.maybe_min());
226        assert_eq!(70.0, max.maybe_max().unwrap());
227
228        let range = FrequencyLimit::range(50.0, 70.0);
229        assert_eq!(50.0, range.maybe_min().unwrap());
230        assert_eq!(70.0, range.maybe_max().unwrap());
231
232        assert_eq!(None, FrequencyLimit::All.maybe_min());
233        assert_eq!(None, FrequencyLimit::All.maybe_max());
234    }
235
236    #[test]
237    fn test_ok() {
238        FrequencyLimit::min(50.0).verify(100.0).unwrap();
239        FrequencyLimit::max(50.0).verify(100.0).unwrap();
240        // useless, but not an hard error
241        FrequencyLimit::range(50.0, 50.0).verify(100.0).unwrap();
242        FrequencyLimit::range(50.0, 70.0).verify(100.0).unwrap();
243    }
244}