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
}
pub fn advance_byte(&mut self) {
for _ in 0..8 {
self.next_bit();
}
}
}
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)
}
pub fn dvb_t_soft_llr(sym: num_complex::Complex32, v: usize) -> Option<Vec<f32>> {
let table = dvb_t_axis_table(v)?;
let scale = crate::modulate::qam::axis_scale(v);
let k = v / 2;
let axis_llrs = |coord: f32| -> Vec<f32> {
let mut out = vec![0.0f32; k];
for (b, slot) in out.iter_mut().enumerate() {
let shift = k - 1 - b;
let mut d0 = f32::INFINITY;
let mut d1 = f32::INFINITY;
for (idx, &lvl) in table.iter().enumerate() {
let d = coord - lvl as f32 * scale;
let d_sq = d * d;
if (idx >> shift) & 1 == 0 {
d0 = d0.min(d_sq);
} else {
d1 = d1.min(d_sq);
}
}
*slot = d1 - d0;
}
out
};
let il = axis_llrs(sym.re);
let ql = axis_llrs(sym.im);
let mut out = vec![0.0f32; v];
for j in 0..k {
out[2 * j] = il[j];
out[2 * j + 1] = ql[j];
}
Some(out)
}
pub fn is_dvb_t_constellation(order: ConstellationOrder) -> bool {
matches!(
order,
ConstellationOrder::Qpsk | ConstellationOrder::Qam16 | ConstellationOrder::Qam64
)
}
use crate::fec::{
ConvCode, InnerFec, InterleaverKind, OuterFec, PunctureRate, ScramblerKind, ScramblerPos,
};
use crate::modulate::{ConstellationOrder, Mcs, McsTable, OfdmConfig};
use crate::multicarrier::{CarrierGrid, 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,
];
pub const DVB_T_TPS_CARRIERS_2K: [usize; 17] = [
34, 50, 209, 346, 413, 569, 595, 688, 790, 901, 1073, 1219, 1262, 1286, 1469, 1594, 1687,
];
pub const DVB_T_SCATTERED_PHASES: usize = 4;
#[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, }
}
pub const fn from_cp_len_2k(cp_len: usize) -> Option<Self> {
match cp_len {
64 => Some(GuardInterval::G1_32),
128 => Some(GuardInterval::G1_16),
256 => Some(GuardInterval::G1_8),
512 => Some(GuardInterval::G1_4),
_ => None,
}
}
}
#[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 scattered_pilot_indices(phase: usize) -> Vec<usize> {
let start = 3 * (phase % DVB_T_SCATTERED_PHASES);
(start..=DVB_T_KMAX).step_by(12).collect()
}
pub fn tps_carrier_indices() -> &'static [usize] {
&DVB_T_TPS_CARRIERS_2K
}
pub fn tps_carrier_bins() -> [usize; DVB_T_TPS_CARRIERS_2K.len()] {
core::array::from_fn(|i| {
active_to_signed(DVB_T_TPS_CARRIERS_2K[i]).rem_euclid(DVB_T_N_FFT as i32) as usize
})
}
pub fn continual_pilot_bins() -> [usize; DVB_T_CONTINUAL_PILOTS_2K.len()] {
core::array::from_fn(|i| {
active_to_signed(DVB_T_CONTINUAL_PILOTS_2K[i]).rem_euclid(DVB_T_N_FFT as i32) as usize
})
}
pub fn dvb_t_2k_plans(guard: GuardInterval) -> [CarrierPlan; DVB_T_SCATTERED_PHASES] {
let wk = wk_prbs(DVB_T_ACTIVE_CARRIERS);
core::array::from_fn(|phase| {
let mut reserved: std::collections::BTreeSet<usize> =
DVB_T_CONTINUAL_PILOTS_2K.iter().copied().collect();
reserved.extend(scattered_pilot_indices(phase));
reserved.extend(DVB_T_TPS_CARRIERS_2K.iter().copied());
let pilots: Vec<(i32, C32)> = reserved
.iter()
.map(|&a| (active_to_signed(a), boosted_pilot_value(wk[a])))
.collect();
let data: Vec<i32> = (0..=DVB_T_KMAX)
.filter(|a| !reserved.contains(a))
.map(active_to_signed)
.collect();
assert_eq!(
data.len(),
DVB_T_DATA_CARRIERS,
"scattered plan phase {phase} must carry exactly {DVB_T_DATA_CARRIERS} data carriers"
);
CarrierPlan::new(DVB_T_N_FFT, guard.cp_len_2k())
.with_data_carriers(data)
.with_pilot_carriers(pilots)
})
}
#[derive(Debug, Clone)]
struct ScatteredGridCycle {
grids: [CarrierGrid; DVB_T_SCATTERED_PHASES],
ref_pilots: [Vec<(usize, C32)>; DVB_T_SCATTERED_PHASES],
phase: usize,
}
impl ScatteredGridCycle {
fn new(guard: GuardInterval) -> Self {
let plans = dvb_t_2k_plans(guard);
let grids = plans.each_ref().map(CarrierGrid::from_plan);
let tps: std::collections::HashSet<usize> = tps_carrier_bins().into_iter().collect();
let ref_pilots = grids.each_ref().map(|g| {
g.pilot_bins()
.iter()
.copied()
.filter(|&(bin, _)| !tps.contains(&bin))
.collect::<Vec<_>>()
});
Self {
grids,
ref_pilots,
phase: 0,
}
}
fn current(&self) -> &CarrierGrid {
&self.grids[self.phase]
}
fn current_ref_pilots(&self) -> &[(usize, C32)] {
&self.ref_pilots[self.phase]
}
fn advance(&mut self) {
self.phase = (self.phase + 1) % DVB_T_SCATTERED_PHASES;
}
fn reset(&mut self) {
self.phase = 0;
}
}
#[derive(Debug, Clone)]
pub struct ScatteredPilotMapper {
cycle: ScatteredGridCycle,
}
impl ScatteredPilotMapper {
pub fn new(guard: GuardInterval) -> Self {
Self {
cycle: ScatteredGridCycle::new(guard),
}
}
pub fn num_data_carriers(&self) -> usize {
DVB_T_DATA_CARRIERS
}
pub fn n_fft(&self) -> usize {
DVB_T_N_FFT
}
pub fn reset(&mut self) {
self.cycle.reset();
}
pub fn map_symbol(&mut self, data: &[C32], freq_out: &mut [C32]) {
let grid = self.cycle.current();
let n_fft = grid.n_fft();
debug_assert!(data.len() >= grid.num_data_carriers());
debug_assert!(freq_out.len() >= n_fft);
for bin in freq_out[..n_fft].iter_mut() {
*bin = C32::default();
}
for (k, &bin) in grid.data_bins().iter().enumerate() {
freq_out[bin] = data[k];
}
for &(bin, value) in grid.pilot_bins() {
freq_out[bin] = value;
}
self.cycle.advance();
}
}
#[derive(Debug, Clone)]
pub struct ScatteredPilotExtractor {
cycle: ScatteredGridCycle,
}
impl ScatteredPilotExtractor {
pub fn new(guard: GuardInterval) -> Self {
Self {
cycle: ScatteredGridCycle::new(guard),
}
}
pub fn num_data_carriers(&self) -> usize {
DVB_T_DATA_CARRIERS
}
pub fn n_fft(&self) -> usize {
DVB_T_N_FFT
}
pub fn reset(&mut self) {
self.cycle.reset();
}
pub fn current_pilot_bins(&self) -> &[(usize, C32)] {
self.cycle.current_ref_pilots()
}
pub fn data_bins(&self) -> &[usize] {
self.cycle.current().data_bins()
}
pub fn extract_symbol(&mut self, freq: &[C32], data_out: &mut [C32]) {
let grid = self.cycle.current();
debug_assert!(freq.len() >= grid.n_fft());
debug_assert!(data_out.len() >= grid.num_data_carriers());
for (k, &bin) in grid.data_bins().iter().enumerate() {
data_out[k] = freq[bin];
}
self.cycle.advance();
}
}
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;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NbBandwidth {
Bw333kHz,
Bw1MHz,
Bw2MHz,
}
impl NbBandwidth {
pub const fn occupied_hz(self) -> f32 {
match self {
NbBandwidth::Bw333kHz => 333_000.0,
NbBandwidth::Bw1MHz => 1_000_000.0,
NbBandwidth::Bw2MHz => 2_000_000.0,
}
}
pub fn fs(self) -> f32 {
dvb_t_fs_for_bandwidth(self.occupied_hz())
}
pub fn is_pluto_continuous_tx(self) -> bool {
self.fs() >= 521_000.0
}
}
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 {
dvb_t_config_with_plan(dvb_t_2k_plan(guard), occupied_hz)
}
pub fn dvb_t_scattered_config(guard: GuardInterval, occupied_hz: f32) -> OfdmConfig {
let plan = dvb_t_2k_plans(guard)[0].clone();
dvb_t_config_with_plan(plan, occupied_hz).with_dvb_t_scattered(true)
}
fn dvb_t_config_with_plan(plan: CarrierPlan, occupied_hz: f32) -> OfdmConfig {
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,
})
}
pub const DVB_T_FRAME_OUTER: OuterFec = OuterFec::ReedSolomon {
n: 204,
n_parity: 16,
};
pub const DVB_T_FRAME_OUTER_IL: InterleaverKind = InterleaverKind::Convolutional {
branches: 12,
depth: 17,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DvbTLinkParams {
pub guard: GuardInterval,
pub constellation: ConstellationOrder,
pub code_rate: PunctureRate,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DvbTFrameParams {
pub link: DvbTLinkParams,
pub frame_number: u8,
pub cell_id: u8,
}
impl DvbTFrameParams {
pub fn guard(self) -> GuardInterval {
self.link.guard
}
pub fn constellation(self) -> ConstellationOrder {
self.link.constellation
}
pub fn code_rate(self) -> PunctureRate {
self.link.code_rate
}
pub fn inner(self) -> InnerFec {
InnerFec::Convolutional {
rate: self.link.code_rate,
code: ConvCode::DvbK7,
}
}
pub fn tps_word(self) -> crate::waveform::dvb_t_tps::TpsWord {
crate::waveform::dvb_t_tps::TpsWord {
frame_number: self.frame_number,
constellation: self.link.constellation,
code_rate_hp: self.link.code_rate,
guard: self.link.guard,
cell_id: self.cell_id,
}
}
pub fn config(self) -> OfdmConfig {
let plan0 = dvb_t_2k_plans(self.link.guard)[0].clone();
let fs = dvb_t_fs_for_bandwidth(1_000_000.0);
OfdmConfig::new(plan0, fs, 0.0, 1.0, self.link.constellation).with_dvb_t_scattered(true)
}
}