use crate::ics_info::IcsInfo;
use crate::ics_info::WindowSequence;
#[cfg(test)]
use crate::swb_offset::{long_window_offsets, short_window_offsets};
use crate::swb_offset::{long_window_offsets_family, short_window_offsets_family, FrameFamily};
use crate::tns_coef::{tns_ar_filter, tns_decode_coef_to_lpc, tns_ma_filter};
use crate::tns_data::{num_windows, TnsData};
use crate::tns_max::{clamp_tns_band_family, clamp_tns_order};
use crate::{Error, Result};
pub fn tns_decode_frame(
spec: &mut [f64],
tns: &TnsData,
window_sequence: WindowSequence,
max_sfb: u8,
aot: u8,
fs_index: u8,
) -> Result<()> {
tns_frame_filter(
spec,
tns,
FrameFamily::Lc1024,
window_sequence,
max_sfb,
aot,
fs_index,
TnsFilterKind::Synthesis,
)
}
pub fn tns_decode_frame_ics(
spec: &mut [f64],
tns: &TnsData,
ics_info: &IcsInfo,
aot: u8,
fs_index: u8,
) -> Result<()> {
tns_frame_filter(
spec,
tns,
ics_info.family,
ics_info.window_sequence,
ics_info.max_sfb,
aot,
fs_index,
TnsFilterKind::Synthesis,
)
}
pub fn tns_analysis_frame(
spec: &mut [f64],
tns: &TnsData,
window_sequence: WindowSequence,
max_sfb: u8,
aot: u8,
fs_index: u8,
) -> Result<()> {
tns_frame_filter(
spec,
tns,
FrameFamily::Lc1024,
window_sequence,
max_sfb,
aot,
fs_index,
TnsFilterKind::Analysis,
)
}
pub fn tns_analysis_frame_ics(
spec: &mut [f64],
tns: &TnsData,
ics_info: &IcsInfo,
aot: u8,
fs_index: u8,
) -> Result<()> {
tns_frame_filter(
spec,
tns,
ics_info.family,
ics_info.window_sequence,
ics_info.max_sfb,
aot,
fs_index,
TnsFilterKind::Analysis,
)
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum TnsFilterKind {
Synthesis,
Analysis,
}
#[allow(clippy::too_many_arguments)]
fn tns_frame_filter(
spec: &mut [f64],
tns: &TnsData,
family: FrameFamily,
window_sequence: WindowSequence,
max_sfb: u8,
aot: u8,
fs_index: u8,
kind: TnsFilterKind,
) -> Result<()> {
let windows = num_windows(window_sequence);
let (window_len, offsets) = if window_sequence.is_eight_short() {
(
family.short_window_len().ok_or(Error::LdShortWindow)?,
short_window_offsets_family(family, fs_index)?,
)
} else {
(
family.frame_len(),
long_window_offsets_family(family, fs_index)?,
)
};
if tns.windows.len() != windows {
return Err(Error::TnsFrameInvalid);
}
if spec.len() != windows * window_len {
return Err(Error::TnsFrameInvalid);
}
let num_swb = offsets.len() - 1;
for (w, tns_window) in tns.windows.iter().enumerate() {
let coef_res_bits = 3 + u32::from(tns_window.coef_res);
let window_spec = &mut spec[w * window_len..(w + 1) * window_len];
let mut bottom = num_swb;
for filter in &tns_window.filters {
let top = bottom;
bottom = top.saturating_sub(filter.length as usize);
let tns_order = clamp_tns_order(filter.order, aot, window_sequence, fs_index)? as usize;
if tns_order == 0 {
continue;
}
if filter.coef.len() < tns_order {
return Err(Error::TnsFrameInvalid);
}
let coef: Vec<u32> = filter.coef[..tns_order]
.iter()
.map(|&c| u32::from(c))
.collect();
let lpc =
tns_decode_coef_to_lpc(coef_res_bits, u32::from(filter.coef_compress), &coef)?;
let start_band = clamp_tns_band_family(
bottom as u8,
max_sfb,
family,
aot,
window_sequence,
fs_index,
)?;
let end_band =
clamp_tns_band_family(top as u8, max_sfb, family, aot, window_sequence, fs_index)?;
let start = offsets[start_band as usize] as usize;
let end = offsets[end_band as usize] as usize;
if end <= start {
continue;
}
let size = end - start;
let (filter_start, inc) = if filter.direction {
(end - 1, -1)
} else {
(start, 1)
};
match kind {
TnsFilterKind::Synthesis => {
tns_ar_filter(window_spec, filter_start, size, inc, &lpc)?;
}
TnsFilterKind::Analysis => {
tns_ma_filter(window_spec, filter_start, size, inc, &lpc)?;
}
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tns_data::{TnsFilter, TnsWindow};
use crate::tns_max::{tns_max_bands, tns_max_order, AOT_AAC_LC, AOT_AAC_MAIN};
const FS_48K: u8 = 3;
fn ramp(len: usize) -> Vec<f64> {
(0..len).map(|i| (i % 97) as f64 * 0.25 - 12.0).collect()
}
fn long_window(filters: Vec<TnsFilter>, coef_res: bool) -> TnsData {
TnsData {
windows: vec![TnsWindow { coef_res, filters }],
}
}
fn no_filter_window() -> TnsWindow {
TnsWindow {
coef_res: false,
filters: vec![],
}
}
#[test]
fn empty_tns_data_leaves_spectrum_untouched() {
let mut spec = ramp(1024);
let want = spec.clone();
let tns = long_window(vec![], false);
tns_decode_frame(
&mut spec,
&tns,
WindowSequence::OnlyLong,
49,
AOT_AAC_LC,
FS_48K,
)
.unwrap();
assert_eq!(spec, want);
}
#[test]
fn order_zero_filter_is_a_no_op() {
let mut spec = ramp(1024);
let want = spec.clone();
let tns = long_window(
vec![TnsFilter {
length: 49,
order: 0,
direction: false,
coef_compress: false,
coef: vec![],
}],
false,
);
tns_decode_frame(
&mut spec,
&tns,
WindowSequence::OnlyLong,
49,
AOT_AAC_LC,
FS_48K,
)
.unwrap();
assert_eq!(spec, want);
}
#[test]
fn zero_length_region_is_a_no_op() {
let mut spec = ramp(1024);
let want = spec.clone();
let tns = long_window(
vec![TnsFilter {
length: 0,
order: 2,
direction: false,
coef_compress: false,
coef: vec![1, 2],
}],
false,
);
tns_decode_frame(
&mut spec,
&tns,
WindowSequence::OnlyLong,
49,
AOT_AAC_LC,
FS_48K,
)
.unwrap();
assert_eq!(spec, want);
}
#[test]
fn single_upward_filter_matches_manual_composition() {
let offsets = long_window_offsets(FS_48K).unwrap();
let num_swb = offsets.len() - 1; let length = 10_u8;
let coef: Vec<u8> = vec![1, 7, 2]; let order = coef.len() as u8;
let mut spec = ramp(1024);
let mut want = spec.clone();
let cap = tns_max_bands(AOT_AAC_LC, WindowSequence::OnlyLong, FS_48K).unwrap() as usize;
let top = num_swb.min(cap).min(num_swb);
let bottom = (num_swb - length as usize).min(cap).min(num_swb);
let start = offsets[bottom] as usize;
let end = offsets[top] as usize;
let coef_u32: Vec<u32> = coef.iter().map(|&c| u32::from(c)).collect();
let lpc = tns_decode_coef_to_lpc(3, 0, &coef_u32).unwrap();
tns_ar_filter(&mut want, start, end - start, 1, &lpc).unwrap();
let tns = long_window(
vec![TnsFilter {
length,
order,
direction: false,
coef_compress: false,
coef,
}],
false,
);
tns_decode_frame(
&mut spec,
&tns,
WindowSequence::OnlyLong,
num_swb as u8,
AOT_AAC_LC,
FS_48K,
)
.unwrap();
assert_eq!(spec, want);
assert_ne!(spec[start..end], ramp(1024)[start..end]);
}
#[test]
fn downward_filter_matches_manual_composition() {
let offsets = long_window_offsets(FS_48K).unwrap();
let num_swb = offsets.len() - 1;
let length = 8_u8;
let coef: Vec<u8> = vec![3, 14, 9]; let order = coef.len() as u8;
let mut spec = ramp(1024);
let mut want = spec.clone();
let cap = tns_max_bands(AOT_AAC_LC, WindowSequence::OnlyLong, FS_48K).unwrap() as usize;
let top = num_swb.min(cap);
let bottom = (num_swb - length as usize).min(cap);
let start = offsets[bottom] as usize;
let end = offsets[top] as usize;
let coef_u32: Vec<u32> = coef.iter().map(|&c| u32::from(c)).collect();
let lpc = tns_decode_coef_to_lpc(4, 0, &coef_u32).unwrap();
tns_ar_filter(&mut want, end - 1, end - start, -1, &lpc).unwrap();
let tns = long_window(
vec![TnsFilter {
length,
order,
direction: true,
coef_compress: false,
coef,
}],
true, );
tns_decode_frame(
&mut spec,
&tns,
WindowSequence::OnlyLong,
num_swb as u8,
AOT_AAC_LC,
FS_48K,
)
.unwrap();
assert_eq!(spec, want);
}
#[test]
fn filter_region_counts_down_from_top_band_and_leaves_rest_untouched() {
let offsets = long_window_offsets(FS_48K).unwrap();
let num_swb = offsets.len() - 1;
let cap = tns_max_bands(AOT_AAC_LC, WindowSequence::OnlyLong, FS_48K).unwrap() as usize;
assert_eq!(cap, 40);
let length = 15_u8;
let bottom = num_swb - length as usize; let start = offsets[bottom] as usize;
let end = offsets[cap] as usize;
let mut spec = ramp(1024);
let before = spec.clone();
let tns = long_window(
vec![TnsFilter {
length,
order: 1,
direction: false,
coef_compress: false,
coef: vec![2],
}],
false,
);
tns_decode_frame(
&mut spec,
&tns,
WindowSequence::OnlyLong,
num_swb as u8,
AOT_AAC_LC,
FS_48K,
)
.unwrap();
assert_eq!(spec[..start], before[..start]);
assert_eq!(spec[end..], before[end..]);
assert_ne!(spec[start..end], before[start..end]);
}
#[test]
fn second_filter_covers_bands_below_the_first() {
let offsets = long_window_offsets(FS_48K).unwrap();
let num_swb = offsets.len() - 1;
let cap = tns_max_bands(AOT_AAC_LC, WindowSequence::OnlyLong, FS_48K).unwrap() as usize;
let (len0, len1) = (14_u8, 7_u8);
let mut spec = ramp(1024);
let mut want = spec.clone();
let top0 = num_swb;
let bottom0 = top0 - len0 as usize;
let lpc0 = tns_decode_coef_to_lpc(3, 0, &[4]).unwrap();
let s0 = offsets[bottom0.min(cap)] as usize;
let e0 = offsets[top0.min(cap)] as usize;
tns_ar_filter(&mut want, s0, e0 - s0, 1, &lpc0).unwrap();
let top1 = bottom0;
let bottom1 = top1 - len1 as usize;
let lpc1 = tns_decode_coef_to_lpc(3, 0, &[7, 1]).unwrap();
let s1 = offsets[bottom1.min(cap)] as usize;
let e1 = offsets[top1.min(cap)] as usize;
tns_ar_filter(&mut want, e1 - 1, e1 - s1, -1, &lpc1).unwrap();
let tns = long_window(
vec![
TnsFilter {
length: len0,
order: 1,
direction: false,
coef_compress: false,
coef: vec![4],
},
TnsFilter {
length: len1,
order: 2,
direction: true,
coef_compress: false,
coef: vec![7, 1],
},
],
false,
);
tns_decode_frame(
&mut spec,
&tns,
WindowSequence::OnlyLong,
num_swb as u8,
AOT_AAC_LC,
FS_48K,
)
.unwrap();
assert_eq!(spec, want);
assert!(s1 < e1 && e1 == s0 && s0 < e0);
}
#[test]
fn length_overrun_saturates_bottom_at_band_zero() {
let offsets = long_window_offsets(FS_48K).unwrap();
let num_swb = offsets.len() - 1;
let cap = tns_max_bands(AOT_AAC_LC, WindowSequence::OnlyLong, FS_48K).unwrap() as usize;
let mut spec = ramp(1024);
let mut want = spec.clone();
let lpc = tns_decode_coef_to_lpc(3, 0, &[5]).unwrap();
let end = offsets[num_swb.min(cap)] as usize;
tns_ar_filter(&mut want, 0, end, 1, &lpc).unwrap();
let tns = long_window(
vec![TnsFilter {
length: 63,
order: 1,
direction: false,
coef_compress: false,
coef: vec![5],
}],
false,
);
tns_decode_frame(
&mut spec,
&tns,
WindowSequence::OnlyLong,
num_swb as u8,
AOT_AAC_LC,
FS_48K,
)
.unwrap();
assert_eq!(spec, want);
}
#[test]
fn max_sfb_clamps_the_filter_region_top() {
let offsets = long_window_offsets(FS_48K).unwrap();
let num_swb = offsets.len() - 1;
let max_sfb = 20_u8;
let end = offsets[max_sfb as usize] as usize;
let mut spec = ramp(1024);
let before = spec.clone();
let tns = long_window(
vec![TnsFilter {
length: 30,
order: 1,
direction: false,
coef_compress: false,
coef: vec![6],
}],
false,
);
tns_decode_frame(
&mut spec,
&tns,
WindowSequence::OnlyLong,
max_sfb,
AOT_AAC_LC,
FS_48K,
)
.unwrap();
assert_eq!(spec[end..], before[end..]);
let start = offsets[(num_swb - 30).min(max_sfb as usize)] as usize;
assert_ne!(spec[start..end], before[start..end]);
}
#[test]
fn fully_clamped_region_is_a_no_op() {
let mut spec = ramp(1024);
let want = spec.clone();
let tns = long_window(
vec![TnsFilter {
length: 5,
order: 1,
direction: false,
coef_compress: false,
coef: vec![3],
}],
false,
);
tns_decode_frame(
&mut spec,
&tns,
WindowSequence::OnlyLong,
10,
AOT_AAC_LC,
FS_48K,
)
.unwrap();
assert_eq!(spec, want);
}
#[test]
fn wire_order_is_clamped_by_tns_max_order() {
let cap = tns_max_order(AOT_AAC_LC, WindowSequence::OnlyLong, FS_48K).unwrap() as usize;
assert_eq!(cap, 12);
let coef: Vec<u8> = (0..15).map(|i| (i % 8) as u8).collect();
let offsets = long_window_offsets(FS_48K).unwrap();
let num_swb = offsets.len() - 1;
let band_cap =
tns_max_bands(AOT_AAC_LC, WindowSequence::OnlyLong, FS_48K).unwrap() as usize;
let length = 12_u8;
let bottom = num_swb - length as usize;
let start = offsets[bottom.min(band_cap)] as usize;
let end = offsets[num_swb.min(band_cap)] as usize;
let mut spec = ramp(1024);
let mut want = spec.clone();
let coef_u32: Vec<u32> = coef[..cap].iter().map(|&c| u32::from(c)).collect();
let lpc = tns_decode_coef_to_lpc(3, 0, &coef_u32).unwrap();
assert_eq!(lpc.len(), cap + 1);
tns_ar_filter(&mut want, start, end - start, 1, &lpc).unwrap();
let tns = long_window(
vec![TnsFilter {
length,
order: 15,
direction: false,
coef_compress: false,
coef,
}],
false,
);
tns_decode_frame(
&mut spec,
&tns,
WindowSequence::OnlyLong,
num_swb as u8,
AOT_AAC_LC,
FS_48K,
)
.unwrap();
assert_eq!(spec, want);
}
#[test]
fn aac_main_long_window_allows_order_up_to_20() {
let coef: Vec<u8> = (0..15).map(|i| ((i * 3) % 8) as u8).collect();
let mk = |aot: u8| {
let mut spec = ramp(1024);
let tns = long_window(
vec![TnsFilter {
length: 12,
order: 15,
direction: false,
coef_compress: false,
coef: coef.clone(),
}],
false,
);
tns_decode_frame(&mut spec, &tns, WindowSequence::OnlyLong, 49, aot, FS_48K).unwrap();
spec
};
assert_ne!(mk(AOT_AAC_MAIN), mk(AOT_AAC_LC));
}
#[test]
fn short_sequence_filters_only_the_targeted_window() {
let offsets = short_window_offsets(FS_48K).unwrap();
let num_swb = offsets.len() - 1; let mut windows: Vec<TnsWindow> = (0..8).map(|_| no_filter_window()).collect();
windows[3] = TnsWindow {
coef_res: false,
filters: vec![TnsFilter {
length: num_swb as u8,
order: 2,
direction: false,
coef_compress: false,
coef: vec![1, 6],
}],
};
let tns = TnsData { windows };
let mut spec = ramp(1024);
let before = spec.clone();
tns_decode_frame(
&mut spec,
&tns,
WindowSequence::EightShort,
num_swb as u8,
AOT_AAC_LC,
FS_48K,
)
.unwrap();
assert_eq!(spec[..3 * 128], before[..3 * 128]);
assert_eq!(spec[4 * 128..], before[4 * 128..]);
assert_ne!(spec[3 * 128..4 * 128], before[3 * 128..4 * 128]);
let cap = tns_max_bands(AOT_AAC_LC, WindowSequence::EightShort, FS_48K).unwrap() as usize;
let end = offsets[num_swb.min(cap)] as usize;
let lpc = tns_decode_coef_to_lpc(3, 0, &[1, 6]).unwrap();
let mut want_w3 = before[3 * 128..4 * 128].to_vec();
tns_ar_filter(&mut want_w3, 0, end, 1, &lpc).unwrap();
assert_eq!(spec[3 * 128..4 * 128], want_w3);
}
#[test]
fn rejects_spectrum_length_mismatch() {
let mut spec = ramp(512);
let tns = long_window(vec![], false);
assert!(matches!(
tns_decode_frame(
&mut spec,
&tns,
WindowSequence::OnlyLong,
49,
AOT_AAC_LC,
FS_48K
),
Err(Error::TnsFrameInvalid)
));
}
#[test]
fn rejects_window_count_mismatch() {
let mut spec = ramp(1024);
let tns = long_window(vec![], false);
assert!(matches!(
tns_decode_frame(
&mut spec,
&tns,
WindowSequence::EightShort,
14,
AOT_AAC_LC,
FS_48K
),
Err(Error::TnsFrameInvalid)
));
}
#[test]
fn rejects_coef_shorter_than_clamped_order() {
let mut spec = ramp(1024);
let tns = long_window(
vec![TnsFilter {
length: 10,
order: 3,
direction: false,
coef_compress: false,
coef: vec![1], }],
false,
);
assert!(matches!(
tns_decode_frame(
&mut spec,
&tns,
WindowSequence::OnlyLong,
49,
AOT_AAC_LC,
FS_48K
),
Err(Error::TnsFrameInvalid)
));
}
#[test]
fn rejects_unsupported_fs_index() {
let mut spec = ramp(1024);
let tns = long_window(vec![], false);
assert!(matches!(
tns_decode_frame(
&mut spec,
&tns,
WindowSequence::OnlyLong,
49,
AOT_AAC_LC,
12
),
Err(Error::IcsInfoUnsupportedSampleRateIndex(12))
));
}
#[test]
fn propagates_coef_out_of_range_from_decode() {
let mut spec = ramp(1024);
let tns = long_window(
vec![TnsFilter {
length: 10,
order: 1,
direction: false,
coef_compress: true,
coef: vec![4],
}],
false,
);
assert!(matches!(
tns_decode_frame(
&mut spec,
&tns,
WindowSequence::OnlyLong,
49,
AOT_AAC_LC,
FS_48K
),
Err(Error::TnsCoefOutOfRange)
));
}
#[test]
fn analysis_then_synthesis_is_identity() {
let tns = long_window(
vec![
TnsFilter {
length: 12,
order: 3,
direction: false,
coef_compress: false,
coef: vec![1, 7, 2],
},
TnsFilter {
length: 8,
order: 2,
direction: true,
coef_compress: false,
coef: vec![6, 3],
},
],
false,
);
let original = ramp(1024);
let mut spec = original.clone();
tns_analysis_frame(
&mut spec,
&tns,
WindowSequence::OnlyLong,
49,
AOT_AAC_LC,
FS_48K,
)
.unwrap();
assert_ne!(spec, original);
tns_decode_frame(
&mut spec,
&tns,
WindowSequence::OnlyLong,
49,
AOT_AAC_LC,
FS_48K,
)
.unwrap();
for (g, w) in spec.iter().zip(original.iter()) {
assert!((g - w).abs() < 1e-9, "analysis∘synthesis drift: {g} vs {w}");
}
}
#[test]
fn analysis_no_filters_is_noop() {
let tns = TnsData {
windows: vec![no_filter_window()],
};
let original = ramp(1024);
let mut spec = original.clone();
tns_analysis_frame(
&mut spec,
&tns,
WindowSequence::OnlyLong,
49,
AOT_AAC_LC,
FS_48K,
)
.unwrap();
assert_eq!(spec, original);
}
}