1use crate::core::{FrameLayout, ModulationParams, Protocol, ProtocolId, SyncMode};
35use crate::fec::ConvFano232;
36use crate::msg::Jt72Codec;
37
38pub mod interleave;
39pub mod rx;
40pub mod search;
41pub mod sync_pattern;
42pub mod tx;
43
44pub use interleave::{deinterleave, deinterleave_llrs, interleave};
45pub use rx::demodulate_aligned;
46pub use search::{SearchParams, SyncCandidate, coarse_search};
47pub use sync_pattern::{JT9_ISYNC, JT9_SYNC_BLOCKS, JT9_SYNC_POSITIONS};
48pub use tx::{encode_channel_symbols, synthesize_audio, synthesize_standard};
49
50pub fn decode_at(
53 audio: &[f32],
54 sample_rate: u32,
55 start_sample: usize,
56 base_freq_hz: f32,
57) -> Option<crate::msg::Jt72Message> {
58 use crate::core::{DecodeContext, FecCodec, FecOpts, MessageCodec};
59
60 let llrs = rx::demodulate_aligned(audio, sample_rate, start_sample, base_freq_hz);
61 let codec = ConvFano232;
62 let res = codec.decode_soft(&llrs, &FecOpts::default())?;
63 let mut payload = [0u8; 72];
64 payload.copy_from_slice(&res.info);
65 crate::msg::Jt72Codec::default().unpack(&payload, &DecodeContext::default())
66}
67
68#[derive(Clone, Debug)]
70pub struct Jt9Decode {
71 pub message: crate::msg::Jt72Message,
72 pub freq_hz: f32,
73 pub start_sample: usize,
74}
75
76pub fn decode_scan(
81 audio: &[f32],
82 sample_rate: u32,
83 nominal_start_sample: usize,
84 params: &search::SearchParams,
85) -> Vec<Jt9Decode> {
86 use crate::core::ModulationParams;
87 let nsps = (sample_rate as f32 * <Jt9 as ModulationParams>::SYMBOL_DT).round() as usize;
88 let cands = search::coarse_search(audio, sample_rate, nominal_start_sample, params);
89 let mut seen: Vec<Jt9Decode> = Vec::new();
90 for c in cands {
91 let Some(msg) = decode_at(audio, sample_rate, c.start_sample, c.freq_hz) else {
92 continue;
93 };
94 let dup = seen.iter().any(|prev| {
95 prev.message == msg
96 && (prev.freq_hz - c.freq_hz).abs() <= 2.0
97 && (prev.start_sample as i64 - c.start_sample as i64).abs() <= nsps as i64
98 });
99 if !dup {
100 seen.push(Jt9Decode {
101 message: msg,
102 freq_hz: c.freq_hz,
103 start_sample: c.start_sample,
104 });
105 }
106 }
107 seen
108}
109
110pub fn decode_scan_default(audio: &[f32], sample_rate: u32) -> Vec<Jt9Decode> {
112 decode_scan(audio, sample_rate, 0, &search::SearchParams::default())
113}
114
115#[derive(Copy, Clone, Debug, Default)]
117pub struct Jt9;
118
119impl ModulationParams for Jt9 {
120 const NTONES: u32 = 9;
121 const BITS_PER_SYMBOL: u32 = 3; const NSPS: u32 = 6912;
125 const SYMBOL_DT: f32 = 6912.0 / 12_000.0;
126 const TONE_SPACING_HZ: f32 = 12_000.0 / 6912.0; const GRAY_MAP: &'static [u8] = &[0, 1, 3, 2, 6, 7, 5, 4];
131 const GFSK_BT: f32 = 0.0;
134 const GFSK_HMOD: f32 = 1.0;
135 const NFFT_PER_SYMBOL_FACTOR: u32 = 2;
137 const NSTEP_PER_SYMBOL: u32 = 2;
139 const NDOWN: u32 = 8;
142}
143
144impl FrameLayout for Jt9 {
145 const N_DATA: u32 = 69;
146 const N_SYNC: u32 = 16;
147 const N_SYMBOLS: u32 = 85;
148 const N_RAMP: u32 = 0;
149 const SYNC_MODE: SyncMode = SyncMode::Block(&JT9_SYNC_BLOCKS);
150 const T_SLOT_S: f32 = 60.0;
151 const TX_START_OFFSET_S: f32 = 0.0;
154}
155
156impl Protocol for Jt9 {
157 type Fec = ConvFano232;
160 type Msg = Jt72Codec;
162 const ID: ProtocolId = ProtocolId::Jt9;
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use crate::core::FecCodec;
169
170 #[test]
171 fn jt9_trait_surface() {
172 assert_eq!(<Jt9 as ModulationParams>::NTONES, 9);
173 assert_eq!(<Jt9 as ModulationParams>::BITS_PER_SYMBOL, 3);
174 assert_eq!(<Jt9 as ModulationParams>::NSPS, 6912);
175 assert!((<Jt9 as ModulationParams>::SYMBOL_DT - 0.576).abs() < 1e-3,);
176 assert_eq!(<Jt9 as FrameLayout>::N_SYMBOLS, 85);
177 assert_eq!(<Jt9 as FrameLayout>::N_SYNC, 16);
178 assert_eq!(<Jt9 as FrameLayout>::N_DATA, 69);
179 assert_eq!(<Jt9 as FrameLayout>::T_SLOT_S, 60.0);
180
181 match <Jt9 as FrameLayout>::SYNC_MODE {
182 SyncMode::Block(blocks) => {
183 assert_eq!(blocks.len(), 16);
184 assert_eq!(blocks[0].start_symbol, 0);
185 assert_eq!(blocks[15].start_symbol, 84);
186 for b in blocks {
187 assert_eq!(b.pattern, &[0u8]);
188 }
189 }
190 SyncMode::Interleaved { .. } => panic!("JT9 must use Block sync"),
191 }
192
193 assert_eq!(<<Jt9 as Protocol>::Fec as FecCodec>::N, 206);
194 assert_eq!(<<Jt9 as Protocol>::Fec as FecCodec>::K, 72);
195 }
196}