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