use alloc::vec::Vec;
use super::Ft4;
use crate::engine::dsp::downsample::DownsampleCfg;
use crate::engine::dsp::subtract::SubtractCfg;
use crate::engine::pipeline;
use crate::msg::pipeline_ap;
pub use crate::engine::pipeline::{DecodeDepth, DecodeResult, DecodeStrictness, FftCache};
pub use crate::msg::ApHint;
use crate::msg::decode_request::{
DecodeOutcome, DecodeRequest, FrameDecodable, SniperRequest, SupportsSicRounds,
};
pub const FT4_DOWNSAMPLE: DownsampleCfg = DownsampleCfg {
input_rate: 12_000,
fft1_size: 92_160,
fft2_size: 5_120,
tone_spacing_hz: 20.833,
leading_pad_tones: 1.5,
trailing_pad_tones: 1.5,
ntones: 4,
edge_taper_bins: 101,
};
pub const FT4_SUBTRACT: SubtractCfg = SubtractCfg {
sample_rate: 12_000.0,
tone_spacing_hz: 20.833,
samples_per_symbol: 576,
base_offset_s: 0.5,
gfsk: Some(crate::engine::dsp::subtract::GfskParams {
bt: 1.0,
hmod: 1.0,
ramp_samples: 576 / 8,
}),
};
const REFINE_STEPS: i32 = 32;
const SYNC_Q_MIN: u32 = 8;
fn dedup_known(raw: Vec<DecodeResult>, known: &[DecodeResult]) -> Vec<DecodeResult> {
raw.into_iter()
.filter(|r| !known.iter().any(|k| k.info == r.info))
.collect()
}
impl pipeline::GenericPipelineProtocol for Ft4 {}
impl FrameDecodable for Ft4 {
type DecodeResult = DecodeResult;
fn __single_pass(req: &DecodeRequest<'_, Self>) -> DecodeOutcome<Self> {
let (raw, fft_cache) = pipeline::decode_frame::<Ft4>(
req.audio,
&FT4_DOWNSAMPLE,
req.freq_min,
req.freq_max,
req.sync_min,
req.freq_hint,
req.depth,
req.max_cand,
req.strictness,
req.eq_mode,
SYNC_Q_MIN,
);
DecodeOutcome {
results: dedup_known(raw, req.known),
fft_cache,
}
}
fn __sniper(req: &SniperRequest<'_, Self>) -> DecodeOutcome<Self> {
let max_cand = req.max_cand.min(15);
let results = pipeline_ap::decode_sniper_ap::<Ft4>(
req.audio,
&FT4_DOWNSAMPLE,
req.target_freq,
250.0,
req.sync_min,
req.depth,
max_cand,
req.strictness,
req.eq_mode,
REFINE_STEPS,
SYNC_Q_MIN / 2,
req.ap_hint,
);
let fft_cache = FftCache(crate::engine::dsp::downsample::build_fft_cache(
req.audio,
&FT4_DOWNSAMPLE,
));
DecodeOutcome { results, fft_cache }
}
}
impl SupportsSicRounds for Ft4 {
fn __flat_sic(req: &DecodeRequest<'_, Self>) -> DecodeOutcome<Self> {
let raw = pipeline::decode_frame_subtract::<Ft4>(
req.audio,
&FT4_DOWNSAMPLE,
&FT4_SUBTRACT,
req.freq_min,
req.freq_max,
req.sync_min,
req.freq_hint,
req.depth,
req.max_cand,
req.strictness,
req.sic_rounds,
SYNC_Q_MIN,
700,
false,
1.0,
);
let fft_cache = FftCache(crate::engine::dsp::downsample::build_fft_cache(
req.audio,
&FT4_DOWNSAMPLE,
));
DecodeOutcome {
results: dedup_known(raw, req.known),
fft_cache,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::msg::decode_request::DecodeRequest;
#[test]
fn decode_request_accepts_all_param_combos() {
let empty = vec![0i16; 12 * 7500]; for osd in [false, true] {
let _ = DecodeRequest::<Ft4>::new(&empty, 100.0, 3000.0, 1.0, 5)
.osd(osd)
.decode();
let _ = DecodeRequest::<Ft4>::new(&empty, 100.0, 3000.0, 1.0, 5)
.osd(osd)
.sic_rounds(3)
.decode();
let _ = DecodeRequest::<Ft4>::sniper(&empty, 1500.0, 5)
.osd(osd)
.decode();
}
}
}