use std::f32::consts::PI;
fn design_rrc_filter(k: usize, m: usize, beta: f32) -> Vec<f32> {
let h_len = 2 * k * m + 1;
let mut h = vec![0.0f32; h_len];
let kf = k as f32;
let mf = m as f32;
for (n, coef) in h.iter_mut().enumerate() {
let nf = n as f32;
let z = nf / kf - mf;
if z.abs() < 1e-5 {
*coef = 1.0 - beta + 4.0 * beta / PI;
} else {
let g = 1.0 - 16.0 * beta * beta * z * z;
let g_squared = g * g;
if g_squared < 1e-5 {
let g1 = 1.0 + 2.0 / PI;
let g2 = (0.25 * PI / beta).sin();
let g3 = 1.0 - 2.0 / PI;
let g4 = (0.25 * PI / beta).cos();
*coef = beta / 2.0_f32.sqrt() * (g1 * g2 + g3 * g4);
} else {
let t1 = ((1.0 + beta) * PI * z).cos();
let t2 = ((1.0 - beta) * PI * z).sin();
let t3 = 1.0 / (4.0 * beta * z);
let t4 = 4.0 * beta / (PI * (1.0 - 16.0 * beta * beta * z * z));
*coef = t4 * (t1 + t2 * t3);
}
}
}
h
}
fn compute_derivative_filter(h: &[f32]) -> Vec<f32> {
let h_len = h.len();
let mut dh = vec![0.0f32; h_len];
for i in 0..h_len {
if i == 0 {
dh[i] = h[i + 1] - h[h_len - 1];
} else if i == h_len - 1 {
dh[i] = h[0] - h[i - 1];
} else {
dh[i] = h[i + 1] - h[i - 1];
}
}
let hdh_max = h
.iter()
.zip(dh.iter())
.map(|(a, b)| (a * b).abs())
.fold(0.0f32, f32::max);
if hdh_max > 1e-10 {
let scale = 0.06 / hdh_max;
for coef in &mut dh {
*coef *= scale;
}
}
dh
}
#[derive(Debug, Clone)]
struct PolyphaseFilterBank {
npfb: usize,
h_sub_len: usize,
filters: Vec<Vec<f32>>,
buffer_i: Vec<f32>,
buffer_q: Vec<f32>,
buf_idx: usize,
}
impl PolyphaseFilterBank {
fn new(h: &[f32], npfb: usize) -> Self {
let h_len = h.len();
let h_sub_len = h_len.div_ceil(npfb);
let mut filters = vec![vec![0.0f32; h_sub_len]; npfb];
for (i, &coef) in h.iter().enumerate() {
let phase = i % npfb;
let idx = i / npfb;
if idx < h_sub_len {
filters[phase][idx] = coef;
}
}
let buffer_len = h_sub_len;
Self {
npfb,
h_sub_len,
filters,
buffer_i: vec![0.0; buffer_len],
buffer_q: vec![0.0; buffer_len],
buf_idx: 0,
}
}
fn push(&mut self, i: f32, q: f32) {
self.buffer_i[self.buf_idx] = i;
self.buffer_q[self.buf_idx] = q;
self.buf_idx = (self.buf_idx + 1) % self.h_sub_len;
}
fn execute(&self, phase: usize) -> (f32, f32) {
let filter = &self.filters[phase.min(self.npfb - 1)];
let mut sum_i = 0.0f32;
let mut sum_q = 0.0f32;
for (j, &coef) in filter.iter().enumerate() {
let idx = (self.buf_idx + self.h_sub_len - 1 - j) % self.h_sub_len;
sum_i += self.buffer_i[idx] * coef;
sum_q += self.buffer_q[idx] * coef;
}
(sum_i, sum_q)
}
fn reset(&mut self) {
self.buffer_i.fill(0.0);
self.buffer_q.fill(0.0);
self.buf_idx = 0;
}
}
#[derive(Debug, Clone)]
pub struct SymSync {
k: usize,
npfb: usize,
mf: PolyphaseFilterBank,
dmf: PolyphaseFilterBank,
tau: f32,
bf: f32,
b: i32,
rate: f32,
del: f32,
q: f32,
q_hat: f32,
bandwidth: f32,
rate_adjustment: f32,
alpha: f32,
is_locked: bool,
}
impl SymSync {
pub fn new(k: usize, npfb: usize, h: &[f32], bandwidth: f32) -> Self {
let dh = compute_derivative_filter(h);
let mf = PolyphaseFilterBank::new(h, npfb);
let dmf = PolyphaseFilterBank::new(&dh, npfb);
let rate = k as f32;
let alpha = 1.0 - bandwidth;
let rate_adjustment = 0.5 * bandwidth;
Self {
k,
npfb,
mf,
dmf,
tau: 0.0,
bf: 0.0,
b: 0,
rate,
del: rate,
q: 0.0,
q_hat: 0.0,
bandwidth,
rate_adjustment,
alpha,
is_locked: false,
}
}
pub fn new_rnyquist(k: usize, npfb: usize, m: usize, beta: f32, bandwidth: f32) -> Self {
let h = design_rrc_filter(k * npfb, m, beta);
Self::new(k, npfb, &h, bandwidth)
}
pub fn set_bandwidth(&mut self, bandwidth: f32) {
self.bandwidth = bandwidth.clamp(0.0, 1.0);
self.alpha = 1.0 - self.bandwidth;
self.rate_adjustment = 0.5 * self.bandwidth;
}
pub fn get_tau(&self) -> f32 {
self.tau
}
pub fn get_rate(&self) -> f32 {
self.rate
}
pub fn lock(&mut self) {
self.is_locked = true;
}
pub fn unlock(&mut self) {
self.is_locked = false;
}
pub fn is_locked(&self) -> bool {
self.is_locked
}
pub fn reset(&mut self) {
self.mf.reset();
self.dmf.reset();
self.tau = 0.0;
self.bf = 0.0;
self.b = 0;
self.rate = self.k as f32;
self.del = self.rate;
self.q = 0.0;
self.q_hat = 0.0;
}
pub fn push(&mut self, i: f32, q: f32) -> Option<(f32, f32)> {
self.mf.push(i, q);
self.dmf.push(i, q);
if self.b < self.npfb as i32 {
let (mf_i, mf_q) = self.mf.execute(self.b as usize);
let out_i = mf_i / self.k as f32;
let out_q = mf_q / self.k as f32;
if !self.is_locked {
let (dmf_i, dmf_q) = self.dmf.execute(self.b as usize);
self.q = mf_i * dmf_i + mf_q * dmf_q;
self.q = self.q.clamp(-1.0, 1.0);
self.q_hat = self.alpha * self.q_hat + (1.0 - self.alpha) * self.q;
self.rate += self.rate_adjustment * self.q_hat;
self.del = self.rate + self.q_hat;
}
self.tau += self.del;
self.bf = self.tau * self.npfb as f32;
self.b = self.bf.round() as i32;
self.tau -= 1.0;
self.bf -= self.npfb as f32;
self.b -= self.npfb as i32;
return Some((out_i, out_q));
}
self.tau -= 1.0;
self.bf -= self.npfb as f32;
self.b -= self.npfb as i32;
None
}
pub fn execute(&mut self, input: &[(f32, f32)]) -> Vec<(f32, f32)> {
let mut output = Vec::with_capacity(input.len() / self.k + 1);
for &(i, q) in input {
if let Some(sym) = self.push(i, q) {
output.push(sym);
}
}
output
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rrc_filter_design() {
let h = design_rrc_filter(4, 3, 0.35);
assert_eq!(h.len(), 25);
let mid = h.len() / 2;
for i in 0..mid {
assert!(
(h[i] - h[h.len() - 1 - i]).abs() < 0.01,
"Filter not symmetric at index {}",
i
);
}
}
#[test]
fn test_derivative_filter() {
let h = vec![1.0, 2.0, 3.0, 2.0, 1.0];
let dh = compute_derivative_filter(&h);
assert_eq!(dh.len(), h.len());
}
#[test]
fn test_polyphase_filterbank_creation() {
let h = design_rrc_filter(3 * 32, 3, 0.8);
let pfb = PolyphaseFilterBank::new(&h, 32);
assert_eq!(pfb.npfb, 32);
assert_eq!(pfb.filters.len(), 32);
}
#[test]
fn test_symsync_creation() {
let ss = SymSync::new_rnyquist(3, 32, 3, 0.8, 0.01);
assert_eq!(ss.k, 3);
assert_eq!(ss.npfb, 32);
assert!(!ss.is_locked());
}
#[test]
fn test_symsync_basic_operation() {
let mut ss = SymSync::new_rnyquist(3, 32, 3, 0.8, 0.01);
let mut output_count = 0;
for i in 0..100 {
let phase = 2.0 * PI * (i as f32) / 3.0;
let sample_i = phase.cos();
let sample_q = phase.sin();
if ss.push(sample_i, sample_q).is_some() {
output_count += 1;
}
}
assert!(
output_count > 20,
"Expected ~33 outputs, got {}",
output_count
);
assert!(
output_count < 50,
"Expected ~33 outputs, got {}",
output_count
);
}
#[test]
fn test_symsync_lock() {
let mut ss = SymSync::new_rnyquist(3, 32, 3, 0.8, 0.01);
ss.lock();
assert!(ss.is_locked());
ss.unlock();
assert!(!ss.is_locked());
}
#[test]
fn test_symsync_reset() {
let mut ss = SymSync::new_rnyquist(3, 32, 3, 0.8, 0.01);
for _ in 0..10 {
ss.push(1.0, 0.0);
}
ss.reset();
assert_eq!(ss.tau, 0.0);
assert_eq!(ss.b, 0);
}
#[test]
fn test_symsync_bandwidth() {
let mut ss = SymSync::new_rnyquist(3, 32, 3, 0.8, 0.01);
ss.set_bandwidth(0.05);
assert!((ss.bandwidth - 0.05).abs() < 1e-6);
ss.set_bandwidth(2.0);
assert!((ss.bandwidth - 1.0).abs() < 1e-6);
ss.set_bandwidth(-0.5);
assert!((ss.bandwidth - 0.0).abs() < 1e-6);
}
}