use super::xorshift::XorShift32;
use crate::core::fixed::fixed::Q15;
use crate::core::fixed::tables::sine;
use crate::core::fixed::units::Phase;
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, Hash, PartialEq)]
pub enum Waveform {
#[default]
BipolarSine,
BipolarSquare,
BipolarRampDown,
BipolarRampUp,
BipolarTriangle,
AutoVibSine,
AutoVibSquare,
AutoVibRampDown,
Random,
}
impl Waveform {
pub fn value_q15(&self, phase: Phase) -> Q15 {
match self {
Waveform::BipolarSine => -sine(phase),
Waveform::BipolarSquare => {
if phase.is_first_half() {
Q15::NEG_ONE
} else {
Q15::ONE
}
}
Waveform::BipolarRampDown => Q15::from_raw(phase.raw_as_i16().wrapping_neg()),
Waveform::BipolarRampUp => Q15::from_raw(phase.raw_as_i16()),
Waveform::BipolarTriangle => {
let into_quarter = phase.raw() % Phase::QUARTER.raw();
let t = Q15::from_ratio(into_quarter as i32, Phase::QUARTER.raw() as i32);
if phase.raw() < Phase::QUARTER.raw() {
t } else if phase.raw() < Phase::HALF.raw() {
Q15::ONE.saturating_sub(t) } else if phase.raw() < Phase::THREE_QUARTERS.raw() {
t.neg() } else {
Q15::ONE.saturating_sub(t).neg() }
}
Waveform::AutoVibSine => sine(phase),
Waveform::AutoVibSquare => {
if phase.is_first_half() {
Q15::ONE
} else {
Q15::NEG_ONE
}
}
Waveform::AutoVibRampDown => {
let p = phase.to_q15_unsigned().raw() as i32;
Q15::from_raw((Q15::ONE.raw() as i32 - 2 * p) as i16)
}
Waveform::Random => Q15::ZERO,
}
}
}
#[derive(Default, Clone, Copy, Debug)]
pub struct WaveformState {
wf: Waveform,
rng: XorShift32,
}
impl WaveformState {
pub fn new(wf: Waveform) -> Self {
Self {
wf,
rng: XorShift32::default(),
}
}
#[inline]
pub fn waveform(&self) -> Waveform {
self.wf
}
pub fn value_q15(&mut self, phase: Phase) -> Q15 {
if let Waveform::Random = self.wf {
let n = self.rng.next().unwrap() as i16;
Q15::from_raw(n)
} else {
self.wf.value_q15(phase)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq_q15(a: Q15, b: Q15, tol: i16) -> bool {
(a.raw() as i32 - b.raw() as i32).abs() <= tol as i32
}
#[test]
fn bipolar_sine_falls_first() {
let wf = Waveform::BipolarSine;
assert!(approx_eq_q15(wf.value_q15(Phase::ZERO), Q15::ZERO, 4));
assert!(approx_eq_q15(wf.value_q15(Phase::QUARTER), Q15::NEG_ONE, 4));
assert!(approx_eq_q15(wf.value_q15(Phase::HALF), Q15::ZERO, 4));
assert!(approx_eq_q15(
wf.value_q15(Phase::THREE_QUARTERS),
Q15::ONE,
4
));
}
#[test]
fn random_waveform_not_stuck() {
let mut ws = WaveformState::new(Waveform::Random);
let first = ws.value_q15(Phase::ZERO);
let second = ws.value_q15(Phase::ZERO);
assert_ne!(first.raw(), second.raw(), "Random waveform appears stuck");
}
#[test]
fn random_waveform_is_bipolar() {
let mut ws = WaveformState::new(Waveform::Random);
let mut saw_negative = false;
let mut saw_positive = false;
for _ in 0..1024 {
let v = ws.value_q15(Phase::ZERO).raw();
saw_negative |= v < 0;
saw_positive |= v > 0;
}
assert!(
saw_negative && saw_positive,
"Random should span both signs (neg={saw_negative}, pos={saw_positive})"
);
}
#[test]
fn autovib_sine_rises_first() {
let wf = Waveform::AutoVibSine;
assert!(approx_eq_q15(wf.value_q15(Phase::ZERO), Q15::ZERO, 4));
assert!(approx_eq_q15(wf.value_q15(Phase::QUARTER), Q15::ONE, 4));
assert!(approx_eq_q15(wf.value_q15(Phase::HALF), Q15::ZERO, 4));
assert!(approx_eq_q15(
wf.value_q15(Phase::THREE_QUARTERS),
Q15::NEG_ONE,
4
));
assert_eq!(
wf.value_q15(Phase::QUARTER).raw(),
-Waveform::BipolarSine.value_q15(Phase::QUARTER).raw()
);
}
#[test]
fn autovib_square_rises_first() {
let wf = Waveform::AutoVibSquare;
assert_eq!(wf.value_q15(Phase::ZERO), Q15::ONE);
assert_eq!(wf.value_q15(Phase::QUARTER), Q15::ONE);
assert_eq!(wf.value_q15(Phase::HALF), Q15::NEG_ONE);
assert_eq!(wf.value_q15(Phase::THREE_QUARTERS), Q15::NEG_ONE);
assert!(
wf.value_q15(Phase::ZERO).raw() > 0
&& Waveform::BipolarSquare.value_q15(Phase::ZERO).raw() < 0
);
}
#[test]
fn autovib_ramp_down_peaks_at_phase_zero() {
let wf = Waveform::AutoVibRampDown;
assert!(approx_eq_q15(wf.value_q15(Phase::ZERO), Q15::ONE, 2));
assert!(approx_eq_q15(wf.value_q15(Phase::HALF), Q15::ZERO, 4));
assert!(approx_eq_q15(
wf.value_q15(Phase::THREE_QUARTERS),
Q15::HALF.neg(),
4
));
}
#[test]
fn bipolar_ramp_up_mirrors_ramp_down() {
const SAMPLES: u32 = 64;
const STEP: u16 = ((1u32 << 16) / SAMPLES) as u16;
for k in 0..SAMPLES {
let phase = Phase::from_raw((k as u16).wrapping_mul(STEP));
let up = Waveform::BipolarRampUp.value_q15(phase).raw();
let down = Waveform::BipolarRampDown.value_q15(phase).raw();
assert_eq!(
up,
down.wrapping_neg(),
"mismatch at phase {:#x}",
phase.raw()
);
}
}
}