use alloc::sync::Arc;
use alloc::vec::Vec;
use core::marker::PhantomData;
use crate::engine::Protocol;
use crate::engine::protocol::DecodeContext;
use crate::fec::qra::FadingModel;
use crate::msg::ApHint;
use crate::msg::hash_table::CallsignHashTable;
use super::Q65Result;
use super::search::SearchParams;
fn ctx_from_hash_table(hash_table: Option<&Arc<CallsignHashTable>>) -> DecodeContext {
DecodeContext {
callsign_hash_table: hash_table
.map(|ht| Arc::clone(ht) as Arc<dyn core::any::Any + Send + Sync>),
}
}
pub trait Q65SubMode: Protocol {}
impl Q65SubMode for super::Q65a15 {}
impl Q65SubMode for super::Q65a30 {}
impl Q65SubMode for super::Q65a60 {}
impl Q65SubMode for super::Q65b60 {}
impl Q65SubMode for super::Q65c60 {}
impl Q65SubMode for super::Q65d60 {}
impl Q65SubMode for super::Q65e60 {}
impl Q65SubMode for super::Q65d120 {}
impl Q65SubMode for super::Q65e120 {}
impl Q65SubMode for super::Q65a300 {}
pub struct DecodeRequest<'a, P: Q65SubMode> {
audio: &'a [f32],
sample_rate: u32,
nominal_start_sample: usize,
params: SearchParams,
ap_hint: Option<&'a ApHint>,
ap_list: Option<&'a [[i32; 63]]>,
fading: Option<(FadingModel, f32)>,
on_result: Option<&'a (dyn Fn(&Q65Result) + Sync)>,
hash_table: Option<Arc<CallsignHashTable>>,
_marker: PhantomData<P>,
}
impl<'a, P: Q65SubMode> DecodeRequest<'a, P> {
pub fn new(
audio: &'a [f32],
sample_rate: u32,
nominal_start_sample: usize,
params: SearchParams,
) -> Self {
Self {
audio,
sample_rate,
nominal_start_sample,
params,
ap_hint: None,
ap_list: None,
fading: None,
on_result: None,
hash_table: None,
_marker: PhantomData,
}
}
pub fn sniper(
audio: &'a [f32],
sample_rate: u32,
start_sample: usize,
base_freq_hz: f32,
) -> SniperRequest<'a, P> {
SniperRequest::new(audio, sample_rate, start_sample, base_freq_hz)
}
pub fn ap_hint(mut self, hint: &'a ApHint) -> Self {
self.ap_hint = Some(hint);
self
}
pub fn ap_list(mut self, candidates: &'a [[i32; 63]]) -> Self {
self.ap_list = Some(candidates);
self
}
pub fn fading(mut self, model: FadingModel, b90_ts: f32) -> Self {
self.fading = Some((model, b90_ts));
self
}
pub fn on_result(mut self, cb: &'a (dyn Fn(&Q65Result) + Sync)) -> Self {
self.on_result = Some(cb);
self
}
pub fn hash_table(mut self, ht: Arc<CallsignHashTable>) -> Self {
self.hash_table = Some(ht);
self
}
pub fn decode(&self) -> Vec<Q65Result> {
let ctx = ctx_from_hash_table(self.hash_table.as_ref());
let pad = {
let want = (self.params.time_tolerance_early_sec.max(0.0) * self.sample_rate as f32)
.round() as usize;
want.saturating_sub(self.nominal_start_sample)
};
let padded: Option<Vec<f32>> = (pad > 0).then(|| {
let mut v = alloc::vec![0.0f32; pad];
v.extend_from_slice(self.audio);
v
});
let audio: &[f32] = padded.as_deref().unwrap_or(self.audio);
let nominal = self.nominal_start_sample + pad;
let sample_rate = self.sample_rate;
let untranslate = move |r: &mut Q65Result| {
r.dt_sec = (r.start_sample as f32 - pad as f32) / sample_rate as f32;
r.start_sample = r.start_sample.saturating_sub(pad);
};
let wrapped;
let on_result: Option<&(dyn Fn(&Q65Result) + Sync)> = match self.on_result {
Some(cb) if pad > 0 => {
wrapped = move |r: &Q65Result| {
let mut t = r.clone();
untranslate(&mut t);
cb(&t);
};
Some(&wrapped)
}
other => other,
};
let mut out = self.decode_dispatch(audio, nominal, on_result, &ctx);
if pad > 0 {
for r in &mut out {
untranslate(r);
}
}
out
}
fn decode_dispatch(
&self,
audio: &[f32],
nominal_start_sample: usize,
on_result: Option<&(dyn Fn(&Q65Result) + Sync)>,
ctx: &DecodeContext,
) -> Vec<Q65Result> {
if let Some(candidates) = self.ap_list {
return super::rx::decode_scan_with_ap_list_for::<P>(
audio,
self.sample_rate,
nominal_start_sample,
&self.params,
candidates,
on_result,
ctx,
);
}
if let Some((model, b90_ts)) = self.fading {
return super::rx::decode_scan_fading_for::<P>(
audio,
self.sample_rate,
nominal_start_sample,
&self.params,
b90_ts,
model,
self.ap_hint,
on_result,
ctx,
);
}
match self.ap_hint {
Some(hint) => super::rx::decode_scan_with_ap_for::<P>(
audio,
self.sample_rate,
nominal_start_sample,
&self.params,
hint,
on_result,
ctx,
),
None => super::rx::decode_scan_for::<P>(
audio,
self.sample_rate,
nominal_start_sample,
&self.params,
on_result,
ctx,
),
}
}
}
pub struct SniperRequest<'a, P: Q65SubMode> {
audio: &'a [f32],
sample_rate: u32,
start_sample: usize,
base_freq_hz: f32,
ap_hint: Option<&'a ApHint>,
ap_list: Option<&'a [[i32; 63]]>,
fading: Option<(FadingModel, f32)>,
on_result: Option<&'a (dyn Fn(&Q65Result) + Sync)>,
hash_table: Option<Arc<CallsignHashTable>>,
_marker: PhantomData<P>,
}
impl<'a, P: Q65SubMode> SniperRequest<'a, P> {
pub fn new(audio: &'a [f32], sample_rate: u32, start_sample: usize, base_freq_hz: f32) -> Self {
Self {
audio,
sample_rate,
start_sample,
base_freq_hz,
ap_hint: None,
ap_list: None,
fading: None,
on_result: None,
hash_table: None,
_marker: PhantomData,
}
}
pub fn ap_hint(mut self, hint: &'a ApHint) -> Self {
self.ap_hint = Some(hint);
self
}
pub fn ap_list(mut self, candidates: &'a [[i32; 63]]) -> Self {
self.ap_list = Some(candidates);
self
}
pub fn fading(mut self, model: FadingModel, b90_ts: f32) -> Self {
self.fading = Some((model, b90_ts));
self
}
pub fn on_result(mut self, cb: &'a (dyn Fn(&Q65Result) + Sync)) -> Self {
self.on_result = Some(cb);
self
}
pub fn hash_table(mut self, ht: Arc<CallsignHashTable>) -> Self {
self.hash_table = Some(ht);
self
}
pub fn decode(&self) -> Option<Q65Result> {
let ctx = ctx_from_hash_table(self.hash_table.as_ref());
let result = if let Some(candidates) = self.ap_list {
super::rx::decode_at_with_ap_list_for::<P>(
self.audio,
self.sample_rate,
self.start_sample,
self.base_freq_hz,
candidates,
&ctx,
)
} else if let Some((model, b90_ts)) = self.fading {
super::rx::decode_at_fading_for::<P>(
self.audio,
self.sample_rate,
self.start_sample,
self.base_freq_hz,
b90_ts,
model,
self.ap_hint,
&ctx,
)
} else {
match self.ap_hint {
Some(hint) => super::rx::decode_at_with_ap_for::<P>(
self.audio,
self.sample_rate,
self.start_sample,
self.base_freq_hz,
hint,
&ctx,
),
None => super::rx::decode_at_for::<P>(
self.audio,
self.sample_rate,
self.start_sample,
self.base_freq_hz,
&ctx,
),
}
};
if let (Some(cb), Some(r)) = (self.on_result, &result) {
cb(r);
}
result
}
}
pub struct MultiPeriodRequest<'a, P: Q65SubMode> {
audio_slots: &'a [&'a [f32]],
sample_rate: u32,
nominal_start_sample: usize,
params: SearchParams,
ap_list: Option<&'a [[i32; 63]]>,
on_result: Option<&'a (dyn Fn(&Q65Result) + Sync)>,
hash_table: Option<Arc<CallsignHashTable>>,
_marker: PhantomData<P>,
}
impl<'a, P: Q65SubMode> MultiPeriodRequest<'a, P> {
pub fn new(
audio_slots: &'a [&'a [f32]],
sample_rate: u32,
nominal_start_sample: usize,
params: SearchParams,
) -> Self {
Self {
audio_slots,
sample_rate,
nominal_start_sample,
params,
ap_list: None,
on_result: None,
hash_table: None,
_marker: PhantomData,
}
}
pub fn ap_list(mut self, candidates: &'a [[i32; 63]]) -> Self {
self.ap_list = Some(candidates);
self
}
pub fn on_result(mut self, cb: &'a (dyn Fn(&Q65Result) + Sync)) -> Self {
self.on_result = Some(cb);
self
}
pub fn hash_table(mut self, ht: Arc<CallsignHashTable>) -> Self {
self.hash_table = Some(ht);
self
}
pub fn decode(&self) -> Vec<Q65Result> {
let ctx = ctx_from_hash_table(self.hash_table.as_ref());
super::rx::decode_multi_period_for::<P>(
self.audio_slots,
self.sample_rate,
self.nominal_start_sample,
&self.params,
self.ap_list,
self.on_result,
&ctx,
)
}
}