use crate::fixed_point::arith::{add, extract_l};
use crate::fixed_point::arith32::l_mult;
use crate::fixed_point::shift::{shl, shr};
use crate::fixed_point::types::{DspContext, Word16, Word32};
const L_SUBFR: usize = 40;
const NB_PULSE: usize = 10;
pub const PN_INITIAL_SEED: Word32 = Word32(0x7081_6958);
const PULSE: Word16 = Word16(4096);
pub fn pseudonoise(reg: &mut Word32, no_bits: usize) -> Word16 {
let mut noise_bits = 0i16;
for _ in 0..no_bits {
let sn = ((reg.0 & 0x0000_0001) != 0) ^ ((reg.0 & 0x1000_0000) != 0);
noise_bits = (noise_bits << 1) | i16::try_from(reg.0 & 1).expect("one bit");
reg.0 >>= 1;
if sn {
reg.0 |= 0x4000_0000;
}
}
Word16(noise_bits)
}
pub fn build_cn_code(ctx: &mut DspContext, reg: &mut Word32) -> [Word16; L_SUBFR] {
let mut cod = [Word16(0); L_SUBFR];
for k in 0..NB_PULSE {
let draw = pseudonoise(reg, 2);
let tenfold = extract_l(l_mult(ctx, draw, Word16(10)));
let scaled = shr(ctx, tenfold, 1);
let i = add(ctx, scaled, Word16(i16::try_from(k).expect("k < 10")));
let sign = pseudonoise(reg, 1);
let position = usize::try_from(i.0).expect("a two-bit draw times ten, plus k, is 0..=39");
cod[position] = if sign.0 > 0 { PULSE } else { Word16(-PULSE.0) };
}
cod
}
#[must_use]
pub fn a_refl(ctx: &mut DspContext, a: &[Word16]) -> [Word16; 10] {
use crate::fixed_point::arith::{abs_s, round, sub};
use crate::fixed_point::arith32::{l_deposit_h, l_msu, l_sub};
use crate::fixed_point::div::div_s;
use crate::fixed_point::shift::{l_shl, l_shr_r, norm_l};
const M: usize = 10;
assert_eq!(
a.len(),
M,
"A_Refl takes the ten coefficients after the leading 1.0"
);
let mut refl = [Word16(0); M];
let mut state = [Word16(0); M];
state.copy_from_slice(a);
let mut next = [Word16(0); M];
for i in (0..M).rev() {
let magnitude = abs_s(ctx, state[i]);
if sub(ctx, magnitude, Word16(4096)).0 >= 0 {
return [Word16(0); M];
}
refl[i] = shl(ctx, state[i], 3);
let l_temp = l_mult(ctx, refl[i], refl[i]);
let mut l_acc = l_sub(ctx, Word32(i32::MAX), l_temp);
let norm_shift = norm_l(l_acc);
let scale = sub(ctx, Word16(15), Word16(norm_shift));
l_acc = l_shl(ctx, l_acc, norm_shift);
let norm_prod = round(ctx, l_acc);
let mult_factor = div_s(Word16(16384), norm_prod);
for j in 0..i {
let mut acc = l_deposit_h(state[j]);
acc = l_msu(ctx, acc, refl[i], state[i - j - 1]);
let temp = round(ctx, acc);
let mut l_temp = l_mult(ctx, mult_factor, temp);
l_temp = l_shr_r(ctx, l_temp, scale.0);
if l_temp.0.unsigned_abs() > 32767 {
return [Word16(0); M];
}
next[j] = extract_l(l_temp);
}
state[..i].copy_from_slice(&next[..i]);
}
refl
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_shift_register_is_balanced_and_does_not_cycle_short() {
let mut reg = PN_INITIAL_SEED;
let mut ones = 0u32;
let mut seen_initial_again = false;
for _ in 0..1_000_000 {
ones += u32::from(pseudonoise(&mut reg, 1).0 != 0);
if reg == PN_INITIAL_SEED {
seen_initial_again = true;
}
}
assert!(
!seen_initial_again,
"the register returned to its seed inside a million draws"
);
assert!(
(490_000..=510_000).contains(&ones),
"{ones} ones in a million draws is not a balanced sequence"
);
}
#[test]
fn a_wide_draw_is_narrow_draws_packed_msb_first() {
let mut wide = PN_INITIAL_SEED;
let mut narrow = PN_INITIAL_SEED;
for _ in 0..64 {
let w = pseudonoise(&mut wide, 3).0;
let a = pseudonoise(&mut narrow, 1).0;
let b = pseudonoise(&mut narrow, 1).0;
let c = pseudonoise(&mut narrow, 1).0;
assert_eq!(w, (a << 2) | (b << 1) | c);
}
assert_eq!(wide, narrow);
}
#[test]
fn the_codebook_vector_holds_ten_pulses_on_ten_tracks() {
let mut ctx = DspContext::default();
let mut reg = PN_INITIAL_SEED;
let (mut positives, mut negatives) = (0usize, 0usize);
for _ in 0..200 {
let cod = build_cn_code(&mut ctx, &mut reg);
let placed: Vec<usize> = (0..L_SUBFR).filter(|&i| cod[i].0 != 0).collect();
assert_eq!(placed.len(), NB_PULSE, "pulses collided");
let mut tracks: Vec<usize> = placed.iter().map(|&p| p % NB_PULSE).collect();
tracks.sort_unstable();
assert_eq!(
tracks,
(0..NB_PULSE).collect::<Vec<_>>(),
"two pulses shared a track"
);
positives += placed.iter().filter(|&&p| cod[p].0 > 0).count();
negatives += placed.iter().filter(|&&p| cod[p].0 < 0).count();
}
assert_eq!(positives + negatives, 2000);
assert!(
positives > 800 && negatives > 800,
"signs are not balanced: {positives}/{negatives}"
);
}
#[test]
fn a_first_order_filters_reflection_coefficient_is_its_own_coefficient() {
let mut ctx = DspContext::default();
let mut a = [Word16(0); 10];
a[0] = Word16(1000); let refl = a_refl(&mut ctx, &a);
assert_eq!(refl[0], Word16(8000), "0.244 in Q12 is 8000 in Q15");
assert!(refl[1..].iter().all(|c| c.0 == 0));
}
#[test]
fn an_out_of_range_coefficient_aborts_the_recursion() {
let mut ctx = DspContext::default();
let mut a = [Word16(0); 10];
a[9] = Word16(4096); assert_eq!(a_refl(&mut ctx, &a), [Word16(0); 10]);
a[9] = Word16(4095);
assert_ne!(a_refl(&mut ctx, &a), [Word16(0); 10], "4095 must not abort");
}
}