use crate::platform::SnrFloor;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SignTuple {
pub norm: f32,
pub drift: f32,
pub slew: f32,
}
impl SignTuple {
#[inline]
pub const fn new(norm: f32, drift: f32, slew: f32) -> Self {
Self { norm, drift, slew }
}
#[inline]
pub const fn zero() -> Self {
Self { norm: 0.0, drift: 0.0, slew: 0.0 }
}
#[inline]
pub fn is_outward_drift(&self) -> bool {
self.drift > 0.0
}
#[inline]
pub fn is_abrupt_slew(&self, delta_s: f32) -> bool {
self.slew.abs() > delta_s
}
}
pub struct SignWindow<const W: usize> {
norms: [f32; W],
prev_drift: f32,
head: usize,
count: usize,
}
impl<const W: usize> SignWindow<W> {
pub const fn new() -> Self {
Self {
norms: [0.0; W],
prev_drift: 0.0,
head: 0,
count: 0,
}
}
pub fn push(&mut self, norm: f32, sub_threshold: bool, snr_floor: SnrFloor) -> SignTuple {
core::hint::black_box(snr_floor);
self.norms[self.head] = norm;
self.head = (self.head + 1) % W;
if self.count < W {
self.count += 1;
}
if sub_threshold || self.count < 2 {
self.prev_drift = 0.0;
return SignTuple::new(norm, 0.0, 0.0);
}
let filled = self.count.min(W);
let mut sum_diff = 0.0_f32;
let mut n_diffs = 0usize;
for i in 1..filled {
let cur_idx = (self.head + W - 1 - (i - 1)) % W;
let prev_idx = (self.head + W - 1 - i) % W;
sum_diff += self.norms[cur_idx] - self.norms[prev_idx];
n_diffs += 1;
}
let drift = if n_diffs > 0 {
sum_diff / n_diffs as f32
} else {
0.0
};
let slew = drift - self.prev_drift;
self.prev_drift = drift;
SignTuple::new(norm, drift, slew)
}
pub fn reset(&mut self) {
self.norms = [0.0; W];
self.prev_drift = 0.0;
self.head = 0;
self.count = 0;
}
}
impl<const W: usize> Default for SignWindow<W> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::platform::SnrFloor;
#[test]
fn sign_tuple_zero_is_admissible() {
let s = SignTuple::zero();
assert_eq!(s.norm, 0.0);
assert!(!s.is_outward_drift());
assert!(!s.is_abrupt_slew(0.01));
}
#[test]
fn window_drift_monotone_increase() {
let mut w = SignWindow::<5>::new();
let floor = SnrFloor::default();
let mut last_drift = -f32::INFINITY;
for i in 0..8u32 {
let norm = i as f32 * 0.01;
let sig = w.push(norm, false, floor);
if i >= 2 {
assert!(sig.drift >= 0.0, "drift should be non-negative for increasing norms");
let _ = last_drift; last_drift = sig.drift;
}
}
let _ = last_drift;
}
#[test]
fn window_sub_threshold_forces_zero_drift() {
let mut w = SignWindow::<5>::new();
let floor = SnrFloor::default();
for i in 0..5u32 {
let norm = i as f32 * 0.05;
let sig = w.push(norm, true, floor);
assert_eq!(sig.drift, 0.0);
assert_eq!(sig.slew, 0.0);
}
}
#[test]
fn window_reset_clears_state() {
let mut w = SignWindow::<5>::new();
let floor = SnrFloor::default();
for i in 0..5u32 {
w.push(i as f32 * 0.1, false, floor);
}
w.reset();
let sig = w.push(0.05, false, floor);
assert_eq!(sig.drift, 0.0);
}
}