autd3-rs 0.9.0

Core async client library for the AUTD3 phased-array kit.
Documentation
#![allow(clippy::cast_possible_truncation)]

use core::time::Duration;

use crate::Freq;
use crate::value::{Nearest, SamplingConfig, SamplingConfigError};

#[derive(Clone, Copy, Debug, PartialEq)]
enum StmConfigInner {
    Freq(Freq<f32>),
    Period(Duration),
    Sampling(SamplingConfig),
    FreqNearest(Freq<f32>),
    PeriodNearest(Duration),
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct StmConfig(StmConfigInner);

impl StmConfig {
    #[must_use]
    pub fn new(value: impl Into<StmConfig>) -> Self {
        value.into()
    }

    #[must_use]
    pub fn into_sampling_config(self, size: usize) -> SamplingConfig {
        let size = size.max(1);
        match self.0 {
            StmConfigInner::Freq(freq) => SamplingConfig::new(freq * size as f32),
            StmConfigInner::Period(period) => {
                if period.as_nanos() % size as u128 == 0 {
                    SamplingConfig::new(period / size as u32)
                } else {
                    SamplingConfig::new(SamplingConfigError::StmPeriodIndivisible(period, size))
                }
            }
            StmConfigInner::Sampling(config) => config,
            StmConfigInner::FreqNearest(freq) => SamplingConfig::new(Nearest(freq * size as f32)),
            StmConfigInner::PeriodNearest(period) => {
                SamplingConfig::new(Nearest(period / size as u32))
            }
        }
    }
}

impl From<Freq<f32>> for StmConfig {
    fn from(freq: Freq<f32>) -> Self {
        Self(StmConfigInner::Freq(freq))
    }
}

impl From<Duration> for StmConfig {
    fn from(period: Duration) -> Self {
        Self(StmConfigInner::Period(period))
    }
}

impl From<SamplingConfig> for StmConfig {
    fn from(config: SamplingConfig) -> Self {
        Self(StmConfigInner::Sampling(config))
    }
}

impl From<Nearest<Freq<f32>>> for StmConfig {
    fn from(freq: Nearest<Freq<f32>>) -> Self {
        Self(StmConfigInner::FreqNearest(freq.0))
    }
}

impl From<Nearest<Duration>> for StmConfig {
    fn from(period: Nearest<Duration>) -> Self {
        Self(StmConfigInner::PeriodNearest(period.0))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::units::Hz;

    #[test]
    fn stm_freq_multiplies_sampling_rate_by_size() {
        assert_eq!(
            StmConfig::new(100.0 * Hz).into_sampling_config(4).divide(),
            Ok(100)
        );
    }

    #[test]
    fn stm_period_divides_by_size() {
        assert_eq!(
            StmConfig::new(Duration::from_millis(1))
                .into_sampling_config(4)
                .divide(),
            Ok(10)
        );
    }

    #[test]
    fn stm_period_that_does_not_divide_is_rejected_instead_of_truncated() {
        let period = Duration::from_nanos(50_001);
        assert_eq!(
            StmConfig::new(period).into_sampling_config(2).divide(),
            Err(SamplingConfigError::StmPeriodIndivisible(period, 2))
        );
    }

    #[test]
    fn stm_nearest_period_still_rounds_an_indivisible_period() {
        assert_eq!(
            StmConfig::new(Nearest(Duration::from_nanos(50_001)))
                .into_sampling_config(2)
                .divide(),
            Ok(1)
        );
    }

    #[test]
    fn stm_sampling_config_passes_through_regardless_of_size() {
        assert_eq!(
            StmConfig::new(SamplingConfig::FREQ_4K)
                .into_sampling_config(7)
                .divide(),
            Ok(10)
        );
    }

    #[test]
    fn stm_nearest_freq_rounds_to_a_valid_divider() {
        assert!(
            StmConfig::new(Nearest(4001.0 * Hz))
                .into_sampling_config(1)
                .divide()
                .is_ok()
        );
    }

    #[test]
    fn stm_nearest_period_rounds_to_a_valid_divider() {
        assert!(
            StmConfig::new(Nearest(Duration::from_micros(251)))
                .into_sampling_config(1)
                .divide()
                .is_ok()
        );
    }
}