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