use arraydeque::ArrayDeque;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct SymbolEstimate {
pub data: [f32; 2],
pub err: f32,
}
#[allow(dead_code)]
impl SymbolEstimate {
pub fn new(zero: f32, sym: f32, err: f32) -> Self {
Self {
data: [zero, sym],
err,
}
}
pub fn sym(&self) -> f32 {
self.data[1]
}
}
#[derive(Clone, Debug)]
pub struct TimingLoop {
samples_per_ted: f32,
period_min: f32,
period_max: f32,
loop_alpha: f32,
loop_beta: f32,
period_avg: f32,
period_inst: f32,
ted: ZeroCrossingTed,
}
impl TimingLoop {
pub fn new(samples_per_symbol: f32, loop_bandwidth: f32, max_deviation: f32) -> Self {
const NEED_SPS: f32 = ZeroCrossingTed::SAMPLES_PER_SYMBOL as f32;
let (loop_alpha, loop_beta) = compute_loop_alphabeta(loop_bandwidth);
let samples_per_ted = samples_per_symbol / NEED_SPS;
let period_deviation = samples_per_symbol * f32::clamp(max_deviation, 0.0, 0.5);
let period_avg = samples_per_ted;
let period_inst = samples_per_ted;
let period_min = period_avg - period_deviation;
let period_max = period_avg + period_deviation;
Self {
samples_per_ted,
period_min,
period_max,
loop_alpha,
loop_beta,
period_avg,
period_inst,
ted: ZeroCrossingTed::default(),
}
}
pub fn reset(&mut self) {
self.ted.reset();
self.period_avg = self.samples_per_ted;
self.period_inst = self.samples_per_ted;
}
pub fn set_loop_bandwidth(&mut self, loop_bandwidth: f32) {
let (loop_alpha, loop_beta) = compute_loop_alphabeta(loop_bandwidth);
self.loop_alpha = loop_alpha;
self.loop_beta = loop_beta;
}
pub fn input(&mut self, sample: f32, offset: f32) -> (f32, Option<SymbolEstimate>) {
let sym = self.ted.input(sample);
(self.advance_loop(offset, &sym), sym)
}
pub fn samples_per_ted(&self) -> f32 {
self.samples_per_ted
}
fn advance_loop(&mut self, offset: f32, sym: &Option<SymbolEstimate>) -> f32 {
let offset = offset.clamp(-0.5f32, 0.5f32);
match sym {
Some(sym) => {
let err = f32::clamp(sym.err - offset / self.samples_per_ted, -1.0f32, 1.0f32);
self.period_avg += self.loop_beta * err;
self.period_avg = self.period_avg.clamp(self.period_min, self.period_max);
self.period_inst = self.period_avg + self.loop_alpha * err + offset;
if self.period_inst < 0.0f32 {
self.period_inst = self.period_avg;
}
}
None => {
self.period_inst += offset;
}
}
self.period_inst
}
}
#[derive(Clone, Debug)]
pub struct ZeroCrossingTed {
history: ArrayDeque<f32, 3, arraydeque::Wrapping>,
sample_counter: u32,
}
#[allow(dead_code)]
impl ZeroCrossingTed {
pub const SAMPLES_PER_SYMBOL: u32 = 2;
pub fn new() -> Self {
Self::default()
}
pub fn reset(&mut self) {
self.history.clear();
for _i in 0..self.history.capacity() {
self.history.push_back(0.0f32);
}
self.sample_counter = 0;
}
pub fn input(&mut self, sample: f32) -> Option<SymbolEstimate> {
self.history.push_back(sample);
self.sample_counter = (self.sample_counter + 1) % ZeroCrossingTed::SAMPLES_PER_SYMBOL;
if self.sample_counter == 1 {
let err = zero_crossing_metric(&self.history);
Some(SymbolEstimate::new(self.history[1], self.history[2], err))
} else {
None
}
}
}
impl Default for ZeroCrossingTed {
fn default() -> Self {
let mut out = ZeroCrossingTed {
history: ArrayDeque::default(),
sample_counter: 0,
};
out.reset();
out
}
}
#[inline]
fn zero_crossing_metric<A>(v: &A) -> f32
where
A: std::ops::Index<usize, Output = f32> + ?Sized,
{
v[1] * (fsk_decision(v[0]) - fsk_decision(v[2]))
}
#[inline]
fn fsk_decision(sym: f32) -> f32 {
sym.signum()
}
fn compute_loop_alphabeta(loop_bandwidth: f32) -> (f32, f32) {
let omega_n_norm = 2.0f32 * std::f32::consts::PI * loop_bandwidth;
let k0 = 2.0f32;
let k1 = f32::exp(-omega_n_norm);
let sinh_zeta_omega_n_t = f32::sinh(omega_n_norm);
let alpha = k0 * k1 * sinh_zeta_omega_n_t;
let beta = k0 * (1.0f32 - k1 * (sinh_zeta_omega_n_t + 1.0f32));
(alpha, beta)
}
#[cfg(test)]
mod tests {
use super::*;
use assert_approx_eq::assert_approx_eq;
use nalgebra::DVector;
fn gen_sinusoid(period: usize) -> DVector<f32> {
let twopi = 2.0f32 * std::f32::consts::PI;
DVector::from_iterator(
period,
(0..period)
.into_iter()
.map(|n| f32::sin(twopi * (n as f32) / (period as f32))),
)
}
#[test]
fn test_zero_crossing_metric() {
const DEAD_ON: &[f32] = &[1.0, 0.0, -1.0];
const DEAD_ON_LOW_LEVEL: &[f32] = &[-1.0, 0.0, 1.0];
const CONSTANT_HIGH: &[f32] = &[1.0, 1.0, 1.0];
const CONSTANT_LOW: &[f32] = &[-1.0, -1.0, -1.0];
const TIMING_EARLY: &[f32] = &[0.8, 0.2, -0.8];
const TIMING_LATE: &[f32] = &[0.8, -0.2, -0.8];
assert_approx_eq!(zero_crossing_metric(DEAD_ON), 0.0f32);
assert_approx_eq!(zero_crossing_metric(DEAD_ON_LOW_LEVEL), 0.0f32);
assert_approx_eq!(zero_crossing_metric(CONSTANT_HIGH), 0.0f32);
assert_approx_eq!(zero_crossing_metric(CONSTANT_LOW), 0.0f32);
assert_approx_eq!(zero_crossing_metric(TIMING_EARLY), 0.4f32);
assert_approx_eq!(zero_crossing_metric(TIMING_LATE), -0.4f32);
}
#[test]
fn test_compute_loop_alphabeta() {
let (alpha, beta) = compute_loop_alphabeta(0.0f32);
assert_approx_eq!(alpha, 0.0f32);
assert_approx_eq!(beta, 0.0f32);
let (alpha, beta) = compute_loop_alphabeta(0.5f32);
assert_approx_eq!(alpha, 0.99813f32, 1.0e-4);
assert_approx_eq!(beta, 0.91544f32, 1.0e-4);
let (alpha, beta) = compute_loop_alphabeta(1.0f32);
assert_approx_eq!(alpha, 1.0f32, 1.0e-4);
assert_approx_eq!(beta, 0.99627f32, 1.0e-4);
}
#[test]
fn test_zero_crossing_ted() {
let mut ted = ZeroCrossingTed::new();
assert!(ted.input(0.8f32).is_some());
assert!(ted.input(0.2f32).is_none());
match ted.input(-0.8f32) {
Some(sym) => {
assert_eq!(-0.8f32, sym.sym());
assert_approx_eq!(0.4f32, sym.err);
}
_ => unreachable!(),
}
assert!(ted.input(0.2f32).is_none());
match ted.input(0.8f32) {
Some(sym) => {
assert_eq!(0.8f32, sym.sym());
assert_approx_eq!(-0.4f32, sym.err);
}
_ => unreachable!(),
}
}
#[test]
fn test_timing_loop_advance() {
let mut timing = TimingLoop::new(32.0f32, 0.25, 0.125f32);
assert_approx_eq!(timing.period_inst, 16.0f32);
assert_approx_eq!(timing.period_max, 16.0f32 + 4.0f32);
assert_approx_eq!(timing.advance_loop(0.0f32, &None), 16.0f32);
assert_approx_eq!(timing.advance_loop(0.5f32, &None), 16.5f32);
assert_approx_eq!(timing.advance_loop(-0.5f32, &None), 16.0f32);
assert_approx_eq!(timing.advance_loop(-0.5f32, &None), 15.5f32);
timing.reset();
assert_approx_eq!(timing.period_inst, 16.0f32);
assert_approx_eq!(
timing.advance_loop(0.0f32, &Some(SymbolEstimate::new(0.0f32, 1.0f32, 0.0f32))),
16.0f32
);
let early = 0.5f32;
assert_approx_eq!(
timing.advance_loop(
early,
&Some(SymbolEstimate::new(0.0f32, 0.95f32, early / 16.0f32))
),
16.5f32
);
assert_approx_eq!(
timing.advance_loop(
-early,
&Some(SymbolEstimate::new(0.0f32, 0.95f32, -early / 16.0f32))
),
15.5f32
);
}
fn timing_test(
timing: &mut TimingLoop,
inp: &DVector<f32>,
start_sample: usize,
verbose: bool,
) -> SymbolEstimate {
let mut offset = 0.0f32;
let mut sa = start_sample;
let mut last_sym = SymbolEstimate::default();
timing.reset();
for _i in 0..128 {
let (skip, sym) = timing.input(inp[sa], offset);
let whole = skip.round();
offset = skip - whole;
if verbose {
println!("skip: {}, whole: {}, offset: {}", skip, whole, offset);
}
sa += whole as usize;
sa = sa % inp.len();
if let Some(s) = sym {
last_sym = s;
if verbose {
println!("{:?}", last_sym);
}
}
}
last_sym
}
#[test]
fn test_timing_loop_bestcase() {
const SAMPLES_PER_SYMBOL: usize = 32;
let inp = gen_sinusoid(2 * SAMPLES_PER_SYMBOL);
assert_eq!(2 * SAMPLES_PER_SYMBOL, inp.len());
assert_approx_eq!(0.0f32, inp[0]);
assert_approx_eq!(1.0f32, inp[16]);
assert_approx_eq!(-1.0f32, inp[48]);
let mut timing = TimingLoop::new(SAMPLES_PER_SYMBOL as f32, 0.25, 0.125f32);
let last_sym = timing_test(&mut timing, &inp, 16, false);
assert!(last_sym.sym().abs() > 0.99);
assert!(last_sym.err < 1e-4);
}
#[test]
fn test_timing_loop_nearworst() {
const SAMPLES_PER_SYMBOL: usize = 32;
let inp = gen_sinusoid(2 * SAMPLES_PER_SYMBOL);
let mut timing = TimingLoop::new(SAMPLES_PER_SYMBOL as f32, 0.25, 0.125f32);
let last_sym = timing_test(&mut timing, &inp, 15, false);
assert!(last_sym.sym().abs() > 0.99);
assert!(last_sym.err < 1e-4);
}
#[test]
fn test_timing_loop_worstcase() {
const SAMPLES_PER_SYMBOL: usize = 32;
let inp = gen_sinusoid(2 * SAMPLES_PER_SYMBOL);
let mut timing = TimingLoop::new(SAMPLES_PER_SYMBOL as f32, 0.25, 0.125f32);
let last_sym = timing_test(&mut timing, &inp, 0, false);
assert!(last_sym.sym().abs() > 0.99);
assert!(last_sym.err < 1e-4);
}
#[test]
fn test_timing_loop_misc() {
const SAMPLES_PER_SYMBOL: usize = 32;
let inp = gen_sinusoid(2 * SAMPLES_PER_SYMBOL);
let mut timing = TimingLoop::new(SAMPLES_PER_SYMBOL as f32, 0.20, 0.125f32);
let last_sym = timing_test(&mut timing, &inp, 16, false);
assert!(last_sym.sym().abs() > 0.99);
assert!(last_sym.err < 1e-4);
let mut timing = TimingLoop::new(SAMPLES_PER_SYMBOL as f32, 0.05, 0.125f32);
let last_sym = timing_test(&mut timing, &inp, 3, false);
assert!(last_sym.sym().abs() > 0.99);
assert!(last_sym.err < 1e-4);
}
}