autd3_core/sampling_config/
mod.rs

1mod error;
2
3use std::{fmt::Debug, num::NonZeroU16};
4
5use crate::{
6    defined::{Freq, Hz, ULTRASOUND_FREQ},
7    utils::float::is_integer,
8};
9
10pub use error::SamplingConfigError;
11
12/// Nearest type.
13#[derive(Copy, Clone, Debug, PartialEq)]
14pub struct Nearest<T: Copy + Clone + Debug + PartialEq>(pub T);
15
16/// The configuration for sampling.
17#[derive(Clone, Copy)]
18pub enum SamplingConfig {
19    #[doc(hidden)]
20    Division(NonZeroU16),
21    #[doc(hidden)]
22    Freq(Freq<f32>),
23    #[doc(hidden)]
24    Period(std::time::Duration),
25    #[doc(hidden)]
26    FreqNearest(Nearest<Freq<f32>>),
27    #[doc(hidden)]
28    PeriodNearest(Nearest<std::time::Duration>),
29}
30
31impl PartialEq for SamplingConfig {
32    fn eq(&self, other: &Self) -> bool {
33        match (self.division(), other.division()) {
34            (Ok(lhs), Ok(rhs)) => lhs == rhs,
35            _ => false,
36        }
37    }
38}
39
40impl std::fmt::Debug for SamplingConfig {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        match self {
43            SamplingConfig::Division(div) => write!(f, "SamplingConfig::Division({})", div),
44            SamplingConfig::Freq(freq) => write!(f, "SamplingConfig::Freq({:?})", freq),
45            SamplingConfig::Period(period) => write!(f, "SamplingConfig::Period({:?})", period),
46            SamplingConfig::FreqNearest(nearest) => {
47                write!(f, "SamplingConfig::FreqNearest({:?})", nearest)
48            }
49            SamplingConfig::PeriodNearest(nearest) => {
50                write!(f, "SamplingConfig::PeriodNearest({:?})", nearest)
51            }
52        }
53    }
54}
55
56impl From<NonZeroU16> for SamplingConfig {
57    fn from(value: NonZeroU16) -> Self {
58        Self::Division(value)
59    }
60}
61
62impl From<Freq<f32>> for SamplingConfig {
63    fn from(value: Freq<f32>) -> Self {
64        Self::Freq(value)
65    }
66}
67
68impl From<std::time::Duration> for SamplingConfig {
69    fn from(value: std::time::Duration) -> Self {
70        Self::Period(value)
71    }
72}
73
74impl SamplingConfig {
75    /// A [`SamplingConfig`] of 40kHz.
76    pub const FREQ_40K: Self = SamplingConfig::Freq(Freq { freq: 40000. });
77    /// A [`SamplingConfig`] of 4kHz.
78    pub const FREQ_4K: Self = SamplingConfig::Freq(Freq { freq: 4000. });
79
80    /// Creates a new [`SamplingConfig`].
81    #[must_use]
82    pub fn new(value: impl Into<SamplingConfig>) -> Self {
83        value.into()
84    }
85
86    /// The division number of the sampling frequency.
87    ///
88    /// The sampling frequency is [`ULTRASOUND_FREQ`] / `division`.
89    pub fn division(&self) -> Result<u16, SamplingConfigError> {
90        match *self {
91            SamplingConfig::Division(div) => Ok(div.get()),
92            SamplingConfig::Freq(freq) => {
93                let freq_max = ULTRASOUND_FREQ.hz() as f32 * Hz;
94                let freq_min = freq_max / u16::MAX as f32;
95                if !(freq_min..=freq_max).contains(&freq) {
96                    return Err(SamplingConfigError::FreqOutOfRangeF(
97                        freq, freq_min, freq_max,
98                    ));
99                }
100                let division = ULTRASOUND_FREQ.hz() as f32 / freq.hz();
101                if !is_integer(division as _) {
102                    return Err(SamplingConfigError::FreqInvalidF(freq));
103                }
104                Ok(division as _)
105            }
106            SamplingConfig::Period(duration) => {
107                use crate::defined::ULTRASOUND_PERIOD;
108
109                let period_min = ULTRASOUND_PERIOD;
110                let period_max = std::time::Duration::from_micros(
111                    u16::MAX as u64 * ULTRASOUND_PERIOD.as_micros() as u64,
112                );
113                if !(period_min..=period_max).contains(&duration) {
114                    return Err(SamplingConfigError::PeriodOutOfRange(
115                        duration, period_min, period_max,
116                    ));
117                }
118                if duration.as_nanos() % ULTRASOUND_PERIOD.as_nanos() != 0 {
119                    return Err(SamplingConfigError::PeriodInvalid(duration));
120                }
121                Ok((duration.as_nanos() / ULTRASOUND_PERIOD.as_nanos()) as _)
122            }
123            SamplingConfig::FreqNearest(nearest) => Ok((ULTRASOUND_FREQ.hz() as f32
124                / nearest.0.hz())
125            .clamp(1.0, u16::MAX as f32)
126            .round() as u16),
127            SamplingConfig::PeriodNearest(nearest) => {
128                use crate::defined::ULTRASOUND_PERIOD;
129
130                Ok(((nearest.0.as_nanos() + ULTRASOUND_PERIOD.as_nanos() / 2)
131                    / ULTRASOUND_PERIOD.as_nanos())
132                .clamp(1, u16::MAX as u128) as u16)
133            }
134        }
135    }
136
137    /// The sampling frequency.
138    pub fn freq(&self) -> Result<Freq<f32>, SamplingConfigError> {
139        Ok(ULTRASOUND_FREQ.hz() as f32 / self.division()? as f32 * Hz)
140    }
141
142    /// The sampling period.
143    pub fn period(&self) -> Result<std::time::Duration, SamplingConfigError> {
144        Ok(crate::defined::ULTRASOUND_PERIOD * self.division()? as u32)
145    }
146}
147
148impl SamplingConfig {
149    /// Converts to a [`SamplingConfig`] with the nearest frequency or period among the possible values.
150    #[must_use]
151    pub const fn into_nearest(self) -> SamplingConfig {
152        match self {
153            SamplingConfig::Freq(freq) => SamplingConfig::FreqNearest(Nearest(freq)),
154            SamplingConfig::Period(period) => SamplingConfig::PeriodNearest(Nearest(period)),
155            _ => self,
156        }
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use crate::defined::{Hz, kHz};
163
164    use crate::defined::ULTRASOUND_PERIOD;
165    use std::time::Duration;
166
167    use super::*;
168
169    #[rstest::rstest]
170    #[test]
171    #[case(Ok(1), NonZeroU16::MIN)]
172    #[case(Ok(u16::MAX), NonZeroU16::MAX)]
173    #[case(Ok(1), 40000. * Hz)]
174    #[case(Ok(10), 4000. * Hz)]
175    #[case(Err(SamplingConfigError::FreqInvalidF((ULTRASOUND_FREQ.hz() as f32 - 1.) * Hz)), (ULTRASOUND_FREQ.hz() as f32 - 1.) * Hz)]
176    #[case(Err(SamplingConfigError::FreqOutOfRangeF(0. * Hz, ULTRASOUND_FREQ.hz() as f32 * Hz / u16::MAX as f32, ULTRASOUND_FREQ.hz() as f32 * Hz)), 0. * Hz)]
177    #[case(Err(SamplingConfigError::FreqOutOfRangeF(40000. * Hz + 1. * Hz, ULTRASOUND_FREQ.hz() as f32 * Hz / u16::MAX as f32, ULTRASOUND_FREQ.hz() as f32 * Hz)), 40000. * Hz + 1. * Hz)]
178    #[case(Ok(1), Duration::from_micros(25))]
179    #[case(Ok(10), Duration::from_micros(250))]
180    #[case(Err(SamplingConfigError::PeriodInvalid(Duration::from_micros(u16::MAX as u64 * ULTRASOUND_PERIOD.as_micros() as u64) - Duration::from_nanos(1))), Duration::from_micros(u16::MAX as u64 * ULTRASOUND_PERIOD.as_micros() as u64) - Duration::from_nanos(1))]
181    #[case(Err(SamplingConfigError::PeriodOutOfRange(ULTRASOUND_PERIOD / 2, ULTRASOUND_PERIOD, Duration::from_micros(u16::MAX as u64 * ULTRASOUND_PERIOD.as_micros() as u64))), ULTRASOUND_PERIOD / 2)]
182    #[case(Err(SamplingConfigError::PeriodOutOfRange(Duration::from_micros(u16::MAX as u64 * ULTRASOUND_PERIOD.as_micros() as u64) * 2, ULTRASOUND_PERIOD, Duration::from_micros(u16::MAX as u64 * ULTRASOUND_PERIOD.as_micros() as u64))), Duration::from_micros(u16::MAX as u64 * ULTRASOUND_PERIOD.as_micros() as u64) * 2)]
183    fn division(
184        #[case] expect: Result<u16, SamplingConfigError>,
185        #[case] value: impl Into<SamplingConfig>,
186    ) {
187        assert_eq!(expect, SamplingConfig::new(value).division());
188    }
189
190    #[rstest::rstest]
191    #[test]
192    #[case(Ok(40000. * Hz), NonZeroU16::MIN)]
193    #[case(Ok(0.61036086 * Hz), NonZeroU16::MAX)]
194    #[case(Ok(40000. * Hz), 40000. * Hz)]
195    #[case(Ok(4000. * Hz), 4000. * Hz)]
196    #[case(Ok(40000. * Hz), Duration::from_micros(25))]
197    #[case(Ok(4000. * Hz), Duration::from_micros(250))]
198    fn freq(
199        #[case] expect: Result<Freq<f32>, SamplingConfigError>,
200        #[case] value: impl Into<SamplingConfig>,
201    ) {
202        assert_eq!(expect, SamplingConfig::new(value).freq());
203    }
204
205    #[rstest::rstest]
206    #[test]
207    #[case(Ok(Duration::from_micros(25)), NonZeroU16::MIN)]
208    #[case(Ok(Duration::from_micros(1638375)), NonZeroU16::MAX)]
209    #[case(Ok(Duration::from_micros(25)), 40000. * Hz)]
210    #[case(Ok(Duration::from_micros(250)), 4000. * Hz)]
211    #[case(Ok(Duration::from_micros(25)), Duration::from_micros(25))]
212    #[case(Ok(Duration::from_micros(250)), Duration::from_micros(250))]
213    fn period(
214        #[case] expect: Result<Duration, SamplingConfigError>,
215        #[case] value: impl Into<SamplingConfig>,
216    ) {
217        assert_eq!(expect, SamplingConfig::new(value).period());
218    }
219
220    #[rstest::rstest]
221    #[test]
222    #[case::min(u16::MAX, (40000. / u16::MAX as f32) * Hz)]
223    #[case::max(1, 40000. * Hz)]
224    #[case::not_supported_max(1, (ULTRASOUND_FREQ.hz() as f32 - 1.) * Hz)]
225    #[case::out_of_range_min(u16::MAX, 0. * Hz)]
226    #[case::out_of_range_max(1, 40000. * Hz + 1. * Hz)]
227    fn from_freq_nearest(#[case] expected: u16, #[case] freq: Freq<f32>) {
228        assert_eq!(
229            Ok(expected),
230            SamplingConfig::new(freq).into_nearest().division()
231        );
232    }
233
234    #[rstest::rstest]
235    #[test]
236    #[case::min(1, ULTRASOUND_PERIOD)]
237    #[case::max(u16::MAX, Duration::from_micros(u16::MAX as u64 * ULTRASOUND_PERIOD.as_micros() as u64))]
238    #[case::not_supported_max(u16::MAX, Duration::from_micros(u16::MAX as u64 * ULTRASOUND_PERIOD.as_micros() as u64) - Duration::from_nanos(1))]
239    #[case::out_of_range_min(1, ULTRASOUND_PERIOD / 2)]
240    #[case::out_of_range_max(u16::MAX, Duration::from_micros(u16::MAX as u64 * ULTRASOUND_PERIOD.as_micros() as u64) * 2)]
241    fn from_period_nearest(#[case] expected: u16, #[case] p: Duration) {
242        assert_eq!(
243            Ok(expected),
244            SamplingConfig::new(p).into_nearest().division()
245        );
246    }
247
248    #[rstest::rstest]
249    #[case(
250        SamplingConfig::Division(NonZeroU16::MIN),
251        SamplingConfig::Division(NonZeroU16::MIN)
252    )]
253    #[case(SamplingConfig::FreqNearest(Nearest(1. * Hz)), SamplingConfig::Freq(1. * Hz))]
254    #[case(
255        SamplingConfig::PeriodNearest(Nearest(Duration::from_micros(1))),
256        SamplingConfig::Period(Duration::from_micros(1))
257    )]
258    #[case(SamplingConfig::FreqNearest(Nearest(1. * Hz)), SamplingConfig::FreqNearest(Nearest(1. * Hz)))]
259    #[case(
260        SamplingConfig::PeriodNearest(Nearest(Duration::from_micros(1))),
261        SamplingConfig::PeriodNearest(Nearest(Duration::from_micros(1)))
262    )]
263    #[test]
264    fn into_nearest(#[case] expect: SamplingConfig, #[case] config: SamplingConfig) {
265        assert_eq!(expect, config.into_nearest());
266    }
267
268    #[rstest::rstest]
269    #[case(true, SamplingConfig::FREQ_40K, SamplingConfig::FREQ_40K)]
270    #[case(true, SamplingConfig::FREQ_40K, SamplingConfig::new(NonZeroU16::MIN))]
271    #[case(true, SamplingConfig::FREQ_40K, SamplingConfig::new(40. * kHz))]
272    #[case(
273        true,
274        SamplingConfig::FREQ_40K,
275        SamplingConfig::new(std::time::Duration::from_micros(25))
276    )]
277    #[case(false, SamplingConfig::new(41. * kHz), SamplingConfig::new(41. * kHz))]
278    #[test]
279    fn partial_eq(#[case] expect: bool, #[case] lhs: SamplingConfig, #[case] rhs: SamplingConfig) {
280        assert_eq!(expect, lhs == rhs);
281    }
282
283    #[rstest::rstest]
284    #[case(
285        "SamplingConfig::Division(1)",
286        SamplingConfig::Division(NonZeroU16::MIN)
287    )]
288    #[case("SamplingConfig::Freq(1 Hz)", SamplingConfig::Freq(1. * Hz))]
289    #[case(
290        "SamplingConfig::Period(1µs)",
291        SamplingConfig::Period(Duration::from_micros(1))
292    )]
293    #[case("SamplingConfig::FreqNearest(Nearest(1 Hz))", SamplingConfig::FreqNearest(Nearest(1. * Hz)))]
294    #[case(
295        "SamplingConfig::PeriodNearest(Nearest(1µs))",
296        SamplingConfig::PeriodNearest(Nearest(Duration::from_micros(1)))
297    )]
298    #[test]
299    fn debug(#[case] expect: &str, #[case] config: SamplingConfig) {
300        assert_eq!(expect, format!("{:?}", config));
301    }
302}