#![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()
);
}
}