use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
use num_complex::Complex;
#[cfg(not(feature = "std"))]
use num_traits::Float;
use super::dsp::downsample::{DownsampleCfg, build_fft_cache, downsample_cached};
use super::dsp::subtract::{SubtractCfg, subtract_tones};
use super::equalize::{EqMode, equalize_local};
use super::llr::{compute_llr, compute_snr_db, symbol_spectra, sync_quality};
use super::sync::{SyncCandidate, coarse_sync, fine_sync_power_per_block, refine_candidate};
use super::tx::codeword_to_itone;
use super::{FecCodec, FecOpts, MessageCodec, Protocol};
pub type FftCache = Vec<Complex<f32>>;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DecodeDepth {
Bp,
BpAll,
BpAllOsd,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum DecodeStrictness {
Strict,
#[default]
Normal,
Deep,
}
impl DecodeStrictness {
pub fn osd_max_errors(self, osd_depth: u8) -> u32 {
match (self, osd_depth) {
(Self::Strict, 3) => 20,
(Self::Strict, 4) => 24,
(Self::Strict, _) => 22,
(Self::Normal, 3) => 26,
(Self::Normal, 4) => 30,
(Self::Normal, _) => 29,
(Self::Deep, 3) => 30,
(Self::Deep, 4) => 36,
(Self::Deep, _) => 40,
}
}
pub fn osd_score_min(self) -> f32 {
match self {
Self::Strict => 3.0,
Self::Normal => 2.2,
Self::Deep => 2.0,
}
}
}
#[derive(Debug, Clone)]
pub struct DecodeResult {
pub info: Box<[u8]>,
pub freq_hz: f32,
pub dt_sec: f32,
pub hard_errors: u32,
pub sync_score: f32,
pub pass: u8,
pub sync_cv: f32,
pub snr_db: f32,
}
impl DecodeResult {
pub fn message77(&self) -> &[u8] {
&self.info[..77]
}
}
pub fn process_candidate_basic<P: Protocol>(
cand: &SyncCandidate,
fft_cache: &[Complex<f32>],
cfg: &DownsampleCfg,
depth: DecodeDepth,
strictness: DecodeStrictness,
known: &[DecodeResult],
eq_mode: EqMode,
refine_steps: i32,
sync_q_min: u32,
) -> Option<DecodeResult> {
let ntones = P::NTONES as usize;
let n_sym = P::N_SYMBOLS as usize;
let ds_rate = 12_000.0 / P::NDOWN as f32;
let tx_start = P::TX_START_OFFSET_S;
let cd0 = downsample_cached(fft_cache, cand.freq_hz, cfg);
let refined = refine_candidate::<P>(&cd0, cand, refine_steps);
let i_start = ((refined.dt_sec + tx_start) * ds_rate).round() as usize;
let cs_raw = symbol_spectra::<P>(&cd0, i_start);
let nsync = sync_quality::<P>(&cs_raw);
if nsync <= sync_q_min {
return None;
}
let per_block = fine_sync_power_per_block::<P>(&cd0, i_start);
let sync_cv = if !per_block.is_empty() {
let n = per_block.len() as f32;
let mean = per_block.iter().sum::<f32>() / n;
if mean > f32::EPSILON {
let var = per_block.iter().map(|&x| (x - mean).powi(2)).sum::<f32>() / n;
var.sqrt() / mean
} else {
0.0
}
} else {
0.0
};
let _ = ntones;
let _ = n_sym;
let decode = |cs: &[Complex<f32>]| -> Option<DecodeResult> {
let mut llr_set = compute_llr::<P>(cs);
deinterleave_llr_set::<P>(&mut llr_set);
let variants = match depth {
DecodeDepth::Bp => vec![(&llr_set.llra, 0u8)],
DecodeDepth::BpAll | DecodeDepth::BpAllOsd => vec![
(&llr_set.llra, 0),
(&llr_set.llrb, 1),
(&llr_set.llrc, 2),
(&llr_set.llrd, 3),
],
};
let fec = P::Fec::default();
let bp_opts = FecOpts {
bp_max_iter: 30,
osd_depth: 0,
ap_mask: None,
verify_info: Some(<P::Msg as MessageCodec>::verify_info),
};
for (llr, pass_id) in &variants {
if let Some(r) = fec.decode_soft(llr, &bp_opts) {
let itone = encode_tones_for_snr::<P>(&r.info, &fec);
let snr_db = compute_snr_db::<P>(cs, &itone);
return Some(DecodeResult {
info: r.info.into_boxed_slice(),
freq_hz: cand.freq_hz,
dt_sec: refined.dt_sec,
hard_errors: r.hard_errors,
sync_score: refined.score,
pass: *pass_id,
sync_cv,
snr_db,
});
}
}
if depth == DecodeDepth::BpAllOsd && nsync >= 12 && cand.score >= strictness.osd_score_min()
{
let freq_dup = known
.iter()
.any(|r| (r.freq_hz - cand.freq_hz).abs() < 20.0);
if !freq_dup {
let osd_depth: u8 = if nsync >= 18 { 3 } else { 2 };
let osd_opts = FecOpts {
bp_max_iter: 30,
osd_depth: osd_depth as u32,
ap_mask: None,
verify_info: Some(<P::Msg as MessageCodec>::verify_info),
};
for (llr, _) in &variants {
if let Some(r) = fec.decode_soft(llr, &osd_opts) {
if r.hard_errors >= strictness.osd_max_errors(osd_depth) {
continue;
}
let itone = encode_tones_for_snr::<P>(&r.info, &fec);
let snr_db = compute_snr_db::<P>(cs, &itone);
return Some(DecodeResult {
info: r.info.into_boxed_slice(),
freq_hz: cand.freq_hz,
dt_sec: refined.dt_sec,
hard_errors: r.hard_errors,
sync_score: refined.score,
pass: if osd_depth == 3 { 5 } else { 4 },
sync_cv,
snr_db,
});
}
}
if nsync >= 18 {
let osd4_opts = FecOpts {
bp_max_iter: 30,
osd_depth: 4,
ap_mask: None,
verify_info: Some(<P::Msg as MessageCodec>::verify_info),
};
for (llr, _) in &variants {
if let Some(r) = fec.decode_soft(llr, &osd4_opts) {
if r.hard_errors >= strictness.osd_max_errors(4) {
continue;
}
let itone = encode_tones_for_snr::<P>(&r.info, &fec);
let snr_db = compute_snr_db::<P>(cs, &itone);
return Some(DecodeResult {
info: r.info.into_boxed_slice(),
freq_hz: cand.freq_hz,
dt_sec: refined.dt_sec,
hard_errors: r.hard_errors,
sync_score: refined.score,
pass: 13,
sync_cv,
snr_db,
});
}
}
}
}
}
None
};
match eq_mode {
EqMode::Off => decode(&cs_raw),
EqMode::Local => {
let mut cs_eq = cs_raw.clone();
equalize_local::<P>(&mut cs_eq);
decode(&cs_eq)
}
EqMode::Adaptive => {
let mut cs_eq = cs_raw.clone();
equalize_local::<P>(&mut cs_eq);
if let Some(r) = decode(&cs_eq) {
return Some(r);
}
decode(&cs_raw)
}
}
}
fn deinterleave_llr_set<P: Protocol>(set: &mut crate::core::llr::LlrSet) {
if let Some(table) = P::CODEWORD_INTERLEAVE {
deinterleave_llr_vec(&mut set.llra, table);
deinterleave_llr_vec(&mut set.llrb, table);
deinterleave_llr_vec(&mut set.llrc, table);
deinterleave_llr_vec(&mut set.llrd, table);
}
}
fn deinterleave_llr_vec(llr: &mut [f32], table: &[u16]) {
debug_assert_eq!(
llr.len(),
table.len(),
"interleave table length must match LLR length"
);
let original: Vec<f32> = llr.to_vec();
for j in 0..llr.len() {
llr[table[j] as usize] = original[j];
}
}
fn encode_tones_for_snr<P: Protocol>(info: &[u8], fec: &P::Fec) -> Vec<u8> {
let mut cw = vec![0u8; P::Fec::N];
fec.encode(info, &mut cw);
codeword_to_itone::<P>(&cw)
}
pub fn decode_frame<P: Protocol>(
audio: &[i16],
cfg: &DownsampleCfg,
freq_min: f32,
freq_max: f32,
sync_min: f32,
freq_hint: Option<f32>,
depth: DecodeDepth,
max_cand: usize,
strictness: DecodeStrictness,
eq_mode: EqMode,
refine_steps: i32,
sync_q_min: u32,
) -> (Vec<DecodeResult>, FftCache) {
let candidates = coarse_sync::<P>(audio, freq_min, freq_max, sync_min, freq_hint, max_cand);
let fft_cache = build_fft_cache(audio, cfg);
if candidates.is_empty() {
return (Vec::new(), fft_cache);
}
#[cfg(feature = "parallel")]
let raw: Vec<DecodeResult> = candidates
.par_iter()
.filter_map(|cand| {
process_candidate_basic::<P>(
cand,
&fft_cache,
cfg,
depth,
strictness,
&[],
eq_mode,
refine_steps,
sync_q_min,
)
})
.collect();
#[cfg(not(feature = "parallel"))]
let raw: Vec<DecodeResult> = candidates
.iter()
.filter_map(|cand| {
process_candidate_basic::<P>(
cand,
&fft_cache,
cfg,
depth,
strictness,
&[],
eq_mode,
refine_steps,
sync_q_min,
)
})
.collect();
let mut results: Vec<DecodeResult> = Vec::new();
for r in raw {
if !results.iter().any(|x| x.info == r.info) {
results.push(r);
}
}
(results, fft_cache)
}
pub fn decode_frame_subtract<P: Protocol>(
audio: &[i16],
ds_cfg: &DownsampleCfg,
sub_cfg: &SubtractCfg,
freq_min: f32,
freq_max: f32,
sync_min: f32,
freq_hint: Option<f32>,
depth: DecodeDepth,
max_cand: usize,
strictness: DecodeStrictness,
refine_steps: i32,
sync_q_min: u32,
) -> Vec<DecodeResult> {
let mut residual = audio.to_vec();
let mut all_results: Vec<DecodeResult> = Vec::new();
let passes: &[f32] = &[1.0, 0.75, 0.5];
let fec = P::Fec::default();
for &factor in passes {
let candidates = coarse_sync::<P>(
&residual,
freq_min,
freq_max,
sync_min * factor,
freq_hint,
max_cand,
);
if candidates.is_empty() {
continue;
}
let fft_cache = build_fft_cache(&residual, ds_cfg);
#[cfg(feature = "parallel")]
let new: Vec<DecodeResult> = candidates
.par_iter()
.filter_map(|cand| {
process_candidate_basic::<P>(
cand,
&fft_cache,
ds_cfg,
depth,
strictness,
&all_results,
EqMode::Off,
refine_steps,
sync_q_min,
)
})
.collect();
#[cfg(not(feature = "parallel"))]
let new: Vec<DecodeResult> = candidates
.iter()
.filter_map(|cand| {
process_candidate_basic::<P>(
cand,
&fft_cache,
ds_cfg,
depth,
strictness,
&all_results,
EqMode::Off,
refine_steps,
sync_q_min,
)
})
.collect();
let mut deduped: Vec<DecodeResult> = Vec::new();
for r in new {
if !all_results.iter().any(|k| k.info == r.info)
&& !deduped.iter().any(|x| x.info == r.info)
{
deduped.push(r);
}
}
for r in &deduped {
let qsb = (r.sync_cv > 0.3) as u32 as f32;
let gain = 1.0 - 0.5 * qsb;
let tones = encode_tones_for_snr::<P>(&r.info, &fec);
subtract_tones(&mut residual, &tones, r.freq_hz, r.dt_sec, gain, sub_cfg);
}
all_results.extend(deduped);
}
all_results
}