autd3-rs 0.2.0

Core async client library for the AUTD3 phased-array kit.
Documentation
use crate::error::{Error, PayloadError};
use crate::mirror::FirmwareState;
use crate::params::MOD_BUFFER_SAMPLES;
use crate::protocol::{Cmd, PAYLOAD_BYTES};
use crate::value::{LoopBehavior, ModulationBank, SamplingConfig};

use super::{Distribution, Operation, silencer_constraint};

#[derive(Clone, Copy, Debug)]
pub struct ConfigModulation {
    pub bank: ModulationBank,
    pub config: SamplingConfig,
    pub size: usize,
    pub loop_behavior: LoopBehavior,
}

impl Operation for ConfigModulation {
    fn frames(&self) -> usize {
        1
    }

    fn distribution(&self) -> Distribution {
        Distribution::Broadcast
    }

    fn encode(
        &self,
        _device: usize,
        _frame: usize,
        out: &mut [u8; PAYLOAD_BYTES],
    ) -> Result<Cmd, Error> {
        let divider = self
            .config
            .divide()
            .map_err(|e| Error::InvalidPayload(PayloadError::from(e)))?;
        if self.size == 0 || self.size > MOD_BUFFER_SAMPLES {
            return Err(Error::InvalidPayload(
                PayloadError::ModulationSizeOutOfRange {
                    size: self.size,
                    max: MOD_BUFFER_SAMPLES,
                },
            ));
        }
        out[0] = self.bank.as_u8();
        out[2..4].copy_from_slice(&divider.to_le_bytes());
        out[4..8].copy_from_slice(
            &u32::try_from(self.size)
                .expect("bounded by MOD_BUFFER_SAMPLES")
                .to_le_bytes(),
        );
        out[8..10].copy_from_slice(&self.loop_behavior.rep().to_le_bytes());
        Ok(Cmd::ConfigModulation)
    }

    fn reflect(&self, device: usize, state: &mut FirmwareState) -> Result<(), Error> {
        let divider = self
            .config
            .divide()
            .map_err(|e| Error::InvalidPayload(PayloadError::from(e)))?;
        if let Err(v) = state.silencer.check_mod_div(divider) {
            return Err(silencer_constraint(device, v));
        }
        state.silencer.note_mod_div(self.bank.as_u8(), divider);
        state
            .transition
            .note_mod_loop(self.bank.as_u8(), self.loop_behavior);
        Ok(())
    }
}

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

    fn encode(op: ConfigModulation) -> Result<(Cmd, [u8; PAYLOAD_BYTES]), Error> {
        let mut out = [0u8; PAYLOAD_BYTES];
        let cmd = op.encode(0, 0, &mut out)?;
        Ok((cmd, out))
    }

    #[test]
    fn config_modulation_lays_out_fields() {
        let (cmd, payload) = encode(ConfigModulation {
            bank: ModulationBank::B1,
            config: SamplingConfig::new(NonZeroU16::new(10).unwrap()),
            size: 4000,
            loop_behavior: LoopBehavior::Finite(NonZeroU16::new(10).unwrap()),
        })
        .unwrap();

        assert_eq!(cmd, Cmd::ConfigModulation);
        assert_eq!(payload[0], 1);
        assert_eq!(payload[1], 0);
        assert_eq!(&payload[2..4], &10u16.to_le_bytes());
        assert_eq!(&payload[4..8], &4000u32.to_le_bytes());
        assert_eq!(&payload[8..10], &9u16.to_le_bytes());
        assert!(payload[10..].iter().all(|&b| b == 0));
    }

    #[test]
    fn config_modulation_rejects_invalid_fields() {
        let base = ConfigModulation {
            bank: ModulationBank::B0,
            config: SamplingConfig::new(NonZeroU16::MIN),
            size: 1,
            loop_behavior: LoopBehavior::Infinite,
        };
        assert!(matches!(
            encode(ConfigModulation {
                config: SamplingConfig::new(core::time::Duration::from_nanos(1)),
                ..base
            }),
            Err(Error::InvalidPayload(_))
        ));
        assert!(matches!(
            encode(ConfigModulation { size: 0, ..base }),
            Err(Error::InvalidPayload(_))
        ));
        assert!(matches!(
            encode(ConfigModulation {
                size: MOD_BUFFER_SAMPLES + 1,
                ..base
            }),
            Err(Error::InvalidPayload(_))
        ));
        assert!(
            encode(ConfigModulation {
                size: MOD_BUFFER_SAMPLES,
                ..base
            })
            .is_ok()
        );
    }
}