autd3/modulation/
sine.rs

1use std::f32::consts::PI;
2
3use autd3_core::{
4    common::{Angle, Freq, rad},
5    derive::*,
6    firmware::SamplingConfig,
7};
8
9use super::sampling_mode::{Nearest, SamplingMode};
10
11/// The option of [`Sine`].
12#[derive(Clone, Copy, Debug, PartialEq)]
13pub struct SineOption {
14    /// The intensity of the modulation. The default value is [`u8::MAX`].
15    pub intensity: u8,
16    /// The offset of the modulation. The default value is `0x80`.
17    pub offset: u8,
18    /// The phase of the modulation. The default value is `0 rad`.
19    pub phase: Angle,
20    /// If `true`, the modulation value is clamped to the range of `u8`. If `false`, returns an error if the value is out of range. The default value is `false`.
21    pub clamp: bool,
22    /// The sampling configuration of the modulation. The default value is [`SamplingConfig::FREQ_4K`].
23    pub sampling_config: SamplingConfig,
24}
25
26impl Default for SineOption {
27    fn default() -> Self {
28        Self {
29            intensity: u8::MAX,
30            offset: 0x80,
31            phase: 0. * rad,
32            clamp: false,
33            sampling_config: SamplingConfig::FREQ_4K,
34        }
35    }
36}
37
38/// Sine wave modulation
39///
40/// The modulation value is calculated as `⌊intensity / 2 * sin(2 * PI * freq * t + phase) + offset⌋`, where `t` is time, and `intensity`, `offset`, and `phase` can be set by the [`SineOption`].
41#[derive(Modulation, Clone, Copy, PartialEq, Debug)]
42pub struct Sine<S: Into<SamplingMode> + Clone + Copy + std::fmt::Debug> {
43    /// The frequency of the sine wave.
44    pub freq: S,
45    /// The option of the modulation.
46    pub option: SineOption,
47}
48
49impl<S: Into<SamplingMode> + Clone + Copy + std::fmt::Debug> Sine<S> {
50    /// Create a new [`Sine`].
51    #[must_use]
52    pub const fn new(freq: S, option: SineOption) -> Self {
53        Self { freq, option }
54    }
55}
56
57impl Sine<Freq<f32>> {
58    /// Converts to the nearest frequency that can be output.
59    ///
60    /// # Examples
61    ///
62    /// ```
63    /// # use autd3::prelude::*;
64    /// Sine {
65    ///     freq: 150.0 * Hz,
66    ///     option: Default::default(),
67    /// }.into_nearest();
68    /// ```
69    #[must_use]
70    pub const fn into_nearest(self) -> Sine<Nearest> {
71        Sine {
72            freq: Nearest(self.freq),
73            option: self.option,
74        }
75    }
76}
77
78impl<S: Into<SamplingMode> + Clone + Copy + std::fmt::Debug> Sine<S> {
79    pub(super) fn calc_raw(&self) -> Result<impl Iterator<Item = f32>, ModulationError> {
80        let sampling_mode: SamplingMode = self.freq.into();
81        let (n, rep) = sampling_mode.validate(self.option.sampling_config)?;
82        let intensity = self.option.intensity;
83        let offset = self.option.offset;
84        let phase = self.option.phase.radian();
85        Ok((0..n).map(move |i| {
86            (intensity as f32 / 2. * (2.0 * PI * (rep * i) as f32 / n as f32 + phase).sin())
87                + offset as f32
88        }))
89    }
90}
91
92impl<S: Into<SamplingMode> + Clone + Copy + std::fmt::Debug> Modulation for Sine<S> {
93    fn calc(self) -> Result<Vec<u8>, ModulationError> {
94        self.calc_raw()?
95            .map(|v| v.floor() as i16)
96            .map(|v| {
97                if (u8::MIN as i16..=u8::MAX as _).contains(&v) {
98                    Ok(v as _)
99                } else if self.option.clamp {
100                    Ok(v.clamp(u8::MIN as _, u8::MAX as _) as _)
101                } else {
102                    Err(ModulationError::new(format!(
103                        "Sine modulation value ({}) is out of range [{}, {}]",
104                        v,
105                        u8::MIN,
106                        u8::MAX,
107                    )))?
108                }
109            })
110            .collect::<Result<Vec<_>, ModulationError>>()
111    }
112
113    fn sampling_config(&self) -> SamplingConfig {
114        self.option.sampling_config
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use autd3_driver::common::{Hz, rad};
121
122    use super::*;
123
124    #[rstest::rstest]
125    #[case(
126        Ok(vec![
127            128, 157, 185, 210, 231, 245, 253, 255, 249, 236, 218, 194, 167, 138, 108, 79, 53, 31, 14, 4, 0, 4, 14, 31, 53, 79, 108, 138, 167, 194, 218, 236, 249, 255, 253, 245, 231, 210, 185, 157, 128, 98, 70, 45, 24, 10, 2, 0, 6, 19, 37, 61, 88, 117, 147, 176, 202, 224, 241, 251, 255, 251, 241, 224, 202, 176, 147, 117, 88, 61, 37, 19, 6, 0, 2, 10, 24, 45, 70, 98,
128        ]),
129        150.*Hz
130    )]
131    #[case(
132        Ok(vec![
133            128, 157, 185, 210, 231, 245, 253, 255, 249, 236, 218, 194, 167, 138, 108, 79, 53, 31, 14, 4, 0, 4, 14, 31, 53, 79, 108, 138, 167, 194, 218, 236, 249, 255, 253, 245, 231, 210, 185, 157, 128, 98, 70, 45, 24, 10, 2, 0, 6, 19, 37, 61, 88, 117, 147, 176, 202, 224, 241, 251, 255, 251, 241, 224, 202, 176, 147, 117, 88, 61, 37, 19, 6, 0, 2, 10, 24, 45, 70, 98,
134        ]),
135        150*Hz
136    )]
137    #[case(
138        Ok(vec![128, 167, 202, 231, 249, 255, 249, 231, 202, 167, 127, 88, 53, 24, 6, 0, 6, 24, 53, 88]),
139        200.*Hz
140    )]
141    #[case(
142        Ok(vec![128, 167, 202, 231, 249, 255, 249, 231, 202, 167, 127, 88, 53, 24, 6, 0, 6, 24, 53, 88]),
143        200*Hz
144    )]
145    #[case(
146        Ok(vec![
147            128, 248, 208, 62, 2, 109, 240, 222, 79, 0, 90, 230, 234, 97, 1, 73, 218, 243, 115, 4, 57, 203, 250, 134, 10, 42, 188, 254, 152, 18, 29, 170, 255, 170, 29, 18, 152, 254, 188, 42, 10, 134, 250, 203, 57, 4, 115, 243, 218, 73, 1, 97, 234, 230, 90, 0, 79, 222, 240, 109, 2, 62, 208, 248, 127, 7, 47, 193, 253, 146, 15, 33, 176, 255, 165, 25, 21, 158, 254, 182, 37, 12, 140, 251, 198, 52, 5, 121, 245, 213, 67, 1, 103, 237, 226, 85, 0, 85, 226, 237, 103, 1, 67, 213, 245, 121, 5, 52, 198, 251, 140, 12, 37, 182, 254, 158, 21, 25, 165, 255, 176, 33, 15, 146, 253, 193, 47, 7
148        ]),
149        781.25*Hz
150    )]
151    #[case(
152        Err(ModulationError::new("Frequency (150.01 Hz) cannot be output with the sampling config (SamplingConfig::Freq(4000 Hz)).")),
153        150.01*Hz
154    )]
155    #[case(
156        Err(ModulationError::new("Frequency (2000 Hz) is equal to or greater than the Nyquist frequency (2000 Hz)")),
157        2000.*Hz
158    )]
159    #[case(
160        Err(ModulationError::new("Frequency (2000 Hz) is equal to or greater than the Nyquist frequency (2000 Hz)")),
161        2000*Hz
162    )]
163    #[case(
164        Err(ModulationError::new("Frequency (4000 Hz) is equal to or greater than the Nyquist frequency (2000 Hz)")),
165        4000.*Hz
166    )]
167    #[case(
168        Err(ModulationError::new("Frequency (4000 Hz) is equal to or greater than the Nyquist frequency (2000 Hz)")),
169        4000*Hz
170    )]
171    #[case(
172        Err(ModulationError::new("Frequency (-0.1 Hz) must be valid positive value")),
173        -0.1*Hz
174    )]
175    #[case(
176        Err(ModulationError::new("Frequency must not be zero. If intentional, use `Static` instead.")),
177        0*Hz
178    )]
179    #[case(
180        Err(ModulationError::new("Frequency must not be zero. If intentional, use `Static` instead.")),
181        0.*Hz
182    )]
183    fn new(
184        #[case] expect: Result<Vec<u8>, ModulationError>,
185        #[case] freq: impl Into<SamplingMode> + Copy + std::fmt::Debug,
186    ) {
187        let m = Sine::new(freq, SineOption::default());
188        assert_eq!(u8::MAX, m.option.intensity);
189        assert_eq!(0x80, m.option.offset);
190        assert_eq!(0. * rad, m.option.phase);
191        assert_eq!(SamplingConfig::FREQ_4K, m.sampling_config());
192        assert_eq!(expect, m.calc());
193    }
194
195    #[rstest::rstest]
196    #[case(
197        Ok(vec![
198            128, 157, 185, 209, 230, 245, 253, 255, 250, 238, 220, 198, 171, 142, 113, 84, 57, 35, 17, 5, 0, 2, 10, 25, 46, 70, 98,
199        ]),
200        150.*Hz
201    )]
202    #[case(
203        Ok(vec![128, 167, 202, 231, 249, 255, 249, 231, 202, 167, 127, 88, 53, 24, 6, 0, 6, 24, 53, 88]),
204        200.*Hz
205    )]
206    #[case(
207        Err(ModulationError::new("Frequency (NaN Hz) must be valid value")),
208        f32::NAN * Hz
209    )]
210    fn new_nearest(#[case] expect: Result<Vec<u8>, ModulationError>, #[case] freq: Freq<f32>) {
211        let m = Sine {
212            freq,
213            option: SineOption::default(),
214        }
215        .into_nearest();
216        assert_eq!(u8::MAX, m.option.intensity);
217        assert_eq!(0x80, m.option.offset);
218        assert_eq!(0. * rad, m.option.phase);
219        assert_eq!(SamplingConfig::FREQ_4K, m.sampling_config());
220        assert_eq!(expect, m.calc());
221    }
222
223    #[rstest::rstest]
224    #[case(
225        Err(ModulationError::new("Sine modulation value (-1) is out of range [0, 255]")),
226        0x00,
227        false
228    )]
229    #[case(
230        Ok(vec![0, 39, 74, 103, 121, 127, 121, 103, 74, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
231        0x00,
232        true
233    )]
234    #[test]
235    fn out_of_range(
236        #[case] expect: Result<Vec<u8>, ModulationError>,
237        #[case] offset: u8,
238        #[case] clamp: bool,
239    ) {
240        assert_eq!(
241            expect,
242            Sine {
243                freq: 200 * Hz,
244                option: SineOption {
245                    offset,
246                    clamp,
247                    ..Default::default()
248                }
249            }
250            .calc()
251        );
252    }
253}