acoustic_ofdm/debug.rs
1// Copyright (c) 2026 Elias S. G. Carotti
2
3use rustfft::num_complex::Complex32;
4
5use crate::config::OfdmConfig;
6use crate::modem;
7
8/// Packet-level metadata produced during oracle encoding.
9///
10/// Rationale:
11/// - The oracle path needs exact packet boundaries and the corresponding
12/// complex baseband samples in order to separate modem failures from search
13/// failures.
14#[derive(Clone, Debug)]
15pub struct EncodedPacketMeta {
16 /// Zero-based fragment index inside the encoded burst.
17 pub frag_index: usize,
18 /// Total number of fragments in the encoded burst.
19 pub frag_count: usize,
20 /// Packet start offset, in passband audio samples, inside [`EncodedBurst::audio`].
21 pub packet_start: usize,
22 /// Packet length, in passband audio samples.
23 pub packet_len: usize,
24 /// Complex baseband packet waveform before passband conversion.
25 pub xbb: Vec<Complex32>,
26}
27
28/// Full encoded burst with passband audio plus oracle packet metadata.
29#[derive(Clone, Debug)]
30pub struct EncodedBurst {
31 /// Complete burst waveform at the audio sample rate.
32 pub audio: Vec<f32>,
33 /// Per-packet metadata for oracle decode and debugging.
34 pub packet_meta: Vec<EncodedPacketMeta>,
35}
36
37/// Summary diagnostics for one decoded passband packet window.
38///
39/// Rationale:
40/// - This is the compact debug summary used by the CLI to decide whether a run
41/// failed because of timing, CFO, channel estimation, or payload demodulation.
42#[derive(Clone, Debug)]
43pub struct PassbandDiagnostics {
44 /// Whether the packet window contained enough samples for sync, training,
45 /// and at least one OFDM data symbol.
46 pub enough_samples: bool,
47 /// Chosen sync offset, in baseband samples, relative to the start of the
48 /// post-wake/post-guard window.
49 pub sync_off: usize,
50 /// Coarse carrier-frequency offset estimate in hertz.
51 pub cfo_hz: f32,
52 /// RMS magnitude over the repeated-half sync region.
53 pub sync_rms: f32,
54 /// Peak magnitude over the repeated-half sync region.
55 pub sync_peak: f32,
56 /// RMS magnitude over the post-sync OFDM payload region.
57 pub post_rms: f32,
58 /// Peak magnitude over the post-sync OFDM payload region.
59 pub post_peak: f32,
60 /// RMS magnitude over the training symbol after CP removal.
61 pub train_rms: f32,
62 /// Minimum channel-estimate magnitude observed across used bins.
63 pub hest_mag_min: f32,
64 /// Mean channel-estimate magnitude observed across used bins.
65 pub hest_mag_mean: f32,
66 /// Maximum channel-estimate magnitude observed across used bins.
67 pub hest_mag_max: f32,
68 /// Training-symbol reconstruction EVM after equalization.
69 pub train_recon_evm: f32,
70 /// Residual pilot EVM before the final pilot-based cleanup.
71 pub pilot_residual_evm: f32,
72 /// Pilot EVM after the final pilot-based cleanup.
73 pub pilot_post_evm: f32,
74 /// Decision-directed EVM over data carriers after equalization.
75 pub post_eq_evm: f32,
76 /// Whether the packet parser accepted the decoded packet.
77 pub decoded: bool,
78 /// Decoded payload length when parsing succeeded.
79 pub decoded_payload_len: Option<usize>,
80}
81
82/// Equalizer input/output samples for a constellation plot.
83#[derive(Clone, Debug)]
84pub struct PassbandConstellationDump {
85 /// Raw FFT-bin samples on data carriers before equalization.
86 pub pre_eq: Vec<Complex32>,
87 /// Equalized data-carrier samples after pilot/training correction.
88 pub post_eq: Vec<Complex32>,
89}
90
91/// Per-symbol pilot-tracking summary for one packet.
92#[derive(Clone, Debug)]
93pub struct PassbandPilotTrackDump {
94 /// Residual pilot phase estimate per data symbol, in radians.
95 pub pilot_phase_rad: Vec<f32>,
96 /// Pilot EVM before the final pilot cleanup.
97 pub pilot_evm_pre: Vec<f32>,
98 /// Pilot EVM after the final pilot cleanup.
99 pub pilot_evm_post: Vec<f32>,
100 /// Mean magnitude of the current channel estimate per tracked symbol.
101 pub hest_mag_mean: Vec<f32>,
102 /// Maximum magnitude of the current channel estimate per tracked symbol.
103 pub hest_mag_max: Vec<f32>,
104}
105
106/// One per-bin debug row for the passband bin dump.
107#[derive(Clone, Debug)]
108pub struct PassbandBinDumpRow {
109 /// One-based OFDM data-symbol index inside the packet payload section.
110 pub data_symbol_idx: usize,
111 /// FFT bin index used by this carrier.
112 pub used_bin: usize,
113 /// Carrier role: `"pilot"` or `"data"`.
114 pub role: &'static str,
115 /// Complex carrier value before equalization.
116 pub pre_eq: Complex32,
117 /// Complex carrier value after equalization.
118 pub post_eq: Complex32,
119 /// Known reference symbol when available, mainly for pilot carriers.
120 pub reference: Option<Complex32>,
121}
122
123/// Full per-bin dump for a few decoded OFDM symbols.
124#[derive(Clone, Debug)]
125pub struct PassbandBinDump {
126 /// Flat row list across symbols and used bins.
127 pub rows: Vec<PassbandBinDumpRow>,
128}
129
130/// One row of the channel-comparison dump.
131#[derive(Clone, Debug)]
132pub struct PassbandChannelCompareRow {
133 /// One-based OFDM data-symbol index inside the packet payload section.
134 pub data_symbol_idx: usize,
135 /// FFT bin index used by this carrier.
136 pub used_bin: usize,
137 /// Carrier role: `"pilot"` or `"data"`.
138 pub role: &'static str,
139 /// Actual per-bin channel computed from known transmitted and received bins.
140 pub actual_h: Complex32,
141 /// Training-only channel estimate used as the baseline equalizer model.
142 pub estimated_h_train: Complex32,
143 /// Pilot-adjusted channel estimate after residual per-symbol correction.
144 pub estimated_h_pilot: Complex32,
145}
146
147/// Full channel-comparison dump for a few OFDM symbols.
148#[derive(Clone, Debug)]
149pub struct PassbandChannelCompareDump {
150 /// Flat row list across symbols and used bins.
151 pub rows: Vec<PassbandChannelCompareRow>,
152}
153
154/// Timing metric dump around the repeated-half synchronizer.
155#[derive(Clone, Debug)]
156pub struct PassbandSyncDump {
157 /// Coarse Schmidl-Cox timing estimate.
158 pub coarse_sync_off: usize,
159 /// Refined timing estimate after the local refinement step.
160 pub refined_sync_off: usize,
161 /// Schmidl-Cox metric trace used for visualization/debugging.
162 pub metrics: Vec<f32>,
163}
164
165/// IQ-chain dump used to inspect the RX downconversion path.
166#[derive(Clone, Debug)]
167pub struct PassbandIqChainDump {
168 /// Complex signal after IQ downconversion and low-pass filtering at the audio rate.
169 pub downconverted_audio_rate: Vec<Complex32>,
170 /// Complex signal after optional resampling to the baseband rate.
171 pub baseband_rate: Vec<Complex32>,
172 /// Audio sample rate in hertz.
173 pub fs_audio: f32,
174 /// Decoder baseband sample rate in hertz.
175 pub fs_baseband: f32,
176}
177
178/// Extract equalizer input/output constellation samples for a packet window.
179pub fn dump_passband_constellation(
180 pkt_audio: &[f32],
181 cfg: &OfdmConfig,
182 sync_off: f32,
183) -> Option<PassbandConstellationDump> {
184 modem::dump_passband_constellation_impl(pkt_audio, cfg, sync_off)
185}
186
187/// Extract per-symbol pilot tracking diagnostics for a packet window.
188pub fn dump_passband_pilot_tracking(
189 pkt_audio: &[f32],
190 cfg: &OfdmConfig,
191) -> Option<PassbandPilotTrackDump> {
192 modem::dump_passband_pilot_tracking_impl(pkt_audio, cfg)
193}
194
195/// Extract pre/post-equalization carrier values for a packet window.
196pub fn dump_passband_bins(pkt_audio: &[f32], cfg: &OfdmConfig) -> Option<PassbandBinDump> {
197 modem::dump_passband_bins_impl(pkt_audio, cfg)
198}
199
200/// Extract pre/post-equalization carrier values using a known sync offset.
201pub fn dump_passband_bins_with_sync(
202 pkt_audio: &[f32],
203 cfg: &OfdmConfig,
204 sync_off: f32,
205) -> Option<PassbandBinDump> {
206 modem::dump_passband_bins_with_sync_impl(pkt_audio, cfg, sync_off)
207}
208
209/// Compare actual and estimated channel samples using a known transmitted packet.
210pub fn dump_passband_channel_compare_with_sync(
211 payload: &[u8],
212 pkt_audio: &[f32],
213 cfg: &OfdmConfig,
214 sync_off: f32,
215) -> Option<PassbandChannelCompareDump> {
216 modem::dump_passband_channel_compare_with_sync_impl(payload, pkt_audio, cfg, sync_off)
217}
218
219/// Extract Schmidl-Cox timing metrics for one packet window.
220pub fn dump_passband_sync_metric(pkt_audio: &[f32], cfg: &OfdmConfig) -> Option<PassbandSyncDump> {
221 modem::dump_passband_sync_metric_impl(pkt_audio, cfg)
222}
223
224/// Dump the RX IQ chain before and after resampling.
225pub fn dump_passband_iq_chain(pkt_audio: &[f32], cfg: &OfdmConfig) -> Option<PassbandIqChainDump> {
226 modem::dump_passband_iq_chain_impl(pkt_audio, cfg)
227}