pub const DVB_T_PRBS_INIT: u16 = 0b100_1010_1000_0000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DvbTEnergyDispersal {
reg: u16,
}
impl Default for DvbTEnergyDispersal {
fn default() -> Self {
Self::new()
}
}
impl DvbTEnergyDispersal {
pub fn new() -> Self {
Self {
reg: DVB_T_PRBS_INIT,
}
}
pub fn reset(&mut self) {
self.reg = DVB_T_PRBS_INIT;
}
#[inline]
fn next_bit(&mut self) -> u8 {
let fb = (self.reg ^ (self.reg >> 1)) & 1;
self.reg = (self.reg >> 1) | (fb << 14);
fb as u8
}
pub fn feed_in_place(&mut self, data: &mut [u8]) {
for byte in data.iter_mut() {
let mut out = 0u8;
for bit in (0..8).rev() {
let pn = self.next_bit();
out |= ((((*byte >> bit) & 1) ^ pn) & 1) << bit;
}
*byte = out;
}
}
pub fn feed(&mut self, data: &[u8]) -> Vec<u8> {
let mut out = data.to_vec();
self.feed_in_place(&mut out);
out
}
}
const DVB_T_AXIS_QPSK: [i32; 2] = [1, -1];
const DVB_T_AXIS_16QAM: [i32; 4] = [3, 1, -3, -1];
const DVB_T_AXIS_64QAM: [i32; 8] = [7, 5, 1, 3, -7, -5, -1, -3];
fn dvb_t_axis_table(v: usize) -> Option<&'static [i32]> {
match v {
2 => Some(&DVB_T_AXIS_QPSK),
4 => Some(&DVB_T_AXIS_16QAM),
6 => Some(&DVB_T_AXIS_64QAM),
_ => None,
}
}
#[inline]
fn axis_index(bits: &[u8]) -> usize {
bits.iter()
.fold(0usize, |acc, &b| (acc << 1) | (b & 1) as usize)
}
pub fn dvb_t_map_symbol(bits: &[u8]) -> Option<num_complex::Complex32> {
let v = bits.len();
let table = dvb_t_axis_table(v)?;
let scale = crate::modulate::qam::axis_scale(v);
let i_bits: Vec<u8> = bits.iter().step_by(2).copied().collect();
let q_bits: Vec<u8> = bits.iter().skip(1).step_by(2).copied().collect();
let i = table[axis_index(&i_bits)] as f32 * scale;
let q = table[axis_index(&q_bits)] as f32 * scale;
Some(num_complex::Complex32::new(i, q))
}
pub fn dvb_t_demap_symbol(sym: num_complex::Complex32, v: usize) -> Option<Vec<u8>> {
let table = dvb_t_axis_table(v)?;
let scale = crate::modulate::qam::axis_scale(v);
let k = v / 2; let nearest = |coord: f32| -> usize {
let mut best = 0usize;
let mut best_d = f32::INFINITY;
for (idx, &lvl) in table.iter().enumerate() {
let d = (coord - lvl as f32 * scale).abs();
if d < best_d {
best_d = d;
best = idx;
}
}
best
};
let i_idx = nearest(sym.re);
let q_idx = nearest(sym.im);
let unpack = |idx: usize| -> Vec<u8> { (0..k).rev().map(|b| ((idx >> b) & 1) as u8).collect() };
let ib = unpack(i_idx);
let qb = unpack(q_idx);
let mut out = vec![0u8; v];
for j in 0..k {
out[2 * j] = ib[j]; out[2 * j + 1] = qb[j]; }
Some(out)
}
use crate::fec::{
ConvCode, InnerFec, InterleaverKind, OuterFec, PunctureRate, ScramblerKind, ScramblerPos,
};
use crate::modulate::{ConstellationOrder, Mcs, McsTable, OfdmConfig};
use crate::multicarrier::CarrierPlan;
use num_complex::Complex32 as C32;
pub const DVB_T_N_FFT: usize = 2048;
pub const DVB_T_KMAX: usize = 1704;
pub const DVB_T_ACTIVE_CARRIERS: usize = DVB_T_KMAX + 1; pub const DVB_T_DATA_CARRIERS: usize = 1512;
const DVB_T_CENTER: i32 = (DVB_T_KMAX / 2) as i32;
pub const DVB_T_CONTINUAL_PILOTS_2K: [usize; 45] = [
0, 48, 54, 87, 141, 156, 192, 201, 255, 279, 282, 333, 432, 450, 483, 525, 531, 618, 636, 714,
759, 765, 780, 804, 873, 888, 918, 939, 942, 969, 984, 1050, 1101, 1107, 1110, 1137, 1140,
1146, 1206, 1269, 1323, 1377, 1491, 1683, 1704,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GuardInterval {
G1_32,
G1_16,
G1_8,
G1_4,
}
impl GuardInterval {
pub const fn cp_len_2k(self) -> usize {
match self {
GuardInterval::G1_32 => DVB_T_N_FFT / 32, GuardInterval::G1_16 => DVB_T_N_FFT / 16, GuardInterval::G1_8 => DVB_T_N_FFT / 8, GuardInterval::G1_4 => DVB_T_N_FFT / 4, }
}
}
#[inline]
pub const fn active_to_signed(a: usize) -> i32 {
a as i32 - DVB_T_CENTER
}
pub fn wk_prbs(len: usize) -> Vec<u8> {
let mut reg: u16 = 0x7FF; let mut out = Vec::with_capacity(len);
for _ in 0..len {
let bit = ((reg >> 10) & 1) as u8;
out.push(bit);
let fb = ((reg >> 10) ^ (reg >> 1)) & 1;
reg = ((reg << 1) | fb) & 0x7FF;
}
out
}
#[inline]
pub fn boosted_pilot_value(wk: u8) -> C32 {
C32::new((4.0 / 3.0) * 2.0 * (0.5 - wk as f32), 0.0)
}
pub fn dvb_t_2k_plan(guard: GuardInterval) -> CarrierPlan {
let wk = wk_prbs(DVB_T_ACTIVE_CARRIERS);
let pilots: Vec<(i32, C32)> = DVB_T_CONTINUAL_PILOTS_2K
.iter()
.map(|&a| (active_to_signed(a), boosted_pilot_value(wk[a])))
.collect();
let pilot_set: std::collections::HashSet<usize> =
DVB_T_CONTINUAL_PILOTS_2K.iter().copied().collect();
let data: Vec<i32> = (0..=DVB_T_KMAX)
.filter(|a| !pilot_set.contains(a))
.map(active_to_signed)
.collect();
CarrierPlan::new(DVB_T_N_FFT, guard.cp_len_2k())
.with_data_carriers(data)
.with_pilot_carriers(pilots)
}
pub fn dvb_t_fs_for_bandwidth(occupied_hz: f32) -> f32 {
occupied_hz * DVB_T_N_FFT as f32 / DVB_T_ACTIVE_CARRIERS as f32
}
pub fn dvb_t_occupied_bw(fs: f32) -> f32 {
fs * DVB_T_ACTIVE_CARRIERS as f32 / DVB_T_N_FFT as f32
}
pub const DVB_T_FS_333KHZ: f32 = 333_000.0 * DVB_T_N_FFT as f32 / DVB_T_ACTIVE_CARRIERS as f32;
pub const DVB_T_FS_1MHZ: f32 = 1_000_000.0 * DVB_T_N_FFT as f32 / DVB_T_ACTIVE_CARRIERS as f32;
pub const DVB_T_FS_2MHZ: f32 = 2_000_000.0 * DVB_T_N_FFT as f32 / DVB_T_ACTIVE_CARRIERS as f32;
pub fn dvb_t_mcs_table() -> McsTable {
let rs = OuterFec::ReedSolomon {
n: 204,
n_parity: 16,
};
let conv = |rate| InnerFec::Convolutional {
rate,
code: ConvCode::DvbK7,
};
McsTable::new(vec![
Mcs::new(ConstellationOrder::Qpsk, conv(PunctureRate::R1_2), rs),
Mcs::new(ConstellationOrder::Qpsk, conv(PunctureRate::R2_3), rs),
Mcs::new(ConstellationOrder::Qam16, conv(PunctureRate::R3_4), rs),
])
}
pub fn dvb_t_config(guard: GuardInterval, occupied_hz: f32) -> OfdmConfig {
let plan = dvb_t_2k_plan(guard);
let fs = dvb_t_fs_for_bandwidth(occupied_hz);
OfdmConfig::new(plan, fs, 0.0, 1.0, ConstellationOrder::Qpsk)
.with_scrambler(ScramblerKind::DvbTEnergyDispersal)
.with_scrambler_pos(ScramblerPos::BeforeOuterFec)
.with_outer_interleaver(InterleaverKind::Convolutional {
branches: 12,
depth: 17,
})
}