use crate::modulate::ofdm::ConstellationOrder;
use num_complex::Complex32 as C32;
use std::ops::Range;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum BitOutcome {
#[default]
Clean = 0,
Corrected = 1,
Uncorrected = 2,
Introduced = 3,
}
impl BitOutcome {
#[inline(always)]
pub(crate) fn classify(arrived_correct: bool, decoder_agrees: bool) -> Self {
match (arrived_correct, decoder_agrees) {
(true, true) => BitOutcome::Clean,
(false, true) => BitOutcome::Corrected,
(false, false) => BitOutcome::Uncorrected,
(true, false) => BitOutcome::Introduced,
}
}
#[inline(always)]
pub fn arrived_wrong(self) -> bool {
matches!(self, BitOutcome::Corrected | BitOutcome::Uncorrected)
}
#[inline(always)]
pub fn decoder_disagreed(self) -> bool {
matches!(self, BitOutcome::Uncorrected | BitOutcome::Introduced)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OfdmProbeFrame {
pub sequence_num: Option<u32>,
pub constellation: ConstellationOrder,
pub(crate) symbols: Range<usize>,
pub(crate) correction: Range<usize>,
pub codeword_bits: usize,
pub codeword_info_bits: usize,
pub decoded: bool,
}
#[derive(Debug, Clone, Default)]
pub struct OfdmRxProbe {
pub(crate) symbols: Vec<C32>,
pub(crate) correction: Vec<BitOutcome>,
pub(crate) frames: Vec<OfdmProbeFrame>,
pub(crate) estimate: Vec<u8>,
}
impl OfdmRxProbe {
pub fn new() -> Self {
Self::default()
}
pub fn frames(&self) -> &[OfdmProbeFrame] {
&self.frames
}
pub fn symbols(&self) -> &[C32] {
&self.symbols
}
pub fn correction(&self) -> &[BitOutcome] {
&self.correction
}
pub fn iter(&self) -> impl Iterator<Item = ProbedFrame<'_>> {
self.frames.iter().map(move |meta| ProbedFrame {
meta,
symbols: &self.symbols[meta.symbols.clone()],
correction: &self.correction[meta.correction.clone()],
})
}
pub fn is_empty(&self) -> bool {
self.frames.is_empty()
}
pub fn clear(&mut self) {
self.symbols.clear();
self.correction.clear();
self.frames.clear();
}
pub(crate) fn mark(&self) -> ProbeMark {
ProbeMark {
symbols: self.symbols.len(),
correction: self.correction.len(),
frames: self.frames.len(),
}
}
pub(crate) fn rollback(&mut self, mark: ProbeMark) {
debug_assert!(
self.symbols.len() >= mark.symbols
&& self.correction.len() >= mark.correction
&& self.frames.len() >= mark.frames,
"probe buffers must only grow between mark and rollback"
);
self.symbols.truncate(mark.symbols);
self.correction.truncate(mark.correction);
self.frames.truncate(mark.frames);
}
pub(crate) fn push_undecoded(
&mut self,
sym_start: usize,
constellation: ConstellationOrder,
sequence_num: Option<u32>,
) {
let end = self.correction.len();
self.frames.push(OfdmProbeFrame {
sequence_num,
constellation,
symbols: sym_start..self.symbols.len(),
correction: end..end,
codeword_bits: 0,
codeword_info_bits: 0,
decoded: false,
});
}
pub(crate) fn push_decoded(
&mut self,
sym_start: usize,
meta: ProbeMeta,
truth: &[u8],
received: &[u8],
) {
let Self {
correction,
estimate,
frames,
symbols,
} = self;
let start = correction.len();
let n = truth.len().min(received.len()).min(estimate.len());
correction.extend(
(0..n).map(|i| BitOutcome::classify(received[i] == truth[i], estimate[i] == truth[i])),
);
frames.push(OfdmProbeFrame {
sequence_num: meta.sequence_num,
constellation: meta.constellation,
symbols: sym_start..symbols.len(),
correction: start..correction.len(),
codeword_bits: meta.codeword_bits,
codeword_info_bits: meta.codeword_info_bits,
decoded: true,
});
}
}
#[derive(Debug, Clone, Copy)]
pub struct ProbedFrame<'a> {
pub meta: &'a OfdmProbeFrame,
pub symbols: &'a [C32],
pub correction: &'a [BitOutcome],
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct ProbeMeta {
pub(crate) sequence_num: Option<u32>,
pub(crate) constellation: ConstellationOrder,
pub(crate) codeword_bits: usize,
pub(crate) codeword_info_bits: usize,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct ProbeMark {
symbols: usize,
correction: usize,
frames: usize,
}