1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//! FT8 protocol parameters (derived from WSJT-X ft8_params.f90)
/// LDPC (174,91): information bits = 77 msg + 14 CRC
pub const KK: usize = 91;
/// Data symbols
pub const ND: usize = 58;
/// Sync symbols (3 Costas 7×7 arrays)
pub const NS: usize = 21;
/// Total channel symbols
pub const NN: usize = NS + ND; // 79
/// Samples per symbol at 12000 S/s
pub const NSPS: usize = 1920;
/// Samples in a full 15-second waveform
pub const NZ: usize = NSPS * NN; // 151,680
/// Samples in the receive buffer (15 s × 12000 S/s)
pub const NMAX: usize = 15 * 12000; // 180,000
/// FFT size for symbol spectra (2 × NSPS)
pub const NFFT1: usize = 2 * NSPS; // 3840
pub const NH1: usize = NFFT1 / 2; // 1920
/// Rough time-sync step size. WSJT-X reference uses 1/4-symbol
/// (= NSPS/4 = 480) which gives `coarse_sync` 2× the time-axis
/// resolution but doubles spec memory + stage-1 FFT count vs the
/// `nstep-half` build (NSPS/2 = 960). Embedded targets enable
/// `nstep-half` to fit the LX6/LX7 budget; host stays on NSPS/4.
pub const NSTEP: usize = NSPS / 4; // 480
pub const NSTEP: usize = NSPS / 2; // 960
/// Number of symbol spectra at the active step size.
pub const NHSYM: usize = NMAX / NSTEP - 3;
/// Downsample factor: 12000 Hz → 200 Hz
pub const NDOWN: usize = 60;
/// Downsampled sample rate (Hz)
pub const DS_RATE: usize = 12000 / NDOWN; // 200
/// Samples per symbol after downsampling
pub const DS_SPB: usize = NSPS / NDOWN; // 32
/// Downsampled buffer length
pub const NP2: usize = 3200;
/// LDPC codeword length
pub const LDPC_N: usize = 174;
/// LDPC information bits (message + CRC14)
pub const LDPC_K: usize = 91;
/// LDPC parity bits
pub const LDPC_M: usize = LDPC_N - LDPC_K; // 83
/// Message payload bits (before CRC)
pub const MSG_BITS: usize = 77;
/// CRC width
pub const CRC_BITS: usize = 14;
/// Costas array tone pattern
pub const COSTAS: = ;
/// Symbol offsets of the three Costas arrays within a frame
/// Positions: 0-6, 36-42, 72-78
pub const COSTAS_POS: = ;
/// Number of tones in FT8 (8-FSK)
pub const NTONES: usize = 8;
/// Symbol duration (seconds)
pub const SYMBOL_DT: f32 = NSPS as f32 / 12000.0; // 0.16 s
/// Time step at 1/4-symbol resolution (seconds)
pub const TSTEP: f32 = SYMBOL_DT / 4.0; // 0.04 s
/// Frequency resolution at NFFT1 (Hz/bin)
pub const DF: f32 = 12000.0 / NFFT1 as f32; // 3.125 Hz
/// Gray code map for 8-FSK (tone index → 3-bit Gray code)
pub const GRAYMAP: = ;
/// LLR scale factor (empirically tuned in WSJT-X ft8b.f90)
pub const LLR_SCALE: f32 = 2.83;
/// Default maximum BP decoder iterations (= WSJT-X
/// `bpdecode174_91.f90` `maxiterations`). Runtime-tunable via
/// `crate::ft8::decode_block::DecodeTuning` / `_tuned` entry points
/// — embedded targets (LX6 / LX7) lower this to trade weak-signal
/// recall for post-SlotEnd time budget.
pub const DEFAULT_BP_MAX_ITER: u32 = 30;
/// Backwards-compat alias used by the legacy `decode.rs` path.
pub const BP_MAX_ITER: u32 = DEFAULT_BP_MAX_ITER;