use crate::pulse_data::PulseData;
use crate::{Error, Result};
pub const LONG_WINDOW_LEN: u16 = 1024;
pub const SHORT_WINDOW_LEN: u16 = 128;
const SWB_OFFSET_LONG_44100_48000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 48, 56, 64, 72, 80, 88, 96, 108, 120, 132, 144, 160,
176, 196, 216, 240, 264, 292, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 672, 704,
736, 768, 800, 832, 864, 896, 928, 1024,
];
const SWB_OFFSET_LONG_32000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 48, 56, 64, 72, 80, 88, 96, 108, 120, 132, 144, 160,
176, 196, 216, 240, 264, 292, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 672, 704,
736, 768, 800, 832, 864, 896, 928, 960, 992, 1024,
];
const SWB_OFFSET_LONG_8000: &[u16] = &[
0, 12, 24, 36, 48, 60, 72, 84, 96, 108, 120, 132, 144, 156, 172, 188, 204, 220, 236, 252, 268,
288, 308, 328, 348, 372, 396, 420, 448, 476, 508, 544, 580, 620, 664, 712, 764, 820, 880, 944,
1024,
];
const SWB_OFFSET_LONG_11025_12000_16000: &[u16] = &[
0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 100, 112, 124, 136, 148, 160, 172, 184, 196, 212,
228, 244, 260, 280, 300, 320, 344, 368, 396, 424, 456, 492, 532, 572, 616, 664, 716, 772, 832,
896, 960, 1024,
];
const SWB_OFFSET_LONG_22050_24000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 52, 60, 68, 76, 84, 92, 100, 108, 116, 124, 136,
148, 160, 172, 188, 204, 220, 240, 260, 284, 308, 336, 364, 396, 432, 468, 508, 552, 600, 652,
704, 768, 832, 896, 960, 1024,
];
const SWB_OFFSET_LONG_64000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 64, 72, 80, 88, 100, 112, 124, 140,
156, 172, 192, 216, 240, 268, 304, 344, 384, 424, 464, 504, 544, 584, 624, 664, 704, 744, 784,
824, 864, 904, 944, 984, 1024,
];
const SWB_OFFSET_LONG_88200_96000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 64, 72, 80, 88, 96, 108, 120, 132,
144, 156, 172, 188, 212, 240, 276, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024,
];
const SWB_OFFSET_SHORT_32000_44100_48000: &[u16] =
&[0, 4, 8, 12, 16, 20, 28, 36, 44, 56, 68, 80, 96, 112, 128];
const SWB_OFFSET_SHORT_8000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 36, 44, 52, 60, 72, 88, 108, 128,
];
const SWB_OFFSET_SHORT_11025_12000_16000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 32, 40, 48, 60, 72, 88, 108, 128,
];
const SWB_OFFSET_SHORT_22050_24000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 36, 44, 52, 64, 76, 92, 108, 128,
];
const SWB_OFFSET_SHORT_64000: &[u16] = &[0, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 92, 128];
const SWB_OFFSET_SHORT_88200_96000: &[u16] = &[0, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 92, 128];
pub const SWB_OFFSET_LONG_WINDOW: [&[u16]; 13] = [
SWB_OFFSET_LONG_88200_96000, SWB_OFFSET_LONG_88200_96000, SWB_OFFSET_LONG_64000, SWB_OFFSET_LONG_44100_48000, SWB_OFFSET_LONG_44100_48000, SWB_OFFSET_LONG_32000, SWB_OFFSET_LONG_22050_24000, SWB_OFFSET_LONG_22050_24000, SWB_OFFSET_LONG_11025_12000_16000, SWB_OFFSET_LONG_11025_12000_16000, SWB_OFFSET_LONG_11025_12000_16000, SWB_OFFSET_LONG_8000, &[], ];
pub const SWB_OFFSET_SHORT_WINDOW: [&[u16]; 13] = [
SWB_OFFSET_SHORT_88200_96000, SWB_OFFSET_SHORT_88200_96000, SWB_OFFSET_SHORT_64000, SWB_OFFSET_SHORT_32000_44100_48000, SWB_OFFSET_SHORT_32000_44100_48000, SWB_OFFSET_SHORT_32000_44100_48000, SWB_OFFSET_SHORT_22050_24000, SWB_OFFSET_SHORT_22050_24000, SWB_OFFSET_SHORT_11025_12000_16000, SWB_OFFSET_SHORT_11025_12000_16000, SWB_OFFSET_SHORT_11025_12000_16000, SWB_OFFSET_SHORT_8000, &[], ];
pub fn long_window_offsets(fs_index: u8) -> Result<&'static [u16]> {
let idx = fs_index as usize;
if idx >= SWB_OFFSET_LONG_WINDOW.len() {
return Err(Error::IcsInfoUnsupportedSampleRateIndex(fs_index));
}
let slice = SWB_OFFSET_LONG_WINDOW[idx];
if slice.is_empty() {
return Err(Error::IcsInfoUnsupportedSampleRateIndex(fs_index));
}
Ok(slice)
}
pub fn short_window_offsets(fs_index: u8) -> Result<&'static [u16]> {
let idx = fs_index as usize;
if idx >= SWB_OFFSET_SHORT_WINDOW.len() {
return Err(Error::IcsInfoUnsupportedSampleRateIndex(fs_index));
}
let slice = SWB_OFFSET_SHORT_WINDOW[idx];
if slice.is_empty() {
return Err(Error::IcsInfoUnsupportedSampleRateIndex(fs_index));
}
Ok(slice)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FrameFamily {
#[default]
Lc1024,
Lc960,
Ld512,
Ld480,
}
impl FrameFamily {
pub fn from_aot_and_flag(aot: u8, frame_length_flag: bool) -> Self {
match (aot == 23, frame_length_flag) {
(false, false) => FrameFamily::Lc1024,
(false, true) => FrameFamily::Lc960,
(true, false) => FrameFamily::Ld512,
(true, true) => FrameFamily::Ld480,
}
}
pub fn frame_len(self) -> usize {
match self {
FrameFamily::Lc1024 => 1024,
FrameFamily::Lc960 => 960,
FrameFamily::Ld512 => 512,
FrameFamily::Ld480 => 480,
}
}
pub fn long_transform_len(self) -> usize {
2 * self.frame_len()
}
pub fn short_window_len(self) -> Option<usize> {
match self {
FrameFamily::Lc1024 => Some(128),
FrameFamily::Lc960 => Some(120),
FrameFamily::Ld512 | FrameFamily::Ld480 => None,
}
}
pub fn short_transform_len(self) -> Option<usize> {
self.short_window_len().map(|w| 2 * w)
}
pub fn is_ld(self) -> bool {
matches!(self, FrameFamily::Ld512 | FrameFamily::Ld480)
}
}
const SWB_OFFSET_LONG_960_44100_48000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 48, 56, 64, 72, 80, 88, 96, 108, 120, 132, 144, 160,
176, 196, 216, 240, 264, 292, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 672, 704,
736, 768, 800, 832, 864, 896, 928, 960,
];
const SWB_OFFSET_LONG_960_32000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 48, 56, 64, 72, 80, 88, 96, 108, 120, 132, 144, 160,
176, 196, 216, 240, 264, 292, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 672, 704,
736, 768, 800, 832, 864, 896, 928, 960,
];
const SWB_OFFSET_LONG_960_8000: &[u16] = &[
0, 12, 24, 36, 48, 60, 72, 84, 96, 108, 120, 132, 144, 156, 172, 188, 204, 220, 236, 252, 268,
288, 308, 328, 348, 372, 396, 420, 448, 476, 508, 544, 580, 620, 664, 712, 764, 820, 880, 944,
960,
];
const SWB_OFFSET_LONG_960_11025_12000_16000: &[u16] = &[
0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 100, 112, 124, 136, 148, 160, 172, 184, 196, 212,
228, 244, 260, 280, 300, 320, 344, 368, 396, 424, 456, 492, 532, 572, 616, 664, 716, 772, 832,
896, 960,
];
const SWB_OFFSET_LONG_960_22050_24000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 52, 60, 68, 76, 84, 92, 100, 108, 116, 124, 136,
148, 160, 172, 188, 204, 220, 240, 260, 284, 308, 336, 364, 396, 432, 468, 508, 552, 600, 652,
704, 768, 832, 896, 960,
];
const SWB_OFFSET_LONG_960_64000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 64, 72, 80, 88, 100, 112, 124, 140,
156, 172, 192, 216, 240, 268, 304, 344, 384, 424, 464, 504, 544, 584, 624, 664, 704, 744, 784,
824, 864, 904, 944, 960,
];
const SWB_OFFSET_LONG_960_88200_96000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 64, 72, 80, 88, 96, 108, 120, 132,
144, 156, 172, 188, 212, 240, 276, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960,
];
const SWB_OFFSET_SHORT_120_32000_44100_48000: &[u16] =
&[0, 4, 8, 12, 16, 20, 28, 36, 44, 56, 68, 80, 96, 112, 120];
const SWB_OFFSET_SHORT_120_8000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 36, 44, 52, 60, 72, 88, 108, 120,
];
const SWB_OFFSET_SHORT_120_11025_12000_16000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 32, 40, 48, 60, 72, 88, 108, 120,
];
const SWB_OFFSET_SHORT_120_22050_24000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 36, 44, 52, 64, 76, 92, 108, 120,
];
const SWB_OFFSET_SHORT_120_64000: &[u16] = &[0, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 92, 120];
const SWB_OFFSET_SHORT_120_88200_96000: &[u16] =
&[0, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 92, 120];
pub const SWB_OFFSET_LONG_WINDOW_960: [&[u16]; 13] = [
SWB_OFFSET_LONG_960_88200_96000, SWB_OFFSET_LONG_960_88200_96000, SWB_OFFSET_LONG_960_64000, SWB_OFFSET_LONG_960_44100_48000, SWB_OFFSET_LONG_960_44100_48000, SWB_OFFSET_LONG_960_32000, SWB_OFFSET_LONG_960_22050_24000, SWB_OFFSET_LONG_960_22050_24000, SWB_OFFSET_LONG_960_11025_12000_16000, SWB_OFFSET_LONG_960_11025_12000_16000, SWB_OFFSET_LONG_960_11025_12000_16000, SWB_OFFSET_LONG_960_8000, &[], ];
pub const SWB_OFFSET_SHORT_WINDOW_120: [&[u16]; 13] = [
SWB_OFFSET_SHORT_120_88200_96000, SWB_OFFSET_SHORT_120_88200_96000, SWB_OFFSET_SHORT_120_64000, SWB_OFFSET_SHORT_120_32000_44100_48000, SWB_OFFSET_SHORT_120_32000_44100_48000, SWB_OFFSET_SHORT_120_32000_44100_48000, SWB_OFFSET_SHORT_120_22050_24000, SWB_OFFSET_SHORT_120_22050_24000, SWB_OFFSET_SHORT_120_11025_12000_16000, SWB_OFFSET_SHORT_120_11025_12000_16000, SWB_OFFSET_SHORT_120_11025_12000_16000, SWB_OFFSET_SHORT_120_8000, &[], ];
const SWB_OFFSET_LD_512_44100_48000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 68, 76, 84, 92, 100, 112, 124,
136, 148, 164, 184, 208, 236, 268, 300, 332, 364, 396, 428, 460, 512,
];
const SWB_OFFSET_LD_512_32000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 64, 72, 80, 88, 96, 108, 120, 132,
144, 160, 176, 192, 212, 236, 260, 288, 320, 352, 384, 416, 448, 480, 512,
];
const SWB_OFFSET_LD_512_22050_24000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 52, 60, 68, 80, 92, 104, 120, 140, 164, 192, 224,
256, 288, 320, 352, 384, 416, 448, 480, 512,
];
const SWB_OFFSET_LD_480_44100_48000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 64, 72, 80, 88, 96, 108, 120, 132,
144, 156, 172, 188, 212, 240, 272, 304, 336, 368, 400, 432, 480,
];
const SWB_OFFSET_LD_480_32000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 72, 80, 88, 96, 104, 112, 124,
136, 148, 164, 180, 200, 224, 256, 288, 320, 352, 384, 416, 448, 480,
];
const SWB_OFFSET_LD_480_22050_24000: &[u16] = &[
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 52, 60, 68, 80, 92, 104, 120, 140, 164, 192, 224,
256, 288, 320, 352, 384, 416, 448, 480,
];
fn ld_table_slot(fs_index: u8) -> Result<usize> {
match fs_index {
0..=4 => Ok(0), 5 => Ok(1), 6..=11 => Ok(2), other => Err(Error::IcsInfoUnsupportedSampleRateIndex(other)),
}
}
const SWB_OFFSET_LD_512: [&[u16]; 3] = [
SWB_OFFSET_LD_512_44100_48000,
SWB_OFFSET_LD_512_32000,
SWB_OFFSET_LD_512_22050_24000,
];
const SWB_OFFSET_LD_480: [&[u16]; 3] = [
SWB_OFFSET_LD_480_44100_48000,
SWB_OFFSET_LD_480_32000,
SWB_OFFSET_LD_480_22050_24000,
];
pub fn long_window_offsets_family(family: FrameFamily, fs_index: u8) -> Result<&'static [u16]> {
match family {
FrameFamily::Lc1024 => long_window_offsets(fs_index),
FrameFamily::Lc960 => {
let idx = fs_index as usize;
let slice = SWB_OFFSET_LONG_WINDOW_960
.get(idx)
.copied()
.unwrap_or(&[][..]);
if slice.is_empty() {
return Err(Error::IcsInfoUnsupportedSampleRateIndex(fs_index));
}
Ok(slice)
}
FrameFamily::Ld512 => Ok(SWB_OFFSET_LD_512[ld_table_slot(fs_index)?]),
FrameFamily::Ld480 => Ok(SWB_OFFSET_LD_480[ld_table_slot(fs_index)?]),
}
}
pub fn short_window_offsets_family(family: FrameFamily, fs_index: u8) -> Result<&'static [u16]> {
match family {
FrameFamily::Lc1024 => short_window_offsets(fs_index),
FrameFamily::Lc960 => {
let idx = fs_index as usize;
let slice = SWB_OFFSET_SHORT_WINDOW_120
.get(idx)
.copied()
.unwrap_or(&[][..]);
if slice.is_empty() {
return Err(Error::IcsInfoUnsupportedSampleRateIndex(fs_index));
}
Ok(slice)
}
FrameFamily::Ld512 | FrameFamily::Ld480 => Err(Error::LdShortWindow),
}
}
pub fn apply_pulse_data(x_quant: &mut [i32], fs_index: u8, pulse_data: &PulseData) -> Result<()> {
apply_pulse_data_family(x_quant, FrameFamily::Lc1024, fs_index, pulse_data)
}
pub fn apply_pulse_data_family(
x_quant: &mut [i32],
family: FrameFamily,
fs_index: u8,
pulse_data: &PulseData,
) -> Result<()> {
if x_quant.len() < family.frame_len() {
return Err(Error::PulseDataEncodeInvalid);
}
if pulse_data.pulses.is_empty() || pulse_data.pulses.len() > crate::pulse_data::MAX_PULSES {
return Err(Error::PulseDataEncodeInvalid);
}
let offsets = long_window_offsets_family(family, fs_index)?;
let start_sfb = pulse_data.pulse_start_sfb as usize;
if start_sfb >= offsets.len() - 1 {
return Err(Error::PulseDataEncodeInvalid);
}
let mut k = offsets[start_sfb] as usize;
let len = x_quant.len().min(family.frame_len());
for pulse in &pulse_data.pulses {
k += pulse.offset as usize;
if k >= len {
return Err(Error::PulseDataEncodeInvalid);
}
let amp = pulse.amp as i32;
if x_quant[k] > 0 {
x_quant[k] += amp;
} else {
x_quant[k] -= amp;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn num_swb_long_window() -> [u8; 12] {
[41, 41, 47, 49, 49, 51, 47, 47, 43, 43, 43, 40]
}
fn num_swb_short_window() -> [u8; 12] {
[12, 12, 12, 14, 14, 14, 15, 15, 15, 15, 15, 15]
}
#[test]
fn long_offset_lengths_match_num_swb() {
let counts = num_swb_long_window();
for fs_index in 0..12_u8 {
let offsets = long_window_offsets(fs_index).unwrap();
assert_eq!(
offsets.len(),
counts[fs_index as usize] as usize + 1,
"fs_index {} long-window offset table length",
fs_index,
);
}
}
#[test]
fn short_offset_lengths_match_num_swb() {
let counts = num_swb_short_window();
for fs_index in 0..12_u8 {
let offsets = short_window_offsets(fs_index).unwrap();
assert_eq!(
offsets.len(),
counts[fs_index as usize] as usize + 1,
"fs_index {} short-window offset table length",
fs_index,
);
}
}
#[test]
fn long_tables_start_at_zero_and_end_at_1024() {
for fs_index in 0..12_u8 {
let offsets = long_window_offsets(fs_index).unwrap();
assert_eq!(offsets[0], 0, "fs_index {} first offset", fs_index);
assert_eq!(
*offsets.last().unwrap(),
LONG_WINDOW_LEN,
"fs_index {} sentinel",
fs_index
);
}
}
#[test]
fn short_tables_start_at_zero_and_end_at_128() {
for fs_index in 0..12_u8 {
let offsets = short_window_offsets(fs_index).unwrap();
assert_eq!(offsets[0], 0, "fs_index {} first offset", fs_index);
assert_eq!(
*offsets.last().unwrap(),
SHORT_WINDOW_LEN,
"fs_index {} sentinel",
fs_index
);
}
}
#[test]
fn long_offsets_are_strictly_monotonic() {
for fs_index in 0..12_u8 {
let offsets = long_window_offsets(fs_index).unwrap();
for w in offsets.windows(2) {
assert!(
w[0] < w[1],
"fs_index {} non-monotonic at {} -> {}",
fs_index,
w[0],
w[1]
);
}
}
}
#[test]
fn short_offsets_are_strictly_monotonic() {
for fs_index in 0..12_u8 {
let offsets = short_window_offsets(fs_index).unwrap();
for w in offsets.windows(2) {
assert!(
w[0] < w[1],
"fs_index {} non-monotonic at {} -> {}",
fs_index,
w[0],
w[1]
);
}
}
}
#[test]
fn fs_index_7350_returns_unsupported() {
assert!(matches!(
long_window_offsets(12),
Err(Error::IcsInfoUnsupportedSampleRateIndex(12))
));
assert!(matches!(
short_window_offsets(12),
Err(Error::IcsInfoUnsupportedSampleRateIndex(12))
));
}
#[test]
fn fs_index_out_of_range_returns_unsupported() {
assert!(matches!(
long_window_offsets(13),
Err(Error::IcsInfoUnsupportedSampleRateIndex(13))
));
assert!(matches!(
long_window_offsets(15),
Err(Error::IcsInfoUnsupportedSampleRateIndex(15))
));
assert!(matches!(
short_window_offsets(15),
Err(Error::IcsInfoUnsupportedSampleRateIndex(15))
));
}
#[test]
fn table_4_129_spot_check_48k() {
let offsets = long_window_offsets(3).unwrap();
assert_eq!(offsets[0], 0);
assert_eq!(offsets[1], 4);
assert_eq!(offsets[10], 40);
assert_eq!(offsets[11], 48);
assert_eq!(offsets[24], 196);
assert_eq!(offsets[49], 1024);
assert_eq!(offsets.len(), 50);
}
#[test]
fn table_4_131_spot_check_32k() {
let offsets = long_window_offsets(5).unwrap();
assert_eq!(offsets[0], 0);
assert_eq!(offsets[50], 992);
assert_eq!(offsets[51], 1024);
assert_eq!(offsets.len(), 52);
}
#[test]
fn table_4_132_spot_check_8k() {
let offsets = long_window_offsets(11).unwrap();
assert_eq!(offsets[0], 0);
assert_eq!(offsets[1], 12);
assert_eq!(offsets[20], 268);
assert_eq!(offsets[40], 1024);
assert_eq!(offsets.len(), 41);
}
#[test]
fn table_4_134_spot_check_16k() {
let offsets = long_window_offsets(8).unwrap();
assert_eq!(offsets[0], 0);
assert_eq!(offsets[1], 8);
assert_eq!(offsets[21], 212);
assert_eq!(offsets[22], 228);
assert_eq!(offsets[43], 1024);
assert_eq!(offsets.len(), 44);
assert_eq!(long_window_offsets(9).unwrap(), offsets);
assert_eq!(long_window_offsets(10).unwrap(), offsets);
}
#[test]
fn table_4_136_spot_check_24k() {
let offsets = long_window_offsets(6).unwrap();
assert_eq!(offsets[0], 0);
assert_eq!(offsets[11], 44);
assert_eq!(offsets[12], 52);
assert_eq!(offsets[23], 148);
assert_eq!(offsets[24], 160);
assert_eq!(offsets[47], 1024);
assert_eq!(offsets.len(), 48);
assert_eq!(long_window_offsets(7).unwrap(), offsets);
}
#[test]
fn table_4_138_spot_check_64k() {
let offsets = long_window_offsets(2).unwrap();
assert_eq!(offsets[0], 0);
assert_eq!(offsets[14], 56);
assert_eq!(offsets[15], 64);
assert_eq!(offsets[22], 140);
assert_eq!(offsets[46], 984);
assert_eq!(offsets[47], 1024);
assert_eq!(offsets.len(), 48);
}
#[test]
fn table_4_140_spot_check_96k() {
let offsets = long_window_offsets(0).unwrap();
assert_eq!(offsets[0], 0);
assert_eq!(offsets[20], 108);
assert_eq!(offsets[21], 120);
assert_eq!(offsets[41], 1024);
assert_eq!(offsets.len(), 42);
assert_eq!(long_window_offsets(1).unwrap(), offsets);
}
#[test]
fn table_4_130_spot_check_48k_short() {
let offsets = short_window_offsets(3).unwrap();
assert_eq!(offsets[0], 0);
assert_eq!(offsets[5], 20);
assert_eq!(offsets[6], 28);
assert_eq!(offsets[14], 128);
assert_eq!(offsets.len(), 15);
assert_eq!(short_window_offsets(4).unwrap(), offsets);
assert_eq!(short_window_offsets(5).unwrap(), offsets);
}
#[test]
fn table_4_133_spot_check_8k_short() {
let offsets = short_window_offsets(11).unwrap();
assert_eq!(offsets[0], 0);
assert_eq!(offsets[7], 28);
assert_eq!(offsets[8], 36);
assert_eq!(offsets[15], 128);
assert_eq!(offsets.len(), 16);
}
#[test]
fn table_4_135_spot_check_16k_short() {
let offsets = short_window_offsets(8).unwrap();
assert_eq!(offsets[0], 0);
assert_eq!(offsets[8], 32);
assert_eq!(offsets[9], 40);
assert_eq!(offsets[15], 128);
assert_eq!(offsets.len(), 16);
assert_eq!(short_window_offsets(9).unwrap(), offsets);
assert_eq!(short_window_offsets(10).unwrap(), offsets);
}
#[test]
fn table_4_137_spot_check_24k_short() {
let offsets = short_window_offsets(6).unwrap();
assert_eq!(offsets[0], 0);
assert_eq!(offsets[7], 28);
assert_eq!(offsets[8], 36);
assert_eq!(offsets[11], 64);
assert_eq!(offsets[15], 128);
assert_eq!(offsets.len(), 16);
assert_eq!(short_window_offsets(7).unwrap(), offsets);
}
#[test]
fn table_4_139_spot_check_64k_short() {
let offsets = short_window_offsets(2).unwrap();
assert_eq!(offsets[0], 0);
assert_eq!(offsets[6], 24);
assert_eq!(offsets[7], 32);
assert_eq!(offsets[11], 92);
assert_eq!(offsets[12], 128);
assert_eq!(offsets.len(), 13);
}
#[test]
fn table_4_141_spot_check_96k_short() {
let offsets = short_window_offsets(0).unwrap();
assert_eq!(offsets[0], 0);
assert_eq!(offsets[7], 32);
assert_eq!(offsets[12], 128);
assert_eq!(offsets.len(), 13);
assert_eq!(short_window_offsets(1).unwrap(), offsets);
}
#[test]
fn apply_pulse_data_single_positive_pulse_48k() {
use crate::pulse_data::{Pulse, PulseData};
let mut x_quant = vec![0_i32; 1024];
x_quant[17] = 7;
let pd = PulseData {
pulse_start_sfb: 3,
pulses: vec![Pulse { offset: 5, amp: 3 }],
};
apply_pulse_data(&mut x_quant, 3, &pd).unwrap();
assert_eq!(x_quant[17], 10);
}
#[test]
fn apply_pulse_data_single_negative_pulse_48k() {
use crate::pulse_data::{Pulse, PulseData};
let mut x_quant = vec![0_i32; 1024];
x_quant[17] = -7;
let pd = PulseData {
pulse_start_sfb: 3,
pulses: vec![Pulse { offset: 5, amp: 3 }],
};
apply_pulse_data(&mut x_quant, 3, &pd).unwrap();
assert_eq!(x_quant[17], -10);
}
#[test]
fn apply_pulse_data_zero_coefficient_subtracts_amp() {
use crate::pulse_data::{Pulse, PulseData};
let mut x_quant = vec![0_i32; 1024];
let pd = PulseData {
pulse_start_sfb: 0,
pulses: vec![Pulse { offset: 1, amp: 4 }],
};
apply_pulse_data(&mut x_quant, 3, &pd).unwrap();
assert_eq!(x_quant[1], -4);
}
#[test]
fn apply_pulse_data_four_pulses_accumulate_k() {
use crate::pulse_data::{Pulse, PulseData};
let mut x_quant = vec![1_i32; 1024];
let pd = PulseData {
pulse_start_sfb: 10,
pulses: vec![
Pulse { offset: 1, amp: 1 },
Pulse { offset: 2, amp: 2 },
Pulse { offset: 3, amp: 3 },
Pulse { offset: 4, amp: 4 },
],
};
apply_pulse_data(&mut x_quant, 3, &pd).unwrap();
assert_eq!(x_quant[41], 2);
assert_eq!(x_quant[43], 3);
assert_eq!(x_quant[46], 4);
assert_eq!(x_quant[50], 5);
}
#[test]
fn apply_pulse_data_overrun_rejected() {
use crate::pulse_data::{Pulse, PulseData};
let mut x_quant = vec![0_i32; 1024];
let pd = PulseData {
pulse_start_sfb: 48, pulses: vec![Pulse { offset: 31, amp: 0 }; 4], };
assert!(matches!(
apply_pulse_data(&mut x_quant, 3, &pd),
Err(Error::PulseDataEncodeInvalid)
));
}
#[test]
fn apply_pulse_data_start_sfb_past_last_band_rejected() {
use crate::pulse_data::{Pulse, PulseData};
let mut x_quant = vec![0_i32; 1024];
let pd = PulseData {
pulse_start_sfb: 49,
pulses: vec![Pulse { offset: 1, amp: 1 }],
};
assert!(matches!(
apply_pulse_data(&mut x_quant, 3, &pd),
Err(Error::PulseDataEncodeInvalid)
));
}
#[test]
fn apply_pulse_data_empty_pulses_rejected() {
use crate::pulse_data::PulseData;
let mut x_quant = vec![0_i32; 1024];
let pd = PulseData {
pulse_start_sfb: 0,
pulses: vec![],
};
assert!(matches!(
apply_pulse_data(&mut x_quant, 3, &pd),
Err(Error::PulseDataEncodeInvalid)
));
}
#[test]
fn apply_pulse_data_too_many_pulses_rejected() {
use crate::pulse_data::{Pulse, PulseData};
let mut x_quant = vec![0_i32; 1024];
let pd = PulseData {
pulse_start_sfb: 0,
pulses: vec![Pulse { offset: 1, amp: 1 }; 5],
};
assert!(matches!(
apply_pulse_data(&mut x_quant, 3, &pd),
Err(Error::PulseDataEncodeInvalid)
));
}
#[test]
fn apply_pulse_data_unsupported_fs_index_rejected() {
use crate::pulse_data::{Pulse, PulseData};
let mut x_quant = vec![0_i32; 1024];
let pd = PulseData {
pulse_start_sfb: 0,
pulses: vec![Pulse { offset: 1, amp: 1 }],
};
assert!(matches!(
apply_pulse_data(&mut x_quant, 12, &pd),
Err(Error::IcsInfoUnsupportedSampleRateIndex(12))
));
}
#[test]
fn long_widths_match_num_swb_sums() {
for fs_index in 0..12_u8 {
let offsets = long_window_offsets(fs_index).unwrap();
let total: u32 = offsets.windows(2).map(|w| (w[1] - w[0]) as u32).sum();
assert_eq!(total, LONG_WINDOW_LEN as u32);
}
}
#[test]
fn short_widths_match_num_swb_sums() {
for fs_index in 0..12_u8 {
let offsets = short_window_offsets(fs_index).unwrap();
let total: u32 = offsets.windows(2).map(|w| (w[1] - w[0]) as u32).sum();
assert_eq!(total, SHORT_WINDOW_LEN as u32);
}
}
#[test]
fn family_resolution_follows_4_5_1_1() {
assert_eq!(
FrameFamily::from_aot_and_flag(2, false),
FrameFamily::Lc1024
);
assert_eq!(FrameFamily::from_aot_and_flag(2, true), FrameFamily::Lc960);
assert_eq!(FrameFamily::from_aot_and_flag(17, true), FrameFamily::Lc960);
assert_eq!(
FrameFamily::from_aot_and_flag(23, false),
FrameFamily::Ld512
);
assert_eq!(FrameFamily::from_aot_and_flag(23, true), FrameFamily::Ld480);
}
#[test]
fn family_lengths() {
assert_eq!(FrameFamily::Lc1024.frame_len(), 1024);
assert_eq!(FrameFamily::Lc1024.long_transform_len(), 2048);
assert_eq!(FrameFamily::Lc1024.short_window_len(), Some(128));
assert_eq!(FrameFamily::Lc1024.short_transform_len(), Some(256));
assert_eq!(FrameFamily::Lc960.frame_len(), 960);
assert_eq!(FrameFamily::Lc960.long_transform_len(), 1920);
assert_eq!(FrameFamily::Lc960.short_window_len(), Some(120));
assert_eq!(FrameFamily::Lc960.short_transform_len(), Some(240));
assert_eq!(FrameFamily::Ld512.frame_len(), 512);
assert_eq!(FrameFamily::Ld512.long_transform_len(), 1024);
assert_eq!(FrameFamily::Ld512.short_window_len(), None);
assert_eq!(FrameFamily::Ld480.frame_len(), 480);
assert_eq!(FrameFamily::Ld480.long_transform_len(), 960);
assert_eq!(FrameFamily::Ld480.short_window_len(), None);
assert!(FrameFamily::Ld512.is_ld());
assert!(FrameFamily::Ld480.is_ld());
assert!(!FrameFamily::Lc1024.is_ld());
assert!(!FrameFamily::Lc960.is_ld());
}
#[test]
fn lc1024_family_lookup_matches_legacy_accessors() {
for fs_index in 0..12_u8 {
assert_eq!(
long_window_offsets_family(FrameFamily::Lc1024, fs_index).unwrap(),
long_window_offsets(fs_index).unwrap()
);
assert_eq!(
short_window_offsets_family(FrameFamily::Lc1024, fs_index).unwrap(),
short_window_offsets(fs_index).unwrap()
);
}
}
#[test]
fn lc960_long_tables_are_the_bracketed_columns() {
for fs_index in 0..12_u8 {
let long1024 = long_window_offsets(fs_index).unwrap();
let long960 = long_window_offsets_family(FrameFamily::Lc960, fs_index).unwrap();
let n = long960.len();
assert_eq!(*long960.last().unwrap(), 960, "fs {} sentinel", fs_index);
assert_eq!(
&long960[..n - 1],
&long1024[..n - 1],
"fs {} shared band starts",
fs_index
);
for &off in &long1024[n - 1..] {
assert!(off >= 960, "fs {} dropped offset {}", fs_index, off);
}
assert_eq!(long960[0], 0);
for w in long960.windows(2) {
assert!(w[0] < w[1], "fs {} non-monotonic", fs_index);
}
}
}
#[test]
fn lc960_expected_num_swb() {
let expected: [usize; 12] = [40, 40, 46, 49, 49, 49, 46, 46, 42, 42, 42, 40];
for fs_index in 0..12_u8 {
let long960 = long_window_offsets_family(FrameFamily::Lc960, fs_index).unwrap();
assert_eq!(
long960.len() - 1,
expected[fs_index as usize],
"fs {} num_swb",
fs_index
);
}
}
#[test]
fn lc960_short_tables_only_rescale_the_sentinel() {
for fs_index in 0..12_u8 {
let short128 = short_window_offsets(fs_index).unwrap();
let short120 = short_window_offsets_family(FrameFamily::Lc960, fs_index).unwrap();
assert_eq!(short120.len(), short128.len(), "fs {}", fs_index);
let n = short120.len();
assert_eq!(&short120[..n - 1], &short128[..n - 1]);
assert_eq!(short120[n - 1], 120);
for w in short120.windows(2) {
assert!(w[0] < w[1], "fs {} non-monotonic", fs_index);
}
}
}
#[test]
fn ld_tables_match_spec_counts_and_sentinels() {
for (fs, num) in [(3u8, 36usize), (4, 36), (5, 37), (6, 31), (7, 31)] {
let t = long_window_offsets_family(FrameFamily::Ld512, fs).unwrap();
assert_eq!(t.len() - 1, num, "LD512 fs {}", fs);
assert_eq!(t[0], 0);
assert_eq!(*t.last().unwrap(), 512);
for w in t.windows(2) {
assert!(w[0] < w[1]);
}
}
for (fs, num) in [(3u8, 35usize), (4, 35), (5, 37), (6, 30), (7, 30)] {
let t = long_window_offsets_family(FrameFamily::Ld480, fs).unwrap();
assert_eq!(t.len() - 1, num, "LD480 fs {}", fs);
assert_eq!(t[0], 0);
assert_eq!(*t.last().unwrap(), 480);
for w in t.windows(2) {
assert!(w[0] < w[1]);
}
}
}
#[test]
fn ld_512_spot_checks() {
let t = long_window_offsets_family(FrameFamily::Ld512, 3).unwrap();
assert_eq!(t[16], 68);
assert_eq!(t[21], 112);
assert_eq!(t[27], 208);
assert_eq!(t[35], 460);
let t = long_window_offsets_family(FrameFamily::Ld512, 5).unwrap();
assert_eq!(t[15], 64);
assert_eq!(t[20], 108);
assert_eq!(t[30], 288);
assert_eq!(t[36], 480);
let t = long_window_offsets_family(FrameFamily::Ld512, 6).unwrap();
assert_eq!(t[12], 52);
assert_eq!(t[18], 120);
assert_eq!(t[25], 320);
assert_eq!(t[30], 480);
}
#[test]
fn ld_480_spot_checks() {
let t = long_window_offsets_family(FrameFamily::Ld480, 4).unwrap();
assert_eq!(t[15], 64);
assert_eq!(t[20], 108);
assert_eq!(t[27], 212);
assert_eq!(t[34], 432);
let t = long_window_offsets_family(FrameFamily::Ld480, 5).unwrap();
assert_eq!(t[17], 72);
assert_eq!(t[23], 124);
assert_eq!(t[29], 224);
assert_eq!(t[36], 448);
let t = long_window_offsets_family(FrameFamily::Ld480, 7).unwrap();
assert_eq!(t[12], 52);
assert_eq!(t[16], 92);
assert_eq!(t[22], 224);
assert_eq!(t[29], 448);
}
#[test]
fn ld_nearest_defined_table_rule() {
let t48 = long_window_offsets_family(FrameFamily::Ld512, 3).unwrap();
for fs in [0u8, 1, 2, 4] {
assert_eq!(
long_window_offsets_family(FrameFamily::Ld512, fs).unwrap(),
t48
);
}
let t22 = long_window_offsets_family(FrameFamily::Ld512, 7).unwrap();
for fs in [6u8, 8, 9, 10, 11] {
assert_eq!(
long_window_offsets_family(FrameFamily::Ld512, fs).unwrap(),
t22
);
}
assert!(matches!(
long_window_offsets_family(FrameFamily::Ld512, 12),
Err(Error::IcsInfoUnsupportedSampleRateIndex(12))
));
}
#[test]
fn ld_short_lookup_is_rejected() {
assert!(matches!(
short_window_offsets_family(FrameFamily::Ld512, 3),
Err(Error::LdShortWindow)
));
assert!(matches!(
short_window_offsets_family(FrameFamily::Ld480, 3),
Err(Error::LdShortWindow)
));
}
#[test]
fn family_widths_sum_to_family_lengths() {
for family in [FrameFamily::Lc960, FrameFamily::Ld512, FrameFamily::Ld480] {
for fs_index in 0..12_u8 {
let long = long_window_offsets_family(family, fs_index).unwrap();
assert_eq!(
*long.last().unwrap() as usize,
family.frame_len(),
"{:?} fs {} long sentinel",
family,
fs_index
);
}
}
for fs_index in 0..12_u8 {
let short = short_window_offsets_family(FrameFamily::Lc960, fs_index).unwrap();
assert_eq!(*short.last().unwrap(), 120);
}
}
#[test]
fn apply_pulse_data_family_uses_family_bounds() {
use crate::pulse_data::{Pulse, PulseData};
let mut x_quant = vec![1_i32; 512];
let pd = PulseData {
pulse_start_sfb: 35,
pulses: vec![Pulse { offset: 31, amp: 2 }],
};
apply_pulse_data_family(&mut x_quant, FrameFamily::Ld512, 3, &pd).unwrap();
assert_eq!(x_quant[491], 3);
let mut x_quant = vec![1_i32; 512];
let pd = PulseData {
pulse_start_sfb: 35,
pulses: vec![Pulse { offset: 31, amp: 2 }; 2], };
assert!(matches!(
apply_pulse_data_family(&mut x_quant, FrameFamily::Ld512, 3, &pd),
Err(Error::PulseDataEncodeInvalid)
));
}
}