use oxideav_core::bits::BitReader;
use oxideav_core::{Error, Result};
use crate::aspx_huffman;
pub struct AspxHcb {
pub name: &'static str,
pub len: &'static [u8],
pub cw: &'static [u32],
pub cb_off: i32,
}
impl AspxHcb {
pub fn decode_delta(&self, br: &mut BitReader<'_>) -> Result<i32> {
debug_assert_eq!(self.len.len(), self.cw.len());
let mut code: u32 = 0;
let mut width: u8 = 0;
while width < 32 {
let b = br.read_u32(1)?;
code = (code << 1) | b;
width += 1;
for (i, &l) in self.len.iter().enumerate() {
if l == width && self.cw[i] == code {
return Ok(i as i32 - self.cb_off);
}
}
}
Err(Error::invalid("ac4: no matching A-SPX Huffman codeword"))
}
}
#[derive(Debug, Clone, Copy)]
pub struct AspxHcbMeta {
pub name: &'static str,
pub codebook_length: u32,
pub cb_off: i32,
}
pub const ASPX_HCB_ENV_LEVEL_15_F0_META: AspxHcbMeta = AspxHcbMeta {
name: "ASPX_HCB_ENV_LEVEL_15_F0",
codebook_length: 71,
cb_off: 0,
};
pub const ASPX_HCB_ENV_LEVEL_15_DF_META: AspxHcbMeta = AspxHcbMeta {
name: "ASPX_HCB_ENV_LEVEL_15_DF",
codebook_length: 141,
cb_off: 70,
};
pub const ASPX_HCB_ENV_LEVEL_15_DT_META: AspxHcbMeta = AspxHcbMeta {
name: "ASPX_HCB_ENV_LEVEL_15_DT",
codebook_length: 141,
cb_off: 70,
};
pub const ASPX_HCB_ENV_BALANCE_15_F0_META: AspxHcbMeta = AspxHcbMeta {
name: "ASPX_HCB_ENV_BALANCE_15_F0",
codebook_length: 25,
cb_off: 0,
};
pub const ASPX_HCB_ENV_BALANCE_15_DF_META: AspxHcbMeta = AspxHcbMeta {
name: "ASPX_HCB_ENV_BALANCE_15_DF",
codebook_length: 49,
cb_off: 24,
};
pub const ASPX_HCB_ENV_BALANCE_15_DT_META: AspxHcbMeta = AspxHcbMeta {
name: "ASPX_HCB_ENV_BALANCE_15_DT",
codebook_length: 49,
cb_off: 24,
};
pub const ASPX_HCB_ENV_LEVEL_30_F0_META: AspxHcbMeta = AspxHcbMeta {
name: "ASPX_HCB_ENV_LEVEL_30_F0",
codebook_length: 36,
cb_off: 0,
};
pub const ASPX_HCB_ENV_LEVEL_30_DF_META: AspxHcbMeta = AspxHcbMeta {
name: "ASPX_HCB_ENV_LEVEL_30_DF",
codebook_length: 71,
cb_off: 35,
};
pub const ASPX_HCB_ENV_LEVEL_30_DT_META: AspxHcbMeta = AspxHcbMeta {
name: "ASPX_HCB_ENV_LEVEL_30_DT",
codebook_length: 71,
cb_off: 35,
};
pub const ASPX_HCB_ENV_BALANCE_30_F0_META: AspxHcbMeta = AspxHcbMeta {
name: "ASPX_HCB_ENV_BALANCE_30_F0",
codebook_length: 13,
cb_off: 0,
};
pub const ASPX_HCB_ENV_BALANCE_30_DF_META: AspxHcbMeta = AspxHcbMeta {
name: "ASPX_HCB_ENV_BALANCE_30_DF",
codebook_length: 25,
cb_off: 12,
};
pub const ASPX_HCB_ENV_BALANCE_30_DT_META: AspxHcbMeta = AspxHcbMeta {
name: "ASPX_HCB_ENV_BALANCE_30_DT",
codebook_length: 25,
cb_off: 12,
};
pub const ASPX_HCB_NOISE_LEVEL_F0_META: AspxHcbMeta = AspxHcbMeta {
name: "ASPX_HCB_NOISE_LEVEL_F0",
codebook_length: 30,
cb_off: 0,
};
pub const ASPX_HCB_NOISE_LEVEL_DF_META: AspxHcbMeta = AspxHcbMeta {
name: "ASPX_HCB_NOISE_LEVEL_DF",
codebook_length: 59,
cb_off: 29,
};
pub const ASPX_HCB_NOISE_LEVEL_DT_META: AspxHcbMeta = AspxHcbMeta {
name: "ASPX_HCB_NOISE_LEVEL_DT",
codebook_length: 59,
cb_off: 29,
};
pub const ASPX_HCB_NOISE_BALANCE_F0_META: AspxHcbMeta = AspxHcbMeta {
name: "ASPX_HCB_NOISE_BALANCE_F0",
codebook_length: 13,
cb_off: 0,
};
pub const ASPX_HCB_NOISE_BALANCE_DF_META: AspxHcbMeta = AspxHcbMeta {
name: "ASPX_HCB_NOISE_BALANCE_DF",
codebook_length: 25,
cb_off: 12,
};
pub const ASPX_HCB_NOISE_BALANCE_DT_META: AspxHcbMeta = AspxHcbMeta {
name: "ASPX_HCB_NOISE_BALANCE_DT",
codebook_length: 25,
cb_off: 12,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HuffmanCodebookId {
EnvLevel15F0,
EnvLevel15Df,
EnvLevel15Dt,
EnvBalance15F0,
EnvBalance15Df,
EnvBalance15Dt,
EnvLevel30F0,
EnvLevel30Df,
EnvLevel30Dt,
EnvBalance30F0,
EnvBalance30Df,
EnvBalance30Dt,
NoiseLevelF0,
NoiseLevelDf,
NoiseLevelDt,
NoiseBalanceF0,
NoiseBalanceDf,
NoiseBalanceDt,
}
pub static ASPX_HCB_ENV_LEVEL_15_F0: AspxHcb = AspxHcb {
name: "ASPX_HCB_ENV_LEVEL_15_F0",
len: aspx_huffman::ASPX_HCB_ENV_LEVEL_15_F0_LEN,
cw: aspx_huffman::ASPX_HCB_ENV_LEVEL_15_F0_CW,
cb_off: 0,
};
pub static ASPX_HCB_ENV_LEVEL_15_DF: AspxHcb = AspxHcb {
name: "ASPX_HCB_ENV_LEVEL_15_DF",
len: aspx_huffman::ASPX_HCB_ENV_LEVEL_15_DF_LEN,
cw: aspx_huffman::ASPX_HCB_ENV_LEVEL_15_DF_CW,
cb_off: 70,
};
pub static ASPX_HCB_ENV_LEVEL_15_DT: AspxHcb = AspxHcb {
name: "ASPX_HCB_ENV_LEVEL_15_DT",
len: aspx_huffman::ASPX_HCB_ENV_LEVEL_15_DT_LEN,
cw: aspx_huffman::ASPX_HCB_ENV_LEVEL_15_DT_CW,
cb_off: 70,
};
pub static ASPX_HCB_ENV_BALANCE_15_F0: AspxHcb = AspxHcb {
name: "ASPX_HCB_ENV_BALANCE_15_F0",
len: aspx_huffman::ASPX_HCB_ENV_BALANCE_15_F0_LEN,
cw: aspx_huffman::ASPX_HCB_ENV_BALANCE_15_F0_CW,
cb_off: 0,
};
pub static ASPX_HCB_ENV_BALANCE_15_DF: AspxHcb = AspxHcb {
name: "ASPX_HCB_ENV_BALANCE_15_DF",
len: aspx_huffman::ASPX_HCB_ENV_BALANCE_15_DF_LEN,
cw: aspx_huffman::ASPX_HCB_ENV_BALANCE_15_DF_CW,
cb_off: 24,
};
pub static ASPX_HCB_ENV_BALANCE_15_DT: AspxHcb = AspxHcb {
name: "ASPX_HCB_ENV_BALANCE_15_DT",
len: aspx_huffman::ASPX_HCB_ENV_BALANCE_15_DT_LEN,
cw: aspx_huffman::ASPX_HCB_ENV_BALANCE_15_DT_CW,
cb_off: 24,
};
pub static ASPX_HCB_ENV_LEVEL_30_F0: AspxHcb = AspxHcb {
name: "ASPX_HCB_ENV_LEVEL_30_F0",
len: aspx_huffman::ASPX_HCB_ENV_LEVEL_30_F0_LEN,
cw: aspx_huffman::ASPX_HCB_ENV_LEVEL_30_F0_CW,
cb_off: 0,
};
pub static ASPX_HCB_ENV_LEVEL_30_DF: AspxHcb = AspxHcb {
name: "ASPX_HCB_ENV_LEVEL_30_DF",
len: aspx_huffman::ASPX_HCB_ENV_LEVEL_30_DF_LEN,
cw: aspx_huffman::ASPX_HCB_ENV_LEVEL_30_DF_CW,
cb_off: 35,
};
pub static ASPX_HCB_ENV_LEVEL_30_DT: AspxHcb = AspxHcb {
name: "ASPX_HCB_ENV_LEVEL_30_DT",
len: aspx_huffman::ASPX_HCB_ENV_LEVEL_30_DT_LEN,
cw: aspx_huffman::ASPX_HCB_ENV_LEVEL_30_DT_CW,
cb_off: 35,
};
pub static ASPX_HCB_ENV_BALANCE_30_F0: AspxHcb = AspxHcb {
name: "ASPX_HCB_ENV_BALANCE_30_F0",
len: aspx_huffman::ASPX_HCB_ENV_BALANCE_30_F0_LEN,
cw: aspx_huffman::ASPX_HCB_ENV_BALANCE_30_F0_CW,
cb_off: 0,
};
pub static ASPX_HCB_ENV_BALANCE_30_DF: AspxHcb = AspxHcb {
name: "ASPX_HCB_ENV_BALANCE_30_DF",
len: aspx_huffman::ASPX_HCB_ENV_BALANCE_30_DF_LEN,
cw: aspx_huffman::ASPX_HCB_ENV_BALANCE_30_DF_CW,
cb_off: 12,
};
pub static ASPX_HCB_ENV_BALANCE_30_DT: AspxHcb = AspxHcb {
name: "ASPX_HCB_ENV_BALANCE_30_DT",
len: aspx_huffman::ASPX_HCB_ENV_BALANCE_30_DT_LEN,
cw: aspx_huffman::ASPX_HCB_ENV_BALANCE_30_DT_CW,
cb_off: 12,
};
pub static ASPX_HCB_NOISE_LEVEL_F0: AspxHcb = AspxHcb {
name: "ASPX_HCB_NOISE_LEVEL_F0",
len: aspx_huffman::ASPX_HCB_NOISE_LEVEL_F0_LEN,
cw: aspx_huffman::ASPX_HCB_NOISE_LEVEL_F0_CW,
cb_off: 0,
};
pub static ASPX_HCB_NOISE_LEVEL_DF: AspxHcb = AspxHcb {
name: "ASPX_HCB_NOISE_LEVEL_DF",
len: aspx_huffman::ASPX_HCB_NOISE_LEVEL_DF_LEN,
cw: aspx_huffman::ASPX_HCB_NOISE_LEVEL_DF_CW,
cb_off: 29,
};
pub static ASPX_HCB_NOISE_LEVEL_DT: AspxHcb = AspxHcb {
name: "ASPX_HCB_NOISE_LEVEL_DT",
len: aspx_huffman::ASPX_HCB_NOISE_LEVEL_DT_LEN,
cw: aspx_huffman::ASPX_HCB_NOISE_LEVEL_DT_CW,
cb_off: 29,
};
pub static ASPX_HCB_NOISE_BALANCE_F0: AspxHcb = AspxHcb {
name: "ASPX_HCB_NOISE_BALANCE_F0",
len: aspx_huffman::ASPX_HCB_NOISE_BALANCE_F0_LEN,
cw: aspx_huffman::ASPX_HCB_NOISE_BALANCE_F0_CW,
cb_off: 0,
};
pub static ASPX_HCB_NOISE_BALANCE_DF: AspxHcb = AspxHcb {
name: "ASPX_HCB_NOISE_BALANCE_DF",
len: aspx_huffman::ASPX_HCB_NOISE_BALANCE_DF_LEN,
cw: aspx_huffman::ASPX_HCB_NOISE_BALANCE_DF_CW,
cb_off: 12,
};
pub static ASPX_HCB_NOISE_BALANCE_DT: AspxHcb = AspxHcb {
name: "ASPX_HCB_NOISE_BALANCE_DT",
len: aspx_huffman::ASPX_HCB_NOISE_BALANCE_DT_LEN,
cw: aspx_huffman::ASPX_HCB_NOISE_BALANCE_DT_CW,
cb_off: 12,
};
pub fn lookup_aspx_hcb(id: HuffmanCodebookId) -> &'static AspxHcb {
match id {
HuffmanCodebookId::EnvLevel15F0 => &ASPX_HCB_ENV_LEVEL_15_F0,
HuffmanCodebookId::EnvLevel15Df => &ASPX_HCB_ENV_LEVEL_15_DF,
HuffmanCodebookId::EnvLevel15Dt => &ASPX_HCB_ENV_LEVEL_15_DT,
HuffmanCodebookId::EnvBalance15F0 => &ASPX_HCB_ENV_BALANCE_15_F0,
HuffmanCodebookId::EnvBalance15Df => &ASPX_HCB_ENV_BALANCE_15_DF,
HuffmanCodebookId::EnvBalance15Dt => &ASPX_HCB_ENV_BALANCE_15_DT,
HuffmanCodebookId::EnvLevel30F0 => &ASPX_HCB_ENV_LEVEL_30_F0,
HuffmanCodebookId::EnvLevel30Df => &ASPX_HCB_ENV_LEVEL_30_DF,
HuffmanCodebookId::EnvLevel30Dt => &ASPX_HCB_ENV_LEVEL_30_DT,
HuffmanCodebookId::EnvBalance30F0 => &ASPX_HCB_ENV_BALANCE_30_F0,
HuffmanCodebookId::EnvBalance30Df => &ASPX_HCB_ENV_BALANCE_30_DF,
HuffmanCodebookId::EnvBalance30Dt => &ASPX_HCB_ENV_BALANCE_30_DT,
HuffmanCodebookId::NoiseLevelF0 => &ASPX_HCB_NOISE_LEVEL_F0,
HuffmanCodebookId::NoiseLevelDf => &ASPX_HCB_NOISE_LEVEL_DF,
HuffmanCodebookId::NoiseLevelDt => &ASPX_HCB_NOISE_LEVEL_DT,
HuffmanCodebookId::NoiseBalanceF0 => &ASPX_HCB_NOISE_BALANCE_F0,
HuffmanCodebookId::NoiseBalanceDf => &ASPX_HCB_NOISE_BALANCE_DF,
HuffmanCodebookId::NoiseBalanceDt => &ASPX_HCB_NOISE_BALANCE_DT,
}
}
pub static ASPX_HCB_ALL: &[(HuffmanCodebookId, &AspxHcb)] = &[
(HuffmanCodebookId::EnvLevel15F0, &ASPX_HCB_ENV_LEVEL_15_F0),
(HuffmanCodebookId::EnvLevel15Df, &ASPX_HCB_ENV_LEVEL_15_DF),
(HuffmanCodebookId::EnvLevel15Dt, &ASPX_HCB_ENV_LEVEL_15_DT),
(
HuffmanCodebookId::EnvBalance15F0,
&ASPX_HCB_ENV_BALANCE_15_F0,
),
(
HuffmanCodebookId::EnvBalance15Df,
&ASPX_HCB_ENV_BALANCE_15_DF,
),
(
HuffmanCodebookId::EnvBalance15Dt,
&ASPX_HCB_ENV_BALANCE_15_DT,
),
(HuffmanCodebookId::EnvLevel30F0, &ASPX_HCB_ENV_LEVEL_30_F0),
(HuffmanCodebookId::EnvLevel30Df, &ASPX_HCB_ENV_LEVEL_30_DF),
(HuffmanCodebookId::EnvLevel30Dt, &ASPX_HCB_ENV_LEVEL_30_DT),
(
HuffmanCodebookId::EnvBalance30F0,
&ASPX_HCB_ENV_BALANCE_30_F0,
),
(
HuffmanCodebookId::EnvBalance30Df,
&ASPX_HCB_ENV_BALANCE_30_DF,
),
(
HuffmanCodebookId::EnvBalance30Dt,
&ASPX_HCB_ENV_BALANCE_30_DT,
),
(HuffmanCodebookId::NoiseLevelF0, &ASPX_HCB_NOISE_LEVEL_F0),
(HuffmanCodebookId::NoiseLevelDf, &ASPX_HCB_NOISE_LEVEL_DF),
(HuffmanCodebookId::NoiseLevelDt, &ASPX_HCB_NOISE_LEVEL_DT),
(
HuffmanCodebookId::NoiseBalanceF0,
&ASPX_HCB_NOISE_BALANCE_F0,
),
(
HuffmanCodebookId::NoiseBalanceDf,
&ASPX_HCB_NOISE_BALANCE_DF,
),
(
HuffmanCodebookId::NoiseBalanceDt,
&ASPX_HCB_NOISE_BALANCE_DT,
),
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AspxFreqResMode {
Signalled,
Low,
DurationDependent,
High,
}
impl AspxFreqResMode {
pub fn from_u32(v: u32) -> Self {
match v & 0b11 {
0 => Self::Signalled,
1 => Self::Low,
2 => Self::DurationDependent,
_ => Self::High,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AspxMasterFreqScale {
LowRes,
HighRes,
}
impl AspxMasterFreqScale {
pub fn from_bit(v: u32) -> Self {
if v == 0 {
Self::LowRes
} else {
Self::HighRes
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AspxQuantStep {
Fine,
Coarse,
}
impl AspxQuantStep {
pub fn from_bit(v: u32) -> Self {
if v == 0 {
Self::Fine
} else {
Self::Coarse
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AspxConfig {
pub quant_mode_env: AspxQuantStep,
pub start_freq: u8,
pub stop_freq: u8,
pub master_freq_scale: AspxMasterFreqScale,
pub interpolation: bool,
pub preflat: bool,
pub limiter: bool,
pub noise_sbg: u8,
pub num_env_bits_fixfix: u8,
pub freq_res_mode: AspxFreqResMode,
}
impl AspxConfig {
pub const BITS: u32 = 15;
pub fn num_noise_sbgroups(&self) -> u32 {
self.noise_sbg as u32 + 1
}
pub fn fixfix_tmp_num_env_bits(&self) -> u32 {
self.num_env_bits_fixfix as u32 + 1
}
pub fn signals_freq_res(&self) -> bool {
matches!(self.freq_res_mode, AspxFreqResMode::Signalled)
}
}
pub fn parse_aspx_config(br: &mut BitReader<'_>) -> Result<AspxConfig> {
let quant_mode_env = AspxQuantStep::from_bit(br.read_u32(1)?);
let start_freq = br.read_u32(3)? as u8;
let stop_freq = br.read_u32(2)? as u8;
let master_freq_scale = AspxMasterFreqScale::from_bit(br.read_u32(1)?);
let interpolation = br.read_bit()?;
let preflat = br.read_bit()?;
let limiter = br.read_bit()?;
let noise_sbg = br.read_u32(2)? as u8;
let num_env_bits_fixfix = br.read_u32(1)? as u8;
let freq_res_mode = AspxFreqResMode::from_u32(br.read_u32(2)?);
Ok(AspxConfig {
quant_mode_env,
start_freq,
stop_freq,
master_freq_scale,
interpolation,
preflat,
limiter,
noise_sbg,
num_env_bits_fixfix,
freq_res_mode,
})
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CompandingControl {
pub sync_flag: Option<bool>,
pub compand_on: Vec<bool>,
pub compand_avg: Option<bool>,
}
pub fn parse_companding_control(
br: &mut BitReader<'_>,
num_chan: u32,
) -> Result<CompandingControl> {
let sync_flag = if num_chan > 1 {
Some(br.read_bit()?)
} else {
None
};
let nc = match sync_flag {
Some(true) => 1,
_ => num_chan,
};
let mut compand_on = Vec::with_capacity(nc as usize);
let mut b_need_avg = false;
for _ in 0..nc {
let bit = br.read_bit()?;
if !bit {
b_need_avg = true;
}
compand_on.push(bit);
}
let compand_avg = if b_need_avg {
Some(br.read_bit()?)
} else {
None
};
Ok(CompandingControl {
sync_flag,
compand_on,
compand_avg,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompandingMode {
Off,
PerSlot,
Averaged,
SyncPerSlot,
SyncAveraged,
}
impl CompandingMode {
pub fn from_control(cc: &CompandingControl, slot: usize) -> Self {
let sync = matches!(cc.sync_flag, Some(true));
let on = if sync {
cc.compand_on.first().copied().unwrap_or(false)
} else {
cc.compand_on.get(slot).copied().unwrap_or(false)
};
let avg = cc.compand_avg.unwrap_or(false);
match (sync, on, avg) {
(false, true, _) => Self::PerSlot,
(false, false, true) => Self::Averaged,
(false, false, false) => Self::Off,
(true, true, _) => Self::SyncPerSlot,
(true, false, true) => Self::SyncAveraged,
(true, false, false) => Self::Off,
}
}
}
pub fn apply_companding_on_qmf_with_mode(
q: &mut [Vec<(f32, f32)>],
sb0: u32,
sb1: u32,
mode: CompandingMode,
) {
if matches!(mode, CompandingMode::Off) {
return;
}
let sb0_u = sb0 as usize;
let sb1_u = sb1 as usize;
if sb1_u <= sb0_u || q.len() < sb1_u {
return;
}
let mut n_slots = usize::MAX;
for row in q.iter().take(sb1_u).skip(sb0_u) {
if row.len() < n_slots {
n_slots = row.len();
}
}
if n_slots == usize::MAX || n_slots == 0 {
return;
}
let k = (sb1_u - sb0_u) as f32;
if k <= 0.0 {
return;
}
const ALPHA: f32 = COMPANDING_ALPHA;
let g_const = companding_g(); let exp_g = (1.0 - ALPHA) / ALPHA;
let mut levels = Vec::with_capacity(n_slots);
for ts in 0..n_slots {
let mut sum: f32 = 0.0;
for row in q.iter().take(sb1_u).skip(sb0_u) {
let (re, im) = row[ts];
let ar = re.abs();
let ai = im.abs();
let e = ar.max(ai) + 0.5 * ar.min(ai);
sum += e;
}
levels.push(0.9105 * (sum / k));
}
let pow_or_one = |l: f32| -> f32 {
if l > 0.0 {
l.powf(exp_g)
} else {
1.0
}
};
let scales: Vec<f32> = match mode {
CompandingMode::PerSlot | CompandingMode::SyncPerSlot => {
levels.iter().map(|&l| pow_or_one(l) * g_const).collect()
}
CompandingMode::Averaged | CompandingMode::SyncAveraged => {
let l_avg = if levels.is_empty() {
0.0
} else {
levels.iter().sum::<f32>() / (levels.len() as f32)
};
let constant = pow_or_one(l_avg) * g_const;
vec![constant; n_slots]
}
CompandingMode::Off => unreachable!("early-returned above"),
};
for ts in 0..n_slots {
let scale = scales[ts];
for row in q.iter_mut().take(sb1_u).skip(sb0_u) {
let (re, im) = row[ts];
row[ts] = (re * scale, im * scale);
}
}
}
pub fn apply_companding_on_qmf(q: &mut [Vec<(f32, f32)>], sbx: u32, sbz: u32) {
apply_companding_on_qmf_with_mode(q, sbx, sbz, CompandingMode::PerSlot);
}
const COMPANDING_ALPHA: f32 = 0.65;
#[inline]
fn companding_g() -> f32 {
2.0_f32.powf(COMPANDING_ALPHA)
}
pub type QmfMatrix = Vec<Vec<(f32, f32)>>;
pub type SyncCompandingEntry<'a> = (&'a mut QmfMatrix, u32, u32);
pub fn compute_companding_levels(q: &[Vec<(f32, f32)>], sb0: u32, sb1: u32) -> Vec<f32> {
let sb0_u = sb0 as usize;
let sb1_u = sb1 as usize;
if sb1_u <= sb0_u || q.len() < sb1_u {
return Vec::new();
}
let mut n_slots = usize::MAX;
for row in q.iter().take(sb1_u).skip(sb0_u) {
if row.len() < n_slots {
n_slots = row.len();
}
}
if n_slots == usize::MAX || n_slots == 0 {
return Vec::new();
}
let k = (sb1_u - sb0_u) as f32;
if k <= 0.0 {
return Vec::new();
}
let mut levels = Vec::with_capacity(n_slots);
for ts in 0..n_slots {
let mut sum: f32 = 0.0;
for row in q.iter().take(sb1_u).skip(sb0_u) {
let (re, im) = row[ts];
let ar = re.abs();
let ai = im.abs();
sum += ar.max(ai) + 0.5 * ar.min(ai);
}
levels.push(0.9105 * (sum / k));
}
levels
}
pub fn levels_to_scales_per_slot(levels: &[f32]) -> Vec<f32> {
let exp_g = (1.0 - COMPANDING_ALPHA) / COMPANDING_ALPHA;
levels
.iter()
.map(|&l| {
let g = if l > 0.0 { l.powf(exp_g) } else { 1.0 };
g * companding_g()
})
.collect()
}
pub fn levels_to_scale_averaged(levels: &[f32]) -> f32 {
if levels.is_empty() {
return companding_g();
}
let l_avg = levels.iter().sum::<f32>() / (levels.len() as f32);
let exp_g = (1.0 - COMPANDING_ALPHA) / COMPANDING_ALPHA;
let g = if l_avg > 0.0 { l_avg.powf(exp_g) } else { 1.0 };
g * companding_g()
}
pub fn apply_companding_scales_on_qmf(
q: &mut [Vec<(f32, f32)>],
sb0: u32,
sb1: u32,
scales: &[f32],
) {
let sb0_u = sb0 as usize;
let sb1_u = sb1 as usize;
if sb1_u <= sb0_u || q.len() < sb1_u || scales.is_empty() {
return;
}
let n_slots = scales.len();
for row in q.iter_mut().take(sb1_u).skip(sb0_u) {
let take = row.len().min(n_slots);
for ts in 0..take {
let (re, im) = row[ts];
let s = scales[ts];
row[ts] = (re * s, im * s);
}
}
}
pub fn apply_synchronised_companding_across_channels(
channels: &mut [SyncCompandingEntry<'_>],
mode: CompandingMode,
) {
if !matches!(
mode,
CompandingMode::SyncPerSlot | CompandingMode::SyncAveraged
) {
return;
}
if channels.is_empty() {
return;
}
let mut per_ch_levels: Vec<(usize, Vec<f32>)> = Vec::with_capacity(channels.len());
let mut common_n_slots = usize::MAX;
for (idx, (q, sb0, sb1)) in channels.iter().enumerate() {
let levels = compute_companding_levels(q, *sb0, *sb1);
if levels.is_empty() {
continue;
}
if levels.len() < common_n_slots {
common_n_slots = levels.len();
}
per_ch_levels.push((idx, levels));
}
if per_ch_levels.is_empty() || common_n_slots == 0 || common_n_slots == usize::MAX {
return;
}
let m = per_ch_levels.len() as f32;
let exp_g = (1.0 - COMPANDING_ALPHA) / COMPANDING_ALPHA;
let per_ch_g: Vec<Vec<f32>> = per_ch_levels
.iter()
.map(|(_, lv)| {
lv.iter()
.take(common_n_slots)
.map(|&l| if l > 0.0 { l.powf(exp_g) } else { 1.0 })
.collect::<Vec<f32>>()
})
.collect();
let synced_scales: Vec<f32> = match mode {
CompandingMode::SyncPerSlot => {
let mut scales = Vec::with_capacity(common_n_slots);
for ts in 0..common_n_slots {
let mut log_sum = 0.0_f32;
for g_row in per_ch_g.iter() {
let g = g_row[ts].max(1e-30); log_sum += g.ln();
}
let g_sync = (log_sum / m).exp();
scales.push(g_sync * companding_g());
}
scales
}
CompandingMode::SyncAveraged => {
let mut log_sum = 0.0_f32;
for (_, lv) in per_ch_levels.iter() {
let l_avg = lv.iter().sum::<f32>() / (lv.len() as f32);
let g_avg = if l_avg > 0.0 { l_avg.powf(exp_g) } else { 1.0 };
log_sum += g_avg.max(1e-30).ln();
}
let g_sync = (log_sum / m).exp();
let constant = g_sync * companding_g();
vec![constant; common_n_slots]
}
_ => unreachable!("guarded by the early-return above"),
};
for &(idx, _) in per_ch_levels.iter() {
let (q, sb0, sb1) = &mut channels[idx];
apply_companding_scales_on_qmf(q, *sb0, *sb1, &synced_scales);
}
}
#[derive(Debug, Clone)]
pub struct FiveXAspxTrailer {
pub xover: u8,
pub frequency_tables: AspxFrequencyTables,
pub primary: FiveXAspxChannelTrailer,
pub secondary: Option<FiveXAspxChannelTrailer>,
}
#[derive(Debug, Clone)]
pub struct FiveXAspxChannelTrailer {
pub framing: AspxFraming,
pub qmode_env: AspxQuantStep,
pub delta_dir: AspxDeltaDir,
pub data_sig: Vec<AspxHuffEnv>,
pub data_noise: Vec<AspxHuffEnv>,
pub add_harmonic: Option<Vec<bool>>,
pub tna_mode: Option<Vec<u8>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AspxIntClass {
FixFix,
FixVar,
VarFix,
VarVar,
}
impl AspxIntClass {
pub fn read(br: &mut BitReader<'_>) -> Result<Self> {
if !br.read_bit()? {
return Ok(Self::FixFix); }
if !br.read_bit()? {
return Ok(Self::FixVar); }
if !br.read_bit()? {
Ok(Self::VarFix) } else {
Ok(Self::VarVar) }
}
pub fn bits(self) -> u32 {
match self {
Self::FixFix => 1,
Self::FixVar => 2,
Self::VarFix | Self::VarVar => 3,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AspxFraming {
pub int_class: AspxIntClass,
pub num_env: u32,
pub num_noise: u32,
pub freq_res: Vec<bool>,
pub var_bord_left: Option<u8>,
pub var_bord_right: Option<u8>,
pub num_rel_left: u8,
pub num_rel_right: u8,
pub rel_bord_left: Vec<u8>,
pub rel_bord_right: Vec<u8>,
pub tsg_ptr: Option<u8>,
}
pub fn parse_aspx_framing(
br: &mut BitReader<'_>,
cfg: &AspxConfig,
b_iframe: bool,
num_aspx_timeslots_over_8: bool,
) -> Result<AspxFraming> {
let note1_bits: u32 = if num_aspx_timeslots_over_8 { 2 } else { 1 };
let int_class = AspxIntClass::read(br)?;
let mut num_rel_left: u8 = 0;
let mut num_rel_right: u8 = 0;
let mut var_bord_left: Option<u8> = None;
let mut var_bord_right: Option<u8> = None;
let mut rel_bord_left: Vec<u8> = Vec::new();
let mut rel_bord_right: Vec<u8> = Vec::new();
let num_env: u32;
let mut freq_res: Vec<bool> = Vec::new();
match int_class {
AspxIntClass::FixFix => {
let envbits = cfg.fixfix_tmp_num_env_bits();
let tmp_num_env = br.read_u32(envbits)?;
num_env = 1u32 << tmp_num_env;
if cfg.signals_freq_res() {
freq_res.push(br.read_bit()?);
}
}
AspxIntClass::FixVar => {
var_bord_right = Some(br.read_u32(2)? as u8);
num_rel_right = br.read_u32(note1_bits)? as u8;
for _ in 0..num_rel_right {
rel_bord_right.push(br.read_u32(note1_bits)? as u8);
}
num_env = u32::from(num_rel_left) + u32::from(num_rel_right) + 1;
}
AspxIntClass::VarVar => {
if b_iframe {
var_bord_left = Some(br.read_u32(2)? as u8);
}
num_rel_left = br.read_u32(note1_bits)? as u8;
for _ in 0..num_rel_left {
rel_bord_left.push(br.read_u32(note1_bits)? as u8);
}
var_bord_right = Some(br.read_u32(2)? as u8);
num_rel_right = br.read_u32(note1_bits)? as u8;
for _ in 0..num_rel_right {
rel_bord_right.push(br.read_u32(note1_bits)? as u8);
}
num_env = u32::from(num_rel_left) + u32::from(num_rel_right) + 1;
}
AspxIntClass::VarFix => {
if b_iframe {
var_bord_left = Some(br.read_u32(2)? as u8);
}
num_rel_left = br.read_u32(note1_bits)? as u8;
for _ in 0..num_rel_left {
rel_bord_left.push(br.read_u32(note1_bits)? as u8);
}
num_env = u32::from(num_rel_left) + u32::from(num_rel_right) + 1;
}
}
let mut tsg_ptr: Option<u8> = None;
if !matches!(int_class, AspxIntClass::FixFix) {
let ptr_bits = ceil_log2(num_env + 2);
tsg_ptr = Some(br.read_u32(ptr_bits)? as u8);
if cfg.signals_freq_res() {
freq_res.reserve(num_env as usize);
for _ in 0..num_env {
freq_res.push(br.read_bit()?);
}
}
}
let num_noise = if num_env > 1 { 2 } else { 1 };
Ok(AspxFraming {
int_class,
num_env,
num_noise,
freq_res,
var_bord_left,
var_bord_right,
num_rel_left,
num_rel_right,
rel_bord_left,
rel_bord_right,
tsg_ptr,
})
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct AspxDeltaDir {
pub sig_delta_dir: Vec<bool>,
pub noise_delta_dir: Vec<bool>,
}
pub fn parse_aspx_delta_dir(br: &mut BitReader<'_>, framing: &AspxFraming) -> Result<AspxDeltaDir> {
let mut sig = Vec::with_capacity(framing.num_env as usize);
for _ in 0..framing.num_env {
sig.push(br.read_bit()?);
}
let mut noise = Vec::with_capacity(framing.num_noise as usize);
for _ in 0..framing.num_noise {
noise.push(br.read_bit()?);
}
Ok(AspxDeltaDir {
sig_delta_dir: sig,
noise_delta_dir: noise,
})
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct AspxHfgenIwc1Ch {
pub tna_mode: Vec<u8>,
pub ah_present: bool,
pub add_harmonic: Vec<bool>,
pub fic_present: bool,
pub fic_used_in_sfb: Vec<bool>,
pub tic_present: bool,
pub tic_used_in_slot: Vec<bool>,
}
pub fn parse_aspx_hfgen_iwc_1ch(
br: &mut BitReader<'_>,
num_sbg_noise: u32,
num_sbg_sig_highres: u32,
num_aspx_timeslots: u32,
) -> Result<AspxHfgenIwc1Ch> {
let mut tna_mode = Vec::with_capacity(num_sbg_noise as usize);
for _ in 0..num_sbg_noise {
tna_mode.push(br.read_u32(2)? as u8);
}
let ah_present = br.read_bit()?;
let mut add_harmonic = vec![false; num_sbg_sig_highres as usize];
if ah_present {
for ah in add_harmonic.iter_mut() {
*ah = br.read_bit()?;
}
}
let fic_present = br.read_bit()?;
let mut fic_used_in_sfb = vec![false; num_sbg_sig_highres as usize];
if fic_present {
for f in fic_used_in_sfb.iter_mut() {
*f = br.read_bit()?;
}
}
let tic_present = br.read_bit()?;
let mut tic_used_in_slot = vec![false; num_aspx_timeslots as usize];
if tic_present {
for t in tic_used_in_slot.iter_mut() {
*t = br.read_bit()?;
}
}
Ok(AspxHfgenIwc1Ch {
tna_mode,
ah_present,
add_harmonic,
fic_present,
fic_used_in_sfb,
tic_present,
tic_used_in_slot,
})
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct AspxHfgenIwc2Ch {
pub tna_mode: [Vec<u8>; 2],
pub ah_left: bool,
pub ah_right: bool,
pub add_harmonic: [Vec<bool>; 2],
pub fic_present: bool,
pub fic_left: bool,
pub fic_right: bool,
pub fic_used_in_sfb: [Vec<bool>; 2],
pub tic_present: bool,
pub tic_copy: bool,
pub tic_left: bool,
pub tic_right: bool,
pub tic_used_in_slot: [Vec<bool>; 2],
}
pub fn parse_aspx_hfgen_iwc_2ch(
br: &mut BitReader<'_>,
aspx_balance: bool,
num_sbg_noise: u32,
num_sbg_sig_highres: u32,
num_aspx_timeslots: u32,
) -> Result<AspxHfgenIwc2Ch> {
let mut tna0 = Vec::with_capacity(num_sbg_noise as usize);
for _ in 0..num_sbg_noise {
tna0.push(br.read_u32(2)? as u8);
}
let tna1 = if !aspx_balance {
let mut v = Vec::with_capacity(num_sbg_noise as usize);
for _ in 0..num_sbg_noise {
v.push(br.read_u32(2)? as u8);
}
v
} else {
tna0.clone()
};
let ah_left = br.read_bit()?;
let mut ah0 = vec![false; num_sbg_sig_highres as usize];
if ah_left {
for a in ah0.iter_mut() {
*a = br.read_bit()?;
}
}
let ah_right = br.read_bit()?;
let mut ah1 = vec![false; num_sbg_sig_highres as usize];
if ah_right {
for a in ah1.iter_mut() {
*a = br.read_bit()?;
}
}
let mut fic0 = vec![false; num_sbg_sig_highres as usize];
let mut fic1 = vec![false; num_sbg_sig_highres as usize];
let fic_present = br.read_bit()?;
let mut fic_left = false;
let mut fic_right = false;
if fic_present {
fic_left = br.read_bit()?;
if fic_left {
for f in fic0.iter_mut() {
*f = br.read_bit()?;
}
}
fic_right = br.read_bit()?;
if fic_right {
for f in fic1.iter_mut() {
*f = br.read_bit()?;
}
}
}
let mut tic0 = vec![false; num_aspx_timeslots as usize];
let mut tic1 = vec![false; num_aspx_timeslots as usize];
let mut tic_copy = false;
let mut tic_left = false;
let mut tic_right = false;
let tic_present = br.read_bit()?;
if tic_present {
tic_copy = br.read_bit()?;
if !tic_copy {
tic_left = br.read_bit()?;
tic_right = br.read_bit()?;
}
if tic_copy || tic_left {
for t in tic0.iter_mut() {
*t = br.read_bit()?;
}
}
if tic_right {
for t in tic1.iter_mut() {
*t = br.read_bit()?;
}
}
if tic_copy {
tic1 = tic0.clone();
}
}
Ok(AspxHfgenIwc2Ch {
tna_mode: [tna0, tna1],
ah_left,
ah_right,
add_harmonic: [ah0, ah1],
fic_present,
fic_left,
fic_right,
fic_used_in_sfb: [fic0, fic1],
tic_present,
tic_copy,
tic_left,
tic_right,
tic_used_in_slot: [tic0, tic1],
})
}
fn ceil_log2(n: u32) -> u32 {
debug_assert!(n >= 1);
if n <= 1 {
0
} else {
32 - (n - 1).leading_zeros()
}
}
pub fn num_qmf_timeslots(frame_len_base: u32) -> u32 {
frame_len_base / 64
}
pub fn num_ts_in_ats(frame_len_base: u32) -> u32 {
if frame_len_base >= 1536 {
2
} else {
1
}
}
pub fn num_aspx_timeslots(frame_len_base: u32) -> u32 {
num_qmf_timeslots(frame_len_base) / num_ts_in_ats(frame_len_base)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AspxDataType {
Signal,
Noise,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AspxStereoMode {
Level,
Balance,
}
pub fn get_aspx_hcb(
data_type: AspxDataType,
quant_mode: AspxQuantStep,
stereo_mode: AspxStereoMode,
hcb_type: AspxHcbType,
) -> HuffmanCodebookId {
use AspxDataType::*;
use AspxHcbType::*;
use AspxQuantStep::*;
use AspxStereoMode::*;
match (data_type, quant_mode, stereo_mode, hcb_type) {
(Signal, Fine, Level, F0) => HuffmanCodebookId::EnvLevel15F0,
(Signal, Fine, Level, Df) => HuffmanCodebookId::EnvLevel15Df,
(Signal, Fine, Level, Dt) => HuffmanCodebookId::EnvLevel15Dt,
(Signal, Fine, Balance, F0) => HuffmanCodebookId::EnvBalance15F0,
(Signal, Fine, Balance, Df) => HuffmanCodebookId::EnvBalance15Df,
(Signal, Fine, Balance, Dt) => HuffmanCodebookId::EnvBalance15Dt,
(Signal, Coarse, Level, F0) => HuffmanCodebookId::EnvLevel30F0,
(Signal, Coarse, Level, Df) => HuffmanCodebookId::EnvLevel30Df,
(Signal, Coarse, Level, Dt) => HuffmanCodebookId::EnvLevel30Dt,
(Signal, Coarse, Balance, F0) => HuffmanCodebookId::EnvBalance30F0,
(Signal, Coarse, Balance, Df) => HuffmanCodebookId::EnvBalance30Df,
(Signal, Coarse, Balance, Dt) => HuffmanCodebookId::EnvBalance30Dt,
(Noise, _, Level, F0) => HuffmanCodebookId::NoiseLevelF0,
(Noise, _, Level, Df) => HuffmanCodebookId::NoiseLevelDf,
(Noise, _, Level, Dt) => HuffmanCodebookId::NoiseLevelDt,
(Noise, _, Balance, F0) => HuffmanCodebookId::NoiseBalanceF0,
(Noise, _, Balance, Df) => HuffmanCodebookId::NoiseBalanceDf,
(Noise, _, Balance, Dt) => HuffmanCodebookId::NoiseBalanceDt,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AspxHcbType {
F0,
Df,
Dt,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct AspxHuffEnv {
pub values: Vec<i32>,
pub direction_time: bool,
}
pub fn parse_aspx_huff_data(
br: &mut BitReader<'_>,
data_type: AspxDataType,
num_sbg: u32,
quant_mode: AspxQuantStep,
stereo_mode: AspxStereoMode,
direction: bool,
) -> Result<AspxHuffEnv> {
let mut out = Vec::with_capacity(num_sbg as usize);
if !direction {
let hcb_f0 = lookup_aspx_hcb(get_aspx_hcb(
data_type,
quant_mode,
stereo_mode,
AspxHcbType::F0,
));
if num_sbg >= 1 {
out.push(hcb_f0.decode_delta(br)?);
}
if num_sbg >= 2 {
let hcb_df = lookup_aspx_hcb(get_aspx_hcb(
data_type,
quant_mode,
stereo_mode,
AspxHcbType::Df,
));
for _ in 1..num_sbg {
out.push(hcb_df.decode_delta(br)?);
}
}
} else {
let hcb_dt = lookup_aspx_hcb(get_aspx_hcb(
data_type,
quant_mode,
stereo_mode,
AspxHcbType::Dt,
));
for _ in 0..num_sbg {
out.push(hcb_dt.decode_delta(br)?);
}
}
Ok(AspxHuffEnv {
values: out,
direction_time: direction,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AspxSbgCounts {
pub num_sbg_sig_highres: u32,
pub num_sbg_sig_lowres: u32,
pub num_sbg_noise: u32,
}
#[allow(clippy::too_many_arguments)] pub fn parse_aspx_ec_data(
br: &mut BitReader<'_>,
data_type: AspxDataType,
num_env: u32,
freq_res: &[bool],
quant_mode: AspxQuantStep,
stereo_mode: AspxStereoMode,
direction: &[bool],
sbg: AspxSbgCounts,
) -> Result<Vec<AspxHuffEnv>> {
if direction.len() < num_env as usize {
return Err(Error::invalid(
"ac4: aspx_ec_data direction vector shorter than num_env",
));
}
let mut out = Vec::with_capacity(num_env as usize);
for (env, &dir) in direction.iter().take(num_env as usize).enumerate() {
let num_sbg = match data_type {
AspxDataType::Signal => {
let use_highres = freq_res.get(env).copied().unwrap_or(true);
if use_highres {
sbg.num_sbg_sig_highres
} else {
sbg.num_sbg_sig_lowres
}
}
AspxDataType::Noise => sbg.num_sbg_noise,
};
let envdata = parse_aspx_huff_data(br, data_type, num_sbg, quant_mode, stereo_mode, dir)?;
out.push(envdata);
}
Ok(out)
}
pub const ASPX_SBG_TEMPLATE_HIGHRES: [u32; 23] = [
18, 19, 20, 21, 22, 23, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 47, 50, 53, 56, 59, 62,
];
pub const ASPX_SBG_TEMPLATE_LOWRES: [u32; 21] = [
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 24, 26, 28, 30, 32, 35, 38, 42, 46,
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AspxFrequencyTables {
pub sbg_master: Vec<u32>,
pub num_sbg_master: u32,
pub sba: u32,
pub sbz: u32,
pub sbg_sig_highres: Vec<u32>,
pub sbx: u32,
pub num_sb_aspx: u32,
pub sbg_sig_lowres: Vec<u32>,
pub sbg_noise: Vec<u32>,
pub counts: AspxSbgCounts,
}
pub fn derive_master_sbg_table(cfg: &AspxConfig) -> (Vec<u32>, u32, u32, u32) {
let start = cfg.start_freq as u32;
let stop = cfg.stop_freq as u32;
let (template, base_count): (&[u32], u32) = match cfg.master_freq_scale {
AspxMasterFreqScale::HighRes => (&ASPX_SBG_TEMPLATE_HIGHRES, 22),
AspxMasterFreqScale::LowRes => (&ASPX_SBG_TEMPLATE_LOWRES, 20),
};
let num_sbg_master = base_count - 2 * start - 2 * stop;
let mut sbg_master = Vec::with_capacity((num_sbg_master + 1) as usize);
for sbg in 0..=num_sbg_master {
sbg_master.push(template[(2 * start + sbg) as usize]);
}
let sba = sbg_master[0];
let sbz = sbg_master[num_sbg_master as usize];
(sbg_master, num_sbg_master, sba, sbz)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AspxSigSbgTables {
pub sbg_sig_highres: Vec<u32>,
pub sbg_sig_lowres: Vec<u32>,
pub sbx: u32,
pub num_sb_aspx: u32,
pub num_sbg_sig_highres: u32,
pub num_sbg_sig_lowres: u32,
}
pub fn derive_sig_sbg_tables(
sbg_master: &[u32],
num_sbg_master: u32,
xover_offset: u32,
) -> Result<AspxSigSbgTables> {
if xover_offset > num_sbg_master {
return Err(Error::invalid(
"ac4: aspx_xover_subband_offset exceeds num_sbg_master",
));
}
let num_sbg_sig_highres = num_sbg_master - xover_offset;
let mut sbg_sig_highres = Vec::with_capacity((num_sbg_sig_highres + 1) as usize);
for sbg in 0..=num_sbg_sig_highres {
sbg_sig_highres.push(sbg_master[(sbg + xover_offset) as usize]);
}
let sbx = sbg_sig_highres[0];
let num_sb_aspx = sbg_sig_highres[num_sbg_sig_highres as usize].saturating_sub(sbx);
let num_sbg_sig_lowres = num_sbg_sig_highres - num_sbg_sig_highres / 2;
let mut sbg_sig_lowres = Vec::with_capacity((num_sbg_sig_lowres + 1) as usize);
sbg_sig_lowres.push(sbg_sig_highres[0]);
if num_sbg_sig_highres % 2 == 0 {
for sbg in 1..=num_sbg_sig_lowres {
sbg_sig_lowres.push(sbg_sig_highres[(2 * sbg) as usize]);
}
} else {
for sbg in 1..=num_sbg_sig_lowres {
sbg_sig_lowres.push(sbg_sig_highres[(2 * sbg - 1) as usize]);
}
}
Ok(AspxSigSbgTables {
sbg_sig_highres,
sbg_sig_lowres,
sbx,
num_sb_aspx,
num_sbg_sig_highres,
num_sbg_sig_lowres,
})
}
pub fn derive_noise_sbg_table(
aspx_noise_sbg: u32,
sbz: u32,
sbx: u32,
sbg_sig_lowres: &[u32],
num_sbg_sig_lowres: u32,
) -> Result<Vec<u32>> {
if sbx == 0 {
return Err(Error::invalid(
"ac4: sbx must be > 0 for noise sbg derivation",
));
}
if sbz <= sbx {
return Err(Error::invalid(
"ac4: sbz must exceed sbx for noise sbg derivation",
));
}
let ratio = (sbz as f64) / (sbx as f64);
let log2_ratio = ratio.log2();
let raw = (aspx_noise_sbg as f64) * log2_ratio + 0.5;
let mut num_sbg_noise = raw.floor().max(1.0) as u32;
if num_sbg_noise > 5 {
num_sbg_noise = 5;
}
if num_sbg_noise > num_sbg_sig_lowres {
num_sbg_noise = num_sbg_sig_lowres.max(1);
}
let mut idx = vec![0u32; (num_sbg_noise + 1) as usize];
let mut sbg_noise = Vec::with_capacity((num_sbg_noise + 1) as usize);
sbg_noise.push(sbg_sig_lowres[0]);
for sbg in 1..=num_sbg_noise {
idx[sbg as usize] = idx[(sbg - 1) as usize];
let remaining = num_sbg_sig_lowres - idx[(sbg - 1) as usize];
let divisor = num_sbg_noise + 1 - sbg;
idx[sbg as usize] += remaining / divisor;
sbg_noise.push(sbg_sig_lowres[idx[sbg as usize] as usize]);
}
Ok(sbg_noise)
}
pub fn derive_aspx_frequency_tables(
cfg: &AspxConfig,
xover_offset: u32,
) -> Result<AspxFrequencyTables> {
let (sbg_master, num_sbg_master, sba, sbz) = derive_master_sbg_table(cfg);
let sig = derive_sig_sbg_tables(&sbg_master, num_sbg_master, xover_offset)?;
let AspxSigSbgTables {
sbg_sig_highres,
sbg_sig_lowres,
sbx,
num_sb_aspx,
num_sbg_sig_highres,
num_sbg_sig_lowres,
} = sig;
let sbg_noise = derive_noise_sbg_table(
cfg.noise_sbg as u32,
sbz,
sbx,
&sbg_sig_lowres,
num_sbg_sig_lowres,
)?;
let num_sbg_noise = (sbg_noise.len() as u32).saturating_sub(1);
Ok(AspxFrequencyTables {
sbg_master,
num_sbg_master,
sba,
sbz,
sbg_sig_highres,
sbx,
num_sb_aspx,
sbg_sig_lowres,
sbg_noise,
counts: AspxSbgCounts {
num_sbg_sig_highres,
num_sbg_sig_lowres,
num_sbg_noise,
},
})
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct AspxPatchTables {
pub sbg_patches: Vec<u32>,
pub num_sbg_patches: u32,
pub sbg_patch_num_sb: Vec<u32>,
pub sbg_patch_start_sb: Vec<u32>,
}
pub fn derive_patch_tables(
sbg_master: &[u32],
num_sbg_master: u32,
sba: u32,
sbx: u32,
num_sb_aspx: u32,
base_samp_freq_is_48: bool,
master_freq_scale_highres: bool,
) -> AspxPatchTables {
let goal_sb: u32 = if base_samp_freq_is_48 { 43 } else { 46 };
let source_band_low: u32 = if master_freq_scale_highres { 4 } else { 2 };
let mut sbg = if goal_sb < sbx + num_sb_aspx {
let mut s = 0u32;
for (i, &val) in sbg_master.iter().enumerate() {
if val < goal_sb {
s = (i + 1) as u32;
} else {
break;
}
}
s
} else {
num_sbg_master
};
let mut msb = sba;
let mut usb = sbx;
let mut sbg_patch_num_sb: Vec<u32> = Vec::new();
let mut sbg_patch_start_sb: Vec<u32> = Vec::new();
let target = sbx + num_sb_aspx;
for _ in 0..32 {
let mut j = sbg as usize;
if j >= sbg_master.len() {
break;
}
let mut sb = sbg_master[j];
loop {
let odd = (sb as i64 - 2 + sba as i64).rem_euclid(2);
let rhs = sba as i64 - source_band_low as i64 + msb as i64 - odd;
if (sb as i64) <= rhs {
break;
}
if j == 0 {
break;
}
j -= 1;
sb = sbg_master[j];
}
let num_sb = sb.saturating_sub(usb);
let odd_final = ((sb as i64 - 2 + sba as i64).rem_euclid(2)) as u32;
let patch_start = sba.saturating_sub(odd_final).saturating_sub(num_sb);
if num_sb > 0 {
sbg_patch_num_sb.push(num_sb);
sbg_patch_start_sb.push(patch_start);
usb = sb;
msb = sb;
} else {
msb = sbx;
}
if (sbg as usize) < sbg_master.len() && sbg_master[sbg as usize].saturating_sub(sb) < 3 {
sbg = num_sbg_master;
}
if sb == target {
break;
}
}
let mut num_sbg_patches = sbg_patch_num_sb.len() as u32;
if num_sbg_patches > 1 && *sbg_patch_num_sb.last().unwrap_or(&0) < 3 {
sbg_patch_num_sb.pop();
sbg_patch_start_sb.pop();
num_sbg_patches -= 1;
}
let mut sbg_patches = Vec::with_capacity((num_sbg_patches + 1) as usize);
sbg_patches.push(sbx);
for i in 0..num_sbg_patches {
let next = sbg_patches[i as usize] + sbg_patch_num_sb[i as usize];
sbg_patches.push(next);
}
AspxPatchTables {
sbg_patches,
num_sbg_patches,
sbg_patch_num_sb,
sbg_patch_start_sb,
}
}
pub fn hf_tile_copy(
q_low: &[Vec<(f32, f32)>],
patches: &AspxPatchTables,
sbx: u32,
num_qmf_subbands: u32,
) -> Vec<Vec<(f32, f32)>> {
let n_ts = q_low.iter().map(|row| row.len()).max().unwrap_or(0);
let mut q_high: Vec<Vec<(f32, f32)>> = (0..num_qmf_subbands)
.map(|_| vec![(0.0f32, 0.0f32); n_ts])
.collect();
let mut sum_sb_patches: u32 = 0;
for i in 0..patches.num_sbg_patches as usize {
let n = patches.sbg_patch_num_sb[i];
let start = patches.sbg_patch_start_sb[i];
for sb_off in 0..n {
let sb_high = sbx + sum_sb_patches + sb_off;
let sb_src = start + sb_off;
if sb_high >= num_qmf_subbands || (sb_src as usize) >= q_low.len() {
continue;
}
let src = &q_low[sb_src as usize];
let dst = &mut q_high[sb_high as usize];
let copy_len = n_ts.min(src.len()).min(dst.len());
dst[..copy_len].copy_from_slice(&src[..copy_len]);
}
sum_sb_patches += n;
}
q_high
}
pub fn apply_flat_envelope_gain(q: &mut [Vec<(f32, f32)>], sbx: u32, sbz: u32, gain: f32) {
for sb in sbx..sbz {
if (sb as usize) >= q.len() {
break;
}
for sample in q[sb as usize].iter_mut() {
sample.0 *= gain;
sample.1 *= gain;
}
}
}
pub fn tab_border_fixfix(num_aspx_timeslots: u32, num_atsg: u32) -> Option<Vec<u32>> {
match (num_aspx_timeslots, num_atsg) {
(6, 1) => Some(vec![0, 6]),
(6, 2) => Some(vec![0, 3, 6]),
(6, 4) => Some(vec![0, 2, 3, 4, 6]),
(8, 1) => Some(vec![0, 8]),
(8, 2) => Some(vec![0, 4, 8]),
(8, 4) => Some(vec![0, 2, 4, 6, 8]),
(12, 1) => Some(vec![0, 12]),
(12, 2) => Some(vec![0, 6, 12]),
(12, 4) => Some(vec![0, 3, 6, 9, 12]),
(15, 1) => Some(vec![0, 15]),
(15, 2) => Some(vec![0, 8, 15]),
(15, 4) => Some(vec![0, 4, 8, 12, 15]),
(16, 1) => Some(vec![0, 16]),
(16, 2) => Some(vec![0, 8, 16]),
(16, 4) => Some(vec![0, 4, 8, 12, 16]),
_ => None,
}
}
pub fn derive_fixfix_atsg(
num_aspx_timeslots: u32,
num_env: u32,
num_noise: u32,
) -> Option<(Vec<u32>, Vec<u32>)> {
let sig = tab_border_fixfix(num_aspx_timeslots, num_env)?;
let noise = tab_border_fixfix(num_aspx_timeslots, num_noise)?;
Some((sig, noise))
}
pub fn derive_fixvar_atsg(num_aspx_timeslots: u32, framing: &AspxFraming) -> Option<Vec<u32>> {
let var_bord_right = framing.var_bord_right? as u32;
let num_env = framing.num_env as usize;
let num_rel = framing.num_rel_right as usize;
if num_env != num_rel + 1 {
return None;
}
let t = num_aspx_timeslots;
if var_bord_right > t {
return None;
}
let mut borders = Vec::with_capacity(num_env + 1);
borders.push(t); let b_right = t - var_bord_right;
borders.push(b_right); let mut anchor = b_right;
for &rel in framing.rel_bord_right.iter().rev() {
let rel = rel as u32;
if rel > anchor {
return None;
}
anchor -= rel;
borders.push(anchor);
}
borders.reverse(); for w in borders.windows(2) {
if w[0] >= w[1] {
return None;
}
}
if borders.len() != num_env + 1 {
return None;
}
Some(borders)
}
pub fn derive_varfix_atsg(num_aspx_timeslots: u32, framing: &AspxFraming) -> Option<Vec<u32>> {
let var_bord_left = framing.var_bord_left? as u32;
let num_env = framing.num_env as usize;
let num_rel = framing.num_rel_left as usize;
if num_env != num_rel + 1 {
return None;
}
let t = num_aspx_timeslots;
if var_bord_left >= t {
return None;
}
let mut borders = Vec::with_capacity(num_env + 1);
borders.push(var_bord_left); let mut anchor = var_bord_left;
for &rel in framing.rel_bord_left.iter() {
let rel = rel as u32;
anchor += rel;
if anchor >= t {
return None;
}
borders.push(anchor);
}
borders.push(t); for w in borders.windows(2) {
if w[0] >= w[1] {
return None;
}
}
if borders.len() != num_env + 1 {
return None;
}
Some(borders)
}
pub fn derive_varvar_atsg(num_aspx_timeslots: u32, framing: &AspxFraming) -> Option<Vec<u32>> {
let var_bord_left = framing.var_bord_left? as u32;
let var_bord_right = framing.var_bord_right? as u32;
let num_env = framing.num_env as usize;
let num_rel_left = framing.num_rel_left as usize;
let num_rel_right = framing.num_rel_right as usize;
if num_env != num_rel_left + num_rel_right + 1 {
return None;
}
let t = num_aspx_timeslots;
if var_bord_left >= t || var_bord_right > t {
return None;
}
let b_right_anchor = t - var_bord_right; let mut borders: Vec<u32> = Vec::with_capacity(num_env + 1);
borders.push(var_bord_left);
let mut anchor = var_bord_left;
for &rel in framing.rel_bord_left.iter() {
anchor += rel as u32;
if anchor >= t {
return None;
}
borders.push(anchor);
}
let mut right_internal: Vec<u32> = Vec::with_capacity(num_rel_right);
let mut r_anchor = b_right_anchor;
for &rel in framing.rel_bord_right.iter().rev() {
let rel = rel as u32;
if rel > r_anchor {
return None;
}
r_anchor -= rel;
right_internal.push(r_anchor);
}
right_internal.reverse(); for b in right_internal {
borders.push(b);
}
borders.push(t);
for w in borders.windows(2) {
if w[0] >= w[1] {
return None;
}
}
if borders.len() != num_env + 1 {
return None;
}
Some(borders)
}
pub fn derive_atsg_borders(
num_aspx_timeslots: u32,
framing: &AspxFraming,
) -> Option<(Vec<u32>, Vec<u32>)> {
match framing.int_class {
AspxIntClass::FixFix => {
derive_fixfix_atsg(num_aspx_timeslots, framing.num_env, framing.num_noise)
}
AspxIntClass::FixVar => {
let sig = derive_fixvar_atsg(num_aspx_timeslots, framing)?;
let noise = if framing.num_noise == 1 {
vec![0, num_aspx_timeslots]
} else {
let mid = sig
.get(sig.len() / 2)
.copied()
.unwrap_or(num_aspx_timeslots / 2);
vec![0, mid, num_aspx_timeslots]
};
Some((sig, noise))
}
AspxIntClass::VarFix => {
let sig = derive_varfix_atsg(num_aspx_timeslots, framing)?;
let noise = if framing.num_noise == 1 {
vec![0, num_aspx_timeslots]
} else {
let mid = sig
.get(sig.len() / 2)
.copied()
.unwrap_or(num_aspx_timeslots / 2);
vec![0, mid, num_aspx_timeslots]
};
Some((sig, noise))
}
AspxIntClass::VarVar => {
let sig = derive_varvar_atsg(num_aspx_timeslots, framing)?;
let noise = if framing.num_noise == 1 {
vec![0, num_aspx_timeslots]
} else {
let mid = sig
.get(sig.len() / 2)
.copied()
.unwrap_or(num_aspx_timeslots / 2);
vec![0, mid, num_aspx_timeslots]
};
Some((sig, noise))
}
}
}
pub fn delta_decode_sig(
deltas: &[AspxHuffEnv],
num_sbg: u32,
qscf_prev_last: &[i32],
delta: i32,
) -> Vec<Vec<i32>> {
let num_env = deltas.len();
let mut qscf: Vec<Vec<i32>> = vec![vec![0_i32; num_env]; num_sbg as usize];
for (atsg, env) in deltas.iter().enumerate() {
if env.direction_time {
#[allow(clippy::needless_range_loop)]
for sbg in 0..(num_sbg as usize) {
let prev = if atsg == 0 {
qscf_prev_last.get(sbg).copied().unwrap_or(0)
} else {
qscf[sbg][atsg - 1]
};
let d = env.values.get(sbg).copied().unwrap_or(0);
qscf[sbg][atsg] = prev + delta * d;
}
} else {
let mut acc: i32 = 0;
#[allow(clippy::needless_range_loop)]
for sbg in 0..(num_sbg as usize) {
let d = env.values.get(sbg).copied().unwrap_or(0);
acc += delta * d;
qscf[sbg][atsg] = acc;
}
}
}
qscf
}
pub fn delta_decode_noise(
deltas: &[AspxHuffEnv],
num_sbg: u32,
qscf_prev_last: &[i32],
delta: i32,
) -> Vec<Vec<i32>> {
let num_env = deltas.len();
let mut qscf: Vec<Vec<i32>> = vec![vec![0_i32; num_env]; num_sbg as usize];
for (atsg, env) in deltas.iter().enumerate() {
if env.direction_time {
#[allow(clippy::needless_range_loop)]
for sbg in 0..(num_sbg as usize) {
let prev = if atsg == 0 {
qscf_prev_last.get(sbg).copied().unwrap_or(0)
} else {
qscf[sbg][atsg - 1]
};
let d = env.values.get(sbg).copied().unwrap_or(0);
qscf[sbg][atsg] = prev + delta * d;
}
} else {
let mut acc: i32 = 0;
#[allow(clippy::needless_range_loop)]
for sbg in 0..(num_sbg as usize) {
let d = env.values.get(sbg).copied().unwrap_or(0);
acc += delta * d;
qscf[sbg][atsg] = acc;
}
}
}
qscf
}
pub fn dequantize_sig_scf(
qscf: &[Vec<i32>],
qmode_env: AspxQuantStep,
delta_dir: &[bool],
num_qmf_subbands: u32,
) -> Vec<Vec<f32>> {
let a: f32 = match qmode_env {
AspxQuantStep::Fine => 2.0,
AspxQuantStep::Coarse => 1.0,
};
let n_subbands = num_qmf_subbands as f32;
let num_sbg = qscf.len();
if num_sbg == 0 {
return Vec::new();
}
let num_env = qscf[0].len();
let mut scf: Vec<Vec<f32>> = vec![vec![0.0_f32; num_env]; num_sbg];
for atsg in 0..num_env {
for sbg in 0..num_sbg {
let q = qscf[sbg][atsg] as f32;
scf[sbg][atsg] = n_subbands * 2_f32.powf(q / a);
}
if num_sbg >= 2
&& !delta_dir.get(atsg).copied().unwrap_or(false)
&& qscf[0][atsg] == 0
&& scf[1][atsg] < 0.0
{
scf[0][atsg] = scf[1][atsg];
}
}
scf
}
pub fn dequantize_noise_scf(qscf: &[Vec<i32>]) -> Vec<Vec<f32>> {
const NOISE_FLOOR_OFFSET: i32 = 6;
let num_sbg = qscf.len();
if num_sbg == 0 {
return Vec::new();
}
let num_env = qscf[0].len();
let mut scf: Vec<Vec<f32>> = vec![vec![0.0_f32; num_env]; num_sbg];
for atsg in 0..num_env {
for sbg in 0..num_sbg {
let q = qscf[sbg][atsg];
scf[sbg][atsg] = 2_f32.powi(NOISE_FLOOR_OFFSET - q);
}
}
scf
}
pub fn estimate_envelope_energy(
q_high: &[Vec<(f32, f32)>],
sbg_sig: &[u32],
atsg_sig: &[u32],
num_ts_in_ats: u32,
num_sb_aspx: u32,
sbx: u32,
aspx_interpolation: bool,
) -> Vec<Vec<f32>> {
let num_atsg_sig = atsg_sig.len().saturating_sub(1);
let mut est: Vec<Vec<f32>> = vec![vec![0.0_f32; num_atsg_sig]; num_sb_aspx as usize];
if num_atsg_sig == 0 || sbg_sig.len() < 2 {
return est;
}
#[allow(clippy::needless_range_loop)] for atsg in 0..num_atsg_sig {
let tsa = atsg_sig[atsg] * num_ts_in_ats;
let tsz = atsg_sig[atsg + 1] * num_ts_in_ats;
let ts_span = tsz.saturating_sub(tsa) as f32;
if ts_span <= 0.0 {
continue;
}
let mut sbg = 0_usize;
#[allow(clippy::needless_range_loop)] for sb in 0..(num_sb_aspx as usize) {
while sbg + 1 < sbg_sig.len().saturating_sub(1) && (sb as u32 + sbx) >= sbg_sig[sbg + 1]
{
sbg += 1;
}
let mut est_sig: f64 = 0.0;
for ts in tsa..tsz {
let ts = ts as usize;
if aspx_interpolation {
let sb_abs = sb + sbx as usize;
if sb_abs < q_high.len() && ts < q_high[sb_abs].len() {
let (re, im) = q_high[sb_abs][ts];
est_sig += (re as f64) * (re as f64) + (im as f64) * (im as f64);
}
} else {
let j_lo = sbg_sig[sbg] as usize;
let j_hi = sbg_sig[sbg + 1] as usize;
for j in j_lo..j_hi {
if j < q_high.len() && ts < q_high[j].len() {
let (re, im) = q_high[j][ts];
est_sig += (re as f64) * (re as f64) + (im as f64) * (im as f64);
}
}
}
}
if aspx_interpolation {
est_sig /= ts_span as f64;
} else {
let band_span = (sbg_sig[sbg + 1] - sbg_sig[sbg]) as f64;
if band_span > 0.0 {
est_sig /= band_span;
}
est_sig /= ts_span as f64;
}
est[sb][atsg] = est_sig as f32;
}
}
est
}
#[derive(Debug, Clone, Default)]
pub struct ScfByQmfSubband {
pub scf_sig_sb: Vec<Vec<f32>>,
pub scf_noise_sb: Vec<Vec<f32>>,
}
#[allow(clippy::too_many_arguments)]
pub fn map_scf_to_qmf_subbands(
scf_sig_sbg: &[Vec<f32>],
scf_noise_sbg: &[Vec<f32>],
sbg_sig: &[u32],
sbg_noise: &[u32],
atsg_sig: &[u32],
atsg_noise: &[u32],
num_sb_aspx: u32,
sbx: u32,
) -> ScfByQmfSubband {
let num_atsg_sig = atsg_sig.len().saturating_sub(1);
let mut scf_sig_sb: Vec<Vec<f32>> = vec![vec![0.0_f32; num_atsg_sig]; num_sb_aspx as usize];
let mut scf_noise_sb: Vec<Vec<f32>> = vec![vec![0.0_f32; num_atsg_sig]; num_sb_aspx as usize];
let num_sbg_sig = sbg_sig.len().saturating_sub(1);
let num_sbg_noise = sbg_noise.len().saturating_sub(1);
let mut atsg_noise_idx: usize = 0;
#[allow(clippy::needless_range_loop)] for atsg in 0..num_atsg_sig {
for sbg in 0..num_sbg_sig {
let lo = sbg_sig[sbg].saturating_sub(sbx) as usize;
let hi = sbg_sig[sbg + 1].saturating_sub(sbx) as usize;
let val = scf_sig_sbg
.get(sbg)
.and_then(|row| row.get(atsg))
.copied()
.unwrap_or(0.0);
#[allow(clippy::needless_range_loop)]
for sb in lo..hi.min(num_sb_aspx as usize) {
scf_sig_sb[sb][atsg] = val;
}
}
if atsg_noise_idx + 1 < atsg_noise.len().saturating_sub(1)
&& atsg_sig.get(atsg).copied().unwrap_or(0)
== atsg_noise
.get(atsg_noise_idx + 1)
.copied()
.unwrap_or(u32::MAX)
{
atsg_noise_idx += 1;
}
for sbg in 0..num_sbg_noise {
let lo = sbg_noise[sbg].saturating_sub(sbx) as usize;
let hi = sbg_noise[sbg + 1].saturating_sub(sbx) as usize;
let val = scf_noise_sbg
.get(sbg)
.and_then(|row| row.get(atsg_noise_idx))
.copied()
.unwrap_or(0.0);
#[allow(clippy::needless_range_loop)]
for sb in lo..hi.min(num_sb_aspx as usize) {
scf_noise_sb[sb][atsg] = val;
}
}
}
ScfByQmfSubband {
scf_sig_sb,
scf_noise_sb,
}
}
pub fn derive_sine_idx_sb(
sbg_sig_highres: &[u32],
add_harmonic: &[bool],
num_atsg_sig: u32,
sbx: u32,
num_sb_aspx: u32,
aspx_tsg_ptr: u32,
prev: Option<(u32, u32, &[Vec<u8>])>,
) -> Vec<Vec<u8>> {
let num_sbg = sbg_sig_highres.len().saturating_sub(1);
let num_atsg = num_atsg_sig as usize;
if num_sbg == 0 || num_atsg == 0 {
return vec![vec![0_u8; num_atsg]; num_sb_aspx as usize];
}
let p_sine_at_end_is_0 = match prev {
Some((prev_tsg, prev_num_atsg, _)) => prev_tsg == prev_num_atsg,
None => false, };
let mut sine_idx_sb: Vec<Vec<u8>> = vec![vec![0_u8; num_atsg]; num_sb_aspx as usize];
#[allow(clippy::needless_range_loop)] for atsg in 0..num_atsg {
for sbg in 0..num_sbg {
let sba = sbg_sig_highres[sbg].saturating_sub(sbx) as usize;
let sbz_local = sbg_sig_highres[sbg + 1].saturating_sub(sbx) as usize;
if sba >= num_sb_aspx as usize {
continue;
}
let hi = sbz_local.min(num_sb_aspx as usize);
let sb_mid = (sba + sbz_local) / 2;
#[allow(clippy::needless_range_loop)]
for sb in sba..hi {
let ah = add_harmonic.get(sbg).copied().unwrap_or(false);
let prev_idx_at_last_env = prev.and_then(|(_, prev_num, prev_mat)| {
if prev_num == 0 {
return None;
}
prev_mat
.get(sb)
.and_then(|row| row.get((prev_num - 1) as usize).copied())
});
let prev_was_set = matches!(prev_idx_at_last_env, Some(v) if v != 0);
let condition = sb == sb_mid
&& ((atsg as u32 >= aspx_tsg_ptr) || p_sine_at_end_is_0 || prev_was_set);
sine_idx_sb[sb][atsg] = if condition && ah { 1 } else { 0 };
}
}
}
sine_idx_sb
}
pub fn derive_sine_area_sb(
sine_idx_sb: &[Vec<u8>],
sbg_sig_per_env: &[Vec<u32>],
sbx: u32,
num_sb_aspx: u32,
) -> Vec<Vec<u8>> {
let num_atsg = sine_idx_sb.first().map(|r| r.len()).unwrap_or(0);
let mut sine_area_sb: Vec<Vec<u8>> = vec![vec![0_u8; num_atsg]; num_sb_aspx as usize];
for (atsg, sbg_sig) in sbg_sig_per_env.iter().enumerate() {
if atsg >= num_atsg {
break;
}
let num_sbg = sbg_sig.len().saturating_sub(1);
for sbg in 0..num_sbg {
let sba = sbg_sig[sbg].saturating_sub(sbx) as usize;
let sbz_local = sbg_sig[sbg + 1].saturating_sub(sbx) as usize;
if sba >= num_sb_aspx as usize {
continue;
}
let hi = sbz_local.min(num_sb_aspx as usize);
let mut b_sine_present = 0_u8;
for sb in sba..hi {
if sine_idx_sb
.get(sb)
.and_then(|row| row.get(atsg))
.copied()
.unwrap_or(0)
== 1
{
b_sine_present = 1;
break;
}
}
#[allow(clippy::needless_range_loop)]
for sb in sba..hi {
sine_area_sb[sb][atsg] = b_sine_present;
}
}
}
sine_area_sb
}
pub fn derive_sine_noise_levels(
scf_sig_sb: &[Vec<f32>],
scf_noise_sb: &[Vec<f32>],
sine_idx_sb: &[Vec<u8>],
) -> (Vec<Vec<f32>>, Vec<Vec<f32>>) {
let num_sb = scf_sig_sb.len();
if num_sb == 0 {
return (Vec::new(), Vec::new());
}
let num_atsg = scf_sig_sb[0].len();
let mut sine_lev_sb: Vec<Vec<f32>> = vec![vec![0.0_f32; num_atsg]; num_sb];
let mut noise_lev_sb: Vec<Vec<f32>> = vec![vec![0.0_f32; num_atsg]; num_sb];
for sb in 0..num_sb {
for atsg in 0..num_atsg {
let sig = scf_sig_sb[sb][atsg];
let noise = scf_noise_sb
.get(sb)
.and_then(|row| row.get(atsg))
.copied()
.unwrap_or(0.0);
let sidx = sine_idx_sb
.get(sb)
.and_then(|row| row.get(atsg))
.copied()
.unwrap_or(0) as f32;
let denom = 1.0 + noise;
let sig_noise_fact = if denom > 0.0 { sig / denom } else { 0.0 };
sine_lev_sb[sb][atsg] = (sig_noise_fact * sidx).max(0.0).sqrt();
noise_lev_sb[sb][atsg] = (sig_noise_fact * noise).max(0.0).sqrt();
}
}
(sine_lev_sb, noise_lev_sb)
}
pub fn compute_sig_gains(
est_sig_sb: &[Vec<f32>],
scf_sig_sb: &[Vec<f32>],
scf_noise_sb: &[Vec<f32>],
) -> Vec<Vec<f32>> {
const EPSILON: f32 = 1.0;
let num_sb = est_sig_sb.len();
if num_sb == 0 {
return Vec::new();
}
let num_atsg = est_sig_sb[0].len();
let mut out: Vec<Vec<f32>> = vec![vec![0.0_f32; num_atsg]; num_sb];
for sb in 0..num_sb {
for atsg in 0..num_atsg {
let est = est_sig_sb[sb][atsg];
let sig = scf_sig_sb
.get(sb)
.and_then(|row| row.get(atsg))
.copied()
.unwrap_or(0.0);
let noise = scf_noise_sb
.get(sb)
.and_then(|row| row.get(atsg))
.copied()
.unwrap_or(0.0);
let denom = (EPSILON + est) * (1.0 + noise);
let ratio = if denom > 0.0 { sig / denom } else { 0.0 };
out[sb][atsg] = ratio.max(0.0).sqrt();
}
}
out
}
pub fn apply_envelope_gains(
q: &mut [Vec<(f32, f32)>],
sig_gain_sb: &[Vec<f32>],
atsg_sig: &[u32],
num_ts_in_ats: u32,
sbx: u32,
num_sb_aspx: u32,
) {
let num_atsg = atsg_sig.len().saturating_sub(1);
if num_atsg == 0 {
return;
}
for atsg in 0..num_atsg {
let tsa = (atsg_sig[atsg] * num_ts_in_ats) as usize;
let tsz = (atsg_sig[atsg + 1] * num_ts_in_ats) as usize;
for sb in 0..(num_sb_aspx as usize) {
let sb_abs = sb + sbx as usize;
if sb_abs >= q.len() {
break;
}
let g = sig_gain_sb
.get(sb)
.and_then(|row| row.get(atsg))
.copied()
.unwrap_or(0.0);
let row = &mut q[sb_abs];
let ts_hi = tsz.min(row.len());
for slot in row[tsa..ts_hi].iter_mut() {
slot.0 *= g;
slot.1 *= g;
}
}
}
}
#[derive(Debug, Clone)]
pub struct AspxEnvelopeAdjuster {
pub atsg_sig: Vec<u32>,
pub num_ts_in_ats: u32,
pub sbx: u32,
pub num_sb_aspx: u32,
pub sig_gain_sb: Vec<Vec<f32>>,
pub scf_sig_sb: Vec<Vec<f32>>,
pub scf_noise_sb: Vec<Vec<f32>>,
pub est_sig_sb: Vec<Vec<f32>>,
}
impl AspxEnvelopeAdjuster {
#[allow(clippy::too_many_arguments)]
pub fn from_deltas(
q_high: &[Vec<(f32, f32)>],
tables: &AspxFrequencyTables,
sig_deltas: &[AspxHuffEnv],
noise_deltas: &[AspxHuffEnv],
qmode_env: AspxQuantStep,
delta_dir_sig: &[bool],
atsg_sig: &[u32],
atsg_noise: &[u32],
num_ts_in_ats: u32,
aspx_interpolation: bool,
) -> Self {
let sbg_sig = &tables.sbg_sig_highres;
let sbg_noise = &tables.sbg_noise;
let num_sbg_sig = (sbg_sig.len() as u32).saturating_sub(1);
let num_sbg_noise = (sbg_noise.len() as u32).saturating_sub(1);
let qscf_sig = delta_decode_sig(sig_deltas, num_sbg_sig, &[], 1);
let qscf_noise = delta_decode_noise(noise_deltas, num_sbg_noise, &[], 1);
let scf_sig_sbg = dequantize_sig_scf(&qscf_sig, qmode_env, delta_dir_sig, 64);
let scf_noise_sbg = dequantize_noise_scf(&qscf_noise);
let est_sig_sb = estimate_envelope_energy(
q_high,
sbg_sig,
atsg_sig,
num_ts_in_ats,
tables.num_sb_aspx,
tables.sbx,
aspx_interpolation,
);
let mapped = map_scf_to_qmf_subbands(
&scf_sig_sbg,
&scf_noise_sbg,
sbg_sig,
sbg_noise,
atsg_sig,
atsg_noise,
tables.num_sb_aspx,
tables.sbx,
);
let sig_gain_sb = compute_sig_gains(&est_sig_sb, &mapped.scf_sig_sb, &mapped.scf_noise_sb);
Self {
atsg_sig: atsg_sig.to_vec(),
num_ts_in_ats,
sbx: tables.sbx,
num_sb_aspx: tables.num_sb_aspx,
sig_gain_sb,
scf_sig_sb: mapped.scf_sig_sb,
scf_noise_sb: mapped.scf_noise_sb,
est_sig_sb,
}
}
pub fn apply(&self, q: &mut [Vec<(f32, f32)>]) {
apply_envelope_gains(
q,
&self.sig_gain_sb,
&self.atsg_sig,
self.num_ts_in_ats,
self.sbx,
self.num_sb_aspx,
);
}
}
#[derive(Debug, Clone, Default)]
pub struct AspxChannelExtState {
pub noise: crate::aspx_noise::NoiseGenState,
pub tone: crate::aspx_tone::ToneGenState,
pub sine_idx_sb_prev: Option<Vec<Vec<u8>>>,
pub tsg_ptr_prev: u32,
pub num_atsg_sig_prev: u32,
pub tns: crate::aspx_tns::AspxTnsState,
pub q_low_prev: Vec<Vec<(f32, f32)>>,
}
impl AspxChannelExtState {
pub fn new() -> Self {
Self::default()
}
pub fn reset(&mut self) {
self.noise.reset();
self.tone.reset();
self.sine_idx_sb_prev = None;
self.tsg_ptr_prev = 0;
self.num_atsg_sig_prev = 0;
self.tns.reset();
self.q_low_prev.clear();
}
}
#[allow(clippy::too_many_arguments)]
pub fn inject_noise_and_tone(
q: &mut [Vec<(f32, f32)>],
adjuster: &AspxEnvelopeAdjuster,
tables: &AspxFrequencyTables,
atsg_noise: &[u32],
add_harmonic: &[bool],
aspx_tsg_ptr: u32,
state: &mut AspxChannelExtState,
) {
const NUM_QMF_SUBBANDS: u32 = crate::qmf::NUM_QMF_SUBBANDS as u32;
let num_atsg_sig = adjuster.atsg_sig.len().saturating_sub(1) as u32;
if num_atsg_sig == 0 {
return;
}
let prev_ref: Option<(u32, u32, &[Vec<u8>])> = state
.sine_idx_sb_prev
.as_ref()
.map(|m| (state.tsg_ptr_prev, state.num_atsg_sig_prev, m.as_slice()));
let sine_idx_sb = derive_sine_idx_sb(
&tables.sbg_sig_highres,
add_harmonic,
num_atsg_sig,
tables.sbx,
tables.num_sb_aspx,
aspx_tsg_ptr,
prev_ref,
);
let (sine_lev_sb, noise_lev_sb) =
derive_sine_noise_levels(&adjuster.scf_sig_sb, &adjuster.scf_noise_sb, &sine_idx_sb);
let qmf_noise = crate::aspx_noise::generate_qmf_noise(
&noise_lev_sb,
&adjuster.atsg_sig,
atsg_noise,
adjuster.num_ts_in_ats,
NUM_QMF_SUBBANDS,
tables.sbx,
tables.num_sb_aspx,
&mut state.noise,
);
let qmf_sine = crate::aspx_tone::generate_qmf_sine(
&sine_lev_sb,
&adjuster.atsg_sig,
adjuster.num_ts_in_ats,
NUM_QMF_SUBBANDS,
tables.sbx,
tables.num_sb_aspx,
&mut state.tone,
);
crate::aspx_tone::hf_assemble(
q,
&qmf_noise,
&qmf_sine,
&adjuster.atsg_sig,
adjuster.num_ts_in_ats,
tables.sbx,
tables.sbz,
);
state.sine_idx_sb_prev = Some(sine_idx_sb);
state.tsg_ptr_prev = aspx_tsg_ptr;
state.num_atsg_sig_prev = num_atsg_sig;
}
#[allow(clippy::too_many_arguments)]
pub fn inject_noise_and_tone_with_limiter(
q: &mut [Vec<(f32, f32)>],
adjuster: &AspxEnvelopeAdjuster,
tables: &AspxFrequencyTables,
patches: &AspxPatchTables,
atsg_noise: &[u32],
add_harmonic: &[bool],
aspx_tsg_ptr: u32,
state: &mut AspxChannelExtState,
) {
const NUM_QMF_SUBBANDS: u32 = crate::qmf::NUM_QMF_SUBBANDS as u32;
let num_atsg_sig = adjuster.atsg_sig.len().saturating_sub(1) as u32;
if num_atsg_sig == 0 {
return;
}
let prev_ref: Option<(u32, u32, &[Vec<u8>])> = state
.sine_idx_sb_prev
.as_ref()
.map(|m| (state.tsg_ptr_prev, state.num_atsg_sig_prev, m.as_slice()));
let sine_idx_sb = derive_sine_idx_sb(
&tables.sbg_sig_highres,
add_harmonic,
num_atsg_sig,
tables.sbx,
tables.num_sb_aspx,
aspx_tsg_ptr,
prev_ref,
);
let (sine_lev_sb, noise_lev_sb) =
derive_sine_noise_levels(&adjuster.scf_sig_sb, &adjuster.scf_noise_sb, &sine_idx_sb);
let sbg_lim = crate::aspx_limiter::derive_sbg_lim(&tables.sbg_sig_lowres, &patches.sbg_patches);
let p_sine_at_end = match state.sine_idx_sb_prev.as_ref() {
Some(_) if state.tsg_ptr_prev == state.num_atsg_sig_prev => Some(state.num_atsg_sig_prev),
_ => None,
};
let limited = crate::aspx_limiter::run(
&adjuster.est_sig_sb,
&adjuster.scf_sig_sb,
&adjuster.sig_gain_sb,
&sine_lev_sb,
&noise_lev_sb,
&sbg_lim,
tables.sbx,
tables.num_sb_aspx,
aspx_tsg_ptr,
p_sine_at_end,
);
apply_envelope_gains(
q,
&limited.sig_gain_sb_adj,
&adjuster.atsg_sig,
adjuster.num_ts_in_ats,
tables.sbx,
tables.num_sb_aspx,
);
let qmf_noise = crate::aspx_noise::generate_qmf_noise(
&limited.noise_lev_sb_adj,
&adjuster.atsg_sig,
atsg_noise,
adjuster.num_ts_in_ats,
NUM_QMF_SUBBANDS,
tables.sbx,
tables.num_sb_aspx,
&mut state.noise,
);
let qmf_sine = crate::aspx_tone::generate_qmf_sine(
&limited.sine_lev_sb_adj,
&adjuster.atsg_sig,
adjuster.num_ts_in_ats,
NUM_QMF_SUBBANDS,
tables.sbx,
tables.num_sb_aspx,
&mut state.tone,
);
crate::aspx_tone::hf_assemble(
q,
&qmf_noise,
&qmf_sine,
&adjuster.atsg_sig,
adjuster.num_ts_in_ats,
tables.sbx,
tables.sbz,
);
state.sine_idx_sb_prev = Some(sine_idx_sb);
state.tsg_ptr_prev = aspx_tsg_ptr;
state.num_atsg_sig_prev = num_atsg_sig;
}
#[cfg(test)]
mod tests {
use super::*;
use oxideav_core::bits::BitWriter;
#[allow(clippy::too_many_arguments)]
fn build_aspx_config_bits(
qmode: u32,
start: u32,
stop: u32,
scale: u32,
interp: bool,
preflat: bool,
limiter: bool,
noise_sbg: u32,
num_env_bits_fixfix: u32,
freq_res_mode: u32,
) -> Vec<u8> {
let mut bw = BitWriter::new();
bw.write_u32(qmode, 1);
bw.write_u32(start, 3);
bw.write_u32(stop, 2);
bw.write_u32(scale, 1);
bw.write_bit(interp);
bw.write_bit(preflat);
bw.write_bit(limiter);
bw.write_u32(noise_sbg, 2);
bw.write_u32(num_env_bits_fixfix, 1);
bw.write_u32(freq_res_mode, 2);
bw.align_to_byte();
bw.finish()
}
#[test]
fn aspx_config_bit_order_matches_table_50() {
let bytes = build_aspx_config_bits(1, 5, 2, 1, true, false, true, 3, 1, 2);
let mut br = BitReader::new(&bytes);
let cfg = parse_aspx_config(&mut br).unwrap();
assert_eq!(cfg.quant_mode_env, AspxQuantStep::Coarse);
assert_eq!(cfg.start_freq, 5);
assert_eq!(cfg.stop_freq, 2);
assert_eq!(cfg.master_freq_scale, AspxMasterFreqScale::HighRes);
assert!(cfg.interpolation);
assert!(!cfg.preflat);
assert!(cfg.limiter);
assert_eq!(cfg.noise_sbg, 3);
assert_eq!(cfg.num_env_bits_fixfix, 1);
assert_eq!(cfg.freq_res_mode, AspxFreqResMode::DurationDependent);
}
#[test]
fn aspx_config_exactly_15_bits() {
let mut bw = BitWriter::new();
bw.write_u32(0, 1);
bw.write_u32(0, 3);
bw.write_u32(0, 2);
bw.write_u32(0, 1);
bw.write_bit(false);
bw.write_bit(false);
bw.write_bit(false);
bw.write_u32(0, 2);
bw.write_u32(0, 1);
bw.write_u32(0, 2);
bw.write_bit(true);
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let _ = parse_aspx_config(&mut br).unwrap();
assert_eq!(br.bit_position(), 15);
assert!(br.read_bit().unwrap());
}
#[test]
fn aspx_config_helpers() {
let cfg = AspxConfig {
quant_mode_env: AspxQuantStep::Fine,
start_freq: 0,
stop_freq: 0,
master_freq_scale: AspxMasterFreqScale::LowRes,
interpolation: false,
preflat: false,
limiter: false,
noise_sbg: 0,
num_env_bits_fixfix: 0,
freq_res_mode: AspxFreqResMode::Signalled,
};
assert_eq!(cfg.num_noise_sbgroups(), 1);
assert_eq!(cfg.fixfix_tmp_num_env_bits(), 1);
assert!(cfg.signals_freq_res());
let cfg2 = AspxConfig {
noise_sbg: 3,
num_env_bits_fixfix: 1,
freq_res_mode: AspxFreqResMode::High,
..cfg
};
assert_eq!(cfg2.num_noise_sbgroups(), 4);
assert_eq!(cfg2.fixfix_tmp_num_env_bits(), 2);
assert!(!cfg2.signals_freq_res());
}
#[test]
fn companding_control_mono() {
let mut bw = BitWriter::new();
bw.write_bit(true); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let cc = parse_companding_control(&mut br, 1).unwrap();
assert!(cc.sync_flag.is_none());
assert_eq!(cc.compand_on, vec![true]);
assert!(cc.compand_avg.is_none());
}
#[test]
fn companding_control_stereo_sync_needs_avg() {
let mut bw = BitWriter::new();
bw.write_bit(true); bw.write_bit(false); bw.write_bit(true); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let cc = parse_companding_control(&mut br, 2).unwrap();
assert_eq!(cc.sync_flag, Some(true));
assert_eq!(cc.compand_on, vec![false]);
assert_eq!(cc.compand_avg, Some(true));
}
#[test]
fn companding_control_stereo_no_sync() {
let mut bw = BitWriter::new();
bw.write_bit(false); bw.write_bit(true); bw.write_bit(true); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let cc = parse_companding_control(&mut br, 2).unwrap();
assert_eq!(cc.sync_flag, Some(false));
assert_eq!(cc.compand_on, vec![true, true]);
assert!(cc.compand_avg.is_none());
}
#[test]
fn companding_control_5ch_one_off() {
let mut bw = BitWriter::new();
bw.write_bit(false); bw.write_bit(true);
bw.write_bit(true);
bw.write_bit(false); bw.write_bit(true);
bw.write_bit(true);
bw.write_bit(false); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let cc = parse_companding_control(&mut br, 5).unwrap();
assert_eq!(cc.sync_flag, Some(false));
assert_eq!(cc.compand_on, vec![true, true, false, true, true]);
assert_eq!(cc.compand_avg, Some(false));
}
#[test]
fn companding_mode_from_control_resolves_all_branches() {
let cc = CompandingControl {
sync_flag: None,
compand_on: vec![true],
compand_avg: None,
};
assert_eq!(
CompandingMode::from_control(&cc, 0),
CompandingMode::PerSlot
);
let cc = CompandingControl {
sync_flag: Some(false),
compand_on: vec![true, true, false, true, true],
compand_avg: Some(false),
};
assert_eq!(
CompandingMode::from_control(&cc, 0),
CompandingMode::PerSlot
);
assert_eq!(
CompandingMode::from_control(&cc, 1),
CompandingMode::PerSlot
);
assert_eq!(CompandingMode::from_control(&cc, 2), CompandingMode::Off);
let cc_avg = CompandingControl {
sync_flag: Some(false),
compand_on: vec![true, false, true, true, true],
compand_avg: Some(true),
};
assert_eq!(
CompandingMode::from_control(&cc_avg, 0),
CompandingMode::PerSlot
);
assert_eq!(
CompandingMode::from_control(&cc_avg, 1),
CompandingMode::Averaged
);
let cc_sync_on = CompandingControl {
sync_flag: Some(true),
compand_on: vec![true],
compand_avg: None,
};
for s in 0..5 {
assert_eq!(
CompandingMode::from_control(&cc_sync_on, s),
CompandingMode::SyncPerSlot
);
}
let cc_sync_off_avg = CompandingControl {
sync_flag: Some(true),
compand_on: vec![false],
compand_avg: Some(true),
};
for s in 0..5 {
assert_eq!(
CompandingMode::from_control(&cc_sync_off_avg, s),
CompandingMode::SyncAveraged
);
}
let cc_sync_off_no_avg = CompandingControl {
sync_flag: Some(true),
compand_on: vec![false],
compand_avg: Some(false),
};
assert_eq!(
CompandingMode::from_control(&cc_sync_off_no_avg, 0),
CompandingMode::Off
);
let cc_short = CompandingControl {
sync_flag: Some(false),
compand_on: vec![true],
compand_avg: None,
};
assert_eq!(
CompandingMode::from_control(&cc_short, 99),
CompandingMode::Off
);
}
#[test]
fn apply_companding_on_qmf_with_mode_off_is_strict_noop() {
let mut q = vec![vec![(0.7_f32, 0.3_f32); 16]; 64];
let q_orig = q.clone();
apply_companding_on_qmf_with_mode(&mut q, 4, 32, CompandingMode::Off);
assert_eq!(q, q_orig);
}
#[test]
fn apply_companding_averaged_uses_constant_gain_across_slots() {
let n_slots = 8usize;
let n_sb = 16usize;
let sb0 = 4u32;
let sb1 = 12u32;
let make_q = || -> Vec<Vec<(f32, f32)>> {
let mut q = vec![vec![(0.0_f32, 0.0_f32); n_slots]; n_sb];
for ts in 0..n_slots {
let v = (ts + 1) as f32;
for row in q.iter_mut().take(sb1 as usize).skip(sb0 as usize) {
row[ts] = (v, 0.0);
}
}
q
};
let mut q_per = make_q();
apply_companding_on_qmf_with_mode(&mut q_per, sb0, sb1, CompandingMode::PerSlot);
let m_per: Vec<f32> = (0..n_slots).map(|ts| q_per[sb0 as usize][ts].0).collect();
let r1 = m_per[0] / 1.0;
let r7 = m_per[7] / 8.0;
assert!(
(r1 - r7).abs() > 1e-3,
"PerSlot must produce slot-varying scales (r1={r1}, r7={r7})"
);
let mut q_avg = make_q();
apply_companding_on_qmf_with_mode(&mut q_avg, sb0, sb1, CompandingMode::Averaged);
let scales: Vec<f32> = (0..n_slots)
.map(|ts| q_avg[sb0 as usize][ts].0 / ((ts + 1) as f32))
.collect();
let s0 = scales[0];
for &s in &scales[1..] {
assert!(
(s - s0).abs() < 1e-4,
"Averaged must apply a constant scale (s0={s0}, s={s})"
);
}
let mut q_sync_avg = make_q();
apply_companding_on_qmf_with_mode(&mut q_sync_avg, sb0, sb1, CompandingMode::SyncAveraged);
for sb in (sb0 as usize)..(sb1 as usize) {
for ts in 0..n_slots {
let a = q_avg[sb][ts];
let b = q_sync_avg[sb][ts];
assert!(
(a.0 - b.0).abs() < 1e-6 && (a.1 - b.1).abs() < 1e-6,
"Averaged and SyncAveraged must coincide for M=1 channel"
);
}
}
}
#[test]
fn apply_companding_with_mode_sb0_override_shifts_band() {
let n_slots = 8usize;
let n_sb = 16usize;
let make_q = || -> Vec<Vec<(f32, f32)>> {
let mut q = vec![vec![(0.0_f32, 0.0_f32); n_slots]; n_sb];
for row in q.iter_mut().take(6).skip(4) {
for cell in row.iter_mut().take(n_slots) {
*cell = (1.0, 0.0);
}
}
for row in q.iter_mut().take(12).skip(8) {
for cell in row.iter_mut().take(n_slots) {
*cell = (3.0, 0.0);
}
}
q
};
let mut q_a = make_q();
apply_companding_on_qmf_with_mode(&mut q_a, 4, 12, CompandingMode::PerSlot);
let mut q_b = make_q();
apply_companding_on_qmf_with_mode(&mut q_b, 8, 12, CompandingMode::PerSlot);
for sb in 4..6 {
for ts in 0..n_slots {
let a = q_a[sb][ts].0;
let b = q_b[sb][ts].0;
assert!(
(b - 1.0).abs() < 1e-6,
"sb0=8 must leave sb={sb} untouched (b={b})"
);
assert!((a - 1.0).abs() > 1e-3, "sb0=4 must scale sb={sb} (a={a})");
}
}
}
fn test_config(num_env_bits_fixfix: u8, freq_res_mode: AspxFreqResMode) -> AspxConfig {
AspxConfig {
quant_mode_env: AspxQuantStep::Fine,
start_freq: 0,
stop_freq: 0,
master_freq_scale: AspxMasterFreqScale::LowRes,
interpolation: false,
preflat: false,
limiter: false,
noise_sbg: 0,
num_env_bits_fixfix,
freq_res_mode,
}
}
#[test]
fn aspx_int_class_prefix_code_matches_table_126() {
let bytes = {
let mut bw = BitWriter::new();
bw.write_bit(false);
bw.align_to_byte();
bw.finish()
};
let mut br = BitReader::new(&bytes);
assert_eq!(AspxIntClass::read(&mut br).unwrap(), AspxIntClass::FixFix);
assert_eq!(br.bit_position(), 1);
let bytes = {
let mut bw = BitWriter::new();
bw.write_bit(true);
bw.write_bit(false);
bw.align_to_byte();
bw.finish()
};
let mut br = BitReader::new(&bytes);
assert_eq!(AspxIntClass::read(&mut br).unwrap(), AspxIntClass::FixVar);
assert_eq!(br.bit_position(), 2);
let bytes = {
let mut bw = BitWriter::new();
bw.write_bit(true);
bw.write_bit(true);
bw.write_bit(false);
bw.align_to_byte();
bw.finish()
};
let mut br = BitReader::new(&bytes);
assert_eq!(AspxIntClass::read(&mut br).unwrap(), AspxIntClass::VarFix);
assert_eq!(br.bit_position(), 3);
let bytes = {
let mut bw = BitWriter::new();
bw.write_bit(true);
bw.write_bit(true);
bw.write_bit(true);
bw.align_to_byte();
bw.finish()
};
let mut br = BitReader::new(&bytes);
assert_eq!(AspxIntClass::read(&mut br).unwrap(), AspxIntClass::VarVar);
assert_eq!(br.bit_position(), 3);
}
#[test]
fn aspx_framing_fixfix_signalled_freq_res() {
let mut bw = BitWriter::new();
bw.write_bit(false); bw.write_u32(2, 2); bw.write_bit(true); bw.write_bit(true); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let cfg = test_config(1, AspxFreqResMode::Signalled);
let f = parse_aspx_framing(&mut br, &cfg, true, false).unwrap();
assert_eq!(f.int_class, AspxIntClass::FixFix);
assert_eq!(f.num_env, 4);
assert_eq!(f.num_noise, 2);
assert_eq!(f.freq_res, vec![true]);
assert_eq!(f.var_bord_left, None);
assert_eq!(f.var_bord_right, None);
assert_eq!(f.num_rel_left, 0);
assert_eq!(f.num_rel_right, 0);
assert!(f.rel_bord_left.is_empty());
assert!(f.rel_bord_right.is_empty());
assert_eq!(f.tsg_ptr, None);
assert_eq!(br.bit_position(), 4);
assert!(br.read_bit().unwrap());
}
#[test]
fn aspx_framing_fixfix_narrow_envbits_no_freq_res() {
let mut bw = BitWriter::new();
bw.write_bit(false); bw.write_bit(true); bw.write_bit(true); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let cfg = test_config(0, AspxFreqResMode::High);
let f = parse_aspx_framing(&mut br, &cfg, false, true).unwrap();
assert_eq!(f.int_class, AspxIntClass::FixFix);
assert_eq!(f.num_env, 2);
assert_eq!(f.num_noise, 2);
assert!(f.freq_res.is_empty());
assert_eq!(br.bit_position(), 2);
assert!(br.read_bit().unwrap());
}
#[test]
fn aspx_framing_fixvar_ts_over_8_with_two_rel() {
let mut bw = BitWriter::new();
bw.write_bit(true);
bw.write_bit(false); bw.write_u32(0b11, 2); bw.write_u32(2, 2); bw.write_u32(0b01, 2); bw.write_u32(0b10, 2); bw.write_u32(0b101, 3); bw.write_bit(true); bw.write_bit(false); bw.write_bit(true); bw.write_bit(true); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let cfg = test_config(0, AspxFreqResMode::Signalled);
let f = parse_aspx_framing(&mut br, &cfg, true, true).unwrap();
assert_eq!(f.int_class, AspxIntClass::FixVar);
assert_eq!(f.num_env, 3);
assert_eq!(f.num_noise, 2);
assert_eq!(f.var_bord_right, Some(0b11));
assert_eq!(f.num_rel_right, 2);
assert_eq!(f.rel_bord_right, vec![0b01, 0b10]);
assert_eq!(f.num_rel_left, 0);
assert!(f.rel_bord_left.is_empty());
assert_eq!(f.var_bord_left, None);
assert_eq!(f.tsg_ptr, Some(0b101));
assert_eq!(f.freq_res, vec![true, false, true]);
assert_eq!(br.bit_position(), 16);
assert!(br.read_bit().unwrap());
}
#[test]
fn aspx_framing_varfix_iframe_ts_short() {
let mut bw = BitWriter::new();
bw.write_bit(true);
bw.write_bit(true);
bw.write_bit(false); bw.write_u32(0b10, 2); bw.write_u32(1, 1); bw.write_u32(1, 1); bw.write_u32(0b11, 2); bw.write_bit(true); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let cfg = test_config(0, AspxFreqResMode::Low);
let f = parse_aspx_framing(&mut br, &cfg, true, false).unwrap();
assert_eq!(f.int_class, AspxIntClass::VarFix);
assert_eq!(f.num_env, 2);
assert_eq!(f.num_noise, 2);
assert_eq!(f.var_bord_left, Some(0b10));
assert_eq!(f.num_rel_left, 1);
assert_eq!(f.rel_bord_left, vec![1]);
assert_eq!(f.num_rel_right, 0);
assert!(f.rel_bord_right.is_empty());
assert_eq!(f.var_bord_right, None);
assert_eq!(f.tsg_ptr, Some(0b11));
assert!(f.freq_res.is_empty());
assert_eq!(br.bit_position(), 9);
assert!(br.read_bit().unwrap());
}
#[test]
fn aspx_framing_varfix_non_iframe_omits_var_bord_left() {
let mut bw = BitWriter::new();
bw.write_bit(true);
bw.write_bit(true);
bw.write_bit(false); bw.write_u32(0, 1); bw.write_u32(0b10, 2); bw.write_bit(true); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let cfg = test_config(0, AspxFreqResMode::Low);
let f = parse_aspx_framing(&mut br, &cfg, false, false).unwrap();
assert_eq!(f.int_class, AspxIntClass::VarFix);
assert_eq!(f.num_env, 1);
assert_eq!(f.num_noise, 1);
assert_eq!(f.var_bord_left, None);
assert_eq!(f.num_rel_left, 0);
assert_eq!(f.tsg_ptr, Some(0b10));
assert_eq!(br.bit_position(), 6);
assert!(br.read_bit().unwrap());
}
#[test]
fn aspx_framing_varvar_iframe_symmetric() {
let mut bw = BitWriter::new();
bw.write_bit(true);
bw.write_bit(true);
bw.write_bit(true); bw.write_u32(0b01, 2); bw.write_u32(1, 1); bw.write_u32(0, 1); bw.write_u32(0b11, 2); bw.write_u32(1, 1); bw.write_u32(1, 1); bw.write_u32(0b100, 3); bw.write_bit(false); bw.write_bit(true); bw.write_bit(false); bw.write_bit(true); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let cfg = test_config(0, AspxFreqResMode::Signalled);
let f = parse_aspx_framing(&mut br, &cfg, true, false).unwrap();
assert_eq!(f.int_class, AspxIntClass::VarVar);
assert_eq!(f.num_env, 3);
assert_eq!(f.num_noise, 2);
assert_eq!(f.var_bord_left, Some(0b01));
assert_eq!(f.num_rel_left, 1);
assert_eq!(f.rel_bord_left, vec![0]);
assert_eq!(f.var_bord_right, Some(0b11));
assert_eq!(f.num_rel_right, 1);
assert_eq!(f.rel_bord_right, vec![1]);
assert_eq!(f.tsg_ptr, Some(0b100));
assert_eq!(f.freq_res, vec![false, true, false]);
assert_eq!(br.bit_position(), 17);
assert!(br.read_bit().unwrap());
}
#[test]
fn ceil_log2_matches_spec_ptr_bits_formula() {
assert_eq!(ceil_log2(1 + 2), 2);
assert_eq!(ceil_log2(2 + 2), 2);
assert_eq!(ceil_log2(3 + 2), 3);
assert_eq!(ceil_log2(4 + 2), 3);
assert_eq!(ceil_log2(5 + 2), 3);
}
#[test]
fn num_qmf_timeslots_matches_table_189() {
assert_eq!(num_qmf_timeslots(2048), 32);
assert_eq!(num_qmf_timeslots(1920), 30);
assert_eq!(num_qmf_timeslots(1536), 24);
assert_eq!(num_qmf_timeslots(1024), 16);
assert_eq!(num_qmf_timeslots(960), 15);
assert_eq!(num_qmf_timeslots(768), 12);
assert_eq!(num_qmf_timeslots(512), 8);
assert_eq!(num_qmf_timeslots(384), 6);
}
#[test]
fn num_ts_in_ats_matches_table_192() {
assert_eq!(num_ts_in_ats(2048), 2);
assert_eq!(num_ts_in_ats(1920), 2);
assert_eq!(num_ts_in_ats(1536), 2);
assert_eq!(num_ts_in_ats(1024), 1);
assert_eq!(num_ts_in_ats(960), 1);
assert_eq!(num_ts_in_ats(768), 1);
assert_eq!(num_ts_in_ats(512), 1);
assert_eq!(num_ts_in_ats(384), 1);
}
#[test]
fn aspx_delta_dir_reads_num_env_plus_num_noise_bits() {
let framing = AspxFraming {
int_class: AspxIntClass::VarVar,
num_env: 3,
num_noise: 2,
freq_res: Vec::new(),
var_bord_left: None,
var_bord_right: None,
num_rel_left: 0,
num_rel_right: 0,
rel_bord_left: Vec::new(),
rel_bord_right: Vec::new(),
tsg_ptr: None,
};
let mut bw = BitWriter::new();
bw.write_bit(true);
bw.write_bit(false);
bw.write_bit(true);
bw.write_bit(false);
bw.write_bit(true);
bw.write_bit(true); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let dd = parse_aspx_delta_dir(&mut br, &framing).unwrap();
assert_eq!(dd.sig_delta_dir, vec![true, false, true]);
assert_eq!(dd.noise_delta_dir, vec![false, true]);
assert_eq!(br.bit_position(), 5);
assert!(br.read_bit().unwrap());
}
#[test]
fn aspx_delta_dir_single_env_single_noise() {
let framing = AspxFraming {
int_class: AspxIntClass::FixFix,
num_env: 1,
num_noise: 1,
freq_res: Vec::new(),
var_bord_left: None,
var_bord_right: None,
num_rel_left: 0,
num_rel_right: 0,
rel_bord_left: Vec::new(),
rel_bord_right: Vec::new(),
tsg_ptr: None,
};
let mut bw = BitWriter::new();
bw.write_bit(false); bw.write_bit(true); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let dd = parse_aspx_delta_dir(&mut br, &framing).unwrap();
assert_eq!(dd.sig_delta_dir, vec![false]);
assert_eq!(dd.noise_delta_dir, vec![true]);
}
#[test]
fn aspx_hfgen_iwc_1ch_all_gates_off() {
let mut bw = BitWriter::new();
bw.write_u32(0b01, 2); bw.write_u32(0b10, 2); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(true); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let h = parse_aspx_hfgen_iwc_1ch(&mut br, 2, 3, 16).unwrap();
assert_eq!(h.tna_mode, vec![1, 2]);
assert!(!h.ah_present);
assert!(!h.fic_present);
assert!(!h.tic_present);
assert_eq!(h.add_harmonic, vec![false; 3]);
assert_eq!(h.fic_used_in_sfb, vec![false; 3]);
assert_eq!(h.tic_used_in_slot, vec![false; 16]);
assert_eq!(br.bit_position(), 7);
assert!(br.read_bit().unwrap());
}
#[test]
fn aspx_hfgen_iwc_1ch_all_gates_on() {
let mut bw = BitWriter::new();
bw.write_u32(0b11, 2);
bw.write_bit(true);
bw.write_bit(true);
bw.write_bit(false);
bw.write_bit(true);
bw.write_bit(false);
bw.write_bit(true);
bw.write_bit(true);
bw.write_bit(true);
bw.write_bit(false);
bw.write_bit(true);
bw.write_bit(true); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let h = parse_aspx_hfgen_iwc_1ch(&mut br, 1, 2, 3).unwrap();
assert_eq!(h.tna_mode, vec![3]);
assert!(h.ah_present);
assert_eq!(h.add_harmonic, vec![true, false]);
assert!(h.fic_present);
assert_eq!(h.fic_used_in_sfb, vec![false, true]);
assert!(h.tic_present);
assert_eq!(h.tic_used_in_slot, vec![true, false, true]);
assert_eq!(br.bit_position(), 12);
assert!(br.read_bit().unwrap());
}
#[test]
fn aspx_hfgen_iwc_2ch_balance_on_mirrors_tna_and_all_gates_off() {
let mut bw = BitWriter::new();
bw.write_u32(0b01, 2); bw.write_u32(0b10, 2); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(true); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let h = parse_aspx_hfgen_iwc_2ch(&mut br, true, 2, 3, 16).unwrap();
assert_eq!(h.tna_mode[0], vec![1, 2]);
assert_eq!(h.tna_mode[1], vec![1, 2]);
assert!(!h.ah_left && !h.ah_right);
assert!(!h.fic_present && !h.tic_present);
assert_eq!(h.add_harmonic[0], vec![false; 3]);
assert_eq!(h.add_harmonic[1], vec![false; 3]);
assert_eq!(h.fic_used_in_sfb[0], vec![false; 3]);
assert_eq!(h.fic_used_in_sfb[1], vec![false; 3]);
assert_eq!(h.tic_used_in_slot[0], vec![false; 16]);
assert_eq!(h.tic_used_in_slot[1], vec![false; 16]);
assert_eq!(br.bit_position(), 8);
assert!(br.read_bit().unwrap());
}
#[test]
fn aspx_hfgen_iwc_2ch_balance_off_reads_both_tnas() {
let mut bw = BitWriter::new();
bw.write_u32(0b11, 2); bw.write_u32(0b10, 2); bw.write_bit(true); bw.write_bit(true);
bw.write_bit(false); bw.write_bit(false); bw.write_bit(true); bw.write_bit(false); bw.write_bit(true); bw.write_bit(true);
bw.write_bit(true); bw.write_bit(true); bw.write_bit(false); bw.write_bit(true); bw.write_bit(false); bw.write_bit(false);
bw.write_bit(true); bw.write_bit(true); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let h = parse_aspx_hfgen_iwc_2ch(&mut br, false, 1, 2, 2).unwrap();
assert_eq!(h.tna_mode[0], vec![3]);
assert_eq!(h.tna_mode[1], vec![2]);
assert!(h.ah_left);
assert!(!h.ah_right);
assert_eq!(h.add_harmonic[0], vec![true, false]);
assert_eq!(h.add_harmonic[1], vec![false, false]);
assert!(h.fic_present);
assert!(!h.fic_left);
assert!(h.fic_right);
assert_eq!(h.fic_used_in_sfb[0], vec![false, false]);
assert_eq!(h.fic_used_in_sfb[1], vec![true, true]);
assert!(h.tic_present);
assert!(!h.tic_copy);
assert!(h.tic_left);
assert!(!h.tic_right);
assert_eq!(h.tic_used_in_slot[0], vec![false, true]);
assert_eq!(h.tic_used_in_slot[1], vec![false, false]);
assert_eq!(br.bit_position(), 19);
assert!(br.read_bit().unwrap());
}
#[test]
fn aspx_hfgen_iwc_2ch_tic_copy_mirrors_left_into_right() {
let mut bw = BitWriter::new();
bw.write_u32(0b00, 2); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(true); bw.write_bit(true); bw.write_bit(true);
bw.write_bit(false);
bw.write_bit(true); bw.write_bit(true); bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let h = parse_aspx_hfgen_iwc_2ch(&mut br, true, 1, 2, 3).unwrap();
assert_eq!(h.tna_mode[0], vec![0]);
assert_eq!(h.tna_mode[1], vec![0]); assert!(h.tic_present);
assert!(h.tic_copy);
assert_eq!(h.tic_used_in_slot[0], vec![true, false, true]);
assert_eq!(h.tic_used_in_slot[1], vec![true, false, true]);
assert_eq!(br.bit_position(), 10);
assert!(br.read_bit().unwrap());
}
#[test]
fn aspx_hcb_decode_delta_walks_len_cw_arrays() {
static LENS: &[u8] = &[1, 2, 3, 3];
static CWS: &[u32] = &[0b0, 0b10, 0b110, 0b111];
let hcb = AspxHcb {
name: "SYNTHETIC",
len: LENS,
cw: CWS,
cb_off: 2,
};
let mut bw = BitWriter::new();
bw.write_u32(0b111, 3);
bw.write_u32(0b10, 2);
bw.write_u32(0b0, 1);
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
assert_eq!(hcb.decode_delta(&mut br).unwrap(), 1);
assert_eq!(hcb.decode_delta(&mut br).unwrap(), -1);
assert_eq!(hcb.decode_delta(&mut br).unwrap(), -2);
}
#[test]
fn aspx_hcb_annex_a2_metadata_matches_pdf_headers() {
assert_eq!(ASPX_HCB_ENV_LEVEL_15_F0_META.codebook_length, 71);
assert_eq!(ASPX_HCB_ENV_LEVEL_15_F0_META.cb_off, 0);
assert_eq!(ASPX_HCB_ENV_LEVEL_15_DF_META.codebook_length, 141);
assert_eq!(ASPX_HCB_ENV_LEVEL_15_DF_META.cb_off, 70);
assert_eq!(ASPX_HCB_ENV_LEVEL_15_DT_META.codebook_length, 141);
assert_eq!(ASPX_HCB_ENV_LEVEL_15_DT_META.cb_off, 70);
assert_eq!(ASPX_HCB_ENV_BALANCE_15_F0_META.codebook_length, 25);
assert_eq!(ASPX_HCB_ENV_BALANCE_15_DF_META.codebook_length, 49);
assert_eq!(ASPX_HCB_ENV_BALANCE_15_DF_META.cb_off, 24);
assert_eq!(ASPX_HCB_ENV_BALANCE_15_DT_META.codebook_length, 49);
assert_eq!(ASPX_HCB_ENV_BALANCE_15_DT_META.cb_off, 24);
assert_eq!(ASPX_HCB_ENV_LEVEL_30_F0_META.codebook_length, 36);
assert_eq!(ASPX_HCB_ENV_LEVEL_30_DF_META.codebook_length, 71);
assert_eq!(ASPX_HCB_ENV_LEVEL_30_DF_META.cb_off, 35);
assert_eq!(ASPX_HCB_ENV_LEVEL_30_DT_META.codebook_length, 71);
assert_eq!(ASPX_HCB_ENV_LEVEL_30_DT_META.cb_off, 35);
assert_eq!(ASPX_HCB_ENV_BALANCE_30_F0_META.codebook_length, 13);
assert_eq!(ASPX_HCB_ENV_BALANCE_30_DF_META.codebook_length, 25);
assert_eq!(ASPX_HCB_ENV_BALANCE_30_DF_META.cb_off, 12);
assert_eq!(ASPX_HCB_ENV_BALANCE_30_DT_META.codebook_length, 25);
assert_eq!(ASPX_HCB_ENV_BALANCE_30_DT_META.cb_off, 12);
}
#[test]
fn num_aspx_timeslots_matches_pseudocode_75a() {
assert_eq!(num_aspx_timeslots(2048), 16); assert_eq!(num_aspx_timeslots(1920), 15); assert_eq!(num_aspx_timeslots(1536), 12); assert_eq!(num_aspx_timeslots(1024), 16); assert_eq!(num_aspx_timeslots(960), 15); assert_eq!(num_aspx_timeslots(768), 12); assert_eq!(num_aspx_timeslots(512), 8); assert_eq!(num_aspx_timeslots(384), 6); }
fn push_hcb_code(bw: &mut BitWriter, len: u8, value: u32) {
bw.write_u32(value, len as u32);
}
fn round_trip_codebook(hcb: &AspxHcb) {
assert_eq!(
hcb.len.len(),
hcb.cw.len(),
"{}: len[] and cw[] length mismatch",
hcb.name
);
for (i, (&l, &cw)) in hcb.len.iter().zip(hcb.cw.iter()).enumerate() {
assert!(l > 0, "{}: len[{}] = 0", hcb.name, i);
assert!(l <= 32, "{}: len[{}] = {} (> 32)", hcb.name, i, l);
let mut bw = BitWriter::new();
push_hcb_code(&mut bw, l, cw);
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let delta = hcb.decode_delta(&mut br).unwrap_or_else(|e| {
panic!(
"{}: decode_delta failed on symbol {} (cw={:#x}, len={}): {:?}",
hcb.name, i, cw, l, e
);
});
let expected = i as i32 - hcb.cb_off;
assert_eq!(
delta, expected,
"{}: symbol {} decoded to delta {} (expected {})",
hcb.name, i, delta, expected
);
assert_eq!(
br.bit_position(),
l as u64,
"{}: consumed {} bits, expected {}",
hcb.name,
br.bit_position(),
l
);
}
}
#[test]
fn aspx_hcb_env_level_15_f0_round_trip() {
round_trip_codebook(&ASPX_HCB_ENV_LEVEL_15_F0);
}
#[test]
fn aspx_hcb_env_level_15_df_round_trip() {
round_trip_codebook(&ASPX_HCB_ENV_LEVEL_15_DF);
}
#[test]
fn aspx_hcb_env_level_15_dt_round_trip() {
round_trip_codebook(&ASPX_HCB_ENV_LEVEL_15_DT);
}
#[test]
fn aspx_hcb_env_balance_15_f0_round_trip() {
round_trip_codebook(&ASPX_HCB_ENV_BALANCE_15_F0);
}
#[test]
fn aspx_hcb_env_balance_15_df_round_trip() {
round_trip_codebook(&ASPX_HCB_ENV_BALANCE_15_DF);
}
#[test]
fn aspx_hcb_env_balance_15_dt_round_trip() {
round_trip_codebook(&ASPX_HCB_ENV_BALANCE_15_DT);
}
#[test]
fn aspx_hcb_env_level_30_f0_round_trip() {
round_trip_codebook(&ASPX_HCB_ENV_LEVEL_30_F0);
}
#[test]
fn aspx_hcb_env_level_30_df_round_trip() {
round_trip_codebook(&ASPX_HCB_ENV_LEVEL_30_DF);
}
#[test]
fn aspx_hcb_env_level_30_dt_round_trip() {
round_trip_codebook(&ASPX_HCB_ENV_LEVEL_30_DT);
}
#[test]
fn aspx_hcb_env_balance_30_f0_round_trip() {
round_trip_codebook(&ASPX_HCB_ENV_BALANCE_30_F0);
}
#[test]
fn aspx_hcb_env_balance_30_df_round_trip() {
round_trip_codebook(&ASPX_HCB_ENV_BALANCE_30_DF);
}
#[test]
fn aspx_hcb_env_balance_30_dt_round_trip() {
round_trip_codebook(&ASPX_HCB_ENV_BALANCE_30_DT);
}
#[test]
fn aspx_hcb_noise_level_f0_round_trip() {
round_trip_codebook(&ASPX_HCB_NOISE_LEVEL_F0);
}
#[test]
fn aspx_hcb_noise_level_df_round_trip() {
round_trip_codebook(&ASPX_HCB_NOISE_LEVEL_DF);
}
#[test]
fn aspx_hcb_noise_level_dt_round_trip() {
round_trip_codebook(&ASPX_HCB_NOISE_LEVEL_DT);
}
#[test]
fn aspx_hcb_noise_balance_f0_round_trip() {
round_trip_codebook(&ASPX_HCB_NOISE_BALANCE_F0);
}
#[test]
fn aspx_hcb_noise_balance_df_round_trip() {
round_trip_codebook(&ASPX_HCB_NOISE_BALANCE_DF);
}
#[test]
fn aspx_hcb_noise_balance_dt_round_trip() {
round_trip_codebook(&ASPX_HCB_NOISE_BALANCE_DT);
}
#[test]
fn aspx_hcb_all_matches_lookup_and_meta() {
let metas: &[AspxHcbMeta] = &[
ASPX_HCB_ENV_LEVEL_15_F0_META,
ASPX_HCB_ENV_LEVEL_15_DF_META,
ASPX_HCB_ENV_LEVEL_15_DT_META,
ASPX_HCB_ENV_BALANCE_15_F0_META,
ASPX_HCB_ENV_BALANCE_15_DF_META,
ASPX_HCB_ENV_BALANCE_15_DT_META,
ASPX_HCB_ENV_LEVEL_30_F0_META,
ASPX_HCB_ENV_LEVEL_30_DF_META,
ASPX_HCB_ENV_LEVEL_30_DT_META,
ASPX_HCB_ENV_BALANCE_30_F0_META,
ASPX_HCB_ENV_BALANCE_30_DF_META,
ASPX_HCB_ENV_BALANCE_30_DT_META,
ASPX_HCB_NOISE_LEVEL_F0_META,
ASPX_HCB_NOISE_LEVEL_DF_META,
ASPX_HCB_NOISE_LEVEL_DT_META,
ASPX_HCB_NOISE_BALANCE_F0_META,
ASPX_HCB_NOISE_BALANCE_DF_META,
ASPX_HCB_NOISE_BALANCE_DT_META,
];
assert_eq!(ASPX_HCB_ALL.len(), 18);
assert_eq!(ASPX_HCB_ALL.len(), metas.len());
for ((id, hcb), meta) in ASPX_HCB_ALL.iter().zip(metas.iter()) {
assert_eq!(hcb.name, meta.name);
assert_eq!(hcb.len.len(), meta.codebook_length as usize);
assert_eq!(hcb.cw.len(), meta.codebook_length as usize);
assert_eq!(hcb.cb_off, meta.cb_off);
let via_lookup = lookup_aspx_hcb(*id);
assert_eq!(via_lookup.name, hcb.name);
}
}
#[test]
fn get_aspx_hcb_matches_pseudocode_79() {
assert_eq!(
get_aspx_hcb(
AspxDataType::Signal,
AspxQuantStep::Fine,
AspxStereoMode::Level,
AspxHcbType::Df
),
HuffmanCodebookId::EnvLevel15Df
);
assert_eq!(
get_aspx_hcb(
AspxDataType::Signal,
AspxQuantStep::Coarse,
AspxStereoMode::Balance,
AspxHcbType::F0
),
HuffmanCodebookId::EnvBalance30F0
);
assert_eq!(
get_aspx_hcb(
AspxDataType::Signal,
AspxQuantStep::Coarse,
AspxStereoMode::Level,
AspxHcbType::Dt
),
HuffmanCodebookId::EnvLevel30Dt
);
for qm in [AspxQuantStep::Fine, AspxQuantStep::Coarse] {
assert_eq!(
get_aspx_hcb(
AspxDataType::Noise,
qm,
AspxStereoMode::Level,
AspxHcbType::F0
),
HuffmanCodebookId::NoiseLevelF0
);
assert_eq!(
get_aspx_hcb(
AspxDataType::Noise,
qm,
AspxStereoMode::Balance,
AspxHcbType::Dt
),
HuffmanCodebookId::NoiseBalanceDt
);
}
}
#[test]
fn aspx_ec_data_one_env_freq_dir_signal() {
let f0_cb = &ASPX_HCB_ENV_LEVEL_15_F0;
let df_cb = &ASPX_HCB_ENV_LEVEL_15_DF;
let f0_idx = 40usize;
let df_zero_idx = 70usize;
let df_plus1_idx = 71usize;
let mut bw = BitWriter::new();
bw.write_u32(f0_cb.cw[f0_idx], f0_cb.len[f0_idx] as u32);
bw.write_u32(df_cb.cw[df_zero_idx], df_cb.len[df_zero_idx] as u32);
bw.write_u32(df_cb.cw[df_plus1_idx], df_cb.len[df_plus1_idx] as u32);
let total_bits = f0_cb.len[f0_idx] as u64
+ df_cb.len[df_zero_idx] as u64
+ df_cb.len[df_plus1_idx] as u64;
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let out = parse_aspx_ec_data(
&mut br,
AspxDataType::Signal,
1, &[true], AspxQuantStep::Fine,
AspxStereoMode::Level,
&[false], AspxSbgCounts {
num_sbg_sig_highres: 3,
num_sbg_sig_lowres: 2,
num_sbg_noise: 2,
},
)
.unwrap();
assert_eq!(out.len(), 1);
let env = &out[0];
assert!(!env.direction_time);
assert_eq!(env.values, vec![40, 0, 1]);
assert_eq!(br.bit_position(), total_bits);
}
#[test]
fn aspx_ec_data_two_env_time_dir_noise() {
let dt_cb = &ASPX_HCB_NOISE_LEVEL_DT;
let symbols = [29usize, 30usize, 28usize, 31usize];
let mut bw = BitWriter::new();
let mut total_bits: u64 = 0;
for &s in &symbols {
bw.write_u32(dt_cb.cw[s], dt_cb.len[s] as u32);
total_bits += dt_cb.len[s] as u64;
}
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let out = parse_aspx_ec_data(
&mut br,
AspxDataType::Noise,
2, &[], AspxQuantStep::Coarse,
AspxStereoMode::Level,
&[true, true], AspxSbgCounts {
num_sbg_sig_highres: 3,
num_sbg_sig_lowres: 2,
num_sbg_noise: 2,
},
)
.unwrap();
assert_eq!(out.len(), 2);
assert!(out[0].direction_time);
assert_eq!(out[0].values, vec![0, 1]);
assert!(out[1].direction_time);
assert_eq!(out[1].values, vec![-1, 2]);
assert_eq!(br.bit_position(), total_bits);
}
fn cfg_for(scale: AspxMasterFreqScale, start: u8, stop: u8, noise_sbg: u8) -> AspxConfig {
AspxConfig {
quant_mode_env: AspxQuantStep::Fine,
start_freq: start,
stop_freq: stop,
master_freq_scale: scale,
interpolation: false,
preflat: false,
limiter: false,
noise_sbg,
num_env_bits_fixfix: 0,
freq_res_mode: AspxFreqResMode::Signalled,
}
}
#[test]
fn master_sbg_table_highres_full_span() {
let cfg = cfg_for(AspxMasterFreqScale::HighRes, 0, 0, 0);
let (master, num_master, sba, sbz) = derive_master_sbg_table(&cfg);
assert_eq!(num_master, 22);
assert_eq!(sba, 18);
assert_eq!(sbz, 62);
assert_eq!(master.len(), 23);
assert_eq!(master[0], 18);
assert_eq!(master[22], 62);
assert_eq!(master[10], 32);
}
#[test]
fn master_sbg_table_lowres_full_span() {
let cfg = cfg_for(AspxMasterFreqScale::LowRes, 0, 0, 0);
let (master, num_master, sba, sbz) = derive_master_sbg_table(&cfg);
assert_eq!(num_master, 20);
assert_eq!(sba, 10);
assert_eq!(sbz, 46);
assert_eq!(master.first(), Some(&10));
assert_eq!(master.last(), Some(&46));
}
#[test]
fn master_sbg_table_highres_trimmed() {
let cfg = cfg_for(AspxMasterFreqScale::HighRes, 3, 3, 0);
let (master, num_master, sba, sbz) = derive_master_sbg_table(&cfg);
assert_eq!(num_master, 10);
assert_eq!(sba, 24);
assert_eq!(sbz, 44);
assert_eq!(master, vec![24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44]);
}
#[test]
fn sig_sbg_tables_xover_zero_on_highres() {
let cfg = cfg_for(AspxMasterFreqScale::HighRes, 0, 0, 0);
let (master, num_master, _sba, _sbz) = derive_master_sbg_table(&cfg);
let sig = derive_sig_sbg_tables(&master, num_master, 0).unwrap();
assert_eq!(sig.num_sbg_sig_highres, 22);
assert_eq!(sig.sbx, 18);
assert_eq!(sig.num_sb_aspx, 62 - 18);
assert_eq!(sig.sbg_sig_highres, master);
assert_eq!(sig.num_sbg_sig_lowres, 11);
assert_eq!(sig.sbg_sig_lowres.len(), 12);
assert_eq!(sig.sbg_sig_lowres[0], 18);
for k in 1..=sig.num_sbg_sig_lowres as usize {
assert_eq!(sig.sbg_sig_lowres[k], sig.sbg_sig_highres[2 * k]);
}
}
#[test]
fn sig_sbg_tables_xover_odd_branch() {
let cfg = cfg_for(AspxMasterFreqScale::HighRes, 3, 3, 0);
let (master, num_master, _sba, _sbz) = derive_master_sbg_table(&cfg);
assert_eq!(num_master, 10);
let sig = derive_sig_sbg_tables(&master, num_master, 2).unwrap();
assert_eq!(sig.num_sbg_sig_highres, 8);
assert_eq!(sig.sbx, master[2]);
assert_eq!(sig.num_sb_aspx, master[num_master as usize] - sig.sbx);
assert_eq!(sig.num_sbg_sig_lowres, 4);
for k in 1..=sig.num_sbg_sig_lowres as usize {
assert_eq!(sig.sbg_sig_lowres[k], sig.sbg_sig_highres[2 * k]);
}
let sig_odd = derive_sig_sbg_tables(&master, num_master, 3).unwrap();
assert_eq!(sig_odd.num_sbg_sig_highres, 7);
assert_eq!(sig_odd.num_sbg_sig_lowres, 4);
assert_eq!(sig_odd.sbg_sig_lowres[0], sig_odd.sbg_sig_highres[0]);
for k in 1..=sig_odd.num_sbg_sig_lowres as usize {
assert_eq!(
sig_odd.sbg_sig_lowres[k],
sig_odd.sbg_sig_highres[2 * k - 1]
);
}
}
#[test]
fn sig_sbg_tables_xover_exceeds_master_errors() {
let cfg = cfg_for(AspxMasterFreqScale::HighRes, 3, 3, 0);
let (master, num_master, _sba, _sbz) = derive_master_sbg_table(&cfg);
assert!(derive_sig_sbg_tables(&master, num_master, num_master + 1).is_err());
}
#[test]
fn noise_sbg_table_derivation_matches_pseudocode_70() {
let cfg = cfg_for(AspxMasterFreqScale::HighRes, 0, 0, 3);
let tables = derive_aspx_frequency_tables(&cfg, 0).unwrap();
assert_eq!(tables.sbx, 18);
assert_eq!(tables.sbz, 62);
assert!(tables.counts.num_sbg_noise <= 5);
assert_eq!(tables.counts.num_sbg_noise, 5);
assert_eq!(tables.sbg_noise.len(), 6);
assert_eq!(tables.sbg_noise[0], tables.sbg_sig_lowres[0]);
}
#[test]
fn noise_sbg_clamps_to_minimum_of_one() {
let cfg = cfg_for(AspxMasterFreqScale::LowRes, 0, 0, 0);
let tables = derive_aspx_frequency_tables(&cfg, 0).unwrap();
assert_eq!(tables.counts.num_sbg_noise, 1);
assert_eq!(tables.sbg_noise.len(), 2);
assert_eq!(tables.sbg_noise[0], tables.sbg_sig_lowres[0]);
}
#[test]
fn aspx_ec_data_three_env_mixed_freq_res_bit_accounting() {
let f0_cb = &ASPX_HCB_ENV_LEVEL_15_F0;
let df_cb = &ASPX_HCB_ENV_LEVEL_15_DF;
let counts = AspxSbgCounts {
num_sbg_sig_highres: 3,
num_sbg_sig_lowres: 2,
num_sbg_noise: 1,
};
let seq: &[&[(bool, usize)]] = &[
&[(true, 30), (false, 70), (false, 71)],
&[(true, 31), (false, 70)],
&[(true, 32), (false, 70), (false, 70)],
];
let mut bw = BitWriter::new();
let mut total_bits: u64 = 0;
for sbgs in seq {
for &(is_f0, idx) in *sbgs {
let cb = if is_f0 { f0_cb } else { df_cb };
bw.write_u32(cb.cw[idx], cb.len[idx] as u32);
total_bits += cb.len[idx] as u64;
}
}
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let out = parse_aspx_ec_data(
&mut br,
AspxDataType::Signal,
3,
&[true, false, true],
AspxQuantStep::Fine,
AspxStereoMode::Level,
&[false, false, false],
counts,
)
.unwrap();
assert_eq!(out.len(), 3);
assert_eq!(out[0].values, vec![30, 0, 1]);
assert_eq!(out[1].values, vec![31, 0]);
assert_eq!(out[2].values, vec![32, 0, 0]);
assert_eq!(br.bit_position(), total_bits);
}
#[test]
fn aspx_frequency_tables_produce_counts_compatible_with_ec_data() {
let cfg = cfg_for(AspxMasterFreqScale::LowRes, 2, 1, 1);
let tables = derive_aspx_frequency_tables(&cfg, 1).unwrap();
assert_eq!(tables.num_sbg_master, 14);
assert_eq!(tables.counts.num_sbg_sig_highres, 13);
let f0_cb = &ASPX_HCB_ENV_LEVEL_15_F0;
let df_cb = &ASPX_HCB_ENV_LEVEL_15_DF;
let f0_idx = 50usize;
let df_zero_idx = 70usize;
let mut bw = BitWriter::new();
bw.write_u32(f0_cb.cw[f0_idx], f0_cb.len[f0_idx] as u32);
let mut total_bits = f0_cb.len[f0_idx] as u64;
for _ in 1..tables.counts.num_sbg_sig_highres {
bw.write_u32(df_cb.cw[df_zero_idx], df_cb.len[df_zero_idx] as u32);
total_bits += df_cb.len[df_zero_idx] as u64;
}
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let out = parse_aspx_ec_data(
&mut br,
AspxDataType::Signal,
1,
&[true],
AspxQuantStep::Fine,
AspxStereoMode::Level,
&[false],
tables.counts,
)
.unwrap();
assert_eq!(out.len(), 1);
assert_eq!(
out[0].values.len(),
tables.counts.num_sbg_sig_highres as usize
);
assert_eq!(out[0].values[0], 50);
for v in &out[0].values[1..] {
assert_eq!(*v, 0);
}
assert_eq!(br.bit_position(), total_bits);
}
#[test]
fn patch_derivation_midrange_highres() {
let sbg_master: Vec<u32> = ASPX_SBG_TEMPLATE_HIGHRES.to_vec();
let num_sbg_master = (sbg_master.len() - 1) as u32;
let sba = sbg_master[0];
let sbx = 24;
let sbz = *sbg_master.last().unwrap();
let num_sb_aspx = sbz - sbx;
let patches = derive_patch_tables(
&sbg_master,
num_sbg_master,
sba,
sbx,
num_sb_aspx,
true, true, );
assert!(patches.num_sbg_patches <= 5);
assert_eq!(patches.sbg_patches[0], sbx);
assert!(*patches.sbg_patches.last().unwrap() <= sbz);
}
#[test]
fn hf_tile_copy_copies_patch_source() {
let patches = AspxPatchTables {
sbg_patches: vec![8, 12],
num_sbg_patches: 1,
sbg_patch_num_sb: vec![4],
sbg_patch_start_sb: vec![2],
};
let n_ts = 4usize;
let mut q_low: Vec<Vec<(f32, f32)>> = (0..64).map(|_| vec![(0.0, 0.0); n_ts]).collect();
for (src_sb, row) in q_low.iter_mut().enumerate().take(6).skip(2) {
for (ts, cell) in row.iter_mut().enumerate().take(n_ts) {
*cell = (src_sb as f32, ts as f32);
}
}
let q_high = hf_tile_copy(&q_low, &patches, 8, 64);
for (high_off, &src_sb) in (2..6).collect::<Vec<_>>().iter().enumerate() {
let hsb = 8 + high_off;
for ts in 0..n_ts {
assert_eq!(
q_high[hsb][ts], q_low[src_sb as usize][ts],
"mismatch at high_sb={hsb} ts={ts}"
);
}
}
for (sb, row) in q_high.iter().enumerate().take(8) {
for &cell in row.iter().take(n_ts) {
assert_eq!(cell, (0.0, 0.0), "low-band sb={sb} should be zero");
}
}
for (sb, row) in q_high.iter().enumerate().take(64).skip(12) {
for &cell in row.iter().take(n_ts) {
assert_eq!(cell, (0.0, 0.0), "high-band sb={sb} should be zero");
}
}
}
#[test]
fn hf_regen_produces_non_silent_high_band() {
use crate::qmf::{QmfAnalysisBank, NUM_QMF_SUBBANDS};
let n_slots = 32usize;
let n = n_slots * 64;
let mut pcm = vec![0.0f32; n];
let f = 1000.0_f32 / 48_000.0_f32;
for (i, s) in pcm.iter_mut().enumerate() {
*s = (2.0 * std::f32::consts::PI * f * i as f32).sin();
}
let mut ana = QmfAnalysisBank::new();
let slots = ana.process_block(&pcm);
let mut q_low: Vec<Vec<(f32, f32)>> = (0..NUM_QMF_SUBBANDS as u32)
.map(|_| vec![(0.0, 0.0); n_slots])
.collect();
for (ts, slot) in slots.iter().enumerate() {
for (sb, s) in slot.iter().enumerate() {
q_low[sb][ts] = *s;
}
}
let patches = AspxPatchTables {
sbg_patches: vec![16, 32],
num_sbg_patches: 1,
sbg_patch_num_sb: vec![16],
sbg_patch_start_sb: vec![0],
};
for row in q_low.iter_mut().skip(16) {
for s in row.iter_mut() {
*s = (0.0, 0.0);
}
}
let q_high = hf_tile_copy(&q_low, &patches, 16, NUM_QMF_SUBBANDS as u32);
let mut energy = 0.0f64;
for row in q_high.iter().take(32).skip(16) {
for &(re, im) in row.iter() {
energy += (re as f64) * (re as f64) + (im as f64) * (im as f64);
}
}
assert!(energy > 0.0, "HF regen high band has no energy");
}
#[test]
fn aspx_end_to_end_pipeline_produces_non_silent_pcm() {
use crate::qmf::{QmfAnalysisBank, QmfSynthesisBank, NUM_QMF_SUBBANDS};
let n_slots = 60usize;
let n = n_slots * 64;
let mut pcm = vec![0.0f32; n];
let f = 1000.0_f32 / 48_000.0_f32;
for (i, s) in pcm.iter_mut().enumerate() {
*s = (2.0 * std::f32::consts::PI * f * i as f32).sin();
}
let mut ana = QmfAnalysisBank::new();
let slots = ana.process_block(&pcm);
let mut q: Vec<Vec<(f32, f32)>> = (0..NUM_QMF_SUBBANDS as u32)
.map(|_| vec![(0.0, 0.0); n_slots])
.collect();
for (ts, slot) in slots.iter().enumerate() {
for (sb, s) in slot.iter().enumerate() {
q[sb][ts] = *s;
}
}
let sbx = 16u32;
let num_sb_aspx = 16u32;
let sbz = sbx + num_sb_aspx;
for sb in sbx..NUM_QMF_SUBBANDS as u32 {
for s in q[sb as usize].iter_mut() {
*s = (0.0, 0.0);
}
}
let patches = AspxPatchTables {
sbg_patches: vec![sbx, sbz],
num_sbg_patches: 1,
sbg_patch_num_sb: vec![num_sb_aspx],
sbg_patch_start_sb: vec![0],
};
let q_high = hf_tile_copy(&q, &patches, sbx, NUM_QMF_SUBBANDS as u32);
for sb in sbx..sbz {
q[sb as usize] = q_high[sb as usize].clone();
}
apply_flat_envelope_gain(&mut q, sbx, sbz, 0.5);
let mut syn = QmfSynthesisBank::new();
let mut out = Vec::with_capacity(n);
#[allow(clippy::needless_range_loop)] for ts in 0..n_slots {
let mut slot = [(0.0f32, 0.0f32); 64];
for (sb, dst) in slot.iter_mut().enumerate() {
*dst = q[sb][ts];
}
let pcm_row = syn.process_slot(&slot);
out.extend_from_slice(&pcm_row);
}
let start = 1100usize;
let end = n - 10;
let mut energy = 0.0f64;
let mut nonzero = 0usize;
for &sample in &out[start..end] {
let s = sample as f64;
energy += s * s;
if s != 0.0 {
nonzero += 1;
}
}
assert!(
energy > 1e-3,
"ASPX pipeline produced silent PCM (energy={energy})"
);
assert!(
nonzero > (end - start) / 2,
"too many zero samples: {nonzero}/{}",
end - start
);
}
#[test]
fn flat_envelope_gain_scales_range() {
let mut q: Vec<Vec<(f32, f32)>> = (0..16).map(|_| vec![(1.0, 2.0); 4]).collect();
apply_flat_envelope_gain(&mut q, 4, 12, 3.0);
for row in q.iter().take(4) {
for &cell in row.iter().take(4) {
assert_eq!(cell, (1.0, 2.0));
}
}
for row in q.iter().take(12).skip(4) {
for &cell in row.iter().take(4) {
assert_eq!(cell, (3.0, 6.0));
}
}
for row in q.iter().take(16).skip(12) {
for &cell in row.iter().take(4) {
assert_eq!(cell, (1.0, 2.0));
}
}
}
#[test]
fn tab_border_fixfix_matches_table_194() {
assert_eq!(tab_border_fixfix(16, 1), Some(vec![0, 16]));
assert_eq!(tab_border_fixfix(16, 2), Some(vec![0, 8, 16]));
assert_eq!(tab_border_fixfix(16, 4), Some(vec![0, 4, 8, 12, 16]));
assert_eq!(tab_border_fixfix(12, 4), Some(vec![0, 3, 6, 9, 12]));
assert_eq!(tab_border_fixfix(16, 3), None);
assert_eq!(tab_border_fixfix(10, 2), None);
}
#[test]
fn delta_decode_sig_freq_then_time() {
let deltas = vec![
AspxHuffEnv {
values: vec![4, -1, 2, 0],
direction_time: false,
},
AspxHuffEnv {
values: vec![1, 1, 1, 1],
direction_time: true,
},
];
let qscf = delta_decode_sig(&deltas, 4, &[], 1);
assert_eq!(qscf[0][0], 4);
assert_eq!(qscf[1][0], 3);
assert_eq!(qscf[2][0], 5);
assert_eq!(qscf[3][0], 5);
assert_eq!(qscf[0][1], 5);
assert_eq!(qscf[1][1], 4);
assert_eq!(qscf[2][1], 6);
assert_eq!(qscf[3][1], 6);
}
#[test]
fn dequantize_sig_scf_fine_3db_matches_formula() {
let qscf = vec![vec![0, 2]];
let scf = dequantize_sig_scf(&qscf, AspxQuantStep::Fine, &[false, false], 64);
assert!((scf[0][0] - 64.0).abs() < 1e-4);
assert!((scf[0][1] - 128.0).abs() < 1e-4);
}
#[test]
fn dequantize_noise_scf_offset_matches_formula() {
let qscf = vec![vec![0, 6, 12]];
let scf = dequantize_noise_scf(&qscf);
assert!((scf[0][0] - 64.0).abs() < 1e-4);
assert!((scf[0][1] - 1.0).abs() < 1e-4);
assert!((scf[0][2] - (1.0 / 64.0)).abs() < 1e-6);
}
#[test]
fn estimate_envelope_energy_matches_direct_average() {
let num_sb_aspx = 2u32;
let sbx = 2u32;
let sbg_sig = vec![sbx, sbx + num_sb_aspx];
let atsg_sig = vec![0u32, 1, 2];
let num_ts_in_ats = 2u32; let mut q_high: Vec<Vec<(f32, f32)>> = (0..4).map(|_| vec![(0.0, 0.0); 4]).collect();
for row in q_high.iter_mut().take(4).skip(2) {
for cell in row.iter_mut().take(2) {
*cell = (1.0, 0.0);
}
}
for row in q_high.iter_mut().take(4).skip(2) {
for cell in row.iter_mut().take(4).skip(2) {
*cell = (2.0, 0.0);
}
}
let est = estimate_envelope_energy(
&q_high,
&sbg_sig,
&atsg_sig,
num_ts_in_ats,
num_sb_aspx,
sbx,
false,
);
for (sb, est_row) in est.iter().enumerate().take(2) {
assert!((est_row[0] - 1.0).abs() < 1e-5, "env0 sb{sb} mismatch");
assert!((est_row[1] - 4.0).abs() < 1e-5, "env1 sb{sb} mismatch");
}
}
#[test]
fn compute_sig_gains_equals_sqrt_ratio() {
let est = vec![vec![3.0]];
let sig = vec![vec![16.0]];
let noise = vec![vec![0.0]];
let g = compute_sig_gains(&est, &sig, &noise);
assert!((g[0][0] - 2.0).abs() < 1e-5);
}
#[test]
fn envelope_adjustment_follows_per_envelope_gain_profile() {
let cfg = AspxConfig {
quant_mode_env: AspxQuantStep::Fine,
start_freq: 0,
stop_freq: 0,
master_freq_scale: AspxMasterFreqScale::HighRes,
interpolation: false,
preflat: false,
limiter: false,
noise_sbg: 0,
num_env_bits_fixfix: 0,
freq_res_mode: AspxFreqResMode::High,
};
let tables = derive_aspx_frequency_tables(&cfg, 0).unwrap();
let num_aspx_ts = 16u32;
let num_ts_in_ats = 1u32;
let n_qmf_ts = (num_aspx_ts * num_ts_in_ats) as usize;
let mut q: Vec<Vec<(f32, f32)>> = (0..64).map(|_| vec![(0.0, 0.0); n_qmf_ts]).collect();
for row in q
.iter_mut()
.take(tables.sbz as usize)
.skip(tables.sbx as usize)
{
for cell in row.iter_mut().take(n_qmf_ts) {
*cell = (1.0, 0.0);
}
}
let num_sbg_sig = tables.counts.num_sbg_sig_highres;
let sig_deltas = vec![
AspxHuffEnv {
values: vec![2; num_sbg_sig as usize],
direction_time: false,
},
AspxHuffEnv {
values: vec![4; num_sbg_sig as usize],
direction_time: true,
},
];
let num_sbg_noise = (tables.sbg_noise.len() as u32).saturating_sub(1);
let noise_deltas = vec![
AspxHuffEnv {
values: vec![0; num_sbg_noise as usize],
direction_time: false,
},
AspxHuffEnv {
values: vec![0; num_sbg_noise as usize],
direction_time: true,
},
];
let (atsg_sig, atsg_noise) = derive_fixfix_atsg(num_aspx_ts, 2, 2).unwrap();
let adjuster = AspxEnvelopeAdjuster::from_deltas(
&q,
&tables,
&sig_deltas,
&noise_deltas,
AspxQuantStep::Fine,
&[false, true],
&atsg_sig,
&atsg_noise,
num_ts_in_ats,
false,
);
adjuster.apply(&mut q);
let (t0_a, t0_b) = (atsg_sig[0] as usize, atsg_sig[1] as usize);
let (t1_a, t1_b) = (atsg_sig[1] as usize, atsg_sig[2] as usize);
let mut e0 = 0.0_f64;
let mut e1 = 0.0_f64;
for row in q.iter().take(tables.sbz as usize).skip(tables.sbx as usize) {
for &(re, im) in row.iter().take(t0_b).skip(t0_a) {
e0 += (re as f64) * (re as f64) + (im as f64) * (im as f64);
}
for &(re, im) in row.iter().take(t1_b).skip(t1_a) {
e1 += (re as f64) * (re as f64) + (im as f64) * (im as f64);
}
}
assert!(e0 > 0.0, "env 0 energy must be non-zero");
assert!(e1 > 0.0, "env 1 energy must be non-zero");
assert!(
e1 > 3.0 * e0,
"env 1 energy ({e1}) should be ~4x env 0 energy ({e0})"
);
}
#[test]
fn apply_envelope_gains_respects_per_envelope_boundaries() {
let sbx = 2u32;
let num_sb_aspx = 2u32;
let atsg_sig = vec![0u32, 2, 4]; let num_ts_in_ats = 1u32;
let mut q: Vec<Vec<(f32, f32)>> = (0..4).map(|_| vec![(1.0, 0.0); 4]).collect();
let sig_gain_sb = vec![vec![1.0_f32, 3.0], vec![2.0_f32, 4.0]];
apply_envelope_gains(
&mut q,
&sig_gain_sb,
&atsg_sig,
num_ts_in_ats,
sbx,
num_sb_aspx,
);
for &cell in q[2].iter().take(2) {
assert!((cell.0 - 1.0).abs() < 1e-6);
}
for &cell in q[2].iter().take(4).skip(2) {
assert!((cell.0 - 3.0).abs() < 1e-6);
}
for &cell in q[3].iter().take(2) {
assert!((cell.0 - 2.0).abs() < 1e-6);
}
for &cell in q[3].iter().take(4).skip(2) {
assert!((cell.0 - 4.0).abs() < 1e-6);
}
}
#[test]
fn map_scf_to_qmf_subbands_broadcasts_within_group() {
let sbx = 2u32;
let sbg_sig = vec![sbx, sbx + 2, sbx + 4];
let sbg_noise = vec![sbx, sbx + 4];
let atsg_sig = vec![0u32, 1, 2];
let atsg_noise = vec![0u32, 1, 2];
let scf_sig_sbg = vec![vec![10.0_f32, 20.0], vec![30.0, 40.0]];
let scf_noise_sbg = vec![vec![1.0_f32, 2.0]];
let out = map_scf_to_qmf_subbands(
&scf_sig_sbg,
&scf_noise_sbg,
&sbg_sig,
&sbg_noise,
&atsg_sig,
&atsg_noise,
4,
sbx,
);
assert_eq!(out.scf_sig_sb[0][0], 10.0);
assert_eq!(out.scf_sig_sb[0][1], 20.0);
assert_eq!(out.scf_sig_sb[1][0], 10.0);
assert_eq!(out.scf_sig_sb[2][0], 30.0);
assert_eq!(out.scf_sig_sb[3][0], 30.0);
assert_eq!(out.scf_noise_sb[0][0], 1.0);
assert_eq!(out.scf_noise_sb[0][1], 2.0);
assert_eq!(out.scf_noise_sb[3][1], 2.0);
}
#[test]
fn inject_noise_and_tone_adds_tone_and_noise_floor() {
use crate::qmf::{QmfAnalysisBank, QmfSynthesisBank, NUM_QMF_SUBBANDS};
let fs = 48_000.0_f32;
let f0 = 1_000.0_f32;
let num_aspx_ts = 16u32;
let num_ts_in_ats = 2u32;
let n_slots = (num_aspx_ts * num_ts_in_ats) as usize;
let n = n_slots * NUM_QMF_SUBBANDS;
let pcm: Vec<f32> = (0..n)
.map(|i| (2.0 * std::f32::consts::PI * f0 * i as f32 / fs).sin() * 0.5)
.collect();
let mut ana = QmfAnalysisBank::new();
let slots = ana.process_block(&pcm);
let mut q: Vec<Vec<(f32, f32)>> = (0..NUM_QMF_SUBBANDS as u32)
.map(|_| vec![(0.0f32, 0.0f32); n_slots])
.collect();
for (ts, slot) in slots.iter().enumerate() {
for (sb, s) in slot.iter().enumerate() {
q[sb][ts] = *s;
}
}
let cfg = AspxConfig {
quant_mode_env: AspxQuantStep::Fine,
start_freq: 0,
stop_freq: 0,
master_freq_scale: AspxMasterFreqScale::HighRes,
interpolation: false,
preflat: false,
limiter: false,
noise_sbg: 0,
num_env_bits_fixfix: 0,
freq_res_mode: AspxFreqResMode::High,
};
let tables = derive_aspx_frequency_tables(&cfg, 0).unwrap();
let sbx = tables.sbx as usize;
let sbz = tables.sbz as usize;
for row in q.iter_mut().skip(sbx) {
for s in row.iter_mut() {
*s = (0.0, 0.0);
}
}
let patches = derive_patch_tables(
&tables.sbg_master,
tables.num_sbg_master,
tables.sba,
tables.sbx,
tables.num_sb_aspx,
true,
true,
);
let q_high = hf_tile_copy(&q, &patches, tables.sbx, NUM_QMF_SUBBANDS as u32);
q[sbx..sbz].clone_from_slice(&q_high[sbx..sbz]);
let num_sbg_sig = tables.counts.num_sbg_sig_highres;
let num_sbg_noise = (tables.sbg_noise.len() as u32).saturating_sub(1);
let sig_deltas = vec![
AspxHuffEnv {
values: vec![4; num_sbg_sig as usize],
direction_time: false,
},
AspxHuffEnv {
values: vec![0; num_sbg_sig as usize],
direction_time: true,
},
];
let noise_deltas = vec![
AspxHuffEnv {
values: vec![6; num_sbg_noise as usize],
direction_time: false,
},
AspxHuffEnv {
values: vec![0; num_sbg_noise as usize],
direction_time: true,
},
];
let (atsg_sig, atsg_noise) = derive_fixfix_atsg(num_aspx_ts, 2, 2).unwrap();
let adjuster = AspxEnvelopeAdjuster::from_deltas(
&q,
&tables,
&sig_deltas,
&noise_deltas,
AspxQuantStep::Fine,
&[false; 2],
&atsg_sig,
&atsg_noise,
num_ts_in_ats,
false,
);
adjuster.apply(&mut q);
let mut add_harmonic = vec![false; num_sbg_sig as usize];
if !add_harmonic.is_empty() {
add_harmonic[0] = true;
}
let mut state = AspxChannelExtState::new();
inject_noise_and_tone(
&mut q,
&adjuster,
&tables,
&atsg_noise,
&add_harmonic,
0,
&mut state,
);
let mut hf_qmf_energy = 0.0_f64;
for row in q.iter().take(sbz).skip(sbx) {
for &(re, im) in row.iter() {
hf_qmf_energy += (re as f64).powi(2) + (im as f64).powi(2);
}
}
assert!(
hf_qmf_energy > 1.0,
"HF QMF energy too low after injection: {hf_qmf_energy}"
);
assert!(state.sine_idx_sb_prev.is_some());
assert_eq!(state.num_atsg_sig_prev, 2);
assert!(state.noise.prev.is_some());
assert!(state.tone.prev.is_some());
let mut syn = QmfSynthesisBank::new();
let mut out = Vec::with_capacity(n);
#[allow(clippy::needless_range_loop)] for ts in 0..n_slots {
let mut slot = [(0.0f32, 0.0f32); NUM_QMF_SUBBANDS];
for (sb, s) in slot.iter_mut().enumerate() {
*s = q[sb][ts];
}
let row = syn.process_slot(&slot);
out.extend_from_slice(&row);
}
let skip = 384_usize.min(out.len().saturating_sub(64));
let tail = &out[skip..];
let dft_mag_at = |pcm: &[f32], probe_hz: f64| -> f64 {
let mut re = 0.0_f64;
let mut im = 0.0_f64;
for (i, &s) in pcm.iter().enumerate() {
let angle = -2.0 * std::f64::consts::PI * probe_hz * (i as f64) / (fs as f64);
re += (s as f64) * angle.cos();
im += (s as f64) * angle.sin();
}
(re * re + im * im).sqrt() / (pcm.len() as f64)
};
let f_per_sb = fs / (2.0 * NUM_QMF_SUBBANDS as f32);
let hf_probe_mid = (sbx as f32 + (sbz - sbx) as f32 / 2.0 - 2.0) * f_per_sb;
let hf_probe_hi = (sbx as f32 + (sbz - sbx) as f32 / 2.0 + 2.0) * f_per_sb;
let mag_mid = dft_mag_at(tail, hf_probe_mid as f64);
let mag_hi = dft_mag_at(tail, hf_probe_hi as f64);
let mag_1khz_in = dft_mag_at(&pcm[skip..], 1_000.0);
let rms =
(tail.iter().map(|&s| (s as f64).powi(2)).sum::<f64>() / tail.len() as f64).sqrt();
assert!(
rms > 0.01,
"output RMS too low: {rms} (hf_qmf_energy={hf_qmf_energy})"
);
assert!(
mag_mid > 1e-4 && mag_hi > 1e-4,
"HF noise-floor bins too quiet: mid={mag_mid} hi={mag_hi} (1kHz_in={mag_1khz_in})"
);
}
#[test]
fn inject_noise_and_tone_state_advances_between_calls() {
let cfg = AspxConfig {
quant_mode_env: AspxQuantStep::Fine,
start_freq: 0,
stop_freq: 0,
master_freq_scale: AspxMasterFreqScale::LowRes,
interpolation: false,
preflat: false,
limiter: false,
noise_sbg: 0,
num_env_bits_fixfix: 0,
freq_res_mode: AspxFreqResMode::High,
};
let tables = derive_aspx_frequency_tables(&cfg, 0).unwrap();
let num_aspx_ts = 16u32;
let num_ts_in_ats = 1u32;
let n_slots = (num_aspx_ts * num_ts_in_ats) as usize;
let mut q: Vec<Vec<(f32, f32)>> =
(0..64).map(|_| vec![(1.0_f32, 0.0_f32); n_slots]).collect();
let num_sbg_sig = tables.counts.num_sbg_sig_highres;
let num_sbg_noise = (tables.sbg_noise.len() as u32).saturating_sub(1);
let sig_deltas = vec![
AspxHuffEnv {
values: vec![2; num_sbg_sig as usize],
direction_time: false,
},
AspxHuffEnv {
values: vec![0; num_sbg_sig as usize],
direction_time: true,
},
];
let noise_deltas = vec![
AspxHuffEnv {
values: vec![6; num_sbg_noise as usize],
direction_time: false,
},
AspxHuffEnv {
values: vec![0; num_sbg_noise as usize],
direction_time: true,
},
];
let (atsg_sig, atsg_noise) = derive_fixfix_atsg(num_aspx_ts, 2, 2).unwrap();
let adjuster = AspxEnvelopeAdjuster::from_deltas(
&q,
&tables,
&sig_deltas,
&noise_deltas,
AspxQuantStep::Fine,
&[false; 2],
&atsg_sig,
&atsg_noise,
num_ts_in_ats,
false,
);
adjuster.apply(&mut q);
let add_harmonic = vec![true; num_sbg_sig as usize];
let mut q_a = q.clone();
let mut q_b = q.clone();
let mut state = AspxChannelExtState::new();
inject_noise_and_tone(
&mut q_a,
&adjuster,
&tables,
&atsg_noise,
&add_harmonic,
0,
&mut state,
);
inject_noise_and_tone(
&mut q_b,
&adjuster,
&tables,
&atsg_noise,
&add_harmonic,
0,
&mut state,
);
let mut diff_count = 0;
for sb in (tables.sbx as usize)..(tables.sbz as usize) {
for ts in 0..n_slots {
if (q_a[sb][ts].0 - q_b[sb][ts].0).abs() > 1e-6
|| (q_a[sb][ts].1 - q_b[sb][ts].1).abs() > 1e-6
{
diff_count += 1;
}
}
}
assert!(
diff_count > 10,
"noise/tone state should diverge between calls, got {diff_count} diffs"
);
}
#[test]
fn inject_noise_and_tone_with_limiter_runs_full_pipeline() {
let cfg = AspxConfig {
quant_mode_env: AspxQuantStep::Fine,
start_freq: 0,
stop_freq: 0,
master_freq_scale: AspxMasterFreqScale::HighRes,
interpolation: false,
preflat: false,
limiter: true,
noise_sbg: 0,
num_env_bits_fixfix: 0,
freq_res_mode: AspxFreqResMode::High,
};
let tables = derive_aspx_frequency_tables(&cfg, 0).unwrap();
let num_aspx_ts = 16u32;
let num_ts_in_ats = 1u32;
let n_slots = (num_aspx_ts * num_ts_in_ats) as usize;
let mut q: Vec<Vec<(f32, f32)>> =
(0..64).map(|_| vec![(1.0_f32, 0.0_f32); n_slots]).collect();
let num_sbg_sig = tables.counts.num_sbg_sig_highres;
let num_sbg_noise = (tables.sbg_noise.len() as u32).saturating_sub(1);
let sig_deltas = vec![
AspxHuffEnv {
values: vec![0; num_sbg_sig as usize],
direction_time: false,
},
AspxHuffEnv {
values: vec![0; num_sbg_sig as usize],
direction_time: true,
},
];
let noise_deltas = vec![
AspxHuffEnv {
values: vec![0; num_sbg_noise as usize],
direction_time: false,
},
AspxHuffEnv {
values: vec![0; num_sbg_noise as usize],
direction_time: true,
},
];
let (atsg_sig, atsg_noise) = derive_fixfix_atsg(num_aspx_ts, 2, 2).unwrap();
let adjuster = AspxEnvelopeAdjuster::from_deltas(
&q,
&tables,
&sig_deltas,
&noise_deltas,
AspxQuantStep::Fine,
&[false; 2],
&atsg_sig,
&atsg_noise,
num_ts_in_ats,
false,
);
let patches = derive_patch_tables(
&tables.sbg_master,
tables.num_sbg_master,
tables.sba,
tables.sbx,
tables.num_sb_aspx,
true,
true,
);
let add_harmonic = vec![true; num_sbg_sig as usize];
let mut state = AspxChannelExtState::new();
inject_noise_and_tone_with_limiter(
&mut q,
&adjuster,
&tables,
&patches,
&atsg_noise,
&add_harmonic,
0,
&mut state,
);
let mut hf_qmf_energy = 0.0_f64;
let mut max_abs = 0.0_f64;
for row in q.iter().take(tables.sbz as usize).skip(tables.sbx as usize) {
for &(re, im) in row.iter() {
hf_qmf_energy += (re as f64).powi(2) + (im as f64).powi(2);
let m = (re as f64).abs().max((im as f64).abs());
if m > max_abs {
max_abs = m;
}
}
}
assert!(
hf_qmf_energy > 0.001,
"HF QMF energy too low after limiter injection: {hf_qmf_energy}"
);
assert!(
max_abs.is_finite() && max_abs < 1.0e6,
"limiter let an unbounded value through: max |q| = {max_abs}"
);
assert!(state.sine_idx_sb_prev.is_some());
assert!(state.noise.prev.is_some());
assert!(state.tone.prev.is_some());
}
#[test]
fn limiter_caps_sig_gain_against_runaway_baseline() {
let est_sig: Vec<Vec<f32>> = vec![vec![0.0]; 2];
let scf_sig: Vec<Vec<f32>> = vec![vec![100.0]; 2];
let scf_noise: Vec<Vec<f32>> = vec![vec![0.0]; 2];
let sig_gain = compute_sig_gains(&est_sig, &scf_sig, &scf_noise);
for row in sig_gain.iter().take(2) {
assert!((row[0] - 10.0).abs() < 1e-3);
}
let sbg_lim = vec![0u32, 2u32];
let sine = vec![vec![0.0_f32]; 2];
let noise = vec![vec![0.0_f32]; 2];
let out = crate::aspx_limiter::run(
&est_sig, &scf_sig, &sig_gain, &sine, &noise, &sbg_lim, 0, 2, 99, None,
);
for row in out.sig_gain_sb_adj.iter().take(2) {
assert!(row[0].is_finite());
assert!(
row[0]
<= crate::aspx_limiter::MAX_SIG_GAIN * crate::aspx_limiter::MAX_BOOST_FACT
+ 1.0,
"sig_gain_sb_adj exceeded the documented cap: {}",
row[0]
);
}
}
#[allow(dead_code)]
fn fixvar_framing(_t: u32, var_bord_right: u8, rel_bord_right: &[u8]) -> AspxFraming {
let num_rel = rel_bord_right.len() as u8;
AspxFraming {
int_class: AspxIntClass::FixVar,
num_env: (num_rel + 1) as u32,
num_noise: 1,
freq_res: vec![true; (num_rel + 1) as usize],
var_bord_left: None,
var_bord_right: Some(var_bord_right),
num_rel_left: 0,
num_rel_right: num_rel,
rel_bord_left: vec![],
rel_bord_right: rel_bord_right.to_vec(),
tsg_ptr: None,
}
}
#[test]
fn derive_fixvar_atsg_two_env_no_rel() {
let frm = AspxFraming {
int_class: AspxIntClass::FixVar,
num_env: 1,
num_noise: 1,
freq_res: vec![true],
var_bord_left: None,
var_bord_right: Some(4),
num_rel_left: 0,
num_rel_right: 0,
rel_bord_left: vec![],
rel_bord_right: vec![],
tsg_ptr: None,
};
let borders = derive_fixvar_atsg(16, &frm).unwrap();
assert_eq!(borders, vec![12, 16]);
assert_eq!(borders.len(), 2); }
#[test]
fn derive_fixvar_atsg_three_env_one_rel() {
let frm = AspxFraming {
int_class: AspxIntClass::FixVar,
num_env: 2,
num_noise: 1,
freq_res: vec![true; 2],
var_bord_left: None,
var_bord_right: Some(2),
num_rel_left: 0,
num_rel_right: 1,
rel_bord_left: vec![],
rel_bord_right: vec![4],
tsg_ptr: None,
};
let borders = derive_fixvar_atsg(16, &frm).unwrap();
assert_eq!(borders, vec![10, 14, 16]);
assert_eq!(borders.len(), 3); }
#[test]
fn derive_fixvar_atsg_rejects_inconsistent_num_env() {
let frm = AspxFraming {
int_class: AspxIntClass::FixVar,
num_env: 3,
num_noise: 1,
freq_res: vec![true; 3],
var_bord_left: None,
var_bord_right: Some(2),
num_rel_left: 0,
num_rel_right: 0,
rel_bord_left: vec![],
rel_bord_right: vec![],
tsg_ptr: None,
};
assert!(derive_fixvar_atsg(16, &frm).is_none());
}
#[test]
fn derive_varfix_atsg_two_env_no_rel() {
let frm = AspxFraming {
int_class: AspxIntClass::VarFix,
num_env: 1,
num_noise: 1,
freq_res: vec![true],
var_bord_left: Some(4),
var_bord_right: None,
num_rel_left: 0,
num_rel_right: 0,
rel_bord_left: vec![],
rel_bord_right: vec![],
tsg_ptr: None,
};
let borders = derive_varfix_atsg(16, &frm).unwrap();
assert_eq!(borders, vec![4, 16]);
assert_eq!(borders.len(), 2); }
#[test]
fn derive_varfix_atsg_three_env_one_rel() {
let frm = AspxFraming {
int_class: AspxIntClass::VarFix,
num_env: 2,
num_noise: 1,
freq_res: vec![true; 2],
var_bord_left: Some(3),
var_bord_right: None,
num_rel_left: 1,
num_rel_right: 0,
rel_bord_left: vec![5],
rel_bord_right: vec![],
tsg_ptr: None,
};
let borders = derive_varfix_atsg(16, &frm).unwrap();
assert_eq!(borders, vec![3, 8, 16]);
assert_eq!(borders.len(), 3); }
#[test]
fn derive_varfix_atsg_rejects_out_of_range_var_bord() {
let frm = AspxFraming {
int_class: AspxIntClass::VarFix,
num_env: 1,
num_noise: 1,
freq_res: vec![true],
var_bord_left: Some(16),
var_bord_right: None,
num_rel_left: 0,
num_rel_right: 0,
rel_bord_left: vec![],
rel_bord_right: vec![],
tsg_ptr: None,
};
assert!(derive_varfix_atsg(16, &frm).is_none());
}
#[test]
fn derive_varvar_atsg_one_env_no_rels() {
let frm = AspxFraming {
int_class: AspxIntClass::VarVar,
num_env: 1,
num_noise: 1,
freq_res: vec![true],
var_bord_left: Some(4),
var_bord_right: Some(4),
num_rel_left: 0,
num_rel_right: 0,
rel_bord_left: vec![],
rel_bord_right: vec![],
tsg_ptr: None,
};
let borders = derive_varvar_atsg(16, &frm).unwrap();
assert_eq!(borders, vec![4, 16]);
assert_eq!(borders.len(), frm.num_env as usize + 1);
for w in borders.windows(2) {
assert!(w[0] < w[1]);
}
}
#[test]
fn derive_varvar_atsg_two_env_one_right_rel() {
let frm = AspxFraming {
int_class: AspxIntClass::VarVar,
num_env: 2,
num_noise: 1,
freq_res: vec![true; 2],
var_bord_left: Some(2),
var_bord_right: Some(3),
num_rel_left: 0,
num_rel_right: 1,
rel_bord_left: vec![],
rel_bord_right: vec![4],
tsg_ptr: None,
};
let borders = derive_varvar_atsg(16, &frm).unwrap();
assert_eq!(borders, vec![2, 9, 16]);
assert_eq!(borders.len(), 3); }
#[test]
fn derive_atsg_borders_dispatches_fixfix() {
let frm = AspxFraming {
int_class: AspxIntClass::FixFix,
num_env: 2,
num_noise: 1,
freq_res: vec![true; 2],
var_bord_left: None,
var_bord_right: None,
num_rel_left: 0,
num_rel_right: 0,
rel_bord_left: vec![],
rel_bord_right: vec![],
tsg_ptr: None,
};
let (sig, noise) = derive_atsg_borders(16, &frm).unwrap();
assert_eq!(sig, vec![0, 8, 16]);
assert_eq!(noise, vec![0, 16]);
}
#[test]
fn derive_atsg_borders_fixvar_noise_one_env() {
let frm = AspxFraming {
int_class: AspxIntClass::FixVar,
num_env: 1,
num_noise: 1,
freq_res: vec![true],
var_bord_left: None,
var_bord_right: Some(4),
num_rel_left: 0,
num_rel_right: 0,
rel_bord_left: vec![],
rel_bord_right: vec![],
tsg_ptr: None,
};
let (sig, noise) = derive_atsg_borders(16, &frm).unwrap();
assert_eq!(sig, vec![12, 16]);
assert_eq!(noise, vec![0, 16]); }
#[test]
fn compute_companding_levels_returns_constant_on_constant_input() {
let q: Vec<Vec<(f32, f32)>> = vec![vec![(0.4_f32, 0.2_f32); 16]; 64];
let levels = compute_companding_levels(&q, 8, 32);
assert_eq!(levels.len(), 16);
for &l in levels.iter() {
assert!(
(l - 0.45525_f32).abs() < 1e-5,
"expected L=0.45525 on constant input, got {l}"
);
}
}
#[test]
fn compute_companding_levels_returns_empty_on_degenerate_range() {
let q: Vec<Vec<(f32, f32)>> = vec![vec![(1.0_f32, 1.0_f32); 8]; 64];
assert!(compute_companding_levels(&q, 16, 16).is_empty());
assert!(compute_companding_levels(&q, 32, 8).is_empty());
assert!(compute_companding_levels(&q, 0, 65).is_empty());
}
#[test]
fn levels_to_scales_per_slot_matches_legacy_per_slot_branch() {
let n_slots = 8;
let mut q: Vec<Vec<(f32, f32)>> = (0..64)
.map(|sb| {
(0..n_slots)
.map(|ts| {
let v = (sb as f32 + ts as f32 + 1.0) * 0.05;
(v, v * 0.5)
})
.collect()
})
.collect();
let q_baseline = q.clone();
apply_companding_on_qmf_with_mode(&mut q, 4, 16, CompandingMode::PerSlot);
let mut q2 = q_baseline.clone();
let levels = compute_companding_levels(&q2, 4, 16);
let scales = levels_to_scales_per_slot(&levels);
apply_companding_scales_on_qmf(&mut q2, 4, 16, &scales);
for sb in 0..64 {
for ts in 0..n_slots {
let (a_re, a_im) = q[sb][ts];
let (b_re, b_im) = q2[sb][ts];
assert!(
(a_re - b_re).abs() < 1e-5 && (a_im - b_im).abs() < 1e-5,
"split path diverges at (sb={sb}, ts={ts}): legacy=({a_re},{a_im}) split=({b_re},{b_im})"
);
}
}
}
#[test]
fn apply_synchronised_companding_collapses_to_per_slot_for_single_channel() {
let n_slots = 12;
let mut q1: Vec<Vec<(f32, f32)>> = (0..64)
.map(|sb| {
(0..n_slots)
.map(|ts| {
let v = ((sb + 1) as f32) * (1.0 + 0.1 * ts as f32) * 0.05;
(v, v * 0.3)
})
.collect()
})
.collect();
let mut q2 = q1.clone();
apply_companding_on_qmf_with_mode(&mut q1, 6, 24, CompandingMode::SyncPerSlot);
let mut view: Vec<SyncCompandingEntry<'_>> = vec![(&mut q2, 6, 24)];
apply_synchronised_companding_across_channels(&mut view, CompandingMode::SyncPerSlot);
for sb in 0..64 {
for ts in 0..n_slots {
let (a_re, a_im) = q1[sb][ts];
let (b_re, b_im) = q2[sb][ts];
let scale = a_re.abs().max(a_im.abs()).max(1e-3);
let tol = 1e-4 * scale;
assert!(
(a_re - b_re).abs() < tol && (a_im - b_im).abs() < tol,
"M=1 sync diverges from per-slot at (sb={sb}, ts={ts}): per-slot=({a_re},{a_im}) sync=({b_re},{b_im})"
);
}
}
}
#[test]
fn apply_synchronised_companding_uses_geometric_mean_across_channels() {
let n_slots = 6;
let mut q_lo: Vec<Vec<(f32, f32)>> = (0..64)
.map(|_sb| (0..n_slots).map(|_| (0.1_f32, 0.05_f32)).collect())
.collect();
let mut q_hi: Vec<Vec<(f32, f32)>> = (0..64)
.map(|_sb| (0..n_slots).map(|_| (0.9_f32, 0.45_f32)).collect())
.collect();
let q_lo_orig = q_lo.clone();
let q_hi_orig = q_hi.clone();
let l_lo = compute_companding_levels(&q_lo, 4, 16);
let l_hi = compute_companding_levels(&q_hi, 4, 16);
let exp_g = (1.0 - COMPANDING_ALPHA) / COMPANDING_ALPHA;
let g_lo_ts: Vec<f32> = l_lo
.iter()
.map(|&l| if l > 0.0 { l.powf(exp_g) } else { 1.0 })
.collect();
let g_hi_ts: Vec<f32> = l_hi
.iter()
.map(|&l| if l > 0.0 { l.powf(exp_g) } else { 1.0 })
.collect();
let g_synch_ts: Vec<f32> = g_lo_ts
.iter()
.zip(g_hi_ts.iter())
.map(|(&a, &b)| (a * b).sqrt())
.collect();
let mut view: Vec<SyncCompandingEntry<'_>> = vec![(&mut q_lo, 4, 16), (&mut q_hi, 4, 16)];
apply_synchronised_companding_across_channels(&mut view, CompandingMode::SyncPerSlot);
for ts in 0..n_slots {
let ratio_lo = q_lo[5][ts].0 / q_lo_orig[5][ts].0;
let ratio_hi = q_hi[5][ts].0 / q_hi_orig[5][ts].0;
let expected = g_synch_ts[ts] * companding_g();
assert!(
(ratio_lo - expected).abs() < 1e-4,
"sync scale mismatch on lo channel at ts={ts}: got {ratio_lo}, expected {expected}"
);
assert!(
(ratio_hi - expected).abs() < 1e-4,
"sync scale mismatch on hi channel at ts={ts}: got {ratio_hi}, expected {expected}"
);
assert!(
(ratio_lo - ratio_hi).abs() < 1e-4,
"lo/hi sync scales must match at ts={ts}: lo={ratio_lo}, hi={ratio_hi}"
);
}
}
#[test]
fn apply_synchronised_companding_averaged_broadcasts_one_constant_per_channel() {
let n_slots = 8;
let mut q1: Vec<Vec<(f32, f32)>> = (0..64)
.map(|_sb| {
(0..n_slots)
.map(|ts| {
let v = (1.0 + 0.2 * ts as f32) * 0.1;
(v, v * 0.3)
})
.collect()
})
.collect();
let mut q2: Vec<Vec<(f32, f32)>> = (0..64)
.map(|_sb| {
(0..n_slots)
.map(|ts| {
let v = (2.0 - 0.1 * ts as f32) * 0.2;
(v, v * 0.4)
})
.collect()
})
.collect();
let q1_orig = q1.clone();
let q2_orig = q2.clone();
let mut view: Vec<SyncCompandingEntry<'_>> = vec![(&mut q1, 4, 16), (&mut q2, 4, 16)];
apply_synchronised_companding_across_channels(&mut view, CompandingMode::SyncAveraged);
let ratio_q1_first = q1[5][0].0 / q1_orig[5][0].0;
let ratio_q2_first = q2[5][0].0 / q2_orig[5][0].0;
assert!(
(ratio_q1_first - ratio_q2_first).abs() < 1e-4,
"averaged sync scale must match across channels: {ratio_q1_first} vs {ratio_q2_first}"
);
for ts in 1..n_slots {
let r1 = q1[5][ts].0 / q1_orig[5][ts].0;
let r2 = q2[5][ts].0 / q2_orig[5][ts].0;
assert!(
(r1 - ratio_q1_first).abs() < 1e-4,
"averaged scale must be constant across slots on q1 (ts={ts}): {r1} vs {ratio_q1_first}"
);
assert!(
(r2 - ratio_q2_first).abs() < 1e-4,
"averaged scale must be constant across slots on q2 (ts={ts}): {r2} vs {ratio_q2_first}"
);
}
}
#[test]
fn apply_synchronised_companding_is_noop_on_non_sync_modes() {
let mut q: Vec<Vec<(f32, f32)>> = (0..64)
.map(|_| (0..8).map(|_| (0.5_f32, 0.3_f32)).collect())
.collect();
let q_orig = q.clone();
for mode in [
CompandingMode::Off,
CompandingMode::PerSlot,
CompandingMode::Averaged,
] {
let mut view: Vec<SyncCompandingEntry<'_>> = vec![(&mut q, 4, 16)];
apply_synchronised_companding_across_channels(&mut view, mode);
assert_eq!(q, q_orig, "synced helper must no-op on mode {mode:?}");
}
}
#[test]
fn apply_synchronised_companding_handles_empty_inputs() {
let mut empty: Vec<SyncCompandingEntry<'_>> = Vec::new();
apply_synchronised_companding_across_channels(&mut empty, CompandingMode::SyncPerSlot);
let mut q: Vec<Vec<(f32, f32)>> = vec![vec![(1.0_f32, 1.0_f32); 4]; 64];
let q_orig = q.clone();
let mut view: Vec<SyncCompandingEntry<'_>> = vec![(&mut q, 32, 32)];
apply_synchronised_companding_across_channels(&mut view, CompandingMode::SyncPerSlot);
assert_eq!(q, q_orig);
}
}