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(÷r.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()
);
}
}