use crate::fec::{
golay_23_12_decode, golay_23_12_decode_soft, golay_23_12_encode, golay_24_12_decode,
golay_24_12_decode_soft, golay_24_12_encode,
};
#[derive(Clone, Copy, Debug)]
pub struct PitchEntry {
pub l: u8,
pub omega_0: f32,
}
include!(concat!(env!("OUT_DIR"), "/annex_l_pitch.rs"));
include!(concat!(env!("OUT_DIR"), "/annex_m_vuv.rs"));
include!(concat!(env!("OUT_DIR"), "/annex_n_blocks.rs"));
include!(concat!(env!("OUT_DIR"), "/annex_o_gain.rs"));
include!(concat!(env!("OUT_DIR"), "/annex_p_prba24.rs"));
include!(concat!(env!("OUT_DIR"), "/annex_q_prba58.rs"));
include!(concat!(env!("OUT_DIR"), "/annex_r_hoc.rs"));
#[derive(Clone, Copy, Debug)]
pub struct ToneParams {
pub f0: f32,
pub l1: u8,
pub l2: u8,
}
include!(concat!(env!("OUT_DIR"), "/annex_t_tones.rs"));
pub const INFO_WIDTHS: [u8; 4] = [12, 12, 11, 14];
pub const CODE_WIDTHS: [u8; 4] = [24, 23, 11, 14];
pub const INFO_BITS_TOTAL: u16 = 49;
pub const PN_SEQ_LEN: usize = 24;
pub const DIBITS_PER_FRAME: usize = 36;
#[derive(Clone, Copy, Debug)]
pub(crate) struct AnnexSEntry {
pub bit1_vec: u8,
pub bit1_idx: u8,
pub bit0_vec: u8,
pub bit0_idx: u8,
}
include!(concat!(env!("OUT_DIR"), "/annex_s.rs"));
pub fn deinterleave(dibits: &[u8; DIBITS_PER_FRAME]) -> [u32; 4] {
let mut c = [0u32; 4];
for (sym, d) in dibits.iter().enumerate() {
let entry = ANNEX_S[sym];
let hi = u32::from((d >> 1) & 1);
let lo = u32::from(d & 1);
c[entry.bit1_vec as usize] |= hi << entry.bit1_idx;
c[entry.bit0_vec as usize] |= lo << entry.bit0_idx;
}
c
}
pub fn interleave(codewords: &[u32; 4]) -> [u8; DIBITS_PER_FRAME] {
let mut dibits = [0u8; DIBITS_PER_FRAME];
for (sym, entry) in ANNEX_S.iter().enumerate() {
let hi = ((codewords[entry.bit1_vec as usize] >> entry.bit1_idx) & 1) as u8;
let lo = ((codewords[entry.bit0_vec as usize] >> entry.bit0_idx) & 1) as u8;
dibits[sym] = (hi << 1) | lo;
}
dibits
}
pub const SOFT_BITS: usize = 72;
#[derive(Clone, Copy, Debug)]
pub struct SoftCodeVectors {
pub c0: [i8; 24],
pub c1: [i8; 23],
pub c2: [i8; 11],
pub c3: [i8; 14],
}
impl Default for SoftCodeVectors {
fn default() -> Self {
Self { c0: [0i8; 24], c1: [0i8; 23], c2: [0i8; 11], c3: [0i8; 14] }
}
}
pub fn soft_deinterleave(soft: &[i8; SOFT_BITS]) -> SoftCodeVectors {
let mut out = SoftCodeVectors::default();
for (sym, entry) in ANNEX_S.iter().enumerate() {
let hi = soft[2 * sym];
let lo = soft[2 * sym + 1];
place_soft(&mut out, entry.bit1_vec as usize, entry.bit1_idx as usize, hi);
place_soft(&mut out, entry.bit0_vec as usize, entry.bit0_idx as usize, lo);
}
out
}
fn place_soft(out: &mut SoftCodeVectors, vec_idx: usize, idx: usize, s: i8) {
match vec_idx {
0 => out.c0[24 - 1 - idx] = s,
1 => out.c1[23 - 1 - idx] = s,
2 => out.c2[11 - 1 - idx] = s,
3 => out.c3[14 - 1 - idx] = s,
_ => unreachable!(),
}
}
pub fn soft_demodulate_vector<const W: usize>(soft: &mut [i8; W], mask: u32) {
for (i, s) in soft.iter_mut().enumerate() {
let mask_bit = (mask >> (W - 1 - i)) & 1;
if mask_bit == 1 {
*s = s.saturating_neg();
}
}
}
pub fn pn_sequence(u0: u16) -> [u16; PN_SEQ_LEN] {
debug_assert!(u0 < 4096, "û₀ is a 12-bit info word");
let mut pr = [0u16; PN_SEQ_LEN];
pr[0] = u0.wrapping_mul(16);
for n in 1..PN_SEQ_LEN {
pr[n] = (173u32
.wrapping_mul(pr[n - 1] as u32)
.wrapping_add(13849)
& 0xFFFF) as u16;
}
pr
}
pub fn modulation_masks(u0: u16) -> [u32; 4] {
let pr = pn_sequence(u0);
let mut m = 0u32;
for k in 0..23 {
let bit = u32::from(pr[1 + k] >> 15); m |= bit << (22 - k);
}
[0, m, 0, 0]
}
pub fn demodulate(codewords: [u32; 4], u0: u16) -> [u32; 4] {
let masks = modulation_masks(u0);
let mut v = [0u32; 4];
for i in 0..4 {
v[i] = codewords[i] ^ masks[i];
}
v
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Frame {
pub info: [u16; 4],
pub errors: [u8; 4],
}
impl Frame {
pub fn error_total(&self) -> u16 {
self.errors.iter().map(|&e| u16::from(e)).sum()
}
}
pub fn decode_frame(dibits: &[u8; DIBITS_PER_FRAME]) -> Frame {
let c = deinterleave(dibits);
let d0 = golay_24_12_decode(c[0]);
let u0 = d0.info;
let masks = modulation_masks(u0);
let d1 = golay_23_12_decode(c[1] ^ masks[1]);
let u2 = (c[2] & 0x7FF) as u16; let u3 = (c[3] & 0x3FFF) as u16;
Frame {
info: [d0.info, d1.info, u2, u3],
errors: [d0.errors, d1.errors, 0, 0],
}
}
pub fn decode_frame_soft(soft: &[i8; SOFT_BITS]) -> Frame {
let mut c = soft_deinterleave(soft);
let d0 = golay_24_12_decode_soft(&c.c0);
let u0 = d0.info;
let masks = modulation_masks(u0);
soft_demodulate_vector(&mut c.c1, masks[1]);
let d1 = golay_23_12_decode_soft(&c.c1);
let mut u2 = 0u16;
for (i, &s) in c.c2.iter().enumerate() {
if s > 0 {
u2 |= 1u16 << (11 - 1 - i);
}
}
let mut u3 = 0u16;
for (i, &s) in c.c3.iter().enumerate() {
if s > 0 {
u3 |= 1u16 << (14 - 1 - i);
}
}
Frame {
info: [d0.info, d1.info, u2, u3],
errors: [d0.errors, d1.errors, 0, 0],
}
}
pub fn encode_frame(info: &[u16; 4]) -> [u8; DIBITS_PER_FRAME] {
let u: [u16; 4] = core::array::from_fn(|i| {
let w = INFO_WIDTHS[i] as u32;
let m = ((1u32 << w) - 1) as u16;
info[i] & m
});
let v0 = golay_24_12_encode(u[0]);
let v1 = golay_23_12_encode(u[1]);
let v2 = u32::from(u[2]);
let v3 = u32::from(u[3]);
let masks = modulation_masks(u[0]);
let c = [v0, v1 ^ masks[1], v2, v3];
interleave(&c)
}
#[cfg(test)]
mod tests {
use super::*;
fn valid_mask(i: usize) -> u32 {
let len = CODE_WIDTHS[i] as u32;
if len == 32 { u32::MAX } else { (1u32 << len) - 1 }
}
#[test]
fn widths_sum_to_49_and_72() {
assert_eq!(INFO_WIDTHS.iter().map(|&w| u16::from(w)).sum::<u16>(), 49);
assert_eq!(CODE_WIDTHS.iter().map(|&w| u16::from(w)).sum::<u16>(), 72);
assert_eq!(INFO_BITS_TOTAL, 49);
}
#[test]
fn interleave_covers_every_codeword_bit_exactly_once() {
let mut seen = [[false; 24]; 4];
for entry in ANNEX_S.iter() {
for (v, i) in [(entry.bit1_vec, entry.bit1_idx), (entry.bit0_vec, entry.bit0_idx)] {
assert!((i as u8) < CODE_WIDTHS[v as usize]);
assert!(!seen[v as usize][i as usize]);
seen[v as usize][i as usize] = true;
}
}
for (v, row) in seen.iter().enumerate() {
for (i, &b) in row.iter().enumerate().take(CODE_WIDTHS[v] as usize) {
assert!(b, "({v}, {i}) never covered");
}
}
}
#[test]
fn interleave_deinterleave_roundtrip() {
let mut cw = [0u32; 4];
let mut state = 0xFEEDFACEu32;
for i in 0..4 {
state = state.wrapping_mul(1664525).wrapping_add(1013904223);
cw[i] = state & valid_mask(i);
}
let dibits = interleave(&cw);
for d in dibits.iter() {
assert!(*d < 4);
}
let back = deinterleave(&dibits);
assert_eq!(back, cw);
}
#[test]
fn single_bit_propagates_to_one_dibit_position() {
for v in 0..4 {
for idx in 0..CODE_WIDTHS[v] {
let mut cw = [0u32; 4];
cw[v] = 1u32 << idx;
let dibits = interleave(&cw);
let ones: u32 = dibits.iter().map(|d| u32::from(*d).count_ones()).sum();
assert_eq!(ones, 1, "(v={v}, idx={idx})");
assert_eq!(deinterleave(&dibits), cw);
}
}
}
#[test]
fn pn_seed_ambe_plus2_matches_formula() {
for u0 in [0u16, 1, 42, 4095] {
assert_eq!(pn_sequence(u0)[0], u0.wrapping_mul(16));
}
let pr = pn_sequence(1);
assert_eq!(pr[0], 16);
assert_eq!(pr[1], 16617);
}
#[test]
fn modulation_masks_m0_m2_m3_always_zero() {
for u0 in [0u16, 1, 123, 4095] {
let masks = modulation_masks(u0);
assert_eq!(masks[0], 0);
assert_eq!(masks[2], 0);
assert_eq!(masks[3], 0);
}
}
#[test]
fn modulation_masks_m1_fits_within_23_bits() {
for u0 in [0u16, 1, 123, 4095] {
let masks = modulation_masks(u0);
assert!(masks[1] < (1 << 23));
}
}
#[test]
fn demodulate_is_self_inverse() {
let u0 = 0xA5Cu16;
let masks = modulation_masks(u0);
let v: [u32; 4] = [0x123456, 0x654321, 0x7A5, 0x2F2F];
let c: [u32; 4] = core::array::from_fn(|i| v[i] ^ masks[i]);
let v2 = demodulate(c, u0);
assert_eq!(v2, v);
}
fn sample_info(seed: u32) -> [u16; 4] {
let mut state = seed;
core::array::from_fn(|i| {
state = state.wrapping_mul(1664525).wrapping_add(1013904223);
let w = INFO_WIDTHS[i] as u32;
let m = ((1u32 << w) - 1) as u16;
(state as u16) & m
})
}
#[test]
fn frame_roundtrip_zero_and_sampled() {
for seed in [0u32, 1, 0xDEADBEEF, 0xCAFEBABE, 0x12345678] {
let u = if seed == 0 { [0u16; 4] } else { sample_info(seed) };
let dibits = encode_frame(&u);
for (s, d) in dibits.iter().enumerate() {
assert!(*d < 4, "symbol {s} not a dibit (seed {seed:08x})");
}
let back = decode_frame(&dibits);
assert_eq!(back.info, u, "seed 0x{seed:08x}");
assert_eq!(back.errors, [0u8; 4], "seed 0x{seed:08x}");
}
}
#[test]
fn single_bit_flip_in_c0_is_corrected() {
let u = sample_info(0xA5A5A5A5);
let clean = encode_frame(&u);
for bit in 0..24 {
let c = deinterleave(&clean);
let mut c_flipped = c;
c_flipped[0] ^= 1u32 << bit;
let dibits = interleave(&c_flipped);
let out = decode_frame(&dibits);
assert_eq!(out.info, u, "c̃₀ bit {bit}");
assert_eq!(out.errors[0], 1);
}
}
#[test]
fn single_bit_flip_in_c1_is_corrected() {
let u = sample_info(0x5A5A5A5A);
let clean = encode_frame(&u);
for bit in 0..23 {
let c = deinterleave(&clean);
let mut c_flipped = c;
c_flipped[1] ^= 1u32 << bit;
let dibits = interleave(&c_flipped);
let out = decode_frame(&dibits);
assert_eq!(out.info, u, "c̃₁ bit {bit}");
assert_eq!(out.errors[1], 1);
}
}
#[test]
fn uncoded_vectors_have_no_error_correction() {
let u = sample_info(0xFACEFEED);
let clean = encode_frame(&u);
let c = deinterleave(&clean);
let mut c_flip2 = c;
c_flip2[2] ^= 1u32 << 5;
let out2 = decode_frame(&interleave(&c_flip2));
assert_eq!(out2.info[2], u[2] ^ 0b10_0000);
assert_eq!(out2.errors[2], 0);
let mut c_flip3 = c;
c_flip3[3] ^= 1u32 << 11;
let out3 = decode_frame(&interleave(&c_flip3));
assert_eq!(out3.info[3], u[3] ^ (1 << 11));
assert_eq!(out3.errors[3], 0);
}
#[test]
fn annex_l_spot_values_match_spec() {
use core::f32::consts::PI;
assert_eq!(AMBE_PITCH_TABLE.len(), 120);
let p0 = AMBE_PITCH_TABLE[0];
assert_eq!(p0.l, 9);
assert!((p0.omega_0 - 0.049971 * 2.0 * PI).abs() < 1e-5);
let p_last = AMBE_PITCH_TABLE[119];
assert_eq!(p_last.l, 56);
assert!((p_last.omega_0 - 0.008125 * 2.0 * PI).abs() < 1e-5);
}
#[test]
fn annex_l_omega_strictly_decreasing() {
for w in AMBE_PITCH_TABLE.windows(2) {
assert!(w[0].omega_0 > w[1].omega_0);
}
}
#[test]
fn annex_m_all_voiced_and_all_unvoiced_endpoints() {
assert_eq!(AMBE_VUV_CODEBOOK.len(), 32);
for &v in &AMBE_VUV_CODEBOOK[0] { assert!(v); }
for &v in &AMBE_VUV_CODEBOOK[16] { assert!(!v); }
}
#[test]
fn annex_n_block_lengths_sum_to_l() {
assert_eq!(AMBE_BLOCK_LENGTHS.len(), 48);
for (l_idx, row) in AMBE_BLOCK_LENGTHS.iter().enumerate() {
let l = (l_idx + 9) as u32;
let sum: u32 = row.iter().map(|&x| x as u32).sum();
assert_eq!(sum, l, "L={l}");
}
}
#[test]
fn annex_o_spot_values_and_monotone() {
assert_eq!(AMBE_GAIN_LEVELS.len(), 32);
assert!((AMBE_GAIN_LEVELS[0] - (-2.0)).abs() < 1e-6);
assert!((AMBE_GAIN_LEVELS[31] - 6.874496).abs() < 1e-6);
for w in AMBE_GAIN_LEVELS.windows(2) {
assert!(w[0] < w[1]);
}
}
#[test]
fn annex_p_first_entry_matches_spec() {
assert_eq!(AMBE_PRBA24.len(), 512);
let e = AMBE_PRBA24[0];
assert!((e[0] - 0.526055).abs() < 1e-6);
assert!((e[1] - (-0.328567)).abs() < 1e-6);
assert!((e[2] - (-0.304727)).abs() < 1e-6);
}
#[test]
fn annex_q_first_entry_matches_spec() {
assert_eq!(AMBE_PRBA58.len(), 128);
let e = AMBE_PRBA58[0];
assert!((e[0] - (-0.103660)).abs() < 1e-6);
assert!((e[1] - 0.094597).abs() < 1e-6);
assert!((e[2] - (-0.013149)).abs() < 1e-6);
assert!((e[3] - 0.081501).abs() < 1e-6);
}
#[test]
fn annex_r_hoc_shapes_and_spot_value() {
assert_eq!(AMBE_HOC_B5.len(), 32);
assert_eq!(AMBE_HOC_B6.len(), 16);
assert_eq!(AMBE_HOC_B7.len(), 16);
assert_eq!(AMBE_HOC_B8.len(), 8);
let e = AMBE_HOC_B5[0];
assert!((e[0] - 0.264108).abs() < 1e-6);
assert!((e[1] - 0.045976).abs() < 1e-6);
assert!((e[2] - (-0.200999)).abs() < 1e-6);
assert!((e[3] - (-0.122344)).abs() < 1e-6);
}
#[test]
fn width_masking_strips_stray_high_bits() {
let mut u = [0xFFFFu16; 4];
u[0] = 0xFFFF;
let dibits = encode_frame(&u);
let back = decode_frame(&dibits);
for i in 0..4 {
let w = INFO_WIDTHS[i] as u32;
let m = ((1u32 << w) - 1) as u16;
assert_eq!(back.info[i], m, "vector {i} mask mismatch");
}
}
fn dibits_to_soft(dibits: &[u8; DIBITS_PER_FRAME], confidence: i8) -> [i8; SOFT_BITS] {
let mut out = [0i8; SOFT_BITS];
for (sym, &d) in dibits.iter().enumerate() {
let hi = (d >> 1) & 1;
let lo = d & 1;
out[2 * sym] = if hi == 1 { confidence } else { -confidence };
out[2 * sym + 1] = if lo == 1 { confidence } else { -confidence };
}
out
}
#[test]
fn soft_decode_matches_hard_on_clean_input() {
for seed in [0u32, 1, 0xDEADBEEF, 0xCAFEBABE, 0x12345678] {
let u = if seed == 0 { [0u16; 4] } else { sample_info(seed) };
let dibits = encode_frame(&u);
let soft = dibits_to_soft(&dibits, 120);
let soft_frame = decode_frame_soft(&soft);
let hard_frame = decode_frame(&dibits);
assert_eq!(soft_frame.info, hard_frame.info, "seed 0x{seed:08x}");
assert_eq!(soft_frame.info, u, "seed 0x{seed:08x}");
assert_eq!(soft_frame.errors, [0u8; 4]);
}
}
#[test]
fn soft_decode_recovers_weak_bit_error_on_c0() {
let u = sample_info(0xA5A5A5A5);
let dibits = encode_frame(&u);
let mut soft = dibits_to_soft(&dibits, 120);
let targets = [(0u8, 0u8), (0, 5)]; for (vi, msb_pos) in targets {
let lsb_pos = CODE_WIDTHS[vi as usize] - 1 - msb_pos;
let (sym, is_hi) = (0..ANNEX_S.len())
.find_map(|s| {
let e = ANNEX_S[s];
if e.bit1_vec == vi && e.bit1_idx == lsb_pos {
Some((s, true))
} else if e.bit0_vec == vi && e.bit0_idx == lsb_pos {
Some((s, false))
} else {
None
}
})
.expect("annex S covers every (vec, idx)");
let soft_idx = 2 * sym + if is_hi { 0 } else { 1 };
soft[soft_idx] = -soft[soft_idx].signum() * 4;
}
let frame = decode_frame_soft(&soft);
assert_eq!(frame.info, u, "soft recovered 2-error c̃₀");
}
}