1use crate::core::{FrameLayout, ModulationParams, Protocol, ProtocolId, SyncMode};
35use crate::fec::ConvFano232;
36use crate::msg::Jt72Codec;
37
38pub mod baseband;
39pub(crate) mod decode;
40pub(crate) mod demod_bb;
41pub mod interleave;
42pub mod rx;
43pub mod search;
44pub(crate) mod softsym;
45pub mod sync_pattern;
46pub mod tx;
47
48pub use interleave::{deinterleave, deinterleave_llrs, interleave};
49pub use rx::demodulate_aligned;
50pub use search::{SearchParams, SyncCandidate, coarse_search};
51pub use sync_pattern::{JT9_ISYNC, JT9_SYNC_BLOCKS, JT9_SYNC_POSITIONS};
52pub use tx::{encode_channel_symbols, synthesize_audio, synthesize_standard};
53
54pub fn decode_at(
57 audio: &[f32],
58 sample_rate: u32,
59 start_sample: usize,
60 base_freq_hz: f32,
61) -> Option<crate::msg::Jt72Message> {
62 use crate::core::{DecodeContext, FecCodec, FecOpts, MessageCodec};
63
64 let llrs = rx::demodulate_aligned(audio, sample_rate, start_sample, base_freq_hz);
65 let codec = ConvFano232;
66 let res = codec.decode_soft(&llrs, &FecOpts::default())?;
67 let mut payload = [0u8; 72];
68 payload.copy_from_slice(&res.info);
69 crate::msg::Jt72Codec::default().unpack(&payload, &DecodeContext::default())
70}
71
72#[derive(Clone, Debug)]
74pub struct Jt9Decode {
75 pub message: crate::msg::Jt72Message,
76 pub freq_hz: f32,
77 pub start_sample: usize,
78}
79
80pub fn decode_scan(
86 audio: &[f32],
87 sample_rate: u32,
88 nominal_start_sample: usize,
89 params: &search::SearchParams,
90) -> Vec<Jt9Decode> {
91 use crate::core::ModulationParams;
92 let nsps = (sample_rate as f32 * <Jt9 as ModulationParams>::SYMBOL_DT).round() as usize;
93
94 let mut scan_params = *params;
98 scan_params.score_threshold = 0.001;
99 scan_params.max_candidates = 50_000; let mut cands = search::coarse_search(audio, sample_rate, nominal_start_sample, &scan_params);
102
103 cands.sort_unstable_by(|a, b| {
109 b.score
110 .partial_cmp(&a.score)
111 .unwrap_or(std::cmp::Ordering::Equal)
112 });
113 cands.truncate(params.max_candidates.max(32));
114
115 let big_fft = softsym::AudioFft::build(audio);
118
119 let mut seen: Vec<Jt9Decode> = Vec::new();
120 for c in cands {
121 let Some(d) = decode::decode_at_baseband_with_fft(&big_fft, c.freq_hz) else {
122 continue;
123 };
124 let dup = seen.iter().any(|prev| {
125 prev.message == d.message
126 && (prev.freq_hz - d.freq_hz).abs() <= 4.0
127 && (prev.start_sample as i64 - d.start_sample as i64).abs() <= nsps as i64
128 });
129 if !dup {
130 seen.push(d);
131 }
132 }
133 seen
134}
135
136pub fn decode_scan_default(audio: &[f32], sample_rate: u32) -> Vec<Jt9Decode> {
138 decode_scan(audio, sample_rate, 0, &search::SearchParams::default())
139}
140
141#[derive(Copy, Clone, Debug, Default)]
143pub struct Jt9;
144
145impl ModulationParams for Jt9 {
146 const NTONES: u32 = 9;
147 const BITS_PER_SYMBOL: u32 = 3; const NSPS: u32 = 6912;
151 const SYMBOL_DT: f32 = 6912.0 / 12_000.0;
152 const TONE_SPACING_HZ: f32 = 12_000.0 / 6912.0; const GRAY_MAP: &'static [u8] = &[0, 1, 3, 2, 6, 7, 5, 4];
157 const GFSK_BT: f32 = 0.0;
160 const GFSK_HMOD: f32 = 1.0;
161 const NFFT_PER_SYMBOL_FACTOR: u32 = 2;
163 const NSTEP_PER_SYMBOL: u32 = 2;
165 const NDOWN: u32 = 8;
168}
169
170impl FrameLayout for Jt9 {
171 const N_DATA: u32 = 69;
172 const N_SYNC: u32 = 16;
173 const N_SYMBOLS: u32 = 85;
174 const N_RAMP: u32 = 0;
175 const SYNC_MODE: SyncMode = SyncMode::Block(&JT9_SYNC_BLOCKS);
176 const T_SLOT_S: f32 = 60.0;
177 const TX_START_OFFSET_S: f32 = 0.0;
180}
181
182impl Protocol for Jt9 {
183 type Fec = ConvFano232;
186 type Msg = Jt72Codec;
188 const ID: ProtocolId = ProtocolId::Jt9;
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use crate::core::FecCodec;
195
196 #[test]
197 fn jt9_trait_surface() {
198 assert_eq!(<Jt9 as ModulationParams>::NTONES, 9);
199 assert_eq!(<Jt9 as ModulationParams>::BITS_PER_SYMBOL, 3);
200 assert_eq!(<Jt9 as ModulationParams>::NSPS, 6912);
201 assert!((<Jt9 as ModulationParams>::SYMBOL_DT - 0.576).abs() < 1e-3,);
202 assert_eq!(<Jt9 as FrameLayout>::N_SYMBOLS, 85);
203 assert_eq!(<Jt9 as FrameLayout>::N_SYNC, 16);
204 assert_eq!(<Jt9 as FrameLayout>::N_DATA, 69);
205 assert_eq!(<Jt9 as FrameLayout>::T_SLOT_S, 60.0);
206
207 match <Jt9 as FrameLayout>::SYNC_MODE {
208 SyncMode::Block(blocks) => {
209 assert_eq!(blocks.len(), 16);
210 assert_eq!(blocks[0].start_symbol, 0);
211 assert_eq!(blocks[15].start_symbol, 84);
212 for b in blocks {
213 assert_eq!(b.pattern, &[0u8]);
214 }
215 }
216 SyncMode::Interleaved { .. } => panic!("JT9 must use Block sync"),
217 }
218
219 assert_eq!(<<Jt9 as Protocol>::Fec as FecCodec>::N, 206);
220 assert_eq!(<<Jt9 as Protocol>::Fec as FecCodec>::K, 72);
221 }
222}