use num_complex::Complex32 as C32;
use crate::modulate::psk31::{psk31_sps, PSK31_BAUD};
use crate::sync::waterfall::compute_waterfall;
use crate::demodulate::psk31::Bpsk31Demod;
use crate::core::Block;
pub struct Psk31SyncResult {
pub time_sym: usize,
pub freq_bin: usize,
pub carrier_hz: f32,
pub score: f32,
pub soft_bits: Vec<f32>,
}
pub fn psk31_sync(
iq: &[C32],
fs: f32,
base_hz: f32,
max_hz: f32,
min_carrier_syms: usize,
peak_margin_db: f32,
n_bits: usize,
max_cand: usize,
) -> Vec<Psk31SyncResult> {
let sps = psk31_sps(fs);
if sps == 0 || iq.is_empty() {
return Vec::new();
}
let num_syms = iq.len() / sps;
if num_syms == 0 {
return Vec::new();
}
let freq_range = (max_hz - base_hz).max(0.0);
let num_bins = (freq_range / PSK31_BAUD).ceil() as usize + 1;
if num_bins == 0 {
return Vec::new();
}
let wf = compute_waterfall(iq, fs, base_hz, PSK31_BAUD, sps, num_syms, num_bins, 0);
let ln_margin = peak_margin_db * std::f32::consts::LN_2 / 3.0;
let min_run = min_carrier_syms.max(1);
let mut candidates: Vec<Psk31SyncResult> = Vec::new();
for bin in 0..num_bins {
let mut energies: Vec<f32> = (0..num_syms).map(|s| wf.get(s, bin)).collect();
let median = median_f32(&mut energies);
let threshold = median + ln_margin;
let mut run_start: Option<usize> = None;
let mut run_energy_sum = 0.0f32;
let mut run_len = 0usize;
for sym in 0..num_syms {
let e = wf.get(sym, bin);
let e_left = if bin > 0 { wf.get(sym, bin - 1) } else { f32::NEG_INFINITY };
let e_right = if bin + 1 < num_bins { wf.get(sym, bin + 1) } else { f32::NEG_INFINITY };
let is_peak = e > threshold && e >= e_left && e >= e_right;
if is_peak {
if run_start.is_none() {
run_start = Some(sym);
run_energy_sum = 0.0;
run_len = 0;
}
run_energy_sum += e;
run_len += 1;
} else {
if let Some(start) = run_start.take() {
if run_len >= min_run {
record_candidate(
&mut candidates,
start,
bin,
base_hz,
run_energy_sum / run_len as f32,
iq,
fs,
n_bits,
);
}
run_len = 0;
}
}
}
if let Some(start) = run_start.take() {
if run_len >= min_run {
record_candidate(
&mut candidates,
start,
bin,
base_hz,
run_energy_sum / run_len as f32,
iq,
fs,
n_bits,
);
}
}
}
candidates.sort_unstable_by(|a, b| b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal));
candidates.truncate(max_cand);
candidates
}
fn record_candidate(
out: &mut Vec<Psk31SyncResult>,
time_sym: usize,
freq_bin: usize,
base_hz: f32,
score: f32,
iq: &[C32],
fs: f32,
n_bits: usize,
) {
let sps = psk31_sps(fs);
let carrier_hz = base_hz + freq_bin as f32 * PSK31_BAUD;
let start_sample = time_sym * sps;
if start_sample >= iq.len() { return; }
let slice = &iq[start_sample..];
let mut demod = Bpsk31Demod::new(fs, carrier_hz, 1.0);
let mut soft = vec![0.0f32; n_bits + 2];
let wr = demod.process(slice, &mut soft);
soft.truncate(wr.out_written.min(n_bits));
out.push(Psk31SyncResult {
time_sym,
freq_bin,
carrier_hz,
score,
soft_bits: soft,
});
}
fn median_f32(v: &mut [f32]) -> f32 {
if v.is_empty() { return 0.0; }
v.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let mid = v.len() / 2;
if v.len() % 2 == 0 {
(v[mid - 1] + v[mid]) * 0.5
} else {
v[mid]
}
}