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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
// Copyright (c) 2026 G & R Associates LLC
// SPDX-License-Identifier: MIT OR Apache-2.0
// src/sync/dvb_t_gi_sync.rs
//
// Guard-interval (cyclic-prefix) acquisition for a preamble-less OFDM frame —
// the DVB-T way. A conformant DVB-T signal carries NO Schmidl & Cox preamble;
// the receiver finds the symbol boundary and fractional carrier-frequency
// offset directly from the cyclic prefix (the van de Beek / Sandell / Börjesson
// ML estimator, "ML estimation of time and frequency offset in OFDM systems",
// IEEE Trans. Signal Processing, 1997).
//
// Each OFDM symbol is `cp_len + n_fft` samples, where the first `cp_len`
// samples (the guard interval) are a copy of the symbol's last `cp_len`
// samples. So for a correct symbol-start offset `d`, the two windows
// `r[d .. d+cp_len]` and `r[d+n_fft .. d+n_fft+cp_len]` are identical up to
// noise and a phase ramp `exp(j·2π·ε·n_fft)` from a residual CFO `ε` (in
// subcarrier-spacing units). Define the correlation and energy terms
//
// γ(d) = Σ_{k=0}^{cp_len-1} r[d+k] · conj(r[d+n_fft+k])
// Φ(d) = ½ Σ_{k=0}^{cp_len-1} ( |r[d+k]|² + |r[d+n_fft+k]|² ).
//
// The ML timing estimate maximizes the log-likelihood metric
//
// Λ(d) = |γ(d)| − ρ · Φ(d),
//
// where ρ = SNR/(SNR+1) = |correlation coefficient| weights the energy term by
// the signal reliability (ρ→1 at high SNR fully subtracts Φ; ρ→0 at low SNR
// leaves a pure correlation peak-pick). Ranking by |γ| alone — dropping ρ·Φ —
// is only the correct ML rule in the ρ→0 limit and spuriously rewards
// high-energy offsets; the full metric is used here. The fractional CFO comes
// from the angle at the winning offset:
//
// ε̂ = −∠γ / (2π) (cycles per n_fft samples)
// cfo_hz = ε̂ · fs / n_fft = −∠γ · fs / (2π · n_fft).
//
// The estimate is unambiguous only within ±½ a subcarrier spacing (±fs/2n_fft);
// integer-CFO and frame/super-frame lock come from the scattered pilots and TPS
// downstream. This provides the timing + fractional-CFO acquisition an external
// IQ capture needs before any symbol can be demapped.
//
// DIVERGENCE FROM THE PUBLISHED ESTIMATOR. Van de Beek's Λ is derived over a
// SINGLE cyclic prefix. Here γ and Φ may optionally be accumulated coherently
// over up to `max_symbols` consecutive symbols at the same candidate offset
// (like gr-dtv's moving-average correlator), which sharpens the lock for a
// known-length batch with a stable channel. This is coherent, so a large
// residual CFO rotates successive symbols' γ and partially cancels the sum —
// keep `max_symbols` small (a few symbols) when a meaningful CFO may be present,
// or set it to 1 for the strict single-symbol estimator. Fractional-CFO
// estimation itself is unaffected (it reads the accumulated angle, which the
// per-symbol phase ramp shares).
use Complex32 as C32;
/// Tuning for [`dvb_t_gi_sync_with`]: the ML energy-term weight and the coherent
/// accumulation bound.
/// One guard-interval acquisition result: a candidate symbol-start offset with
/// its correlation strength and fractional-CFO estimate. Mirrors
/// [`crate::sync::OfdmSyncResult`]'s role for the preamble path.
/// Searches `iq` for the best guard-interval-aligned symbol start in the offset
/// range `0..search_len`, using the cyclic prefix of a `(n_fft, cp_len)` OFDM
/// symbol and the [default](GiSyncConfig::default) tuning. Returns `None` if
/// `iq` is too short to hold a full symbol plus the search span.
///
/// `search_len` should span at least one full symbol period (`n_fft + cp_len`)
/// so the true peak is included; callers typically pass one symbol period to
/// lock onto the symbol grid. See [`dvb_t_gi_sync_with`] to tune the ML energy
/// weight and the coherent-accumulation bound.
/// Like [`dvb_t_gi_sync`], with an explicit [`GiSyncConfig`] (`ρ` weight and the
/// coherent-accumulation bound `max_symbols`). Selects the offset maximizing the
/// van de Beek ML timing metric `|γ(d)| − ρ·Φ(d)`.
/// Refines a coarse offset to the best cyclic-prefix ML metric in a small window
/// `±radius` around `coarse`, using the [default](GiSyncConfig::default) tuning.
/// A convenience for a receiver that already has an approximate symbol boundary
/// (e.g. from a prior frame) and wants a cheap local re-lock rather than a full-
/// period search.
/// Like [`dvb_t_gi_refine`], with an explicit [`GiSyncConfig`].