Skip to main content

mfsk_core/ft4/
mod.rs

1//! # `ft4` — FT4 decoder and synthesiser
2//!
3//! FT4 shares the LDPC(174, 91) code and WSJT 77-bit message payload with FT8;
4//! only the modulation parameters (4-GFSK at 20.833 baud), frame layout (four
5//! 4-symbol Costas arrays at symbols 0 / 33 / 66 / 99) and DSP ratios differ.
6//! All heavy lifting is delegated to generic code in [`crate::core`]; this
7//! module mainly wires the trait impls and provides decode / synth entry
8//! points.
9//!
10//! ## Quick example
11//!
12//! ```no_run
13//! use mfsk_core::ft4::decode::decode_frame;
14//! use mfsk_core::msg::wsjt77::unpack77;
15//!
16//! # let audio: Vec<i16> = vec![];
17//! // `audio` is 90_000 i16 samples at 12 kHz (7.5 s slot).
18//! for r in decode_frame(&audio, 100.0, 3_000.0, 1.0, /* max_cand */ 100) {
19//!     let msg77: &[u8; 77] = r.message77().try_into().unwrap();
20//!     if let Some(text) = unpack77(msg77) {
21//!         println!("{:7.1} Hz  dt={:+.2} s  SNR={:+.0} dB  {}",
22//!                  r.freq_hz, r.dt_sec, r.snr_db, text);
23//!     }
24//! }
25//! ```
26
27use crate::core::{FrameLayout, ModulationParams, Protocol, ProtocolId, SyncBlock, SyncMode};
28use crate::fec::Ldpc174_91;
29use crate::msg::Wsjt77Message;
30
31pub mod decode;
32pub mod encode;
33
34/// FT4 protocol marker: 4-GFSK, 103 symbols over 7.5 s slot, 20.833 Hz tone
35/// spacing, four different Costas-4 arrays, LDPC(174,91) FEC, WSJT 77-bit
36/// message payload.
37#[derive(Copy, Clone, Debug, Default)]
38pub struct Ft4;
39
40impl ModulationParams for Ft4 {
41    const NTONES: u32 = 4;
42    const BITS_PER_SYMBOL: u32 = 2;
43    const NSPS: u32 = 576; // 48 ms @ 12 kHz
44    const SYMBOL_DT: f32 = 0.048;
45    const TONE_SPACING_HZ: f32 = 20.833;
46    const GRAY_MAP: &'static [u8] = &[0, 1, 3, 2];
47    const GFSK_BT: f32 = 1.0;
48    const GFSK_HMOD: f32 = 1.0;
49    const NFFT_PER_SYMBOL_FACTOR: u32 = 4; // NFFT1 = 4 × NSPS = 2304
50    const NSTEP_PER_SYMBOL: u32 = 2; // half-symbol coarse-sync step (24 ms)
51    const NDOWN: u32 = 18; // 12 000 / 18 ≈ 666.7 Hz baseband
52    // LLR_SCALE tuning (2.0 / 2.83 / 3.5) was measured to give identical
53    // threshold curves — BP already converges within that range. Keeping
54    // the WSJT-X default.
55}
56
57impl FrameLayout for Ft4 {
58    const N_DATA: u32 = 87;
59    const N_SYNC: u32 = 16; // 4 × 4-symbol Costas
60    const N_SYMBOLS: u32 = 103; // active channel symbols (excludes 2 ramp symbols)
61    const N_RAMP: u32 = 2; // 1 each side, NN2 = 105
62    const SYNC_MODE: SyncMode = SyncMode::Block(&FT4_SYNC_BLOCKS);
63    const T_SLOT_S: f32 = 7.5;
64    const TX_START_OFFSET_S: f32 = 0.5;
65}
66
67impl Protocol for Ft4 {
68    type Fec = Ldpc174_91;
69    type Msg = Wsjt77Message;
70    const ID: ProtocolId = ProtocolId::Ft4;
71}
72
73/// FT4's four Costas arrays — each a distinct permutation of `[0,1,2,3]`.
74const FT4_COSTAS_A: [u8; 4] = [0, 1, 3, 2];
75const FT4_COSTAS_B: [u8; 4] = [1, 0, 2, 3];
76const FT4_COSTAS_C: [u8; 4] = [2, 3, 1, 0];
77const FT4_COSTAS_D: [u8; 4] = [3, 2, 0, 1];
78
79const FT4_SYNC_BLOCKS: [SyncBlock; 4] = [
80    SyncBlock {
81        start_symbol: 0,
82        pattern: &FT4_COSTAS_A,
83    },
84    SyncBlock {
85        start_symbol: 33,
86        pattern: &FT4_COSTAS_B,
87    },
88    SyncBlock {
89        start_symbol: 66,
90        pattern: &FT4_COSTAS_C,
91    },
92    SyncBlock {
93        start_symbol: 99,
94        pattern: &FT4_COSTAS_D,
95    },
96];