autd3-rs-core 0.9.0

Core types and link abstraction shared across the AUTD3 phased-array sdk crates.
Documentation
#![allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]

use thiserror::Error;

pub const PULSE_WIDTH_PERIOD: u16 =
    (crate::params::FPGA_CLK_FREQ_HZ / crate::params::ULTRASOUND_FREQ_HZ) as u16;

#[derive(Clone, Copy, Debug, PartialEq, Error)]
#[non_exhaustive]
pub enum PulseWidthError {
    #[error("pulse width ({0}) is out of range [0, {n})", n = PULSE_WIDTH_PERIOD)]
    PulseWidthOutOfRange(u16),
    #[error("duty ratio ({0}) is out of range [0, 1)")]
    DutyRatioOutOfRange(f32),
}

#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
enum PulseWidthInner {
    Duty(f32),
    Raw(u16),
}

#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct PulseWidth {
    inner: PulseWidthInner,
}

impl PulseWidth {
    #[must_use]
    pub const fn new(pulse_width: u16) -> Self {
        Self {
            inner: PulseWidthInner::Raw(pulse_width),
        }
    }

    #[must_use]
    pub const fn from_duty(duty: f32) -> Self {
        Self {
            inner: PulseWidthInner::Duty(duty),
        }
    }

    pub fn pulse_width(self) -> Result<u16, PulseWidthError> {
        let pulse_width = match self.inner {
            PulseWidthInner::Duty(duty) => {
                if !(0.0..1.0).contains(&duty) {
                    return Err(PulseWidthError::DutyRatioOutOfRange(duty));
                }
                (duty * f32::from(PULSE_WIDTH_PERIOD)).round() as u16
            }
            PulseWidthInner::Raw(raw) => raw,
        };
        if pulse_width >= PULSE_WIDTH_PERIOD {
            return Err(PulseWidthError::PulseWidthOutOfRange(pulse_width));
        }
        Ok(pulse_width)
    }
}

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

    #[test]
    fn raw_in_range() {
        assert_eq!(PulseWidth::new(0).pulse_width(), Ok(0));
        assert_eq!(PulseWidth::new(256).pulse_width(), Ok(256));
        assert_eq!(PulseWidth::new(511).pulse_width(), Ok(511));
        assert_eq!(
            PulseWidth::new(512).pulse_width(),
            Err(PulseWidthError::PulseWidthOutOfRange(512))
        );
    }

    #[test]
    fn from_duty_rounds_and_validates() {
        assert_eq!(PulseWidth::from_duty(0.0).pulse_width(), Ok(0));
        assert_eq!(PulseWidth::from_duty(0.5).pulse_width(), Ok(256));
        assert_eq!(PulseWidth::from_duty(511.0 / 512.0).pulse_width(), Ok(511));
        assert_eq!(
            PulseWidth::from_duty(-0.5).pulse_width(),
            Err(PulseWidthError::DutyRatioOutOfRange(-0.5))
        );
        assert_eq!(
            PulseWidth::from_duty(1.0).pulse_width(),
            Err(PulseWidthError::DutyRatioOutOfRange(1.0))
        );
    }
}