use std::f32::consts::PI;
use autd3_core::{
common::{Angle, Freq, rad},
derive::*,
firmware::SamplingConfig,
};
use super::sampling_mode::{Nearest, SamplingMode};
/// The option of [`Sine`].
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SineOption {
/// The intensity of the modulation. The default value is [`u8::MAX`].
pub intensity: u8,
/// The offset of the modulation. The default value is `0x80`.
pub offset: u8,
/// The phase of the modulation. The default value is `0 rad`.
pub phase: Angle,
/// 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`.
pub clamp: bool,
/// The sampling configuration of the modulation. The default value is [`SamplingConfig::FREQ_4K`].
pub sampling_config: SamplingConfig,
}
impl Default for SineOption {
fn default() -> Self {
Self {
intensity: u8::MAX,
offset: 0x80,
phase: 0. * rad,
clamp: false,
sampling_config: SamplingConfig::FREQ_4K,
}
}
}
/// Sine wave modulation
///
/// 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`].
#[derive(Modulation, Clone, Copy, PartialEq, Debug)]
pub struct Sine<S: Into<SamplingMode> + Clone + Copy + std::fmt::Debug> {
/// The frequency of the sine wave.
pub freq: S,
/// The option of the modulation.
pub option: SineOption,
}
impl<S: Into<SamplingMode> + Clone + Copy + std::fmt::Debug> Sine<S> {
/// Create a new [`Sine`].
#[must_use]
pub const fn new(freq: S, option: SineOption) -> Self {
Self { freq, option }
}
}
impl Sine<Freq<f32>> {
/// Converts to the nearest frequency that can be output.
///
/// # Examples
///
/// ```
/// # use autd3::prelude::*;
/// Sine {
/// freq: 150.0 * Hz,
/// option: Default::default(),
/// }.into_nearest();
/// ```
#[must_use]
pub const fn into_nearest(self) -> Sine<Nearest> {
Sine {
freq: Nearest(self.freq),
option: self.option,
}
}
}
impl<S: Into<SamplingMode> + Clone + Copy + std::fmt::Debug> Sine<S> {
pub(super) fn calc_raw(&self) -> Result<impl Iterator<Item = f32>, ModulationError> {
let sampling_mode: SamplingMode = self.freq.into();
let (n, rep) = sampling_mode.validate(self.option.sampling_config)?;
let intensity = self.option.intensity;
let offset = self.option.offset;
let phase = self.option.phase.radian();
Ok((0..n).map(move |i| {
(intensity as f32 / 2. * (2.0 * PI * (rep * i) as f32 / n as f32 + phase).sin())
+ offset as f32
}))
}
}
impl<S: Into<SamplingMode> + Clone + Copy + std::fmt::Debug> Modulation for Sine<S> {
fn calc(self) -> Result<Vec<u8>, ModulationError> {
self.calc_raw()?
.map(|v| v.floor() as i16)
.map(|v| {
if (u8::MIN as i16..=u8::MAX as _).contains(&v) {
Ok(v as _)
} else if self.option.clamp {
Ok(v.clamp(u8::MIN as _, u8::MAX as _) as _)
} else {
Err(ModulationError::new(format!(
"Sine modulation value ({}) is out of range [{}, {}]",
v,
u8::MIN,
u8::MAX,
)))?
}
})
.collect::<Result<Vec<_>, ModulationError>>()
}
fn sampling_config(&self) -> SamplingConfig {
self.option.sampling_config
}
}
#[cfg(test)]
mod tests {
use autd3_driver::common::{Hz, rad};
use super::*;
#[rstest::rstest]
#[case(
Ok(vec![
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,
]),
150.*Hz
)]
#[case(
Ok(vec![
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,
]),
150*Hz
)]
#[case(
Ok(vec![128, 167, 202, 231, 249, 255, 249, 231, 202, 167, 127, 88, 53, 24, 6, 0, 6, 24, 53, 88]),
200.*Hz
)]
#[case(
Ok(vec![128, 167, 202, 231, 249, 255, 249, 231, 202, 167, 127, 88, 53, 24, 6, 0, 6, 24, 53, 88]),
200*Hz
)]
#[case(
Ok(vec![
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
]),
781.25*Hz
)]
#[case(
Err(ModulationError::new("Frequency (150.01 Hz) cannot be output with the sampling config (SamplingConfig::Freq(4000 Hz)).")),
150.01*Hz
)]
#[case(
Err(ModulationError::new("Frequency (2000 Hz) is equal to or greater than the Nyquist frequency (2000 Hz)")),
2000.*Hz
)]
#[case(
Err(ModulationError::new("Frequency (2000 Hz) is equal to or greater than the Nyquist frequency (2000 Hz)")),
2000*Hz
)]
#[case(
Err(ModulationError::new("Frequency (4000 Hz) is equal to or greater than the Nyquist frequency (2000 Hz)")),
4000.*Hz
)]
#[case(
Err(ModulationError::new("Frequency (4000 Hz) is equal to or greater than the Nyquist frequency (2000 Hz)")),
4000*Hz
)]
#[case(
Err(ModulationError::new("Frequency (-0.1 Hz) must be valid positive value")),
-0.1*Hz
)]
#[case(
Err(ModulationError::new("Frequency must not be zero. If intentional, use `Static` instead.")),
0*Hz
)]
#[case(
Err(ModulationError::new("Frequency must not be zero. If intentional, use `Static` instead.")),
0.*Hz
)]
fn new(
#[case] expect: Result<Vec<u8>, ModulationError>,
#[case] freq: impl Into<SamplingMode> + Copy + std::fmt::Debug,
) {
let m = Sine::new(freq, SineOption::default());
assert_eq!(u8::MAX, m.option.intensity);
assert_eq!(0x80, m.option.offset);
assert_eq!(0. * rad, m.option.phase);
assert_eq!(SamplingConfig::FREQ_4K, m.sampling_config());
assert_eq!(expect, m.calc());
}
#[rstest::rstest]
#[case(
Ok(vec![
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,
]),
150.*Hz
)]
#[case(
Ok(vec![128, 167, 202, 231, 249, 255, 249, 231, 202, 167, 127, 88, 53, 24, 6, 0, 6, 24, 53, 88]),
200.*Hz
)]
#[case(
Err(ModulationError::new("Frequency (NaN Hz) must be valid value")),
f32::NAN * Hz
)]
fn new_nearest(#[case] expect: Result<Vec<u8>, ModulationError>, #[case] freq: Freq<f32>) {
let m = Sine {
freq,
option: SineOption::default(),
}
.into_nearest();
assert_eq!(u8::MAX, m.option.intensity);
assert_eq!(0x80, m.option.offset);
assert_eq!(0. * rad, m.option.phase);
assert_eq!(SamplingConfig::FREQ_4K, m.sampling_config());
assert_eq!(expect, m.calc());
}
#[rstest::rstest]
#[case(
Err(ModulationError::new("Sine modulation value (-1) is out of range [0, 255]")),
0x00,
false
)]
#[case(
Ok(vec![0, 39, 74, 103, 121, 127, 121, 103, 74, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
0x00,
true
)]
#[test]
fn out_of_range(
#[case] expect: Result<Vec<u8>, ModulationError>,
#[case] offset: u8,
#[case] clamp: bool,
) {
assert_eq!(
expect,
Sine {
freq: 200 * Hz,
option: SineOption {
offset,
clamp,
..Default::default()
}
}
.calc()
);
}
}