use alloc::string::String;
#[cfg(all(
any(feature = "wspr", feature = "jt65", feature = "jt9"),
any(feature = "fft-rustfft", feature = "fft-extern")
))]
use alloc::string::ToString;
use crate::engine::protocol::ProtocolId;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Decoded {
pub text: String,
pub freq_hz: f32,
pub dt_sec: f32,
pub snr_db: f32,
pub protocol: ProtocolId,
}
#[cfg(all(
any(feature = "ft8", feature = "ft4", feature = "fst4"),
any(feature = "fft-rustfft", feature = "fft-extern")
))]
impl crate::engine::pipeline::DecodeResult {
pub fn to_decoded(
&self,
protocol: ProtocolId,
hash: Option<&crate::msg::hash_table::CallsignHashTable>,
) -> Option<Decoded> {
let text = match hash {
Some(ht) => crate::msg::wsjt77::unpack77_with_hash(self.message77(), ht)?,
None => crate::msg::wsjt77::unpack77(self.message77())?,
};
Some(Decoded {
text,
freq_hz: self.freq_hz,
dt_sec: self.dt_sec,
snr_db: self.snr_db,
protocol,
})
}
}
#[cfg(all(feature = "wspr", any(feature = "fft-rustfft", feature = "fft-extern")))]
impl crate::wspr::WsprResult {
pub fn to_decoded(&self) -> Decoded {
Decoded {
text: self.message.to_string(),
freq_hz: self.freq_hz,
dt_sec: self.dt_sec,
snr_db: self.snr_db,
protocol: ProtocolId::Wspr,
}
}
}
#[cfg(all(feature = "q65", any(feature = "fft-rustfft", feature = "fft-extern")))]
impl crate::q65::Q65Result {
pub fn to_decoded(&self, sample_rate: u32, nominal_start_sample: usize) -> Decoded {
Decoded {
text: self.message.clone(),
freq_hz: self.freq_hz,
dt_sec: dt_from_samples(self.start_sample, nominal_start_sample, sample_rate),
snr_db: self.snr_db,
protocol: ProtocolId::Q65,
}
}
}
#[cfg(all(feature = "jt65", any(feature = "fft-rustfft", feature = "fft-extern")))]
impl crate::jt65::Jt65Result {
pub fn to_decoded(&self, sample_rate: u32, nominal_start_sample: usize) -> Decoded {
Decoded {
text: self.message.to_string(),
freq_hz: self.freq_hz,
dt_sec: dt_from_samples(self.start_sample, nominal_start_sample, sample_rate),
snr_db: self.snr_db,
protocol: ProtocolId::Jt65,
}
}
}
#[cfg(all(feature = "jt9", any(feature = "fft-rustfft", feature = "fft-extern")))]
impl crate::jt9::Jt9Result {
pub fn to_decoded(&self, sample_rate: u32, nominal_start_sample: usize) -> Decoded {
Decoded {
text: self.message.to_string(),
freq_hz: self.freq_hz,
dt_sec: dt_from_samples(self.start_sample, nominal_start_sample, sample_rate),
snr_db: self.snr_db,
protocol: ProtocolId::Jt9,
}
}
}
#[cfg(any(feature = "q65", feature = "jt65", feature = "jt9"))]
#[inline]
fn dt_from_samples(start_sample: usize, nominal_start_sample: usize, sample_rate: u32) -> f32 {
(start_sample as f32 - nominal_start_sample as f32) / sample_rate as f32
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "serde")]
#[test]
fn decoded_and_protocol_id_are_serde() {
fn assert_serde<T: serde::Serialize + serde::de::DeserializeOwned>() {}
assert_serde::<Decoded>();
assert_serde::<ProtocolId>();
}
#[cfg(all(
any(feature = "ft8", feature = "ft4", feature = "fst4"),
any(feature = "fft-rustfft", feature = "fft-extern")
))]
#[test]
fn ft8_family_to_decoded_carries_unpacked_text_and_fields() {
use crate::engine::pipeline::DecodeResult;
use crate::msg::wsjt77::{pack77_type1, unpack77};
let bits = pack77_type1("K1ABC", "W9XYZ", "FN42").expect("packs");
let info = bits.to_vec().into_boxed_slice();
let expected = unpack77(&bits).expect("unpacks");
let r = DecodeResult {
info,
freq_hz: 1234.5,
dt_sec: 0.2,
hard_errors: 0,
sync_score: 3.0,
pass: 1,
sync_cv: 0.1,
snr_db: -5.0,
};
let d = r
.to_decoded(ProtocolId::Ft8, None)
.expect("valid payload unpacks");
assert_eq!(d.text, expected);
assert_eq!(d.freq_hz, 1234.5);
assert_eq!(d.dt_sec, 0.2);
assert_eq!(d.snr_db, -5.0);
assert_eq!(d.protocol, ProtocolId::Ft8);
}
#[cfg(all(feature = "wspr", any(feature = "fft-rustfft", feature = "fft-extern")))]
#[test]
fn wspr_to_decoded_is_infallible_and_passes_dt_through() {
use crate::msg::wspr::WsprMessage;
use crate::wspr::WsprResult;
let msg = WsprMessage::Type1 {
callsign: "K1ABC".into(),
grid: "FN42".into(),
power_dbm: 37,
};
let expected = msg.to_string();
let r = WsprResult {
message: msg,
freq_hz: 1400.0,
start_sample: 0,
dt_sec: 1.0,
drift_hz: 0.0,
info_bits: [0u8; 50],
snr_db: -20.0,
};
let d = r.to_decoded();
assert_eq!(d.text, expected);
assert_eq!(d.freq_hz, 1400.0);
assert_eq!(d.dt_sec, 1.0);
assert_eq!(d.protocol, ProtocolId::Wspr);
}
#[cfg(all(feature = "q65", any(feature = "fft-rustfft", feature = "fft-extern")))]
#[test]
fn q65_to_decoded_derives_dt_from_sample_rate() {
use crate::q65::Q65Result;
let r = Q65Result {
message: "K1ABC W9XYZ FN42".into(),
freq_hz: 1000.0,
start_sample: 12_000,
dt_sec: 1.0,
iterations: 3,
snr_db: -24.0,
};
let d = r.to_decoded(12_000, 6_000);
assert_eq!(d.text, "K1ABC W9XYZ FN42");
assert_eq!(d.protocol, ProtocolId::Q65);
assert!((d.dt_sec - 0.5).abs() < 1e-6);
}
}