use alloc::vec::Vec;
use core::f32::consts::PI;
use num_complex::Complex;
#[cfg(not(feature = "std"))]
use num_traits::Float;
use crate::core::Protocol;
use crate::core::sync::{SyncCandidate, SyncDims, make_costas_ref, score_costas_block};
#[derive(Clone, Debug)]
pub struct Sync2dConfig {
pub coarse_df_half_steps: i32,
pub coarse_df_step_hz: f32,
pub coarse_t_radius: i32,
pub coarse_t_step: i32,
pub fine_df_half_steps: i32,
pub fine_df_step_hz: f32,
pub fine_t_radius: i32,
}
impl Sync2dConfig {
pub fn for_ft4() -> Self {
Self {
coarse_df_half_steps: 4, coarse_df_step_hz: 3.0,
coarse_t_radius: 20,
coarse_t_step: 4,
fine_df_half_steps: 4, fine_df_step_hz: 1.0,
fine_t_radius: 5,
}
}
}
#[derive(Clone, Debug)]
pub struct Sync2dResult {
pub freq_hz: f32,
pub i0: i32,
pub score: f32,
}
fn twiddle_ref(csync: &[Vec<Complex<f32>>], df_hz: f32, ds_rate: f32) -> Vec<Vec<Complex<f32>>> {
if df_hz.abs() < f32::EPSILON {
return csync.to_vec();
}
let omega = 2.0 * PI * df_hz / ds_rate;
csync
.iter()
.enumerate()
.map(|(k, tone_wave)| {
let ds_spb = tone_wave.len();
let mut out = alloc::vec![Complex::new(0.0f32, 0.0); ds_spb];
for (j, slot) in out.iter_mut().enumerate() {
let n = (k * ds_spb + j) as f32;
let p = omega * n;
let twid = Complex::new(p.cos(), p.sin());
*slot = tone_wave[j] * twid;
}
out
})
.collect()
}
fn score_at<P: Protocol>(
cd0: &[Complex<f32>],
blocks_costas: &[(u32, Vec<Vec<Complex<f32>>>)],
df_hz: f32,
i0: i32,
ds_spb: usize,
ds_rate: f32,
) -> f32 {
let _ = P::NTONES; let mut total = 0.0f32;
for (start_sym, csync) in blocks_costas {
let twiddled = twiddle_ref(csync, df_hz, ds_rate);
let off = i0 + (*start_sym as i32) * ds_spb as i32;
total += score_costas_block(cd0, &twiddled, ds_spb, off);
}
total
}
pub fn sync2d_refine<P: Protocol>(
cd0: &[Complex<f32>],
candidate: &SyncCandidate,
cfg: &Sync2dConfig,
) -> Sync2dResult {
let d = SyncDims::of::<P>();
let ds_spb = d.ds_spb;
let ds_rate = d.ds_rate;
let init_i0 = ((candidate.dt_sec + P::TX_START_OFFSET_S) * ds_rate).round() as i32;
let blocks_costas: Vec<(u32, Vec<Vec<Complex<f32>>>)> = P::SYNC_MODE
.blocks()
.iter()
.map(|b| (b.start_symbol, make_costas_ref(b.pattern, ds_spb)))
.collect();
let mut best_df = 0.0f32;
let mut best_i0 = init_i0;
let mut best_score = f32::NEG_INFINITY;
for si in -cfg.coarse_df_half_steps..=cfg.coarse_df_half_steps {
let df = si as f32 * cfg.coarse_df_step_hz;
let mut di = -cfg.coarse_t_radius;
while di <= cfg.coarse_t_radius {
let i0 = init_i0 + di;
let s = score_at::<P>(cd0, &blocks_costas, df, i0, ds_spb, ds_rate);
if s > best_score {
best_score = s;
best_df = df;
best_i0 = i0;
}
di += cfg.coarse_t_step;
}
}
let coarse_winner_df = best_df;
let coarse_winner_i0 = best_i0;
for si in -cfg.fine_df_half_steps..=cfg.fine_df_half_steps {
let df = coarse_winner_df + si as f32 * cfg.fine_df_step_hz;
let mut di = -cfg.fine_t_radius;
while di <= cfg.fine_t_radius {
let i0 = coarse_winner_i0 + di;
let s = score_at::<P>(cd0, &blocks_costas, df, i0, ds_spb, ds_rate);
if s > best_score {
best_score = s;
best_df = df;
best_i0 = i0;
}
di += 1;
}
}
Sync2dResult {
freq_hz: candidate.freq_hz + best_df,
i0: best_i0,
score: best_score,
}
}
fn make_costas_ref_continuous(pattern: &[u8], ds_spb: usize) -> Vec<Complex<f32>> {
let mut out = Vec::with_capacity(pattern.len() * ds_spb);
let mut phi = 0.0f64;
for &tone in pattern {
let dphi = core::f64::consts::TAU * (tone as f64) / (ds_spb as f64);
for _ in 0..ds_spb {
out.push(Complex::new(phi.cos() as f32, phi.sin() as f32));
phi += dphi;
}
}
out
}
fn twiddle_flat_ref(flat_ref: &[Complex<f32>], df_hz: f32, ds_rate: f32) -> Vec<Complex<f32>> {
if df_hz.abs() < f32::EPSILON {
return flat_ref.to_vec();
}
let omega = 2.0 * PI * df_hz / ds_rate;
flat_ref
.iter()
.enumerate()
.map(|(n, &r)| {
let p = omega * n as f32;
r * Complex::new(p.cos(), p.sin())
})
.collect()
}
fn score_flat_coherent(cd0: &[Complex<f32>], flat_ref: &[Complex<f32>], cd0_start: i32) -> f32 {
let np = cd0.len() as i32;
let len = flat_ref.len() as i32;
if cd0_start < 0 || cd0_start + len > np {
return 0.0;
}
let s0 = cd0_start as usize;
let z: Complex<f32> = cd0[s0..s0 + len as usize]
.iter()
.zip(flat_ref.iter())
.map(|(&c, &r)| c * r.conj())
.sum();
z.norm()
}
pub fn fst4_sync_search<P: Protocol>(
cd0: &[Complex<f32>],
candidate: &SyncCandidate,
) -> Sync2dResult {
let d = SyncDims::of::<P>();
let ds_spb = d.ds_spb;
let ds_rate = d.ds_rate;
let baud = P::TONE_SPACING_HZ;
let init_i0 = ((candidate.dt_sec + P::TX_START_OFFSET_S) * ds_rate).round() as i32;
let ishw = (1.5 * ds_rate as f64).floor() as i32;
let flat_blocks: Vec<(i32, Vec<Complex<f32>>)> = P::SYNC_MODE
.blocks()
.iter()
.map(|b| {
let off = b.start_symbol as i32 * ds_spb as i32;
let flat = make_costas_ref_continuous(b.pattern, ds_spb);
(off, flat)
})
.collect();
let score_flat = |twiddled: &Vec<(i32, Vec<Complex<f32>>)>, i0: i32| -> f32 {
twiddled
.iter()
.map(|(off, flat)| score_flat_coherent(cd0, flat, i0 + off))
.sum::<f32>()
};
let mut best_df = 0.0f32;
let mut best_i0 = init_i0;
let mut best_score = f32::NEG_INFINITY;
for si in -12i32..=12 {
let df = si as f32 * 0.1 * baud;
let twiddled: Vec<(i32, Vec<Complex<f32>>)> = flat_blocks
.iter()
.map(|(off, flat)| (*off, twiddle_flat_ref(flat, df, ds_rate)))
.collect();
let mut di = -ishw;
while di <= ishw {
let i0 = init_i0 + di;
let s = score_flat(&twiddled, i0);
if s > best_score {
best_score = s;
best_df = df;
best_i0 = i0;
}
di += 4;
}
}
let coarse_winner_df = best_df;
let coarse_winner_i0 = best_i0;
best_score = 0.0;
for si in -7i32..=7 {
let df = coarse_winner_df + si as f32 * 0.02 * baud;
let twiddled: Vec<(i32, Vec<Complex<f32>>)> = flat_blocks
.iter()
.map(|(off, flat)| (*off, twiddle_flat_ref(flat, df, ds_rate)))
.collect();
for di in -4i32..=4 {
let i0 = coarse_winner_i0 + di;
let s = score_flat(&twiddled, i0);
if s > best_score {
best_score = s;
best_df = df;
best_i0 = i0;
}
}
}
Sync2dResult {
freq_hz: candidate.freq_hz + best_df,
i0: best_i0,
score: best_score,
}
}
pub fn freq_shift_cd0(cd0: &[Complex<f32>], df_hz: f32, ds_rate: f32) -> Vec<Complex<f32>> {
if df_hz.abs() < f32::EPSILON {
return cd0.to_vec();
}
let omega = -2.0 * PI * df_hz / ds_rate;
cd0.iter()
.enumerate()
.map(|(n, &c)| {
let p = omega * n as f32;
c * Complex::new(p.cos(), p.sin())
})
.collect()
}