1use super::Ofdm;
50use comnum_core::{Error, Result, ensure_shape};
51use comnum_math::C64;
52use comnum_math::sequences::lfsr;
53use std::str::FromStr;
54
55pub const SYMBOLS_PER_FRAME: usize = 68;
57pub const FRAMES_PER_SUPERFRAME: usize = 4;
59pub const PILOT_BOOST: f64 = 4.0 / 3.0;
61
62const 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
75const 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
85pub enum Mode {
86 K2,
88 K8,
90}
91
92impl Mode {
93 pub fn carriers(self) -> usize {
95 match self {
96 Mode::K2 => 1705,
97 Mode::K8 => 6817,
98 }
99 }
100
101 pub fn fft_len(self) -> usize {
103 match self {
104 Mode::K2 => 2048,
105 Mode::K8 => 8192,
106 }
107 }
108
109 pub fn data_cells(self) -> usize {
111 match self {
112 Mode::K2 => 1512,
113 Mode::K8 => 6048,
114 }
115 }
116
117 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 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 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 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
157pub enum Guard {
158 G1_4,
160 G1_8,
162 G1_16,
164 G1_32,
166}
167
168impl Guard {
169 pub const ALL: [Guard; 4] = [Guard::G1_4, Guard::G1_8, Guard::G1_16, Guard::G1_32];
171
172 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 pub fn samples(self, mode: Mode) -> usize {
184 mode.fft_len() / self.denominator()
185 }
186
187 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 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
210pub fn elementary_period(bandwidth_hz: f64) -> f64 {
214 7.0 / (8.0 * bandwidth_hz)
215}
216
217pub fn pilot_prbs(n: usize) -> Vec<u8> {
220 lfsr(&[0, 2], 11, &[1; 11], n)
221}
222
223pub 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
246pub enum Cell {
247 ContinualPilot,
249 ScatteredPilot,
251 Tps,
253 Data,
255}
256
257#[derive(Clone, Debug)]
260pub struct DvbtFrame {
261 mode: Mode,
262 reference: Vec<f64>,
264 cells: [Vec<Cell>; 4],
266 data: [Vec<usize>; 4],
268}
269
270impl DvbtFrame {
271 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 pub fn mode(&self) -> Mode {
304 self.mode
305 }
306
307 pub fn cell(&self, l: usize, k: usize) -> Cell {
309 self.cells[l % 4][k]
310 }
311
312 pub fn data_carriers(&self, l: usize) -> &[usize] {
314 &self.data[l % 4]
315 }
316
317 pub fn reference(&self, k: usize) -> f64 {
319 self.reference[k]
320 }
321
322 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 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 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 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 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 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 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 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}