autd3_rs/commands/stm/
config.rs1#![allow(clippy::cast_possible_truncation)]
2
3use core::time::Duration;
4
5use crate::Freq;
6use crate::value::{Nearest, SamplingConfig, SamplingConfigError};
7
8#[derive(Clone, Copy, Debug, PartialEq)]
9enum StmConfigInner {
10 Freq(Freq<f32>),
11 Period(Duration),
12 Sampling(SamplingConfig),
13 FreqNearest(Freq<f32>),
14 PeriodNearest(Duration),
15}
16
17#[derive(Clone, Copy, Debug, PartialEq)]
18pub struct StmConfig(StmConfigInner);
19
20impl StmConfig {
21 #[must_use]
22 pub fn new(value: impl Into<StmConfig>) -> Self {
23 value.into()
24 }
25
26 #[must_use]
27 pub fn into_sampling_config(self, size: usize) -> SamplingConfig {
28 let size = size.max(1);
29 match self.0 {
30 StmConfigInner::Freq(freq) => SamplingConfig::new(freq * size as f32),
31 StmConfigInner::Period(period) => {
32 if period.as_nanos() % size as u128 == 0 {
33 SamplingConfig::new(period / size as u32)
34 } else {
35 SamplingConfig::new(SamplingConfigError::StmPeriodIndivisible(period, size))
36 }
37 }
38 StmConfigInner::Sampling(config) => config,
39 StmConfigInner::FreqNearest(freq) => SamplingConfig::new(Nearest(freq * size as f32)),
40 StmConfigInner::PeriodNearest(period) => {
41 SamplingConfig::new(Nearest(period / size as u32))
42 }
43 }
44 }
45}
46
47impl From<Freq<f32>> for StmConfig {
48 fn from(freq: Freq<f32>) -> Self {
49 Self(StmConfigInner::Freq(freq))
50 }
51}
52
53impl From<Duration> for StmConfig {
54 fn from(period: Duration) -> Self {
55 Self(StmConfigInner::Period(period))
56 }
57}
58
59impl From<SamplingConfig> for StmConfig {
60 fn from(config: SamplingConfig) -> Self {
61 Self(StmConfigInner::Sampling(config))
62 }
63}
64
65impl From<Nearest<Freq<f32>>> for StmConfig {
66 fn from(freq: Nearest<Freq<f32>>) -> Self {
67 Self(StmConfigInner::FreqNearest(freq.0))
68 }
69}
70
71impl From<Nearest<Duration>> for StmConfig {
72 fn from(period: Nearest<Duration>) -> Self {
73 Self(StmConfigInner::PeriodNearest(period.0))
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use crate::units::Hz;
81
82 #[test]
83 fn stm_freq_multiplies_sampling_rate_by_size() {
84 assert_eq!(
85 StmConfig::new(100.0 * Hz).into_sampling_config(4).divide(),
86 Ok(100)
87 );
88 }
89
90 #[test]
91 fn stm_period_divides_by_size() {
92 assert_eq!(
93 StmConfig::new(Duration::from_millis(1))
94 .into_sampling_config(4)
95 .divide(),
96 Ok(10)
97 );
98 }
99
100 #[test]
101 fn stm_period_that_does_not_divide_is_rejected_instead_of_truncated() {
102 let period = Duration::from_nanos(50_001);
103 assert_eq!(
104 StmConfig::new(period).into_sampling_config(2).divide(),
105 Err(SamplingConfigError::StmPeriodIndivisible(period, 2))
106 );
107 }
108
109 #[test]
110 fn stm_nearest_period_still_rounds_an_indivisible_period() {
111 assert_eq!(
112 StmConfig::new(Nearest(Duration::from_nanos(50_001)))
113 .into_sampling_config(2)
114 .divide(),
115 Ok(1)
116 );
117 }
118
119 #[test]
120 fn stm_sampling_config_passes_through_regardless_of_size() {
121 assert_eq!(
122 StmConfig::new(SamplingConfig::FREQ_4K)
123 .into_sampling_config(7)
124 .divide(),
125 Ok(10)
126 );
127 }
128
129 #[test]
130 fn stm_nearest_freq_rounds_to_a_valid_divider() {
131 assert!(
132 StmConfig::new(Nearest(4001.0 * Hz))
133 .into_sampling_config(1)
134 .divide()
135 .is_ok()
136 );
137 }
138
139 #[test]
140 fn stm_nearest_period_rounds_to_a_valid_divider() {
141 assert!(
142 StmConfig::new(Nearest(Duration::from_micros(251)))
143 .into_sampling_config(1)
144 .divide()
145 .is_ok()
146 );
147 }
148}