use crate::modulate::OfdmConfig;
use num_complex::Complex32 as C32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OfdmPreamble {
pub num_repeats: usize,
pub repeat_len: usize,
}
impl OfdmPreamble {
pub fn new(num_repeats: usize, repeat_len: usize) -> Self {
Self {
num_repeats,
repeat_len,
}
}
pub fn total_len(&self) -> usize {
self.num_repeats * self.repeat_len
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct OfdmSyncResult {
pub start_sample: usize,
pub cfo_hz: f32,
pub score: f32,
}
pub fn generate_ofdm_preamble(preamble: &OfdmPreamble, _cfg: &OfdmConfig) -> Vec<C32> {
let base = pseudo_random_unit_sequence(preamble.repeat_len, 0x4F46_444D_5052_4531);
let mut out = Vec::with_capacity(preamble.total_len());
for _ in 0..preamble.num_repeats {
out.extend_from_slice(&base);
}
out
}
fn pseudo_random_unit_sequence(len: usize, seed: u64) -> Vec<C32> {
let mut state = seed;
let mut next_f32 = || -> f32 {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
(state as f32) / (u64::MAX as f32) - 0.5
};
let scale = std::f32::consts::FRAC_1_SQRT_2;
(0..len)
.map(|_| {
let re = if next_f32() >= 0.0 { scale } else { -scale };
let im = if next_f32() >= 0.0 { scale } else { -scale };
C32::new(re, im)
})
.collect()
}
pub fn ofdm_sync(
iq: &[C32],
fs: f32,
preamble: &OfdmPreamble,
search_start: usize,
search_end: usize,
) -> Vec<OfdmSyncResult> {
let repeat_len = preamble.repeat_len;
let num_repeats = preamble.num_repeats;
if repeat_len == 0 || num_repeats < 2 || fs <= 0.0 {
return Vec::new();
}
let preamble_len = preamble.total_len();
let end = search_end.min(iq.len().saturating_sub(preamble_len));
if search_start >= end {
return Vec::new();
}
let mut all = Vec::with_capacity(end - search_start);
let mut r_peak = 0.0f32;
for d in search_start..end {
let mut p = C32::default();
let mut r = 0.0f32;
for seg in 0..num_repeats - 1 {
let a0 = d + seg * repeat_len;
let b0 = a0 + repeat_len;
let (seg_p, seg_r) = correlate_segment(iq, a0, b0, repeat_len);
p += seg_p;
r += seg_r;
}
if r <= 0.0 {
continue;
}
r_peak = r_peak.max(r);
let score = (p.norm_sqr() / (r * r)).clamp(0.0, 1.0);
let cfo_hz = p.im.atan2(p.re) / (core::f32::consts::TAU * repeat_len as f32 / fs);
all.push((
r,
OfdmSyncResult {
start_sample: d,
cfo_hz,
score,
},
));
}
if all.is_empty() || r_peak <= 0.0 {
return Vec::new();
}
let mut results: Vec<OfdmSyncResult> = all
.into_iter()
.map(|(r, mut result)| {
result.score *= r / r_peak;
result
})
.collect();
results.sort_by(|a, b| {
b.score
.partial_cmp(&a.score)
.unwrap_or(std::cmp::Ordering::Equal)
});
results
}
#[inline]
fn correlate_segment(iq: &[C32], a0: usize, b0: usize, len: usize) -> (C32, f32) {
let mut p = C32::default();
let mut r = 0.0f32;
let mut i = 0;
let nn = len & !3;
while i < nn {
p += iq[a0 + i].conj() * iq[b0 + i];
r += iq[b0 + i].norm_sqr();
p += iq[a0 + i + 1].conj() * iq[b0 + i + 1];
r += iq[b0 + i + 1].norm_sqr();
p += iq[a0 + i + 2].conj() * iq[b0 + i + 2];
r += iq[b0 + i + 2].norm_sqr();
p += iq[a0 + i + 3].conj() * iq[b0 + i + 3];
r += iq[b0 + i + 3].norm_sqr();
i += 4;
}
while i < len {
p += iq[a0 + i].conj() * iq[b0 + i];
r += iq[b0 + i].norm_sqr();
i += 1;
}
(p, r)
}