use crate::ics_info::WindowSequence;
use crate::swb_offset::{
long_window_offsets, short_window_offsets, LONG_WINDOW_LEN, SHORT_WINDOW_LEN,
};
use crate::tns_coef::tns_encode_coef;
use crate::tns_data::{num_windows, TnsData, TnsFilter, TnsWindow};
use crate::tns_frame::tns_analysis_frame;
use crate::tns_max::{clamp_tns_band, tns_max_order, AOT_AAC_LC};
use crate::Result;
pub const TNS_ENC_MAX_ORDER: usize = 8;
pub const TNS_GAIN_MIN: f64 = 1.4;
pub const TNS_COEF_TRIM: f64 = 0.1;
const TNS_COEF_RES: bool = true;
struct WindowDecision {
parcor: Vec<f64>,
}
fn autocorrelation(region: &[f64], max_lag: usize) -> Vec<f64> {
let n = region.len();
(0..=max_lag.min(n.saturating_sub(1)))
.map(|lag| (0..n - lag).map(|i| region[i] * region[i + lag]).sum())
.collect()
}
fn levinson(r: &[f64], max_order: usize) -> (Vec<f64>, f64) {
let mut err = r[0];
if err <= 0.0 {
return (Vec::new(), err);
}
let order = max_order.min(r.len().saturating_sub(1));
let mut a = vec![0.0f64; order + 1];
a[0] = 1.0;
let mut k_out = Vec::with_capacity(order);
let mut b = vec![0.0f64; order + 1];
for m in 1..=order {
let mut acc = r[m];
for i in 1..m {
acc += a[i] * r[m - i];
}
let k = -acc / err;
if !k.is_finite() || k.abs() >= 1.0 {
break;
}
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;
k_out.push(k);
err *= 1.0 - k * k;
if err <= 0.0 {
break;
}
}
(k_out, err)
}
fn decide_window(region: &[f64], max_order: usize) -> Option<WindowDecision> {
if region.len() < 2 * max_order.max(1) {
return None;
}
let r = autocorrelation(region, max_order);
if r[0] <= 0.0 {
return None;
}
let (mut parcor, err) = levinson(&r, max_order);
if parcor.is_empty() || err <= 0.0 {
return None;
}
let gain = r[0] / err;
if gain < TNS_GAIN_MIN {
return None;
}
while parcor.last().is_some_and(|k| k.abs() < TNS_COEF_TRIM) {
parcor.pop();
}
if parcor.is_empty() {
return None;
}
Some(WindowDecision { parcor })
}
pub fn detect_and_apply_tns(
spec: &mut [f64],
seq: WindowSequence,
max_sfb: u8,
fs_index: u8,
permit: &[bool],
) -> Result<Option<TnsData>> {
let nw = num_windows(seq);
let (window_len, offsets) = if seq.is_eight_short() {
(SHORT_WINDOW_LEN as usize, short_window_offsets(fs_index)?)
} else {
(LONG_WINDOW_LEN as usize, long_window_offsets(fs_index)?)
};
let num_swb = offsets.len() - 1;
let top = clamp_tns_band(num_swb as u8, max_sfb, AOT_AAC_LC, seq, fs_index)? as usize;
let end = offsets[top] as usize;
let max_order = TNS_ENC_MAX_ORDER.min(tns_max_order(AOT_AAC_LC, seq, fs_index)? as usize);
let mut windows = Vec::with_capacity(nw);
let mut any = false;
for w in 0..nw {
let region = &spec[w * window_len..w * window_len + end];
let decision = if permit.get(w).copied().unwrap_or(false) {
decide_window(region, max_order)
} else {
None
};
let filters = match decision {
Some(d) => {
let coef = tns_encode_coef(4, 0, &d.parcor)?
.into_iter()
.map(|c| c as u8)
.collect::<Vec<u8>>();
any = true;
vec![TnsFilter {
length: num_swb as u8,
order: coef.len() as u8,
direction: false,
coef_compress: false,
coef,
}]
}
None => Vec::new(),
};
windows.push(TnsWindow {
coef_res: TNS_COEF_RES,
filters,
});
}
if !any {
return Ok(None);
}
let tns = TnsData { windows };
tns_analysis_frame(spec, &tns, seq, max_sfb, AOT_AAC_LC, fs_index)?;
Ok(Some(tns))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ics_info::WindowSequence;
use crate::tns_frame::tns_decode_frame;
fn noise(n: usize, seed: u32) -> Vec<f64> {
let mut state = seed;
(0..n)
.map(|_| {
state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
(state as i32) as f64 / 2_147_483_648.0
})
.collect()
}
fn all_pole(x: &[f64], a: &[f64]) -> Vec<f64> {
let mut y = vec![0.0f64; x.len()];
for n in 0..x.len() {
let mut v = x[n];
for (i, &ai) in a.iter().enumerate() {
let d = i + 1;
if n >= d {
v -= ai * y[n - d];
}
}
y[n] = v;
}
y
}
#[test]
fn levinson_recovers_ar1_reflection() {
let x = noise(4096, 0xC0FF_EE00);
let y = all_pole(&x, &[-0.8]);
let r = autocorrelation(&y, 4);
let (k, err) = levinson(&r, 4);
assert!(!k.is_empty());
assert!(
(k[0] + 0.8).abs() < 0.05,
"k1 = {} should approximate -0.8",
k[0]
);
let gain = r[0] / err;
assert!(gain > 2.0, "gain {gain} too low for AR(1) 0.8");
}
#[test]
fn white_region_stays_untns() {
let region = noise(512, 0xDEAD_BEEF);
assert!(decide_window(®ion, TNS_ENC_MAX_ORDER).is_none());
}
#[test]
fn correlated_region_activates_and_analysis_whitens() {
let fs = 3u8;
let seq = WindowSequence::OnlyLong;
let n = LONG_WINDOW_LEN as usize;
let x = noise(n, 0x1234_5678);
let mut spec = all_pole(&x, &[-1.2, 0.5]);
for v in spec.iter_mut() {
*v *= 1000.0;
}
let original = spec.clone();
let max_sfb = (long_window_offsets(fs).unwrap().len() - 1) as u8;
let tns = detect_and_apply_tns(&mut spec, seq, max_sfb, fs, &[true])
.unwrap()
.expect("correlated spectrum must activate TNS");
assert_eq!(tns.windows.len(), 1);
assert_eq!(tns.windows[0].filters.len(), 1);
let f = &tns.windows[0].filters[0];
assert!(f.order >= 1);
assert_eq!(f.coef.len(), f.order as usize);
assert!(!f.direction);
let e = |s: &[f64]| s.iter().map(|&v| v * v).sum::<f64>();
assert!(
e(&spec) < 0.8 * e(&original),
"analysis should whiten: {} vs {}",
e(&spec),
e(&original)
);
tns_decode_frame(&mut spec, &tns, seq, max_sfb, AOT_AAC_LC, fs).unwrap();
for (a, b) in spec.iter().zip(original.iter()) {
assert!((a - b).abs() < 1e-6, "synthesis must invert analysis");
}
}
#[test]
fn short_windows_decide_independently() {
let fs = 3u8;
let seq = WindowSequence::EightShort;
let wlen = SHORT_WINDOW_LEN as usize;
let mut spec = vec![0.0f64; 8 * wlen];
for w in 0..8 {
let seed = 0x9E37_79B9u32.wrapping_add(w as u32);
let x = noise(wlen, seed);
let win = if w == 3 {
all_pole(&x, &[-1.4, 0.6])
} else {
x
};
for (i, v) in win.iter().enumerate() {
spec[w * wlen + i] = v * 500.0;
}
}
let max_sfb = (short_window_offsets(fs).unwrap().len() - 1) as u8;
let tns = detect_and_apply_tns(&mut spec, seq, max_sfb, fs, &[true; 8])
.unwrap()
.expect("window 3 must activate");
assert_eq!(tns.windows.len(), 8);
assert!(!tns.windows[3].filters.is_empty(), "window 3 fires");
for w in [0usize, 1, 2, 4, 5, 6, 7] {
assert!(
tns.windows[w].filters.is_empty(),
"white window {w} must not fire"
);
}
let f = &tns.windows[3].filters[0];
assert!(f.order <= 7);
assert!(f.length <= 15);
}
#[test]
fn silent_spectrum_never_activates() {
let fs = 4u8;
let mut spec = vec![0.0f64; LONG_WINDOW_LEN as usize];
let max_sfb = (long_window_offsets(fs).unwrap().len() - 1) as u8;
let tns = detect_and_apply_tns(&mut spec, WindowSequence::OnlyLong, max_sfb, fs, &[true])
.unwrap();
assert!(tns.is_none());
assert!(spec.iter().all(|&v| v == 0.0));
}
}