use crate::error::ConfigError;
use crate::types::{BaudRate, Bit, SampleRate};
#[derive(Debug, Clone)]
pub struct Slicer {
phase: u32,
step: u32,
last_positive: bool,
primed: bool,
lock: u8,
lock_shift: u32,
}
const SEARCH_SHIFT: u32 = 1;
const LOCK_SHIFT: u32 = 3;
const LOCK_THRESHOLD: u8 = 7;
const LOCK_MAX: u8 = 12;
const WINDOW: i32 = 1 << 30;
impl Slicer {
pub fn new(sample_rate: SampleRate, baud: BaudRate) -> Result<Self, ConfigError> {
let sr = sample_rate.hz();
let bd = baud.bps();
if sr / bd < 2 {
return Err(ConfigError::BaudExceedsSampleRate {
baud: bd,
sample_rate: sr,
});
}
let step = ((((bd as u64) << 32) + (sr as u64) / 2) / (sr as u64)) as u32;
Ok(Self {
phase: 0,
step,
last_positive: true,
primed: false,
lock: 0,
lock_shift: LOCK_SHIFT,
})
}
#[cfg(feature = "tnc")]
#[allow(dead_code)]
pub(crate) fn set_lock_shift(&mut self, shift: u32) {
self.lock_shift = shift.min(15);
}
#[cfg(feature = "tnc")]
pub(crate) fn set_initial_phase(&mut self, phase: u32) {
self.phase = phase;
}
pub fn push(&mut self, metric: i32) -> Option<Bit> {
let positive = metric >= 0;
if self.primed && positive != self.last_positive {
let offset = (self.phase ^ (1 << 31)) as i32;
let shift = if self.lock >= LOCK_THRESHOLD {
self.lock_shift
} else {
SEARCH_SHIFT
};
self.phase = self.phase.wrapping_sub((offset >> shift) as u32);
if offset.unsigned_abs() < WINDOW as u32 {
self.lock = (self.lock + 1).min(LOCK_MAX);
} else {
self.lock = self.lock.saturating_sub(4);
}
}
self.last_positive = positive;
self.primed = true;
let (next, wrapped) = self.phase.overflowing_add(self.step);
self.phase = next;
if wrapped {
Some(if positive { Bit::One } else { Bit::Zero })
} else {
None
}
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use std::vec::Vec;
fn slicer(sr: u32, bd: u32) -> Slicer {
Slicer::new(SampleRate::new(sr).unwrap(), BaudRate::new(bd).unwrap()).unwrap()
}
fn run(mut s: Slicer, bits: &[Bit], spb: usize) -> Vec<Bit> {
run_ref(&mut s, bits, spb)
}
fn run_ref(s: &mut Slicer, bits: &[Bit], spb: usize) -> Vec<Bit> {
let mut out = Vec::new();
for &b in bits {
let metric = match b {
Bit::One => 1_000,
Bit::Zero => -1_000,
};
for _ in 0..spb {
if let Some(bit) = s.push(metric) {
out.push(bit);
}
}
}
out
}
#[test]
fn construction_rejects_low_sample_rate() {
let err = Slicer::new(
SampleRate::new(8_000).unwrap(),
BaudRate::new(4_800).unwrap(),
)
.unwrap_err();
assert_eq!(
err,
ConfigError::BaudExceedsSampleRate {
baud: 4_800,
sample_rate: 8_000
}
);
}
#[test]
fn emits_one_bit_per_cell_when_locked() {
let bits: Vec<Bit> = (0..64)
.map(|i| if i % 2 == 0 { Bit::One } else { Bit::Zero })
.collect();
let out = run(slicer(48_000, 1_200), &bits, 40);
assert!(out.len() >= 63 && out.len() <= 65, "got {}", out.len());
let tail = &out[8..];
for pair in tail.windows(2) {
assert_ne!(pair[0], pair[1], "lost alternation: {out:?}");
}
}
#[test]
fn locks_onto_clean_alternating_pattern_all_rates() {
for sr in [8_000u32, 11_025, 22_050, 44_100, 48_000] {
let spb = (sr / 1_200) as usize;
let bits: Vec<Bit> = (0..64)
.map(|i| if i % 2 == 0 { Bit::One } else { Bit::Zero })
.collect();
let out = run(slicer(sr, 1_200), &bits, spb);
let tail = &out[out.len().saturating_sub(48)..];
for pair in tail.windows(2) {
assert_ne!(pair[0], pair[1], "rate {sr}: {out:?}");
}
}
}
#[test]
fn recovers_from_deliberate_phase_error() {
let mut s = slicer(48_000, 1_200);
s.phase = 0x4000_0000; let mut bits: Vec<Bit> = (0..16)
.map(|i| if i % 2 == 0 { Bit::One } else { Bit::Zero })
.collect();
bits.extend(core::iter::repeat_n(Bit::One, 20));
bits.extend(core::iter::repeat_n(Bit::Zero, 20));
let out = run(s, &bits, 40);
let n = out.len();
let tail = &out[n - 36..];
assert!(tail[..16].iter().all(|&b| b == Bit::One), "{out:?}");
assert!(tail[20..].iter().all(|&b| b == Bit::Zero), "{out:?}");
}
#[test]
fn no_phantom_crossing_on_first_sample() {
let bits: Vec<Bit> = core::iter::repeat_n(Bit::Zero, 32).collect();
let out = run(slicer(48_000, 1_200), &bits, 40);
assert!(out.len() >= 31 && out.len() <= 33);
assert!(out.iter().all(|&b| b == Bit::Zero));
}
#[test]
fn step_rounding_exact_for_even_ratio() {
let s = slicer(48_000, 1_200);
assert_eq!(s.step, 107_374_182);
}
#[test]
fn acquires_lock_on_alternating_preamble() {
let mut s = slicer(48_000, 1_200);
let bits: Vec<Bit> = (0..(LOCK_THRESHOLD as usize + 4))
.map(|i| if i % 2 == 0 { Bit::One } else { Bit::Zero })
.collect();
run_ref(&mut s, &bits, 40);
assert!(s.lock >= LOCK_THRESHOLD, "lock counter = {}", s.lock);
}
#[test]
fn holds_phase_through_transition_free_gap() {
let mut s = slicer(48_000, 1_200);
let mut bits: Vec<Bit> = (0..16)
.map(|i| if i % 2 == 0 { Bit::One } else { Bit::Zero })
.collect();
bits.extend(core::iter::repeat_n(Bit::One, 64));
let out = run_ref(&mut s, &bits, 40);
assert!(s.lock >= LOCK_THRESHOLD, "gap unlocked the loop");
assert!(out[out.len() - 60..].iter().all(|&b| b == Bit::One));
}
#[test]
fn unlocks_after_wild_transitions_and_relocks() {
let mut s = slicer(48_000, 1_200);
let preamble: Vec<Bit> = (0..16)
.map(|i| if i % 2 == 0 { Bit::One } else { Bit::Zero })
.collect();
run_ref(&mut s, &preamble, 40);
assert!(s.lock >= LOCK_THRESHOLD);
let mut sign = 1;
for _ in 0..12 {
for _ in 0..10 {
s.push(sign * 1_000);
}
sign = -sign;
}
assert!(s.lock < LOCK_THRESHOLD, "wild edges failed to unlock");
run_ref(&mut s, &preamble, 40);
assert!(s.lock >= LOCK_THRESHOLD, "failed to re-lock");
}
}