use num_complex::Complex32 as C32;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GiSyncConfig {
pub rho: f32,
pub max_symbols: usize,
pub origin_score_ratio: f32,
}
impl Default for GiSyncConfig {
fn default() -> Self {
Self {
rho: 0.95,
max_symbols: 4,
origin_score_ratio: 0.5,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GiSyncResult {
pub start_sample: usize,
pub cfo_hz: f32,
pub score: f32,
}
pub fn dvb_t_gi_sync(
iq: &[C32],
n_fft: usize,
cp_len: usize,
fs: f32,
search_len: usize,
) -> Option<GiSyncResult> {
dvb_t_gi_sync_with(iq, n_fft, cp_len, fs, search_len, &GiSyncConfig::default())
}
pub fn dvb_t_gi_sync_with(
iq: &[C32],
n_fft: usize,
cp_len: usize,
fs: f32,
search_len: usize,
cfg: &GiSyncConfig,
) -> Option<GiSyncResult> {
if cp_len == 0 || n_fft == 0 {
return None;
}
let need = search_len.saturating_sub(1) + n_fft + cp_len;
if iq.len() < need || search_len == 0 {
return None;
}
let period = n_fft + cp_len;
let max_syms = cfg.max_symbols.max(1);
let mut scored: Vec<(f32, C32, f32)> = Vec::with_capacity(search_len);
let mut max_metric = f32::NEG_INFINITY;
let mut min_metric = f32::INFINITY;
for d in 0..search_len {
let mut gamma = C32::default();
let mut phi = 0.0f32;
let mut base = d;
let mut used = 0usize;
while used < max_syms && base + n_fft + cp_len <= iq.len() {
for k in 0..cp_len {
let a = iq[base + k];
let b = iq[base + n_fft + k];
gamma += a * b.conj();
phi += a.norm_sqr() + b.norm_sqr();
}
base += period;
used += 1;
}
phi *= 0.5;
let metric = gamma.norm() - cfg.rho * phi;
max_metric = max_metric.max(metric);
min_metric = min_metric.min(metric);
scored.push((metric, gamma, phi));
}
let argmax = scored
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.0.total_cmp(&b.0))
.map(|(d, _)| d)
.unwrap_or(0);
let single_score = |d: usize| {
if d + n_fft + cp_len > iq.len() {
return 0.0;
}
let mut gamma = C32::default();
let mut phi = 0.0f32;
for k in 0..cp_len {
let a = iq[d + k];
let b = iq[d + n_fft + k];
gamma += a * b.conj();
phi += a.norm_sqr() + b.norm_sqr();
}
phi *= 0.5;
if phi > 0.0 {
(gamma.norm() / phi).min(1.0)
} else {
0.0
}
};
let phase = argmax % period;
let origin = argmax - phase; let best_d = if cfg.origin_score_ratio > 0.0
&& phase != 0
&& period - phase <= cp_len.div_ceil(2)
&& single_score(origin) >= cfg.origin_score_ratio.clamp(0.0, 1.0) * single_score(argmax)
{
origin
} else {
argmax
};
let (_, best_gamma, best_phi) = scored[best_d];
let score = if best_phi > 0.0 {
(best_gamma.norm() / best_phi).min(1.0)
} else {
0.0
};
let cfo_hz = -best_gamma.im.atan2(best_gamma.re) * fs / (core::f32::consts::TAU * n_fft as f32);
Some(GiSyncResult {
start_sample: best_d,
cfo_hz,
score,
})
}
pub fn dvb_t_gi_refine(
iq: &[C32],
n_fft: usize,
cp_len: usize,
fs: f32,
coarse: usize,
radius: usize,
) -> Option<GiSyncResult> {
dvb_t_gi_refine_with(
iq,
n_fft,
cp_len,
fs,
coarse,
radius,
&GiSyncConfig::default(),
)
}
pub fn dvb_t_gi_refine_with(
iq: &[C32],
n_fft: usize,
cp_len: usize,
fs: f32,
coarse: usize,
radius: usize,
cfg: &GiSyncConfig,
) -> Option<GiSyncResult> {
let start = coarse.saturating_sub(radius);
let span = 2 * radius + 1;
let sub = iq.get(start..)?;
let local = GiSyncConfig {
origin_score_ratio: 0.0,
..*cfg
};
let mut r = dvb_t_gi_sync_with(sub, n_fft, cp_len, fs, span.min(sub.len()), &local)?;
r.start_sample += start;
Some(r)
}
use crate::waveform::dvb_t::continual_pilot_bins;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct IntegerCfoResult {
pub bins: i32,
pub confidence: f32,
}
pub fn dvb_t_integer_cfo(freq: &[C32], n_fft: usize, max_bins: i32) -> Option<IntegerCfoResult> {
if freq.len() < n_fft || n_fft == 0 || max_bins <= 0 {
return None;
}
let pilot_bins = continual_pilot_bins();
let energy_at = |k: i32| -> f32 {
pilot_bins
.iter()
.map(|&b| {
let idx = (b as i32 + k).rem_euclid(n_fft as i32) as usize;
freq[idx].norm_sqr()
})
.sum()
};
let mut best_k = 0i32;
let mut best_energy = f32::NEG_INFINITY;
let mut sum_energy = 0.0f32;
let mut count = 0u32;
for k in -max_bins..=max_bins {
let e = energy_at(k);
sum_energy += e;
count += 1;
if e > best_energy {
best_energy = e;
best_k = k;
}
}
let mean = sum_energy / count as f32;
let confidence = if mean > 0.0 { best_energy / mean } else { 0.0 };
Some(IntegerCfoResult {
bins: best_k,
confidence,
})
}