pub mod fano;
use alloc::vec;
#[cfg(not(feature = "std"))]
use num_traits::Float;
use super::FecCodec;
use crate::core::{FecOpts, FecResult};
#[derive(Copy, Clone, Debug, Default)]
pub struct ConvFano;
impl ConvFano {
pub const NBITS: usize = 81;
pub const DEFAULT_DELTA: i32 = 17;
pub const DEFAULT_MAX_CYCLES: u64 = 10_000;
pub const METRIC_SCALE: f32 = 16.0;
pub const METRIC_BIAS: f32 = 1.0;
}
fn pack_msg_with_tail(info: &[u8]) -> [u8; 11] {
assert_eq!(info.len(), 50, "WSPR info payload must be 50 bits");
let mut packed = [0u8; 11];
for (i, &b) in info.iter().enumerate() {
if b & 1 != 0 {
packed[i / 8] |= 1 << (7 - (i % 8));
}
}
packed
}
impl FecCodec for ConvFano {
const N: usize = 162;
const K: usize = 50;
fn encode(&self, info: &[u8], codeword: &mut [u8]) {
assert_eq!(info.len(), Self::K);
assert_eq!(codeword.len(), Self::N);
let packed = pack_msg_with_tail(info);
let mut out = vec![0u8; 2 * Self::NBITS];
fano::conv_encode(&packed, Self::NBITS, &mut out);
codeword.copy_from_slice(&out);
}
fn decode_soft(&self, llr: &[f32], _opts: &FecOpts) -> Option<FecResult> {
assert_eq!(llr.len(), Self::N);
let bm = fano::build_branch_metrics(llr, Self::METRIC_BIAS, Self::METRIC_SCALE);
let res = fano::fano_decode(
&bm,
Self::NBITS,
Self::DEFAULT_DELTA,
Self::DEFAULT_MAX_CYCLES,
);
if !res.converged {
return None;
}
let mut info = vec![0u8; Self::K];
for i in 0..Self::K {
info[i] = (res.data[i / 8] >> (7 - (i % 8))) & 1;
}
let mut reencoded = vec![0u8; Self::N];
self.encode(&info, &mut reencoded);
let hard_errors = llr
.iter()
.zip(reencoded.iter())
.filter(|&(&l, &c)| (c == 1) != (l < 0.0))
.count() as u32;
Some(FecResult {
info,
hard_errors,
iterations: 0,
})
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct ConvFano232;
impl ConvFano232 {
pub const NBITS: usize = 103;
pub const DEFAULT_DELTA: i32 = 170;
pub const DEFAULT_MAX_CYCLES: u64 = 10_000;
}
#[rustfmt::skip]
const JT9_XX0: [f32; 256] = [
1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000,
1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000,
1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000,
1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000,
1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000,
1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000,
0.988, 1.000, 0.991, 0.993, 1.000, 0.995, 1.000, 0.991,
1.000, 0.991, 0.992, 0.991, 0.990, 0.990, 0.992, 0.996,
0.990, 0.994, 0.993, 0.991, 0.992, 0.989, 0.991, 0.987,
0.985, 0.989, 0.984, 0.983, 0.979, 0.977, 0.971, 0.975,
0.974, 0.970, 0.970, 0.970, 0.967, 0.962, 0.960, 0.957,
0.956, 0.953, 0.942, 0.946, 0.937, 0.933, 0.929, 0.920,
0.917, 0.911, 0.903, 0.895, 0.884, 0.877, 0.869, 0.858,
0.846, 0.834, 0.821, 0.806, 0.790, 0.775, 0.755, 0.737,
0.713, 0.691, 0.667, 0.640, 0.612, 0.581, 0.548, 0.510,
0.472, 0.425, 0.378, 0.328, 0.274, 0.212, 0.146, 0.075,
0.000,-0.079,-0.163,-0.249,-0.338,-0.425,-0.514,-0.606,
-0.706,-0.796,-0.895,-0.987,-1.084,-1.181,-1.280,-1.376,
-1.473,-1.587,-1.678,-1.790,-1.882,-1.992,-2.096,-2.201,
-2.301,-2.411,-2.531,-2.608,-2.690,-2.829,-2.939,-3.058,
-3.164,-3.212,-3.377,-3.463,-3.550,-3.768,-3.677,-3.975,
-4.062,-4.098,-4.186,-4.261,-4.472,-4.621,-4.623,-4.608,
-4.822,-4.870,-4.652,-4.954,-5.108,-5.377,-5.544,-5.995,
-5.632,-5.826,-6.304,-6.002,-6.559,-6.369,-6.658,-7.016,
-6.184,-7.332,-6.534,-6.152,-6.113,-6.288,-6.426,-6.313,
-9.966,-6.371,-9.966,-7.055,-9.966,-6.629,-6.313,-9.966,
-5.858,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,
-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,
-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,
-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,
-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,
-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,-9.966,
];
fn build_jt9_mettab() -> [[i32; 2]; 256] {
const BIAS: f32 = 0.5;
const SCALE: f32 = 50.0;
const IB: usize = 160;
const SLOPE: i32 = 2;
let mut mettab = [[0i32; 2]; 256];
let mut col0 = [0i32; 256];
for (i, val) in JT9_XX0.iter().enumerate() {
col0[i] = (SCALE * (val - BIAS)).round() as i32;
}
let pivot = col0[IB];
for i in (IB + 1)..=255 {
col0[i] = pivot - SLOPE * (i as i32 - IB as i32);
}
for i in 0..=255 {
mettab[i][0] = col0[i];
}
for i in 1..=255 {
mettab[256 - i][1] = col0[i];
}
mettab[0][1] = mettab[1][1];
mettab
}
fn jt9_branch_metrics(llrs: &[f32]) -> alloc::vec::Vec<[i32; 2]> {
let mettab = build_jt9_mettab();
llrs.iter()
.map(|&l| {
let i4 = (-l).round().clamp(-127.0, 127.0) as i32;
let idx = (i4 + 128) as usize;
[mettab[idx][0], mettab[idx][1]]
})
.collect()
}
fn pack_msg_with_tail_jt9(info: &[u8]) -> [u8; 13] {
assert_eq!(info.len(), 72, "JT9 info payload must be 72 bits");
let mut packed = [0u8; 13];
for (i, &b) in info.iter().enumerate() {
if b & 1 != 0 {
packed[i / 8] |= 1 << (7 - (i % 8));
}
}
packed
}
impl FecCodec for ConvFano232 {
const N: usize = 206;
const K: usize = 72;
fn encode(&self, info: &[u8], codeword: &mut [u8]) {
assert_eq!(info.len(), Self::K);
assert_eq!(codeword.len(), Self::N);
let packed = pack_msg_with_tail_jt9(info);
let mut out = vec![0u8; 2 * Self::NBITS];
fano::conv_encode(&packed, Self::NBITS, &mut out);
codeword.copy_from_slice(&out);
}
fn decode_soft(&self, llr: &[f32], opts: &FecOpts) -> Option<FecResult> {
assert_eq!(llr.len(), Self::N);
let bm = jt9_branch_metrics(llr);
let max_cycles = opts.max_cycles_per_bit.unwrap_or(Self::DEFAULT_MAX_CYCLES);
let res = fano::fano_decode(&bm, Self::NBITS, Self::DEFAULT_DELTA, max_cycles);
if !res.converged {
return None;
}
let mut info = vec![0u8; Self::K];
for i in 0..Self::K {
info[i] = (res.data[i / 8] >> (7 - (i % 8))) & 1;
}
let mut reencoded = vec![0u8; Self::N];
self.encode(&info, &mut reencoded);
let hard_errors = llr
.iter()
.zip(reencoded.iter())
.filter(|&(&l, &c)| (c == 1) != (l < 0.0))
.count() as u32;
Some(FecResult {
info,
hard_errors,
iterations: 0,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode_then_decode_roundtrip() {
let codec = ConvFano;
let mut info = vec![0u8; 50];
for (i, slot) in info.iter_mut().enumerate() {
*slot = (((i * 7) ^ 0x2a) & 1) as u8;
}
let mut cw = vec![0u8; 162];
codec.encode(&info, &mut cw);
let llr: Vec<f32> = cw
.iter()
.map(|&b| if b == 0 { 8.0 } else { -8.0 })
.collect();
let r = codec
.decode_soft(&llr, &FecOpts::default())
.expect("perfect LLRs must decode");
assert_eq!(r.info, info);
assert_eq!(r.hard_errors, 0);
}
#[test]
fn jt9_encode_decode_roundtrip() {
let codec = ConvFano232;
let mut info = vec![0u8; 72];
for (i, slot) in info.iter_mut().enumerate() {
*slot = (((i * 11) ^ 0x55) & 1) as u8;
}
let mut cw = vec![0u8; 206];
codec.encode(&info, &mut cw);
let llr: Vec<f32> = cw
.iter()
.map(|&b| if b == 0 { 8.0 } else { -8.0 })
.collect();
let r = codec
.decode_soft(&llr, &FecOpts::default())
.expect("perfect LLRs must decode");
assert_eq!(r.info, info);
assert_eq!(r.hard_errors, 0);
}
#[test]
fn jt9_tolerates_a_few_errors() {
let codec = ConvFano232;
let info: Vec<u8> = (0..72).map(|i| i as u8 & 1).collect();
let mut cw = vec![0u8; 206];
codec.encode(&info, &mut cw);
let mut llr: Vec<f32> = cw
.iter()
.map(|&b| if b == 0 { 100.0 } else { -100.0 })
.collect();
for &pos in &[3usize, 17, 42, 91, 155, 199] {
llr[pos] = -llr[pos] * 0.3;
}
let r = codec
.decode_soft(&llr, &FecOpts::default())
.expect("should correct 6 weak errors");
assert_eq!(r.info, info);
}
#[test]
fn tolerates_a_few_errors() {
let codec = ConvFano;
let info: Vec<u8> = (0..50).map(|i| i as u8 & 1).collect();
let mut cw = vec![0u8; 162];
codec.encode(&info, &mut cw);
let mut llr: Vec<f32> = cw
.iter()
.map(|&b| if b == 0 { 6.0 } else { -6.0 })
.collect();
for &pos in &[3usize, 17, 42, 91, 155] {
llr[pos] = -llr[pos] * 0.3;
}
let r = codec
.decode_soft(&llr, &FecOpts::default())
.expect("should correct 5 weak errors");
assert_eq!(r.info, info);
}
}