use crate::core::{Block, WorkReport};
use num_complex::Complex32 as C32;
const fn check_bits(bits: usize) {
assert!(
bits == 4 || bits == 6 || bits == 8,
"QamDecider: BITS must be 4 (QAM-16), 6 (QAM-64), or 8 (QAM-256)"
);
}
fn axis_scale(bits: usize) -> f32 {
let m = 1usize << (bits / 2);
let avg_e_total = 2.0 * ((m * m - 1) as f64) / 3.0;
(1.0 / avg_e_total.sqrt()) as f32
}
const fn build_threshold_table(bits: usize, scale: f32) -> [f32; 15] {
let k = bits / 2;
let m = 1usize << k;
let mut table = [0.0f32; 15];
let mut j = 0usize;
while j < m - 1 {
let mid_unnorm = (2 * j) as f32 - (m - 2) as f32; table[j] = mid_unnorm * scale;
j += 1;
}
table
}
#[derive(Debug, Clone, Copy)]
pub struct QamDemod {
gain: f32,
}
impl QamDemod {
pub fn new(gain: f32) -> Self {
Self { gain }
}
pub fn set_gain(&mut self, g: f32) {
self.gain = g;
}
}
impl Block for QamDemod {
type In = C32;
type Out = C32;
#[inline(always)]
fn process(&mut self, input: &[C32], output: &mut [C32]) -> WorkReport {
let n = input.len().min(output.len());
let g = self.gain;
let mut i = 0;
let nn = n & !3;
while i < nn {
output[i] = C32::new(g * input[i].re, g * input[i].im);
output[i + 1] = C32::new(g * input[i + 1].re, g * input[i + 1].im);
output[i + 2] = C32::new(g * input[i + 2].re, g * input[i + 2].im);
output[i + 3] = C32::new(g * input[i + 3].re, g * input[i + 3].im);
i += 4;
}
while i < n {
output[i] = C32::new(g * input[i].re, g * input[i].im);
i += 1;
}
WorkReport {
in_read: n,
out_written: n,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct QamDecider<const BITS: usize> {
thresholds: [f32; 15],
}
impl<const BITS: usize> QamDecider<BITS> {
pub fn new() -> Self {
const {
check_bits(BITS);
}
Self {
thresholds: build_threshold_table(BITS, axis_scale(BITS)),
}
}
#[inline(always)]
fn decide_axis(&self, v: f32, out: &mut [u8], base: usize) {
let k = BITS / 2;
let m = 1 << k;
let mut nat = 0usize;
let mut t = 0;
while t < m - 1 {
if v > self.thresholds[t] {
nat += 1;
}
t += 1;
}
let gray = nat ^ (nat >> 1);
let mut b = 0;
while b < k {
out[base + b] = ((gray >> (k - 1 - b)) & 1) as u8;
b += 1;
}
}
}
impl<const BITS: usize> Default for QamDecider<BITS> {
fn default() -> Self {
Self::new()
}
}
impl<const BITS: usize> Block for QamDecider<BITS> {
type In = C32;
type Out = u8;
#[inline(always)]
fn process(&mut self, input: &[C32], output: &mut [u8]) -> WorkReport {
let n_syms = input.len().min(output.len() / BITS);
let k = BITS / 2;
let mut i = 0;
let nn = n_syms & !3;
while i < nn {
self.decide_axis(input[i].re, output, i * BITS);
self.decide_axis(input[i].im, output, i * BITS + k);
self.decide_axis(input[i + 1].re, output, (i + 1) * BITS);
self.decide_axis(input[i + 1].im, output, (i + 1) * BITS + k);
self.decide_axis(input[i + 2].re, output, (i + 2) * BITS);
self.decide_axis(input[i + 2].im, output, (i + 2) * BITS + k);
self.decide_axis(input[i + 3].re, output, (i + 3) * BITS);
self.decide_axis(input[i + 3].im, output, (i + 3) * BITS + k);
i += 4;
}
while i < n_syms {
self.decide_axis(input[i].re, output, i * BITS);
self.decide_axis(input[i].im, output, i * BITS + k);
i += 1;
}
WorkReport {
in_read: n_syms,
out_written: n_syms * BITS,
}
}
}
pub type Qam16Decider = QamDecider<4>;
pub type Qam64Decider = QamDecider<6>;
pub type Qam256Decider = QamDecider<8>;