autd3_core/sampling_config/
mod.rs1mod 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#[derive(Copy, Clone, Debug, PartialEq)]
14pub struct Nearest<T: Copy + Clone + Debug + PartialEq>(pub T);
15
16#[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 pub const FREQ_40K: Self = SamplingConfig::Freq(Freq { freq: 40000. });
77 pub const FREQ_4K: Self = SamplingConfig::Freq(Freq { freq: 4000. });
79
80 #[must_use]
82 pub fn new(value: impl Into<SamplingConfig>) -> Self {
83 value.into()
84 }
85
86 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 pub fn freq(&self) -> Result<Freq<f32>, SamplingConfigError> {
139 Ok(ULTRASOUND_FREQ.hz() as f32 / self.division()? as f32 * Hz)
140 }
141
142 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 #[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}