Skip to main content

autd3_rs/commands/stm/
config.rs

1#![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}