use super::Fst4s60;
use crate::core::dsp::downsample::DownsampleCfg;
use crate::core::equalize::EqMode;
use crate::core::pipeline::{self, FftCache};
pub use crate::core::pipeline::{DecodeDepth, DecodeResult, DecodeStrictness};
pub const FST4_60A_DOWNSAMPLE: DownsampleCfg = DownsampleCfg {
input_rate: 12_000,
fft1_size: 786_432,
fft2_size: 4_096,
tone_spacing_hz: 3.125,
leading_pad_tones: 1.5,
trailing_pad_tones: 1.5,
ntones: 4,
edge_taper_bins: 101,
};
const SYNC_Q_MIN: u32 = 10;
const REFINE_STEPS: i32 = 40;
pub fn decode_frame(
audio: &[i16],
freq_min: f32,
freq_max: f32,
sync_min: f32,
max_cand: usize,
) -> Vec<DecodeResult> {
pipeline::decode_frame::<Fst4s60>(
audio,
&FST4_60A_DOWNSAMPLE,
freq_min,
freq_max,
sync_min,
None,
DecodeDepth::BpAllOsd,
max_cand,
DecodeStrictness::Normal,
EqMode::Off,
REFINE_STEPS,
SYNC_Q_MIN,
)
.0
}
pub fn decode_frame_with_cache(
audio: &[i16],
freq_min: f32,
freq_max: f32,
sync_min: f32,
max_cand: usize,
) -> (Vec<DecodeResult>, FftCache) {
pipeline::decode_frame::<Fst4s60>(
audio,
&FST4_60A_DOWNSAMPLE,
freq_min,
freq_max,
sync_min,
None,
DecodeDepth::BpAllOsd,
max_cand,
DecodeStrictness::Normal,
EqMode::Off,
REFINE_STEPS,
SYNC_Q_MIN,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn synth_decode_roundtrip_cq_ja1abc() {
if std::env::var("RUN_FST4_ROUNDTRIP").is_err() {
eprintln!("skipping FST4 roundtrip (set RUN_FST4_ROUNDTRIP=1 to enable)");
return;
}
use super::super::encode::{message_to_tones, tones_to_i16};
use crate::msg::wsjt77::{pack77, unpack77};
let msg77 = pack77("CQ", "JA1ABC", "PM95").expect("pack77");
let tones = message_to_tones(&msg77);
let audio = tones_to_i16(&tones, 1500.0, 10_000);
let mut slot = vec![0i16; 60 * 12_000];
let offset = 12_000;
let copy_len = audio.len().min(slot.len() - offset);
slot[offset..offset + copy_len].copy_from_slice(&audio[..copy_len]);
let results = decode_frame(&slot, 1000.0, 2000.0, 0.8, 20);
assert!(
!results.is_empty(),
"expected at least one decode from clean synth, got none"
);
let texts: Vec<String> = results
.iter()
.filter_map(|r| {
let msg77: &[u8; 77] = r.message77().try_into().ok()?;
unpack77(msg77)
})
.collect();
assert!(
texts
.iter()
.any(|t| t.contains("JA1ABC") && t.contains("PM95")),
"expected to recover 'JA1ABC PM95', got {:?}",
texts
);
}
}