use core::f64::consts::PI;
use crate::{Error, Result};
const HALF_PI: f64 = PI / 2.0;
pub fn iqfac(coef_res_bits: u32) -> Result<f64> {
if !(3..=4).contains(&coef_res_bits) {
return Err(Error::TnsCoefOutOfRange);
}
let scale = (1u32 << (coef_res_bits - 1)) as f64 - 0.5;
Ok(scale / HALF_PI)
}
pub fn iqfac_m(coef_res_bits: u32) -> Result<f64> {
if !(3..=4).contains(&coef_res_bits) {
return Err(Error::TnsCoefOutOfRange);
}
let scale = (1u32 << (coef_res_bits - 1)) as f64 + 0.5;
Ok(scale / HALF_PI)
}
pub fn sign_extend_coef(coef: u32, coef_res2: u32) -> Result<i32> {
if !(2..=4).contains(&coef_res2) {
return Err(Error::TnsCoefOutOfRange);
}
let field_mask = (1u32 << coef_res2) - 1;
if coef & !field_mask != 0 {
return Err(Error::TnsCoefOutOfRange);
}
let sgn_mask = 1u32 << (coef_res2 - 1);
if coef & sgn_mask != 0 {
let neg_mask = !field_mask;
Ok((coef | neg_mask) as i32)
} else {
Ok(coef as i32)
}
}
pub fn pack_coef(value: i32, coef_res2: u32) -> Result<u32> {
if !(2..=4).contains(&coef_res2) {
return Err(Error::TnsCoefOutOfRange);
}
let half = 1i32 << (coef_res2 - 1);
if !(-half..half).contains(&value) {
return Err(Error::TnsCoefOutOfRange);
}
let field_mask = (1u32 << coef_res2) - 1;
Ok((value as u32) & field_mask)
}
pub fn tns_decode_coef(coef_res_bits: u32, coef_compress: u32, coef: &[u32]) -> Result<Vec<f64>> {
if coef_compress > 1 {
return Err(Error::TnsCoefOutOfRange);
}
let coef_res2 = coef_res_bits
.checked_sub(coef_compress)
.ok_or(Error::TnsCoefOutOfRange)?;
let iq = iqfac(coef_res_bits)?;
let iq_m = iqfac_m(coef_res_bits)?;
let mut out = Vec::with_capacity(coef.len());
for &c in coef {
let signed = sign_extend_coef(c, coef_res2)?;
let divisor = if signed >= 0 { iq } else { iq_m };
out.push((signed as f64 / divisor).sin());
}
Ok(out)
}
pub fn tns_encode_coef(coef_res_bits: u32, coef_compress: u32, r: &[f64]) -> Result<Vec<u32>> {
if coef_compress > 1 {
return Err(Error::TnsCoefOutOfRange);
}
let coef_res2 = coef_res_bits
.checked_sub(coef_compress)
.ok_or(Error::TnsCoefOutOfRange)?;
let iq = iqfac(coef_res_bits)?;
let iq_m = iqfac_m(coef_res_bits)?;
let half = 1i32 << (coef_res2 - 1);
let max_idx = half - 1;
let min_idx = -half;
let mut out = Vec::with_capacity(r.len());
for &value in r {
if !(-1.0..=1.0).contains(&value) {
return Err(Error::TnsCoefOutOfRange);
}
let scale = if value >= 0.0 { iq } else { iq_m };
let raw = (value.asin() * scale).round() as i32;
let clamped = raw.clamp(min_idx, max_idx);
out.push(pack_coef(clamped, coef_res2)?);
}
Ok(out)
}
pub fn lpc_step_up(parcor: &[f64]) -> Vec<f64> {
let order = parcor.len();
let mut a = vec![0.0_f64; order + 1];
a[0] = 1.0;
let mut b = vec![0.0_f64; order + 1];
for m in 1..=order {
let k = parcor[m - 1];
for i in 1..m {
b[i] = a[i] + k * a[m - i];
}
a[1..m].copy_from_slice(&b[1..m]);
a[m] = k;
}
a
}
pub fn tns_decode_coef_to_lpc(
coef_res_bits: u32,
coef_compress: u32,
coef: &[u32],
) -> Result<Vec<f64>> {
let parcor = tns_decode_coef(coef_res_bits, coef_compress, coef)?;
Ok(lpc_step_up(&parcor))
}
pub fn tns_ar_filter(
spectrum: &mut [f64],
start: usize,
size: usize,
inc: i32,
lpc: &[f64],
) -> Result<()> {
if lpc.is_empty() {
return Err(Error::TnsCoefOutOfRange);
}
if inc != 1 && inc != -1 {
return Err(Error::TnsCoefOutOfRange);
}
let order = lpc.len() - 1;
if size == 0 || order == 0 {
if size > 0 {
walk_bounds_check(spectrum.len(), start, size, inc)?;
}
return Ok(());
}
walk_bounds_check(spectrum.len(), start, size, inc)?;
let mut history = vec![0.0_f64; order];
let mut idx = start as isize;
for _ in 0..size {
let x = spectrum[idx as usize];
let mut y = x;
for k in 1..=order {
y -= lpc[k] * history[k - 1];
}
spectrum[idx as usize] = y;
for k in (1..order).rev() {
history[k] = history[k - 1];
}
history[0] = y;
idx += inc as isize;
}
Ok(())
}
pub fn tns_ma_filter(
spectrum: &mut [f64],
start: usize,
size: usize,
inc: i32,
lpc: &[f64],
) -> Result<()> {
if lpc.is_empty() {
return Err(Error::TnsCoefOutOfRange);
}
if inc != 1 && inc != -1 {
return Err(Error::TnsCoefOutOfRange);
}
let order = lpc.len() - 1;
if size == 0 || order == 0 {
if size > 0 {
walk_bounds_check(spectrum.len(), start, size, inc)?;
}
return Ok(());
}
walk_bounds_check(spectrum.len(), start, size, inc)?;
let mut history = vec![0.0_f64; order];
let mut idx = start as isize;
for _ in 0..size {
let x = spectrum[idx as usize];
let mut y = x;
for k in 1..=order {
y += lpc[k] * history[k - 1];
}
spectrum[idx as usize] = y;
for k in (1..order).rev() {
history[k] = history[k - 1];
}
history[0] = x;
idx += inc as isize;
}
Ok(())
}
fn walk_bounds_check(len: usize, start: usize, size: usize, inc: i32) -> Result<()> {
if start >= len {
return Err(Error::TnsCoefOutOfRange);
}
let span = (size - 1) as isize;
let last = start as isize + span * inc as isize;
if last < 0 || last >= len as isize {
return Err(Error::TnsCoefOutOfRange);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn iqfac_matches_spec_formula_for_legal_widths() {
let want3 = 3.5_f64 / HALF_PI;
let want4 = 7.5_f64 / HALF_PI;
assert!((iqfac(3).unwrap() - want3).abs() < 1e-15);
assert!((iqfac(4).unwrap() - want4).abs() < 1e-15);
}
#[test]
fn iqfac_m_matches_spec_formula_for_legal_widths() {
let want3 = 4.5_f64 / HALF_PI;
let want4 = 8.5_f64 / HALF_PI;
assert!((iqfac_m(3).unwrap() - want3).abs() < 1e-15);
assert!((iqfac_m(4).unwrap() - want4).abs() < 1e-15);
}
#[test]
fn iqfac_rejects_widths_outside_3_to_4() {
assert!(matches!(iqfac(0), Err(Error::TnsCoefOutOfRange)));
assert!(matches!(iqfac(2), Err(Error::TnsCoefOutOfRange)));
assert!(matches!(iqfac(5), Err(Error::TnsCoefOutOfRange)));
assert!(matches!(iqfac_m(0), Err(Error::TnsCoefOutOfRange)));
assert!(matches!(iqfac_m(2), Err(Error::TnsCoefOutOfRange)));
assert!(matches!(iqfac_m(5), Err(Error::TnsCoefOutOfRange)));
}
#[test]
fn iqfac_m_is_always_greater_than_iqfac() {
for n in [3, 4] {
assert!(iqfac_m(n).unwrap() > iqfac(n).unwrap());
}
}
#[test]
fn sign_extend_4bit_covers_signed_range() {
let expected: [i32; 16] = [0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1];
for wire in 0_u32..16 {
assert_eq!(
sign_extend_coef(wire, 4).unwrap(),
expected[wire as usize],
"wire {wire:04b}",
);
}
}
#[test]
fn sign_extend_3bit_covers_signed_range() {
let expected: [i32; 8] = [0, 1, 2, 3, -4, -3, -2, -1];
for wire in 0_u32..8 {
assert_eq!(sign_extend_coef(wire, 3).unwrap(), expected[wire as usize]);
}
}
#[test]
fn sign_extend_2bit_covers_signed_range() {
let expected: [i32; 4] = [0, 1, -2, -1];
for wire in 0_u32..4 {
assert_eq!(sign_extend_coef(wire, 2).unwrap(), expected[wire as usize]);
}
}
#[test]
fn sign_extend_rejects_out_of_range_field_width() {
assert!(matches!(
sign_extend_coef(0, 1),
Err(Error::TnsCoefOutOfRange)
));
assert!(matches!(
sign_extend_coef(0, 5),
Err(Error::TnsCoefOutOfRange)
));
}
#[test]
fn sign_extend_rejects_wire_value_that_overflows_field() {
assert!(matches!(
sign_extend_coef(16, 4),
Err(Error::TnsCoefOutOfRange)
));
assert!(matches!(
sign_extend_coef(4, 2),
Err(Error::TnsCoefOutOfRange)
));
}
#[test]
fn pack_coef_round_trips_through_sign_extend_4bit() {
for value in -8_i32..=7 {
let packed = pack_coef(value, 4).unwrap();
assert_eq!(sign_extend_coef(packed, 4).unwrap(), value);
}
}
#[test]
fn pack_coef_round_trips_through_sign_extend_3bit() {
for value in -4_i32..=3 {
let packed = pack_coef(value, 3).unwrap();
assert_eq!(sign_extend_coef(packed, 3).unwrap(), value);
}
}
#[test]
fn pack_coef_round_trips_through_sign_extend_2bit() {
for value in -2_i32..=1 {
let packed = pack_coef(value, 2).unwrap();
assert_eq!(sign_extend_coef(packed, 2).unwrap(), value);
}
}
#[test]
fn pack_coef_rejects_out_of_field_value() {
assert!(matches!(pack_coef(8, 4), Err(Error::TnsCoefOutOfRange)));
assert!(matches!(pack_coef(-9, 4), Err(Error::TnsCoefOutOfRange)));
assert!(matches!(pack_coef(2, 2), Err(Error::TnsCoefOutOfRange)));
assert!(matches!(pack_coef(-3, 2), Err(Error::TnsCoefOutOfRange)));
}
#[test]
fn decode_zero_wire_yields_zero_parcor() {
let parcor = tns_decode_coef(4, 0, &[0, 0, 0]).unwrap();
assert_eq!(parcor.len(), 3);
for v in parcor {
assert!(v.abs() < 1e-15);
}
}
#[test]
fn decode_field_extrema_yield_near_unity_magnitudes() {
let pos = tns_decode_coef(4, 0, &[7]).unwrap()[0];
let neg = tns_decode_coef(4, 0, &[8]).unwrap()[0]; let want_pos = (7.0_f64 / (7.5 / HALF_PI)).sin();
let want_neg = (-8.0_f64 / (8.5 / HALF_PI)).sin();
assert!((pos - want_pos).abs() < 1e-15);
assert!((neg - want_neg).abs() < 1e-15);
assert!(pos.abs() <= 1.0);
assert!(neg.abs() <= 1.0);
}
#[test]
fn decode_negative_branch_uses_iqfac_m() {
let got = tns_decode_coef(4, 0, &[0xF]).unwrap()[0];
let want = (-1.0_f64 / iqfac_m(4).unwrap()).sin();
assert!((got - want).abs() < 1e-15);
}
#[test]
fn decode_3bit_branch_uses_coef_res_bits_3() {
let got_long = tns_decode_coef(3, 0, &[1]).unwrap()[0];
let want_long = (1.0_f64 / iqfac(3).unwrap()).sin();
assert!((got_long - want_long).abs() < 1e-15);
let got_short = tns_decode_coef(3, 1, &[1]).unwrap()[0];
assert!((got_short - want_long).abs() < 1e-15);
}
#[test]
fn decode_rejects_oversized_wire_value_for_compress_path() {
assert!(matches!(
tns_decode_coef(4, 1, &[8]),
Err(Error::TnsCoefOutOfRange)
));
}
#[test]
fn decode_rejects_invalid_coef_res_bits() {
assert!(matches!(
tns_decode_coef(5, 0, &[0]),
Err(Error::TnsCoefOutOfRange)
));
assert!(matches!(
tns_decode_coef(2, 0, &[0]),
Err(Error::TnsCoefOutOfRange)
));
}
#[test]
fn decode_rejects_invalid_coef_compress() {
assert!(matches!(
tns_decode_coef(4, 2, &[0]),
Err(Error::TnsCoefOutOfRange)
));
}
#[test]
fn decode_empty_input_yields_empty_output() {
let parcor = tns_decode_coef(4, 0, &[]).unwrap();
assert!(parcor.is_empty());
}
#[test]
fn encode_zero_parcor_yields_zero_wire() {
let wire = tns_encode_coef(4, 0, &[0.0, 0.0, 0.0]).unwrap();
assert_eq!(wire, vec![0, 0, 0]);
}
#[test]
fn encode_unity_parcor_saturates_to_field_max() {
let wire = tns_encode_coef(4, 0, &[1.0]).unwrap();
assert_eq!(wire, vec![7]);
let wire_neg = tns_encode_coef(4, 0, &[-1.0]).unwrap();
assert_eq!(wire_neg, vec![8]);
}
#[test]
fn encode_rejects_parcor_outside_minus_one_to_plus_one() {
assert!(matches!(
tns_encode_coef(4, 0, &[1.0001]),
Err(Error::TnsCoefOutOfRange)
));
assert!(matches!(
tns_encode_coef(4, 0, &[-1.0001]),
Err(Error::TnsCoefOutOfRange)
));
assert!(matches!(
tns_encode_coef(4, 0, &[f64::NAN]),
Err(Error::TnsCoefOutOfRange)
));
}
#[test]
fn encode_rejects_invalid_coef_res_bits() {
assert!(matches!(
tns_encode_coef(5, 0, &[0.5]),
Err(Error::TnsCoefOutOfRange)
));
}
#[test]
fn round_trip_every_4bit_wire_value_through_decode_then_encode() {
for wire in 0_u32..16 {
let parcor = tns_decode_coef(4, 0, &[wire]).unwrap();
let back = tns_encode_coef(4, 0, &parcor).unwrap();
assert_eq!(back, vec![wire], "wire {wire:04b} round-trip");
}
}
#[test]
fn round_trip_every_3bit_wire_value_through_decode_then_encode() {
for wire in 0_u32..8 {
let parcor = tns_decode_coef(3, 0, &[wire]).unwrap();
let back = tns_encode_coef(3, 0, &parcor).unwrap();
assert_eq!(back, vec![wire], "wire {wire:03b} round-trip");
}
}
#[test]
fn round_trip_with_coef_compress_for_both_res_settings() {
for wire in 0_u32..8 {
let parcor = tns_decode_coef(4, 1, &[wire]).unwrap();
let back = tns_encode_coef(4, 1, &parcor).unwrap();
assert_eq!(back, vec![wire], "coef_res=1 compress=1 wire {wire:03b}");
}
for wire in 0_u32..4 {
let parcor = tns_decode_coef(3, 1, &[wire]).unwrap();
let back = tns_encode_coef(3, 1, &parcor).unwrap();
assert_eq!(back, vec![wire], "coef_res=0 compress=1 wire {wire:02b}");
}
}
#[test]
fn step_up_zero_order_returns_unit_a() {
let a = lpc_step_up(&[]);
assert_eq!(a, vec![1.0]);
}
#[test]
fn step_up_first_order_matches_hand_arithmetic() {
let a = lpc_step_up(&[0.5]);
assert_eq!(a, vec![1.0, 0.5]);
}
#[test]
fn step_up_second_order_matches_hand_arithmetic() {
let (k1, k2) = (0.3, 0.4);
let a = lpc_step_up(&[k1, k2]);
assert_eq!(a.len(), 3);
assert!((a[0] - 1.0).abs() < 1e-15);
assert!((a[1] - k1 * (1.0 + k2)).abs() < 1e-15);
assert!((a[2] - k2).abs() < 1e-15);
}
#[test]
fn step_up_third_order_matches_hand_arithmetic() {
let (k1, k2, k3) = (0.2, 0.3, -0.4);
let a = lpc_step_up(&[k1, k2, k3]);
let want = [
1.0,
k1 * (1.0 + k2) + k3 * k2,
k2 + k3 * k1 * (1.0 + k2),
k3,
];
for i in 0..4 {
assert!(
(a[i] - want[i]).abs() < 1e-15,
"i={i} got {} want {}",
a[i],
want[i],
);
}
}
#[test]
fn step_up_a0_always_one() {
for parcor in [
vec![0.5],
vec![-0.5],
vec![0.1, -0.2],
vec![0.3, -0.4, 0.5, -0.6, 0.7, -0.8, 0.9, -0.95],
] {
let a = lpc_step_up(&parcor);
assert_eq!(a.len(), parcor.len() + 1);
assert!((a[0] - 1.0).abs() < 1e-15);
}
}
#[test]
fn step_up_last_coefficient_is_last_parcor() {
for parcor in [vec![0.3], vec![0.3, -0.5], vec![0.1, 0.2, 0.3, 0.4]] {
let a = lpc_step_up(&parcor);
let last_idx = parcor.len();
assert_eq!(a[last_idx], *parcor.last().unwrap());
}
}
#[test]
fn decode_to_lpc_combines_decode_and_step_up() {
let wire = [3, 5, 0xF]; let parcor = tns_decode_coef(4, 0, &wire).unwrap();
let want = lpc_step_up(&parcor);
let got = tns_decode_coef_to_lpc(4, 0, &wire).unwrap();
assert_eq!(got, want);
}
#[test]
fn decode_to_lpc_propagates_decode_errors() {
assert!(matches!(
tns_decode_coef_to_lpc(5, 0, &[0]),
Err(Error::TnsCoefOutOfRange)
));
}
fn ref_ar_filter(x: &[f64], lpc: &[f64]) -> Vec<f64> {
let order = lpc.len() - 1;
let mut y = vec![0.0_f64; x.len()];
for n in 0..x.len() {
let mut acc = x[n];
for k in 1..=order {
if n >= k {
acc -= lpc[k] * y[n - k];
}
}
y[n] = acc;
}
y
}
#[test]
fn ar_filter_order0_is_identity() {
let mut spec = [1.0, 2.0, 3.0, 4.0];
let before = spec;
tns_ar_filter(&mut spec, 0, 4, 1, &[1.0]).unwrap();
assert_eq!(spec, before);
}
#[test]
fn ar_filter_order1_matches_recurrence_upward() {
let lpc = [1.0, 0.5];
let x = [1.0, 0.0, 0.0, 0.0, 0.0];
let want = ref_ar_filter(&x, &lpc);
let mut spec = x;
tns_ar_filter(&mut spec, 0, 5, 1, &lpc).unwrap();
for (g, w) in spec.iter().zip(want.iter()) {
assert!((g - w).abs() < 1e-12, "got {g} want {w}");
}
let hand = [1.0, -0.5, 0.25, -0.125, 0.0625];
for (g, h) in spec.iter().zip(hand.iter()) {
assert!((g - h).abs() < 1e-12);
}
}
#[test]
fn ar_filter_order3_matches_reference() {
let lpc = [1.0, -0.4, 0.2, 0.1];
let x = [0.7, -1.3, 2.1, 0.0, -0.5, 1.1, 0.9, -0.2];
let want = ref_ar_filter(&x, &lpc);
let mut spec = x;
tns_ar_filter(&mut spec, 0, x.len(), 1, &lpc).unwrap();
for (g, w) in spec.iter().zip(want.iter()) {
assert!((g - w).abs() < 1e-12, "got {g} want {w}");
}
}
#[test]
fn ar_filter_downward_walks_high_to_low() {
let lpc = [1.0, 0.3, -0.15];
let region = [0.5, -0.2, 0.9, 1.4, -0.7];
let mut spec = vec![100.0, 0.5, -0.2, 0.9, 1.4, -0.7, 200.0];
let start = 5; let size = 5;
tns_ar_filter(&mut spec, start, size, -1, &lpc).unwrap();
let mut rev: Vec<f64> = region.iter().rev().copied().collect();
let want_rev = ref_ar_filter(&rev, &lpc);
rev.copy_from_slice(&want_rev);
let want: Vec<f64> = rev.into_iter().rev().collect();
assert_eq!(spec[0], 100.0, "lower sentinel untouched");
assert_eq!(spec[6], 200.0, "upper sentinel untouched");
for (i, w) in want.iter().enumerate() {
assert!(
(spec[1 + i] - w).abs() < 1e-12,
"idx {i}: {} vs {w}",
spec[1 + i]
);
}
}
#[test]
fn ar_filter_only_touches_targeted_region() {
let lpc = [1.0, 0.5];
let mut spec = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
tns_ar_filter(&mut spec, 2, 3, 1, &lpc).unwrap();
assert_eq!(spec[0], 1.0);
assert_eq!(spec[1], 2.0);
assert_eq!(spec[5], 6.0);
let want = ref_ar_filter(&[3.0, 4.0, 5.0], &lpc);
for i in 0..3 {
assert!((spec[2 + i] - want[i]).abs() < 1e-12);
}
}
#[test]
fn ar_filter_zero_size_is_noop() {
let mut spec = [1.0, 2.0, 3.0];
let before = spec;
tns_ar_filter(&mut spec, 0, 0, 1, &[1.0, 0.5]).unwrap();
assert_eq!(spec, before);
}
#[test]
fn ar_filter_rejects_empty_lpc() {
let mut spec = [1.0, 2.0];
assert!(matches!(
tns_ar_filter(&mut spec, 0, 2, 1, &[]),
Err(Error::TnsCoefOutOfRange)
));
}
#[test]
fn ar_filter_rejects_bad_inc() {
let mut spec = [1.0, 2.0];
assert!(matches!(
tns_ar_filter(&mut spec, 0, 2, 0, &[1.0, 0.5]),
Err(Error::TnsCoefOutOfRange)
));
assert!(matches!(
tns_ar_filter(&mut spec, 0, 2, 2, &[1.0, 0.5]),
Err(Error::TnsCoefOutOfRange)
));
}
#[test]
fn ar_filter_rejects_out_of_bounds_walk() {
let mut spec = [1.0, 2.0, 3.0];
assert!(matches!(
tns_ar_filter(&mut spec, 1, 5, 1, &[1.0, 0.5]),
Err(Error::TnsCoefOutOfRange)
));
assert!(matches!(
tns_ar_filter(&mut spec, 1, 3, -1, &[1.0, 0.5]),
Err(Error::TnsCoefOutOfRange)
));
assert!(matches!(
tns_ar_filter(&mut spec, 3, 1, 1, &[1.0, 0.5]),
Err(Error::TnsCoefOutOfRange)
));
}
#[test]
fn ar_filter_end_to_end_from_wire_coef() {
let wire = [3_u32, 0xE]; let lpc = tns_decode_coef_to_lpc(4, 0, &wire).unwrap();
assert_eq!(lpc.len(), 3);
assert_eq!(lpc[0], 1.0);
let x = [0.3, -0.9, 1.2, 0.4, -0.6, 0.1];
let want = ref_ar_filter(&x, &lpc);
let mut spec = x;
tns_ar_filter(&mut spec, 0, x.len(), 1, &lpc).unwrap();
for (g, w) in spec.iter().zip(want.iter()) {
assert!((g - w).abs() < 1e-12);
}
}
fn ref_ma_filter(x: &[f64], lpc: &[f64]) -> Vec<f64> {
let order = lpc.len() - 1;
let mut y = vec![0.0; x.len()];
for n in 0..x.len() {
let mut acc = x[n];
for k in 1..=order {
if n >= k {
acc += lpc[k] * x[n - k];
}
}
y[n] = acc;
}
y
}
#[test]
fn ma_filter_order_zero_is_identity() {
let mut spec = [0.3, -0.9, 1.2, 0.4];
let before = spec;
let n = spec.len();
tns_ma_filter(&mut spec, 0, n, 1, &[1.0]).unwrap();
assert_eq!(spec, before);
}
#[test]
fn ma_filter_matches_reference_upward() {
let lpc = [1.0, 0.5, -0.25];
let x = [0.3, -0.9, 1.2, 0.4, -0.6, 0.1];
let want = ref_ma_filter(&x, &lpc);
let mut spec = x;
tns_ma_filter(&mut spec, 0, x.len(), 1, &lpc).unwrap();
for (g, w) in spec.iter().zip(want.iter()) {
assert!((g - w).abs() < 1e-12, "got {g} want {w}");
}
}
#[test]
fn ma_then_ar_is_identity() {
let lpc = tns_decode_coef_to_lpc(4, 0, &[3, 0xE]).unwrap();
let x = [0.7, -0.2, 1.1, -1.3, 0.05, 0.9, -0.4];
let mut spec = x;
tns_ma_filter(&mut spec, 0, x.len(), 1, &lpc).unwrap();
tns_ar_filter(&mut spec, 0, x.len(), 1, &lpc).unwrap();
for (g, w) in spec.iter().zip(x.iter()) {
assert!((g - w).abs() < 1e-12, "ma∘ar not identity: {g} vs {w}");
}
}
#[test]
fn ma_then_ar_is_identity_downward() {
let lpc = [1.0, -0.4, 0.2];
let x = [0.7, -0.2, 1.1, -1.3, 0.05];
let mut spec = x;
let end = x.len();
tns_ma_filter(&mut spec, end - 1, end, -1, &lpc).unwrap();
tns_ar_filter(&mut spec, end - 1, end, -1, &lpc).unwrap();
for (g, w) in spec.iter().zip(x.iter()) {
assert!((g - w).abs() < 1e-12);
}
}
#[test]
fn ma_filter_rejects_bad_args() {
let mut spec = [1.0, 2.0, 3.0];
assert!(matches!(
tns_ma_filter(&mut spec, 0, 1, 2, &[1.0, 0.5]),
Err(Error::TnsCoefOutOfRange)
));
assert!(matches!(
tns_ma_filter(&mut spec, 1, 5, 1, &[1.0, 0.5]),
Err(Error::TnsCoefOutOfRange)
));
assert!(matches!(
tns_ma_filter(&mut spec, 0, 1, 1, &[]),
Err(Error::TnsCoefOutOfRange)
));
}
}