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 ft4_sync_search<P: Protocol>(
cd0: &[Complex<f32>],
candidate: &SyncCandidate,
) -> Sync2dResult {
ft4_sync_search_window::<P>(cd0, candidate, -344, 1012)
}
pub fn ft4_sync_search_window<P: Protocol>(
cd0: &[Complex<f32>],
candidate: &SyncCandidate,
ib_min: i32,
ib_max: i32,
) -> Sync2dResult {
let d = SyncDims::of::<P>();
let ds_spb = d.ds_spb;
let ds_rate = d.ds_rate;
const COARSE_DT_STEP: i32 = 4;
let blocks_ref: Vec<(i32, Vec<Complex<f32>>)> = P::SYNC_MODE
.blocks()
.iter()
.map(|b| {
let off = b.start_symbol as i32 * ds_spb as i32;
(off, make_costas_ref_continuous(b.pattern, ds_spb))
})
.collect();
let score_at = |i0: i32, step: Complex<f32>, has_df: bool| -> f32 {
blocks_ref
.iter()
.map(|(off, flat)| {
let cd0_start = i0 + off;
let np = cd0.len() as i32;
let len = flat.len() as i32;
if cd0_start < 0 || cd0_start + len > np {
return 0.0;
}
let s0 = cd0_start as usize;
if !has_df {
let z: Complex<f32> = cd0[s0..s0 + len as usize]
.iter()
.zip(flat.iter())
.map(|(&c, &r)| c * r.conj())
.sum();
z.norm()
} else {
let mut twid = Complex::new(1.0f32, 0.0f32);
let z: Complex<f32> = cd0[s0..s0 + len as usize]
.iter()
.zip(flat.iter())
.map(|(&c, &r)| {
let val = c * r.conj() * twid;
twid *= step;
val
})
.sum();
z.norm()
}
})
.sum::<f32>()
};
let mut best_df = 0.0f32;
let mut best_i0 = ((candidate.dt_sec + P::TX_START_OFFSET_S) * ds_rate).round() as i32;
let mut best_score = f32::NEG_INFINITY;
let phasor_for = |df: f32| -> (Complex<f32>, bool) {
let has_df = df.abs() >= f32::EPSILON;
let step = if has_df {
let omega = -2.0 * PI * df / ds_rate;
Complex::new(omega.cos(), omega.sin())
} else {
Complex::new(1.0f32, 0.0f32)
};
(step, has_df)
};
let mut idf = -12i32;
while idf <= 12 {
let df = idf as f32;
let (step, has_df) = phasor_for(df);
let mut i0 = ib_min;
while i0 <= ib_max {
let s = score_at(i0, step, has_df);
if s > best_score {
best_score = s;
best_df = df;
best_i0 = i0;
}
i0 += COARSE_DT_STEP;
}
idf += 3;
}
let coarse_winner_df = best_df;
let coarse_winner_i0 = best_i0;
best_score = f32::NEG_INFINITY;
for si in -4i32..=4 {
let df = coarse_winner_df + si as f32;
let (step, has_df) = phasor_for(df);
for di in -5i32..=5 {
let i0 = coarse_winner_i0 + di;
let s = score_at(i0, step, has_df);
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()
}