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, 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 pub const FREQ_40K: Self = SamplingConfig::Freq(Freq { freq: 40000. });
73 pub const FREQ_4K: Self = SamplingConfig::Freq(Freq { freq: 4000. });
75
76 #[must_use]
78 pub fn new(value: impl Into<SamplingConfig>) -> Self {
79 value.into()
80 }
81
82 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 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 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 #[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}