Skip to main content

comnum_waveform/ofdm/
dvbt.rs

1//! The OFDM frame of DVB-T (ETSI EN 300 744 V1.6.2, Secs. 4.4 to 4.6): the 2K and 8K
2//! modes, the guard intervals, the scattered and continual pilots, the carriers of the
3//! transmission parameter signalling (TPS) and the cells left to the data.
4//!
5//! # Frame
6//!
7//! A frame is [`SYMBOLS_PER_FRAME`] = 68 OFDM symbols $l = 0, \dots, 67$, a super-frame
8//! four frames. A symbol has $K$ carriers $k = 0, \dots, K_{\max}$ (Table 4): $K = 1705$
9//! in the 2K mode, $K = 6817$ in the 8K mode, spaced by $1/T_{U}$, $T_{U} = 2048\,T$ or
10//! $8192\,T$, the elementary period $T = 7/(8B)$ of a channel of bandwidth $B$ (7/64 µs
11//! at 8 MHz, Sec. 4.4); the guard interval $\Delta = T_{U}/4$, $T_{U}/8$, $T_{U}/16$ or
12//! $T_{U}/32$ is the cyclic continuation of the useful part, in front of it (Table 5).
13//! The carrier $k$ is emitted at $k' / T_{U}$, $k' = k - (K_{\max} + K_{\min})/2$, the
14//! centring of [`Ofdm`] ([`DvbtFrame::ofdm`]).
15//!
16//! # Reference signals and cells
17//!
18//! - The pilots carry $\pm 4/3$ ([`PILOT_BOOST`], $E|c|^{2} = 16/9$, Sec. 4.5.5), the sign
19//!   $2(1/2 - w_{k})$ of the PRBS $X^{11} + X^{2} + 1$ started at all ones on carrier
20//!   $K_{\min}$, one value per carrier ([`pilot_prbs`], Sec. 4.5.2, Figure 10);
21//! - the scattered pilots of symbol $l$ are the carriers $k = 3 (l \bmod 4) + 12 p$
22//!   (Sec. 4.5.3), the continual pilots those of Table 7, in every symbol (45 in the 2K
23//!   mode, 177 in the 8K mode, Sec. 4.5.4);
24//! - the TPS carriers of Table 8 (17 and 68, Sec. 4.6) carry, all alike, the
25//!   differential BPSK of one bit per symbol: $\pm 2(1/2 - w_{k})$, the sign $d_{0} = 1$ in
26//!   the first symbol of a frame and $d_{l} = d_{l-1} (1 - 2 s_{l})$ after
27//!   ([`tps_signs`], Sec. 4.6.3);
28//! - the other carriers are the data cells, 1512 (2K) or 6048 (8K) in every symbol
29//!   (Sec. 4.5.1), filled in the order of increasing $k$ (the order that reproduces the
30//!   interleaving example of Annex C, Table C.1; the text does not state it).
31//!
32//! # Examples
33//! ```
34//! use comnum_waveform::ofdm::dvbt::{DvbtFrame, Mode};
35//! let frame = DvbtFrame::new(Mode::K2);
36//! for l in 0..4 {
37//!     assert_eq!(frame.data_carriers(l).len(), 1512);      // Sec. 4.5.1
38//! }
39//! // "PRBS sequence starts: 1111111111100", Figure 10
40//! assert_eq!(&comnum_waveform::ofdm::dvbt::pilot_prbs(13), &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0]);
41//! ```
42//!
43//! # References
44//!
45//! 1. ETSI EN 300 744 V1.6.2, *Digital Video Broadcasting (DVB); Framing structure,
46//!    channel coding and modulation for digital terrestrial television*, 2015, Secs. 4.4
47//!    to 4.6, Tables 4, 5, 7 and 8.
48
49use super::Ofdm;
50use comnum_core::{Error, Result, ensure_shape};
51use comnum_math::C64;
52use comnum_math::sequences::lfsr;
53use std::str::FromStr;
54
55/// OFDM symbols of a frame.
56pub const SYMBOLS_PER_FRAME: usize = 68;
57/// Frames of a super-frame.
58pub const FRAMES_PER_SUPERFRAME: usize = 4;
59/// The amplitude of a pilot, $4/3$ ($E|c|^{2} = 16/9$, Sec. 4.5.5).
60pub const PILOT_BOOST: f64 = 4.0 / 3.0;
61
62/// The continual pilots of the 8K mode, Table 7; the 2K mode uses the first 45.
63const CONTINUAL_PILOTS: [u16; 177] = [
64    0, 48, 54, 87, 141, 156, 192, 201, 255, 279, 282, 333, 432, 450, 483, 525, 531, 618, 636, 714, 759, 765, 780, 804,
65    873, 888, 918, 939, 942, 969, 984, 1050, 1101, 1107, 1110, 1137, 1140, 1146, 1206, 1269, 1323, 1377, 1491, 1683,
66    1704, 1752, 1758, 1791, 1845, 1860, 1896, 1905, 1959, 1983, 1986, 2037, 2136, 2154, 2187, 2229, 2235, 2322, 2340,
67    2418, 2463, 2469, 2484, 2508, 2577, 2592, 2622, 2643, 2646, 2673, 2688, 2754, 2805, 2811, 2814, 2841, 2844, 2850,
68    2910, 2973, 3027, 3081, 3195, 3387, 3408, 3456, 3462, 3495, 3549, 3564, 3600, 3609, 3663, 3687, 3690, 3741, 3840,
69    3858, 3891, 3933, 3939, 4026, 4044, 4122, 4167, 4173, 4188, 4212, 4281, 4296, 4326, 4347, 4350, 4377, 4392, 4458,
70    4509, 4515, 4518, 4545, 4548, 4554, 4614, 4677, 4731, 4785, 4899, 5091, 5112, 5160, 5166, 5199, 5253, 5268, 5304,
71    5313, 5367, 5391, 5394, 5445, 5544, 5562, 5595, 5637, 5643, 5730, 5748, 5826, 5871, 5877, 5892, 5916, 5985, 6000,
72    6030, 6051, 6054, 6081, 6096, 6162, 6213, 6219, 6222, 6249, 6252, 6258, 6318, 6381, 6435, 6489, 6603, 6795, 6816,
73];
74
75/// The TPS carriers of the 8K mode, Table 8; the 2K mode uses the first 17.
76const TPS_CARRIERS: [u16; 68] = [
77    34, 50, 209, 346, 413, 569, 595, 688, 790, 901, 1073, 1219, 1262, 1286, 1469, 1594, 1687, 1738, 1754, 1913, 2050,
78    2117, 2273, 2299, 2392, 2494, 2605, 2777, 2923, 2966, 2990, 3173, 3298, 3391, 3442, 3458, 3617, 3754, 3821, 3977,
79    4003, 4096, 4198, 4309, 4481, 4627, 4670, 4694, 4877, 5002, 5095, 5146, 5162, 5321, 5458, 5525, 5681, 5707, 5800,
80    5902, 6013, 6185, 6331, 6374, 6398, 6581, 6706, 6799,
81];
82
83/// The transmission mode (Table 4).
84#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
85pub enum Mode {
86    /// 2K: 1705 carriers, an FFT of 2048.
87    K2,
88    /// 8K: 6817 carriers, an FFT of 8192.
89    K8,
90}
91
92impl Mode {
93    /// The number of carriers $K$.
94    pub fn carriers(self) -> usize {
95        match self {
96            Mode::K2 => 1705,
97            Mode::K8 => 6817,
98        }
99    }
100
101    /// The size of the FFT, $T_{U} / T$.
102    pub fn fft_len(self) -> usize {
103        match self {
104            Mode::K2 => 2048,
105            Mode::K8 => 8192,
106        }
107    }
108
109    /// The data cells of a symbol, $N_{\max}$ (Sec. 4.3.4.2).
110    pub fn data_cells(self) -> usize {
111        match self {
112            Mode::K2 => 1512,
113            Mode::K8 => 6048,
114        }
115    }
116
117    /// The continual pilots, Table 7.
118    pub fn continual_pilots(self) -> &'static [u16] {
119        match self {
120            Mode::K2 => &CONTINUAL_PILOTS[..45],
121            Mode::K8 => &CONTINUAL_PILOTS,
122        }
123    }
124
125    /// The TPS carriers, Table 8.
126    pub fn tps_carriers(self) -> &'static [u16] {
127        match self {
128            Mode::K2 => &TPS_CARRIERS[..17],
129            Mode::K8 => &TPS_CARRIERS,
130        }
131    }
132
133    /// `2K` or `8K`.
134    pub fn name(self) -> &'static str {
135        match self {
136            Mode::K2 => "2K",
137            Mode::K8 => "8K",
138        }
139    }
140}
141
142impl FromStr for Mode {
143    type Err = Error;
144
145    /// `2K` or `8K` (any case).
146    fn from_str(s: &str) -> Result<Mode> {
147        match s.trim().to_ascii_uppercase().as_str() {
148            "2K" => Ok(Mode::K2),
149            "8K" => Ok(Mode::K8),
150            _ => Err(Error::Config(format!("the DVB-T mode must be 2K or 8K, got {s:?}"))),
151        }
152    }
153}
154
155/// The guard interval $\Delta / T_{U}$ (Table 5).
156#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
157pub enum Guard {
158    /// 1/4.
159    G1_4,
160    /// 1/8.
161    G1_8,
162    /// 1/16.
163    G1_16,
164    /// 1/32.
165    G1_32,
166}
167
168impl Guard {
169    /// The four guard intervals.
170    pub const ALL: [Guard; 4] = [Guard::G1_4, Guard::G1_8, Guard::G1_16, Guard::G1_32];
171
172    /// The denominator $T_{U} / \Delta$.
173    pub fn denominator(self) -> usize {
174        match self {
175            Guard::G1_4 => 4,
176            Guard::G1_8 => 8,
177            Guard::G1_16 => 16,
178            Guard::G1_32 => 32,
179        }
180    }
181
182    /// The guard interval in samples of $T$.
183    pub fn samples(self, mode: Mode) -> usize {
184        mode.fft_len() / self.denominator()
185    }
186
187    /// `1/4`, `1/8`, `1/16` or `1/32`.
188    pub fn name(self) -> &'static str {
189        match self {
190            Guard::G1_4 => "1/4",
191            Guard::G1_8 => "1/8",
192            Guard::G1_16 => "1/16",
193            Guard::G1_32 => "1/32",
194        }
195    }
196}
197
198impl FromStr for Guard {
199    type Err = Error;
200
201    /// `1/4`, `1/8`, `1/16` or `1/32`.
202    fn from_str(s: &str) -> Result<Guard> {
203        Guard::ALL
204            .into_iter()
205            .find(|g| g.name() == s.trim())
206            .ok_or_else(|| Error::Config(format!("the guard interval must be 1/4, 1/8, 1/16 or 1/32, got {s:?}")))
207    }
208}
209
210/// The elementary period $T = 7 / (8 B)$ (s) of a channel of bandwidth $B$ (Hz): 7/64 µs
211/// at 8 MHz, 1/8 µs at 7 MHz, 7/48 µs at 6 MHz, 7/40 µs at 5 MHz (Sec. 4.4, Annexes E
212/// and G).
213pub fn elementary_period(bandwidth_hz: f64) -> f64 {
214    7.0 / (8.0 * bandwidth_hz)
215}
216
217/// The first `n` values $w_{k}$ of the PRBS of the pilots, $X^{11} + X^{2} + 1$, its 11
218/// registers started at one (Sec. 4.5.2, Figure 10): $w_{k+11} = w_{k} \oplus w_{k+2}$.
219pub fn pilot_prbs(n: usize) -> Vec<u8> {
220    lfsr(&[0, 2], 11, &[1; 11], n)
221}
222
223/// The signs $d_{l}$ of the TPS carriers over a frame for the 68 TPS bits $s_{0}, \dots,
224/// s_{67}$ of the frame: $d_{0} = 1$ (the first symbol is the reference, $s_{0}$
225/// initialises the differential BPSK), $d_{l} = d_{l-1} (1 - 2 s_{l})$ (Sec. 4.6.3).
226///
227/// # Errors
228/// [`Error::Shape`] unless there are 68 bits.
229pub fn tps_signs(bits: &[u8]) -> Result<Vec<f64>> {
230    ensure_shape(bits.len() == SYMBOLS_PER_FRAME, || format!("{} TPS bits for a frame of 68", bits.len()))?;
231    let mut d = 1.0;
232    Ok(bits
233        .iter()
234        .enumerate()
235        .map(|(l, &s)| {
236            if l > 0 && s & 1 == 1 {
237                d = -d;
238            }
239            d
240        })
241        .collect())
242}
243
244/// What a carrier of a symbol carries.
245#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
246pub enum Cell {
247    /// A continual pilot (Table 7); it may also be a scattered pilot.
248    ContinualPilot,
249    /// A scattered pilot, $k = 3 (l \bmod 4) + 12 p$.
250    ScatteredPilot,
251    /// A TPS carrier (Table 8).
252    Tps,
253    /// A data cell.
254    Data,
255}
256
257/// The cells of the frame of a mode: what every carrier of every symbol carries, the
258/// pilot PRBS and the data carriers (see the module).
259#[derive(Clone, Debug)]
260pub struct DvbtFrame {
261    mode: Mode,
262    /// $2(1/2 - w_{k})$ for every carrier.
263    reference: Vec<f64>,
264    /// The cells of a symbol for $l \bmod 4 = 0, \dots, 3$.
265    cells: [Vec<Cell>; 4],
266    /// The data carriers for $l \bmod 4 = 0, \dots, 3$, increasing.
267    data: [Vec<usize>; 4],
268}
269
270impl DvbtFrame {
271    /// The frame of the mode `mode`.
272    pub fn new(mode: Mode) -> DvbtFrame {
273        let k_total = mode.carriers();
274        let reference = pilot_prbs(k_total).into_iter().map(|w| 1.0 - 2.0 * f64::from(w)).collect();
275        let mut continual = vec![false; k_total];
276        for &k in mode.continual_pilots() {
277            continual[usize::from(k)] = true;
278        }
279        let mut tps = vec![false; k_total];
280        for &k in mode.tps_carriers() {
281            tps[usize::from(k)] = true;
282        }
283        let cells: [Vec<Cell>; 4] = std::array::from_fn(|r| {
284            (0..k_total)
285                .map(|k| {
286                    if continual[k] {
287                        Cell::ContinualPilot
288                    } else if k >= 3 * r && (k - 3 * r) % 12 == 0 {
289                        Cell::ScatteredPilot
290                    } else if tps[k] {
291                        Cell::Tps
292                    } else {
293                        Cell::Data
294                    }
295                })
296                .collect()
297        });
298        let data = std::array::from_fn(|r| (0..k_total).filter(|&k| cells[r][k] == Cell::Data).collect());
299        DvbtFrame { mode, reference, cells, data }
300    }
301
302    /// The mode.
303    pub fn mode(&self) -> Mode {
304        self.mode
305    }
306
307    /// What carrier `k` of symbol `l` carries.
308    pub fn cell(&self, l: usize, k: usize) -> Cell {
309        self.cells[l % 4][k]
310    }
311
312    /// The data carriers of symbol `l`, increasing ($N_{\max}$ of them).
313    pub fn data_carriers(&self, l: usize) -> &[usize] {
314        &self.data[l % 4]
315    }
316
317    /// The sign $2(1/2 - w_{k})$ of the reference signals of carrier `k`.
318    pub fn reference(&self, k: usize) -> f64 {
319        self.reference[k]
320    }
321
322    /// The mean power $E|c|^{2}$ over the $K$ carriers of a symbol: the data and TPS cells
323    /// at 1, the pilots at 16/9; the same for every symbol (1.0803 in the 2K mode, 1.0800
324    /// in the 8K mode).
325    pub fn mean_carrier_power(&self) -> f64 {
326        let power: f64 = self.cells[0]
327            .iter()
328            .map(|c| match c {
329                Cell::ContinualPilot | Cell::ScatteredPilot => PILOT_BOOST * PILOT_BOOST,
330                Cell::Tps | Cell::Data => 1.0,
331            })
332            .sum();
333        power / self.mode.carriers() as f64
334    }
335
336    /// The $K$ cells of symbol `l`: the pilots, the TPS carriers with the sign
337    /// `tps_sign` ($d_{l}$ of [`tps_signs`]) and the $N_{\max}$ values `data` on the data
338    /// carriers.
339    ///
340    /// # Errors
341    /// [`Error::Shape`] unless `data` has $N_{\max}$ values.
342    pub fn symbol(&self, l: usize, data: &[C64], tps_sign: f64) -> Result<Vec<C64>> {
343        ensure_shape(data.len() == self.mode.data_cells(), || {
344            format!("{} data cells for {} in a {} symbol", data.len(), self.mode.data_cells(), self.mode.name())
345        })?;
346        let mut out: Vec<C64> = self.cells[l % 4]
347            .iter()
348            .zip(&self.reference)
349            .map(|(c, &r)| match c {
350                Cell::ContinualPilot | Cell::ScatteredPilot => C64::new(PILOT_BOOST * r, 0.0),
351                Cell::Tps => C64::new(tps_sign * r, 0.0),
352                Cell::Data => C64::new(0.0, 0.0),
353            })
354            .collect();
355        for (&k, &v) in self.data[l % 4].iter().zip(data) {
356            out[k] = v;
357        }
358        Ok(out)
359    }
360
361    /// The data cells of the $K$ received cells of symbol `l`.
362    ///
363    /// # Errors
364    /// [`Error::Shape`] unless `cells` has $K$ values.
365    pub fn data_of(&self, l: usize, cells: &[C64]) -> Result<Vec<C64>> {
366        ensure_shape(cells.len() == self.mode.carriers(), || {
367            format!("{} cells for the {} carriers of a {} symbol", cells.len(), self.mode.carriers(), self.mode.name())
368        })?;
369        Ok(self.data[l % 4].iter().map(|&k| cells[k]).collect())
370    }
371
372    /// The statistic of the TPS sign of a received symbol: $\sum_{k} \mathrm{Re}\{y_{k}
373    /// h_{k}^{*}\} \cdot 2(1/2 - w_{k})$ over the TPS carriers, of the sign of $d_{l}$
374    /// (`channel` the gains of the carriers, 1 if `None`).
375    ///
376    /// # Errors
377    /// [`Error::Shape`] unless `cells` (and `channel`) have $K$ values.
378    pub fn tps_statistic(&self, cells: &[C64], channel: Option<&[C64]>) -> Result<f64> {
379        let k_total = self.mode.carriers();
380        ensure_shape(cells.len() == k_total && channel.is_none_or(|h| h.len() == k_total), || {
381            format!("the cells and the channel of a {} symbol have {k_total} values", self.mode.name())
382        })?;
383        Ok(self
384            .mode
385            .tps_carriers()
386            .iter()
387            .map(|&k| {
388                let k = usize::from(k);
389                let h = channel.map_or(C64::new(1.0, 0.0), |h| h[k]);
390                (cells[k] * h.conj()).re * self.reference[k]
391            })
392            .sum())
393    }
394
395    /// The OFDM modulator of the mode with the guard interval `guard`: $K$ active carriers
396    /// out of the FFT of $T_{U}/T$, one sample per $T$.
397    ///
398    /// # Errors
399    /// Never: the sizes of the mode are valid.
400    pub fn ofdm(&self, guard: Guard) -> Result<Ofdm> {
401        let base = Ofdm::new(self.mode.fft_len(), self.mode.carriers())?;
402        Ok(Ofdm { oversampling: 1, n_cp: guard.samples(self.mode), ..base })
403    }
404}
405
406#[cfg(test)]
407mod tests {
408    use super::*;
409
410    #[test]
411    fn every_symbol_has_the_data_cells_of_sec_4_5_1() {
412        for mode in [Mode::K2, Mode::K8] {
413            let f = DvbtFrame::new(mode);
414            for l in 0..4 {
415                assert_eq!(f.data_carriers(l).len(), mode.data_cells(), "{} l = {l}", mode.name());
416            }
417            // no TPS carrier is a pilot (Table 8 against Table 7 and the scattered pattern)
418            for &k in mode.tps_carriers() {
419                for l in 0..4 {
420                    assert_eq!(f.cell(l, usize::from(k)), Cell::Tps);
421                }
422            }
423        }
424    }
425
426    #[test]
427    fn the_pilot_prbs_starts_as_figure_10() {
428        // "PRBS sequence starts: 1111111111100..."
429        assert_eq!(&pilot_prbs(13), &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0]);
430        let p = pilot_prbs(2 * 2047);
431        assert_eq!(&p[..2047], &p[2047..], "period 2^11 - 1");
432    }
433
434    #[test]
435    fn the_tables_have_the_lengths_of_the_standard() {
436        assert_eq!((Mode::K2.continual_pilots().len(), Mode::K8.continual_pilots().len()), (45, 177));
437        assert_eq!((Mode::K2.tps_carriers().len(), Mode::K8.tps_carriers().len()), (17, 68));
438        assert!(CONTINUAL_PILOTS.windows(2).all(|w| w[0] < w[1]));
439        assert!(TPS_CARRIERS.windows(2).all(|w| w[0] < w[1]));
440        assert_eq!((CONTINUAL_PILOTS[44], CONTINUAL_PILOTS[176]), (1704, 6816), "the edges are pilots");
441    }
442
443    #[test]
444    fn the_boost_gives_the_mean_power_of_the_carriers() {
445        // 176 boosted cells of the 1705 in the 2K mode, 701 of the 6817 in the 8K mode
446        let p2 = (1512.0 + 17.0 + 176.0 * 16.0 / 9.0) / 1705.0;
447        let p8 = (6048.0 + 68.0 + 701.0 * 16.0 / 9.0) / 6817.0;
448        assert!((DvbtFrame::new(Mode::K2).mean_carrier_power() - p2).abs() < 1e-12);
449        assert!((DvbtFrame::new(Mode::K8).mean_carrier_power() - p8).abs() < 1e-12);
450    }
451
452    #[test]
453    fn a_symbol_places_its_cells() {
454        let f = DvbtFrame::new(Mode::K2);
455        let data: Vec<C64> = (0..1512).map(|i| C64::new(i as f64, -1.0)).collect();
456        let s = f.symbol(1, &data, -1.0).unwrap();
457        assert_eq!(f.data_of(1, &s).unwrap(), data);
458        assert_eq!(s[0], C64::new(-PILOT_BOOST, 0.0), "carrier 0, a continual pilot, w = 1: 2(1/2 - 1) 4/3");
459        assert_eq!(s[34], C64::new(-f.reference(34), 0.0), "the first TPS carrier");
460        let bits: Vec<u8> = [0, 1, 1, 0].iter().chain(&[0; 64]).copied().collect();
461        assert_eq!(&tps_signs(&bits).unwrap()[..5], &[1.0, -1.0, 1.0, 1.0, 1.0]);
462        assert!(f.tps_statistic(&s, None).unwrap() < 0.0);
463    }
464
465    #[test]
466    fn the_modes_and_guards_parse() {
467        assert_eq!("8k".parse::<Mode>().unwrap(), Mode::K8);
468        assert_eq!("1/16".parse::<Guard>().unwrap().samples(Mode::K2), 128);
469        assert!("4K".parse::<Mode>().is_err());
470        assert!((elementary_period(8e6) - 7.0 / 64.0 * 1e-6).abs() < 1e-18);
471    }
472}