use oxideav_core::bits::BitWriter;
use crate::acpl_huffman;
use crate::asf_data::AsfSections;
use crate::aspx;
use crate::aspx_huffman;
use crate::encoder_asf::{
build_band_codebook_cost_table, build_sections_from_dp, compute_snf_dpcm_for_zero_quant_bands,
dp_optimise_sections, pick_best_codebook_for_band, write_scalefac_data, write_section_data,
write_snf_data, write_spectral_data_sections,
};
fn pick_min_len_cw(len: &[u8], cw: &[u32]) -> (u32, u32) {
debug_assert_eq!(len.len(), cw.len());
let (idx, min_len) = len
.iter()
.enumerate()
.min_by_key(|(_, &l)| l)
.map(|(i, &l)| (i, l))
.expect("hcb table must be non-empty");
(cw[idx], min_len as u32)
}
fn pick_zero_delta_cw(len: &[u8], cw: &[u32], cb_off: usize) -> (u32, u32) {
debug_assert_eq!(len.len(), cw.len());
debug_assert!(cb_off < len.len());
(cw[cb_off], len[cb_off] as u32)
}
fn write_aspx_sig_f0(bw: &mut BitWriter, quant: aspx::AspxQuantStep, stereo: aspx::AspxStereoMode) {
let (cw, len) = match (quant, stereo) {
(aspx::AspxQuantStep::Fine, aspx::AspxStereoMode::Level) => pick_min_len_cw(
aspx_huffman::ASPX_HCB_ENV_LEVEL_15_F0_LEN,
aspx_huffman::ASPX_HCB_ENV_LEVEL_15_F0_CW,
),
(aspx::AspxQuantStep::Fine, aspx::AspxStereoMode::Balance) => pick_min_len_cw(
aspx_huffman::ASPX_HCB_ENV_BALANCE_15_F0_LEN,
aspx_huffman::ASPX_HCB_ENV_BALANCE_15_F0_CW,
),
(aspx::AspxQuantStep::Coarse, aspx::AspxStereoMode::Level) => pick_min_len_cw(
aspx_huffman::ASPX_HCB_ENV_LEVEL_30_F0_LEN,
aspx_huffman::ASPX_HCB_ENV_LEVEL_30_F0_CW,
),
(aspx::AspxQuantStep::Coarse, aspx::AspxStereoMode::Balance) => pick_min_len_cw(
aspx_huffman::ASPX_HCB_ENV_BALANCE_30_F0_LEN,
aspx_huffman::ASPX_HCB_ENV_BALANCE_30_F0_CW,
),
};
bw.write_u32(cw, len);
}
fn write_aspx_sig_df_zero(
bw: &mut BitWriter,
quant: aspx::AspxQuantStep,
stereo: aspx::AspxStereoMode,
) {
let (cw, len) = match (quant, stereo) {
(aspx::AspxQuantStep::Fine, aspx::AspxStereoMode::Level) => pick_zero_delta_cw(
aspx_huffman::ASPX_HCB_ENV_LEVEL_15_DF_LEN,
aspx_huffman::ASPX_HCB_ENV_LEVEL_15_DF_CW,
70,
),
(aspx::AspxQuantStep::Fine, aspx::AspxStereoMode::Balance) => pick_zero_delta_cw(
aspx_huffman::ASPX_HCB_ENV_BALANCE_15_DF_LEN,
aspx_huffman::ASPX_HCB_ENV_BALANCE_15_DF_CW,
24,
),
(aspx::AspxQuantStep::Coarse, aspx::AspxStereoMode::Level) => pick_zero_delta_cw(
aspx_huffman::ASPX_HCB_ENV_LEVEL_30_DF_LEN,
aspx_huffman::ASPX_HCB_ENV_LEVEL_30_DF_CW,
35,
),
(aspx::AspxQuantStep::Coarse, aspx::AspxStereoMode::Balance) => pick_zero_delta_cw(
aspx_huffman::ASPX_HCB_ENV_BALANCE_30_DF_LEN,
aspx_huffman::ASPX_HCB_ENV_BALANCE_30_DF_CW,
12,
),
};
bw.write_u32(cw, len);
}
fn write_aspx_noise_f0(bw: &mut BitWriter, stereo: aspx::AspxStereoMode) {
let (cw, len) = match stereo {
aspx::AspxStereoMode::Level => pick_min_len_cw(
aspx_huffman::ASPX_HCB_NOISE_LEVEL_F0_LEN,
aspx_huffman::ASPX_HCB_NOISE_LEVEL_F0_CW,
),
aspx::AspxStereoMode::Balance => pick_min_len_cw(
aspx_huffman::ASPX_HCB_NOISE_BALANCE_F0_LEN,
aspx_huffman::ASPX_HCB_NOISE_BALANCE_F0_CW,
),
};
bw.write_u32(cw, len);
}
fn write_aspx_noise_df_zero(bw: &mut BitWriter, stereo: aspx::AspxStereoMode) {
let (cw, len) = match stereo {
aspx::AspxStereoMode::Level => pick_zero_delta_cw(
aspx_huffman::ASPX_HCB_NOISE_LEVEL_DF_LEN,
aspx_huffman::ASPX_HCB_NOISE_LEVEL_DF_CW,
29,
),
aspx::AspxStereoMode::Balance => pick_zero_delta_cw(
aspx_huffman::ASPX_HCB_NOISE_BALANCE_DF_LEN,
aspx_huffman::ASPX_HCB_NOISE_BALANCE_DF_CW,
12,
),
};
bw.write_u32(cw, len);
}
fn acpl_hcb_arrays(
dt: crate::acpl::AcplDataType,
qm: crate::acpl::AcplQuantMode,
ht: crate::acpl::AcplHcbType,
) -> (&'static [u8], &'static [u32], i32) {
use crate::acpl::AcplDataType::*;
use crate::acpl::AcplHcbType::*;
use crate::acpl::AcplQuantMode::*;
use acpl_huffman::*;
match (dt, qm, ht) {
(Alpha, Coarse, F0) => (ACPL_HCB_ALPHA_COARSE_F0_LEN, ACPL_HCB_ALPHA_COARSE_F0_CW, 8),
(Alpha, Fine, F0) => (ACPL_HCB_ALPHA_FINE_F0_LEN, ACPL_HCB_ALPHA_FINE_F0_CW, 16),
(Alpha, Coarse, Df) => (
ACPL_HCB_ALPHA_COARSE_DF_LEN,
ACPL_HCB_ALPHA_COARSE_DF_CW,
16,
),
(Alpha, Fine, Df) => (ACPL_HCB_ALPHA_FINE_DF_LEN, ACPL_HCB_ALPHA_FINE_DF_CW, 32),
(Alpha, Coarse, Dt) => (
ACPL_HCB_ALPHA_COARSE_DT_LEN,
ACPL_HCB_ALPHA_COARSE_DT_CW,
16,
),
(Alpha, Fine, Dt) => (ACPL_HCB_ALPHA_FINE_DT_LEN, ACPL_HCB_ALPHA_FINE_DT_CW, 32),
(Beta, Coarse, F0) => (ACPL_HCB_BETA_COARSE_F0_LEN, ACPL_HCB_BETA_COARSE_F0_CW, 0),
(Beta, Fine, F0) => (ACPL_HCB_BETA_FINE_F0_LEN, ACPL_HCB_BETA_FINE_F0_CW, 0),
(Beta, Coarse, Df) => (ACPL_HCB_BETA_COARSE_DF_LEN, ACPL_HCB_BETA_COARSE_DF_CW, 4),
(Beta, Fine, Df) => (ACPL_HCB_BETA_FINE_DF_LEN, ACPL_HCB_BETA_FINE_DF_CW, 8),
(Beta, Coarse, Dt) => (ACPL_HCB_BETA_COARSE_DT_LEN, ACPL_HCB_BETA_COARSE_DT_CW, 4),
(Beta, Fine, Dt) => (ACPL_HCB_BETA_FINE_DT_LEN, ACPL_HCB_BETA_FINE_DT_CW, 8),
(Beta3, Coarse, F0) => (ACPL_HCB_BETA3_COARSE_F0_LEN, ACPL_HCB_BETA3_COARSE_F0_CW, 4),
(Beta3, Fine, F0) => (ACPL_HCB_BETA3_FINE_F0_LEN, ACPL_HCB_BETA3_FINE_F0_CW, 8),
(Beta3, Coarse, Df) => (ACPL_HCB_BETA3_COARSE_DF_LEN, ACPL_HCB_BETA3_COARSE_DF_CW, 8),
(Beta3, Fine, Df) => (ACPL_HCB_BETA3_FINE_DF_LEN, ACPL_HCB_BETA3_FINE_DF_CW, 16),
(Beta3, Coarse, Dt) => (ACPL_HCB_BETA3_COARSE_DT_LEN, ACPL_HCB_BETA3_COARSE_DT_CW, 8),
(Beta3, Fine, Dt) => (ACPL_HCB_BETA3_FINE_DT_LEN, ACPL_HCB_BETA3_FINE_DT_CW, 16),
(Gamma, Coarse, F0) => (
ACPL_HCB_GAMMA_COARSE_F0_LEN,
ACPL_HCB_GAMMA_COARSE_F0_CW,
10,
),
(Gamma, Fine, F0) => (ACPL_HCB_GAMMA_FINE_F0_LEN, ACPL_HCB_GAMMA_FINE_F0_CW, 20),
(Gamma, Coarse, Df) => (
ACPL_HCB_GAMMA_COARSE_DF_LEN,
ACPL_HCB_GAMMA_COARSE_DF_CW,
20,
),
(Gamma, Fine, Df) => (ACPL_HCB_GAMMA_FINE_DF_LEN, ACPL_HCB_GAMMA_FINE_DF_CW, 40),
(Gamma, Coarse, Dt) => (
ACPL_HCB_GAMMA_COARSE_DT_LEN,
ACPL_HCB_GAMMA_COARSE_DT_CW,
20,
),
(Gamma, Fine, Dt) => (ACPL_HCB_GAMMA_FINE_DT_LEN, ACPL_HCB_GAMMA_FINE_DT_CW, 40),
}
}
fn write_acpl_f0_zero(
bw: &mut BitWriter,
dt: crate::acpl::AcplDataType,
qm: crate::acpl::AcplQuantMode,
) {
let (len, cw, cb_off) = acpl_hcb_arrays(dt, qm, crate::acpl::AcplHcbType::F0);
let idx = cb_off as usize;
bw.write_u32(cw[idx], len[idx] as u32);
}
fn write_acpl_df_zero(
bw: &mut BitWriter,
dt: crate::acpl::AcplDataType,
qm: crate::acpl::AcplQuantMode,
) {
let (len, cw, cb_off) = acpl_hcb_arrays(dt, qm, crate::acpl::AcplHcbType::Df);
let idx = cb_off as usize;
bw.write_u32(cw[idx], len[idx] as u32);
}
pub fn write_aspx_config(bw: &mut BitWriter, cfg: &aspx::AspxConfig) {
let qmode_bit = match cfg.quant_mode_env {
aspx::AspxQuantStep::Fine => 0,
aspx::AspxQuantStep::Coarse => 1,
};
let scale_bit = match cfg.master_freq_scale {
aspx::AspxMasterFreqScale::LowRes => 0,
aspx::AspxMasterFreqScale::HighRes => 1,
};
let freq_res_bits = match cfg.freq_res_mode {
aspx::AspxFreqResMode::Signalled => 0u32,
aspx::AspxFreqResMode::Low => 1,
aspx::AspxFreqResMode::DurationDependent => 2,
aspx::AspxFreqResMode::High => 3,
};
bw.write_u32(qmode_bit, 1);
bw.write_u32(cfg.start_freq as u32, 3);
bw.write_u32(cfg.stop_freq as u32, 2);
bw.write_u32(scale_bit, 1);
bw.write_bit(cfg.interpolation);
bw.write_bit(cfg.preflat);
bw.write_bit(cfg.limiter);
bw.write_u32(cfg.noise_sbg as u32, 2);
bw.write_u32(cfg.num_env_bits_fixfix as u32, 1);
bw.write_u32(freq_res_bits, 2);
}
pub fn write_acpl_config_2ch(
bw: &mut BitWriter,
num_param_bands_id: u8,
quant_mode_0: crate::acpl::AcplQuantMode,
quant_mode_1: crate::acpl::AcplQuantMode,
) {
bw.write_u32(num_param_bands_id as u32 & 0b11, 2);
let qm0_bit = matches!(quant_mode_0, crate::acpl::AcplQuantMode::Coarse);
let qm1_bit = matches!(quant_mode_1, crate::acpl::AcplQuantMode::Coarse);
bw.write_bit(qm0_bit);
bw.write_bit(qm1_bit);
}
pub fn write_companding_control_2ch_sync_on(bw: &mut BitWriter) {
bw.write_bit(true); bw.write_bit(true); }
type StereoChannelAnalysis = (Vec<i32>, Vec<i32>, Vec<u32>, AsfSections, Option<Vec<i32>>);
fn prepare_stereo_channel(coeffs: &[f32], sfbo: &[u16], max_sfb: u32) -> StereoChannelAnalysis {
let local_end = sfbo[max_sfb as usize] as usize;
let mut qspec = vec![0i32; local_end];
let mut sf_per_band = vec![100i32; max_sfb as usize];
let mut max_quant_idx = vec![0u32; max_sfb as usize];
let mut natural_q_per_band: Vec<Vec<i32>> = Vec::with_capacity(max_sfb as usize);
for sfb in 0..max_sfb as usize {
let a = sfbo[sfb] as usize;
let b = sfbo[sfb + 1] as usize;
let band = &coeffs[a..b.min(coeffs.len())];
let (_cb_picked, sf, q, _cost) = pick_best_codebook_for_band(band);
sf_per_band[sfb] = sf;
let mut max_q: u32 = 0;
for (i, &qi) in q.iter().enumerate() {
qspec[a + i] = qi;
max_q = max_q.max(qi.unsigned_abs());
}
max_quant_idx[sfb] = max_q;
natural_q_per_band.push(q);
}
let cost_table = build_band_codebook_cost_table(&natural_q_per_band);
let dp_sections = dp_optimise_sections(&cost_table, 16);
let sections = build_sections_from_dp(&dp_sections, max_sfb);
let snf = compute_snf_dpcm_for_zero_quant_bands(
coeffs,
sfbo,
max_sfb,
§ions.sfb_cb,
&max_quant_idx,
);
(qspec, sf_per_band, max_quant_idx, sections, snf)
}
fn write_stereo_split_data(
bw: &mut BitWriter,
transform_length: u32,
max_sfb: u32,
coeffs_l: &[f32],
coeffs_r: &[f32],
) {
let sfbo = crate::sfb_offset::sfb_offset_48(transform_length)
.expect("encoder: unsupported transform_length");
let (n_msfb_bits, _, _) =
crate::tables::n_msfb_bits_48(transform_length).expect("encoder: bad tl");
let analysis_l = prepare_stereo_channel(coeffs_l, sfbo, max_sfb);
let analysis_r = prepare_stereo_channel(coeffs_r, sfbo, max_sfb);
bw.write_bit(false);
bw.write_bit(false);
bw.write_bit(true); bw.write_u32(max_sfb, n_msfb_bits); bw.write_bit(false);
bw.write_bit(true);
bw.write_u32(max_sfb, n_msfb_bits);
let (qspec_l, sf_l, max_q_l, sections_l, snf_l) = &analysis_l;
write_section_data(bw, sections_l);
write_spectral_data_sections(bw, qspec_l, sfbo, sections_l);
write_scalefac_data(bw, sf_l, §ions_l.sfb_cb, max_q_l, max_sfb);
write_snf_data(bw, snf_l.as_deref(), §ions_l.sfb_cb, max_q_l, max_sfb);
let (qspec_r, sf_r, max_q_r, sections_r, snf_r) = &analysis_r;
write_section_data(bw, sections_r);
write_spectral_data_sections(bw, qspec_r, sfbo, sections_r);
write_scalefac_data(bw, sf_r, §ions_r.sfb_cb, max_q_r, max_sfb);
write_snf_data(bw, snf_r.as_deref(), §ions_r.sfb_cb, max_q_r, max_sfb);
}
fn write_aspx_data_2ch_minimal(
bw: &mut BitWriter,
cfg: &aspx::AspxConfig,
) -> Result<(), &'static str> {
let xover: u32 = 0;
bw.write_u32(xover, 3);
bw.write_bit(false);
let envbits = cfg.fixfix_tmp_num_env_bits();
bw.write_u32(0, envbits); if cfg.signals_freq_res() {
bw.write_bit(false); }
bw.write_bit(true);
bw.write_bit(false); bw.write_bit(false); bw.write_bit(false);
bw.write_bit(false);
let tables = aspx::derive_aspx_frequency_tables(cfg, xover)
.map_err(|_| "encoder: aspx frequency-tables derivation failed")?;
let counts = tables.counts;
for _ in 0..counts.num_sbg_noise {
bw.write_u32(0, 2);
}
bw.write_bit(false);
bw.write_bit(false);
bw.write_bit(false);
bw.write_bit(false);
let num_sbg_sig = if cfg.signals_freq_res() {
counts.num_sbg_sig_lowres
} else {
counts.num_sbg_sig_highres
};
let num_sbg_noise = counts.num_sbg_noise;
let qmode_ch0 = if cfg.fixfix_tmp_num_env_bits() == 1 {
aspx::AspxQuantStep::Fine
} else {
cfg.quant_mode_env
};
if num_sbg_sig >= 1 {
write_aspx_sig_f0(bw, qmode_ch0, aspx::AspxStereoMode::Level);
}
for _ in 1..num_sbg_sig {
write_aspx_sig_df_zero(bw, qmode_ch0, aspx::AspxStereoMode::Level);
}
let qmode_ch1 = qmode_ch0; if num_sbg_sig >= 1 {
write_aspx_sig_f0(bw, qmode_ch1, aspx::AspxStereoMode::Balance);
}
for _ in 1..num_sbg_sig {
write_aspx_sig_df_zero(bw, qmode_ch1, aspx::AspxStereoMode::Balance);
}
if num_sbg_noise >= 1 {
write_aspx_noise_f0(bw, aspx::AspxStereoMode::Level);
}
for _ in 1..num_sbg_noise {
write_aspx_noise_df_zero(bw, aspx::AspxStereoMode::Level);
}
if num_sbg_noise >= 1 {
write_aspx_noise_f0(bw, aspx::AspxStereoMode::Balance);
}
for _ in 1..num_sbg_noise {
write_aspx_noise_df_zero(bw, aspx::AspxStereoMode::Balance);
}
Ok(())
}
fn write_acpl_data_2ch_minimal(
bw: &mut BitWriter,
num_bands: u32,
quant_mode_0: crate::acpl::AcplQuantMode,
quant_mode_1: crate::acpl::AcplQuantMode,
) {
bw.write_bit(false);
bw.write_bit(false);
let emit_one =
|bw: &mut BitWriter, dt: crate::acpl::AcplDataType, qm: crate::acpl::AcplQuantMode| {
bw.write_bit(false); if num_bands >= 1 {
write_acpl_f0_zero(bw, dt, qm);
}
for _ in 1..num_bands {
write_acpl_df_zero(bw, dt, qm);
}
};
emit_one(bw, crate::acpl::AcplDataType::Alpha, quant_mode_0);
emit_one(bw, crate::acpl::AcplDataType::Alpha, quant_mode_0);
emit_one(bw, crate::acpl::AcplDataType::Beta, quant_mode_0);
emit_one(bw, crate::acpl::AcplDataType::Beta, quant_mode_0);
emit_one(bw, crate::acpl::AcplDataType::Beta3, quant_mode_0);
for _ in 0..6 {
emit_one(bw, crate::acpl::AcplDataType::Gamma, quant_mode_1);
}
}
fn write_acpl_data_2ch_real_beta(
bw: &mut BitWriter,
num_bands: u32,
quant_mode_0: crate::acpl::AcplQuantMode,
quant_mode_1: crate::acpl::AcplQuantMode,
beta1_q_per_band: &[i32],
beta2_q_per_band: &[i32],
) {
bw.write_bit(false);
bw.write_bit(false);
let emit_zero =
|bw: &mut BitWriter, dt: crate::acpl::AcplDataType, qm: crate::acpl::AcplQuantMode| {
bw.write_bit(false); if num_bands >= 1 {
write_acpl_f0_zero(bw, dt, qm);
}
for _ in 1..num_bands {
write_acpl_df_zero(bw, dt, qm);
}
};
let emit_real_beta = |bw: &mut BitWriter, qm: crate::acpl::AcplQuantMode, beta_q: &[i32]| {
bw.write_bit(false); let mut prev_q: i32 = 0;
let mut first = true;
for pb in 0..num_bands {
let b_q = beta_q.get(pb as usize).copied().unwrap_or(0);
if first {
write_acpl_beta_f0_value(bw, qm, b_q);
first = false;
} else {
let delta = b_q - prev_q;
write_acpl_beta_df_value(bw, qm, delta);
}
prev_q = b_q;
}
};
emit_zero(bw, crate::acpl::AcplDataType::Alpha, quant_mode_0);
emit_zero(bw, crate::acpl::AcplDataType::Alpha, quant_mode_0);
emit_real_beta(bw, quant_mode_0, beta1_q_per_band);
emit_real_beta(bw, quant_mode_0, beta2_q_per_band);
emit_zero(bw, crate::acpl::AcplDataType::Beta3, quant_mode_0);
for _ in 0..6 {
emit_zero(bw, crate::acpl::AcplDataType::Gamma, quant_mode_1);
}
}
#[allow(clippy::too_many_arguments)]
pub fn build_5_x_acpl3_body_from_pcm_spectra(
transform_length: u32,
max_sfb: u32,
max_sfb_lfe: Option<u32>,
b_iframe: bool,
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_lfe: Option<&[f32]>,
aspx_cfg: &aspx::AspxConfig,
acpl_num_param_bands_id: u8,
acpl_qm0: crate::acpl::AcplQuantMode,
acpl_qm1: crate::acpl::AcplQuantMode,
pad_target_bytes: usize,
) -> Vec<u8> {
let acpl_num_bands = crate::acpl::num_param_bands_from_id(acpl_num_param_bands_id as u32);
let mut bw = BitWriter::new();
let audio_size = pad_target_bytes as u32;
bw.write_u32(audio_size & 0x7FFF, 15);
bw.write_bit(false);
bw.align_to_byte();
bw.write_u32(4, 3);
if b_iframe {
write_aspx_config(&mut bw, aspx_cfg);
write_acpl_config_2ch(&mut bw, acpl_num_param_bands_id, acpl_qm0, acpl_qm1);
}
if let (Some(lfe), Some(m_lfe)) = (coeffs_lfe, max_sfb_lfe) {
write_lfe_mono_data(&mut bw, transform_length, m_lfe, lfe);
}
write_companding_control_2ch_sync_on(&mut bw);
write_stereo_split_data(&mut bw, transform_length, max_sfb, coeffs_l, coeffs_r);
if b_iframe {
write_aspx_data_2ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_acpl_data_2ch_minimal(&mut bw, acpl_num_bands, acpl_qm0, acpl_qm1);
}
bw.align_to_byte();
while bw.byte_len() < pad_target_bytes {
bw.write_u32(0, 8);
}
let mut bytes = bw.finish();
if bytes.len() > pad_target_bytes {
bytes.truncate(pad_target_bytes);
}
bytes
}
#[allow(clippy::too_many_arguments)]
pub fn build_5_x_acpl3_body_from_pcm_spectra_real_beta(
transform_length: u32,
max_sfb: u32,
max_sfb_lfe: Option<u32>,
b_iframe: bool,
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_lfe: Option<&[f32]>,
aspx_cfg: &aspx::AspxConfig,
acpl_num_param_bands_id: u8,
acpl_qm0: crate::acpl::AcplQuantMode,
acpl_qm1: crate::acpl::AcplQuantMode,
beta_scale: f32,
pad_target_bytes: usize,
) -> Vec<u8> {
let acpl_num_bands = crate::acpl::num_param_bands_from_id(acpl_num_param_bands_id as u32);
let beta1_q = extract_beta_q_per_band_carrier_energy(
coeffs_l,
transform_length,
acpl_num_bands,
0,
beta_scale,
acpl_qm0,
);
let beta2_q = extract_beta_q_per_band_carrier_energy(
coeffs_r,
transform_length,
acpl_num_bands,
0,
beta_scale,
acpl_qm0,
);
let mut bw = BitWriter::new();
let audio_size = pad_target_bytes as u32;
bw.write_u32(audio_size & 0x7FFF, 15);
bw.write_bit(false);
bw.align_to_byte();
bw.write_u32(4, 3);
if b_iframe {
write_aspx_config(&mut bw, aspx_cfg);
write_acpl_config_2ch(&mut bw, acpl_num_param_bands_id, acpl_qm0, acpl_qm1);
}
if let (Some(lfe), Some(m_lfe)) = (coeffs_lfe, max_sfb_lfe) {
write_lfe_mono_data(&mut bw, transform_length, m_lfe, lfe);
}
write_companding_control_2ch_sync_on(&mut bw);
write_stereo_split_data(&mut bw, transform_length, max_sfb, coeffs_l, coeffs_r);
if b_iframe {
write_aspx_data_2ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_acpl_data_2ch_real_beta(
&mut bw,
acpl_num_bands,
acpl_qm0,
acpl_qm1,
&beta1_q,
&beta2_q,
);
}
bw.align_to_byte();
while bw.byte_len() < pad_target_bytes {
bw.write_u32(0, 8);
}
let mut bytes = bw.finish();
if bytes.len() > pad_target_bytes {
bytes.truncate(pad_target_bytes);
}
bytes
}
fn write_lfe_mono_data(
bw: &mut BitWriter,
transform_length: u32,
max_sfb_lfe: u32,
coeffs_lfe: &[f32],
) {
let sfbo = crate::sfb_offset::sfb_offset_48(transform_length)
.expect("encoder: unsupported transform_length");
let (_n_msfb_bits, _, n_msfbl_bits) =
crate::tables::n_msfb_bits_48(transform_length).expect("encoder: bad tl");
assert!(
n_msfbl_bits > 0,
"encoder: LFE not permitted at transform_length = {transform_length}"
);
let n_msfbl_cap = (1u32 << n_msfbl_bits) - 1;
let max_sfb_lfe_clamped = max_sfb_lfe.min(n_msfbl_cap);
bw.write_bit(true);
bw.write_u32(max_sfb_lfe_clamped, n_msfbl_bits);
let (qspec, sf, max_q, sections, snf) =
prepare_stereo_channel(coeffs_lfe, sfbo, max_sfb_lfe_clamped);
write_section_data(bw, §ions);
write_spectral_data_sections(bw, &qspec, sfbo, §ions);
write_scalefac_data(bw, &sf, §ions.sfb_cb, &max_q, max_sfb_lfe_clamped);
write_snf_data(
bw,
snf.as_deref(),
§ions.sfb_cb,
&max_q,
max_sfb_lfe_clamped,
);
}
pub fn write_acpl_config_1ch_full(
bw: &mut BitWriter,
num_param_bands_id: u8,
quant_mode: crate::acpl::AcplQuantMode,
) {
bw.write_u32(num_param_bands_id as u32 & 0b11, 2);
bw.write_bit(matches!(quant_mode, crate::acpl::AcplQuantMode::Coarse));
}
fn write_two_channel_data(
bw: &mut BitWriter,
transform_length: u32,
max_sfb: u32,
coeffs_l: &[f32],
coeffs_r: &[f32],
) {
let sfbo = crate::sfb_offset::sfb_offset_48(transform_length)
.expect("encoder: unsupported transform_length");
let (n_msfb_bits, _, _) =
crate::tables::n_msfb_bits_48(transform_length).expect("encoder: bad tl");
bw.write_bit(true); bw.write_u32(max_sfb, n_msfb_bits); bw.write_u32(0, 2);
for coeffs in [coeffs_l, coeffs_r] {
let (qspec, sf, max_q, sections, snf) = prepare_stereo_channel(coeffs, sfbo, max_sfb);
write_section_data(bw, §ions);
write_spectral_data_sections(bw, &qspec, sfbo, §ions);
write_scalefac_data(bw, &sf, §ions.sfb_cb, &max_q, max_sfb);
write_snf_data(bw, snf.as_deref(), §ions.sfb_cb, &max_q, max_sfb);
}
}
fn write_mono_data_centre(bw: &mut BitWriter, transform_length: u32, max_sfb: u32, coeffs: &[f32]) {
let sfbo = crate::sfb_offset::sfb_offset_48(transform_length)
.expect("encoder: unsupported transform_length");
let (n_msfb_bits, _, _) =
crate::tables::n_msfb_bits_48(transform_length).expect("encoder: bad tl");
bw.write_bit(false);
bw.write_bit(true);
bw.write_u32(max_sfb, n_msfb_bits);
let (qspec, sf, max_q, sections, snf) = prepare_stereo_channel(coeffs, sfbo, max_sfb);
write_section_data(bw, §ions);
write_spectral_data_sections(bw, &qspec, sfbo, §ions);
write_scalefac_data(bw, &sf, §ions.sfb_cb, &max_q, max_sfb);
write_snf_data(bw, snf.as_deref(), §ions.sfb_cb, &max_q, max_sfb);
}
fn write_aspx_data_1ch_minimal(
bw: &mut BitWriter,
cfg: &aspx::AspxConfig,
) -> Result<(), &'static str> {
let xover: u32 = 0;
bw.write_u32(xover, 3);
bw.write_bit(false);
let envbits = cfg.fixfix_tmp_num_env_bits();
bw.write_u32(0, envbits); if cfg.signals_freq_res() {
bw.write_bit(false); }
bw.write_bit(false); bw.write_bit(false);
let tables = aspx::derive_aspx_frequency_tables(cfg, xover)
.map_err(|_| "encoder: aspx frequency-tables derivation failed")?;
let counts = tables.counts;
for _ in 0..counts.num_sbg_noise {
bw.write_u32(0, 2);
}
bw.write_bit(false); bw.write_bit(false); bw.write_bit(false);
let num_sbg_sig = counts.num_sbg_sig_highres;
let num_sbg_noise = counts.num_sbg_noise;
let qmode_sig = if cfg.fixfix_tmp_num_env_bits() == 1 {
aspx::AspxQuantStep::Fine
} else {
cfg.quant_mode_env
};
if num_sbg_sig >= 1 {
write_aspx_sig_f0(bw, qmode_sig, aspx::AspxStereoMode::Level);
}
for _ in 1..num_sbg_sig {
write_aspx_sig_df_zero(bw, qmode_sig, aspx::AspxStereoMode::Level);
}
if num_sbg_noise >= 1 {
write_aspx_noise_f0(bw, aspx::AspxStereoMode::Level);
}
for _ in 1..num_sbg_noise {
write_aspx_noise_df_zero(bw, aspx::AspxStereoMode::Level);
}
Ok(())
}
fn write_acpl_data_1ch_minimal(
bw: &mut BitWriter,
num_bands: u32,
start_band: u32,
quant_mode: crate::acpl::AcplQuantMode,
) {
bw.write_bit(false);
bw.write_bit(false);
let emit_one = |bw: &mut BitWriter, dt: crate::acpl::AcplDataType| {
bw.write_bit(false); if num_bands > start_band {
write_acpl_f0_zero(bw, dt, quant_mode);
}
for _ in (start_band + 1)..num_bands {
write_acpl_df_zero(bw, dt, quant_mode);
}
};
emit_one(bw, crate::acpl::AcplDataType::Alpha);
emit_one(bw, crate::acpl::AcplDataType::Beta);
}
#[allow(clippy::too_many_arguments)]
pub fn build_5_x_acpl2_body_from_pcm_spectra(
transform_length: u32,
max_sfb: u32,
b_iframe: bool,
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_c: &[f32],
aspx_cfg: &aspx::AspxConfig,
acpl_num_param_bands_id: u8,
acpl_quant_mode: crate::acpl::AcplQuantMode,
pad_target_bytes: usize,
) -> Vec<u8> {
let acpl_num_bands = crate::acpl::num_param_bands_from_id(acpl_num_param_bands_id as u32);
let mut bw = BitWriter::new();
let audio_size = pad_target_bytes as u32;
bw.write_u32(audio_size & 0x7FFF, 15);
bw.write_bit(false);
bw.align_to_byte();
bw.write_u32(3, 3);
if b_iframe {
write_aspx_config(&mut bw, aspx_cfg);
write_acpl_config_1ch_full(&mut bw, acpl_num_param_bands_id, acpl_quant_mode);
}
write_companding_control_2ch_sync_on(&mut bw);
bw.write_bit(false);
write_two_channel_data(&mut bw, transform_length, max_sfb, coeffs_l, coeffs_r);
write_mono_data_centre(&mut bw, transform_length, max_sfb, coeffs_c);
if b_iframe {
write_aspx_data_2ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_aspx_data_1ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_acpl_data_1ch_minimal(&mut bw, acpl_num_bands, 0, acpl_quant_mode);
write_acpl_data_1ch_minimal(&mut bw, acpl_num_bands, 0, acpl_quant_mode);
}
bw.align_to_byte();
while bw.byte_len() < pad_target_bytes {
bw.write_u32(0, 8);
}
let mut bytes = bw.finish();
if bytes.len() > pad_target_bytes {
bytes.truncate(pad_target_bytes);
}
bytes
}
#[allow(clippy::too_many_arguments)]
pub fn build_5_x_acpl2_body_from_pcm_spectra_real_alpha_beta(
transform_length: u32,
max_sfb: u32,
b_iframe: bool,
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_c: &[f32],
coeffs_ls: &[f32],
coeffs_rs: &[f32],
aspx_cfg: &aspx::AspxConfig,
acpl_num_param_bands_id: u8,
acpl_quant_mode: crate::acpl::AcplQuantMode,
pad_target_bytes: usize,
) -> Vec<u8> {
let acpl_num_bands = crate::acpl::num_param_bands_from_id(acpl_num_param_bands_id as u32);
let start_band = 0u32;
let (num_l, den_l) = compute_per_band_correlations(
coeffs_l,
coeffs_ls,
transform_length,
acpl_num_bands,
start_band,
);
let (num_r, den_r) = compute_per_band_correlations(
coeffs_r,
coeffs_rs,
transform_length,
acpl_num_bands,
start_band,
);
let alpha_l_real = analytic_alpha_per_band(&num_l, &den_l, acpl_quant_mode);
let alpha_r_real = analytic_alpha_per_band(&num_r, &den_r, acpl_quant_mode);
let alpha_l_q: Vec<i32> = alpha_l_real
.iter()
.map(|&a| quantise_alpha(a, acpl_quant_mode))
.collect();
let alpha_r_q: Vec<i32> = alpha_r_real
.iter()
.map(|&a| quantise_alpha(a, acpl_quant_mode))
.collect();
let (e_c_l, e_s_l) = compute_per_band_energies(
coeffs_l,
coeffs_ls,
transform_length,
acpl_num_bands,
start_band,
);
let (e_c_r, e_s_r) = compute_per_band_energies(
coeffs_r,
coeffs_rs,
transform_length,
acpl_num_bands,
start_band,
);
let alpha_l_dq: Vec<f32> = alpha_l_q
.iter()
.map(|&q| crate::acpl_synth::dequantize_alpha_index(acpl_quant_mode, q).0)
.collect();
let alpha_r_dq: Vec<f32> = alpha_r_q
.iter()
.map(|&q| crate::acpl_synth::dequantize_alpha_index(acpl_quant_mode, q).0)
.collect();
let beta_l_real = analytic_beta_per_band(&e_c_l, &e_s_l, &alpha_l_dq, acpl_quant_mode);
let beta_r_real = analytic_beta_per_band(&e_c_r, &e_s_r, &alpha_r_dq, acpl_quant_mode);
let beta_l_q: Vec<i32> = beta_l_real
.iter()
.map(|&b| quantise_beta_magnitude(b, acpl_quant_mode))
.collect();
let beta_r_q: Vec<i32> = beta_r_real
.iter()
.map(|&b| quantise_beta_magnitude(b, acpl_quant_mode))
.collect();
let mut bw = BitWriter::new();
let audio_size = pad_target_bytes as u32;
bw.write_u32(audio_size & 0x7FFF, 15);
bw.write_bit(false);
bw.align_to_byte();
bw.write_u32(3, 3);
if b_iframe {
write_aspx_config(&mut bw, aspx_cfg);
write_acpl_config_1ch_full(&mut bw, acpl_num_param_bands_id, acpl_quant_mode);
}
write_companding_control_2ch_sync_on(&mut bw);
bw.write_bit(false); write_two_channel_data(&mut bw, transform_length, max_sfb, coeffs_l, coeffs_r);
write_mono_data_centre(&mut bw, transform_length, max_sfb, coeffs_c);
if b_iframe {
write_aspx_data_2ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_aspx_data_1ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_acpl_data_1ch_real_alpha_beta(
&mut bw,
acpl_num_bands,
start_band,
acpl_quant_mode,
&alpha_l_q,
Some(&beta_l_q),
);
write_acpl_data_1ch_real_alpha_beta(
&mut bw,
acpl_num_bands,
start_band,
acpl_quant_mode,
&alpha_r_q,
Some(&beta_r_q),
);
}
bw.align_to_byte();
while bw.byte_len() < pad_target_bytes {
bw.write_u32(0, 8);
}
let mut bytes = bw.finish();
if bytes.len() > pad_target_bytes {
bytes.truncate(pad_target_bytes);
}
bytes
}
pub fn write_acpl_config_1ch_partial(
bw: &mut BitWriter,
num_param_bands_id: u8,
quant_mode: crate::acpl::AcplQuantMode,
qmf_band_minus1: u8,
) {
bw.write_u32(num_param_bands_id as u32 & 0b11, 2);
bw.write_bit(matches!(quant_mode, crate::acpl::AcplQuantMode::Coarse));
bw.write_u32(qmf_band_minus1 as u32 & 0b111, 3);
}
fn write_acpl_1_residual_layer(
bw: &mut BitWriter,
transform_length: u32,
max_sfb_master: u32,
coeffs_ls: &[f32],
coeffs_rs: &[f32],
) -> u32 {
let sfbo = crate::sfb_offset::sfb_offset_48(transform_length)
.expect("encoder: unsupported transform_length");
let (_n_msfb, n_side, _n_msfbl) =
crate::tables::n_msfb_bits_48(transform_length).expect("encoder: bad tl");
let num_sfb_cap = crate::tables::num_sfb_48(transform_length).expect("encoder: bad tl");
let n_side_cap = (1u32 << n_side) - 1;
let max_sfb_master = max_sfb_master.clamp(1, num_sfb_cap.min(n_side_cap));
bw.write_u32(max_sfb_master, n_side);
bw.write_u32(0, 2);
bw.write_u32(0, 2);
for coeffs in [coeffs_ls, coeffs_rs] {
let (qspec, sf, max_q, sections, snf) =
prepare_stereo_channel(coeffs, sfbo, max_sfb_master);
write_section_data(bw, §ions);
write_spectral_data_sections(bw, &qspec, sfbo, §ions);
write_scalefac_data(bw, &sf, §ions.sfb_cb, &max_q, max_sfb_master);
write_snf_data(bw, snf.as_deref(), §ions.sfb_cb, &max_q, max_sfb_master);
}
max_sfb_master
}
#[allow(clippy::too_many_arguments)]
pub fn build_5_x_acpl1_body_from_pcm_spectra(
transform_length: u32,
max_sfb: u32,
max_sfb_master: u32,
b_iframe: bool,
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_c: &[f32],
coeffs_ls: &[f32],
coeffs_rs: &[f32],
aspx_cfg: &aspx::AspxConfig,
acpl_num_param_bands_id: u8,
acpl_quant_mode: crate::acpl::AcplQuantMode,
acpl_qmf_band_minus1: u8,
pad_target_bytes: usize,
) -> Vec<u8> {
let acpl_num_bands = crate::acpl::num_param_bands_from_id(acpl_num_param_bands_id as u32);
let mut bw = BitWriter::new();
let audio_size = pad_target_bytes as u32;
bw.write_u32(audio_size & 0x7FFF, 15);
bw.write_bit(false);
bw.align_to_byte();
bw.write_u32(2, 3);
if b_iframe {
write_aspx_config(&mut bw, aspx_cfg);
write_acpl_config_1ch_partial(
&mut bw,
acpl_num_param_bands_id,
acpl_quant_mode,
acpl_qmf_band_minus1,
);
}
write_companding_control_2ch_sync_on(&mut bw);
bw.write_bit(false);
write_two_channel_data(&mut bw, transform_length, max_sfb, coeffs_l, coeffs_r);
write_acpl_1_residual_layer(
&mut bw,
transform_length,
max_sfb_master,
coeffs_ls,
coeffs_rs,
);
write_mono_data_centre(&mut bw, transform_length, max_sfb, coeffs_c);
if b_iframe {
write_aspx_data_2ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_aspx_data_1ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
let qmf_band = (acpl_qmf_band_minus1 as u32 & 0b111) + 1;
let start_band = crate::acpl::sb_to_pb(qmf_band, acpl_num_bands);
write_acpl_data_1ch_minimal(&mut bw, acpl_num_bands, start_band, acpl_quant_mode);
write_acpl_data_1ch_minimal(&mut bw, acpl_num_bands, start_band, acpl_quant_mode);
}
bw.align_to_byte();
while bw.byte_len() < pad_target_bytes {
bw.write_u32(0, 8);
}
let mut bytes = bw.finish();
if bytes.len() > pad_target_bytes {
bytes.truncate(pad_target_bytes);
}
bytes
}
use crate::acpl_synth::{ALPHA_DQ_COARSE, ALPHA_DQ_FINE};
fn mdct_bin_to_param_band(bin: u32, transform_length: u32, num_param_bands: u32) -> u32 {
let sb = (bin * 64) / transform_length.max(1);
let sb = sb.min(63);
crate::acpl::sb_to_pb(sb, num_param_bands)
}
fn compute_per_band_correlations(
coeffs_carrier: &[f32],
coeffs_surround: &[f32],
transform_length: u32,
num_param_bands: u32,
start_pb: u32,
) -> (Vec<f32>, Vec<f32>) {
let n = num_param_bands as usize;
let mut num = vec![0.0f32; n];
let mut den = vec![0.0f32; n];
let len = coeffs_carrier.len().min(coeffs_surround.len());
for bin in 0..len {
let pb = mdct_bin_to_param_band(bin as u32, transform_length, num_param_bands) as usize;
if (pb as u32) < start_pb {
continue;
}
let xc = coeffs_carrier[bin];
let xs = coeffs_surround[bin];
num[pb] += xc * xs;
den[pb] += xc * xc;
}
(num, den)
}
fn analytic_alpha_per_band(num: &[f32], den: &[f32], qm: crate::acpl::AcplQuantMode) -> Vec<f32> {
let max_abs: f32 = match qm {
crate::acpl::AcplQuantMode::Fine => 2.0, crate::acpl::AcplQuantMode::Coarse => 2.0, };
let sqrt2 = (2.0f32).sqrt();
let mut out = Vec::with_capacity(num.len());
for i in 0..num.len() {
let d = den[i];
if d <= 0.0 || !d.is_finite() {
out.push(0.0);
continue;
}
let ratio = num[i] / d;
let mut a = 1.0 - 2.0 * sqrt2 * ratio;
if !a.is_finite() {
a = 0.0;
}
out.push(a.clamp(-max_abs, max_abs));
}
out
}
fn quantise_alpha(alpha: f32, qm: crate::acpl::AcplQuantMode) -> i32 {
let (table, cb_off): (&[f32], i32) = match qm {
crate::acpl::AcplQuantMode::Fine => (&ALPHA_DQ_FINE, 16),
crate::acpl::AcplQuantMode::Coarse => (&ALPHA_DQ_COARSE, 8),
};
let mut best_lane = 0usize;
let mut best_err = f32::INFINITY;
for (lane, &v) in table.iter().enumerate() {
let err = (v - alpha).abs();
if err < best_err {
best_err = err;
best_lane = lane;
}
}
(best_lane as i32) - cb_off
}
pub fn write_acpl_data_1ch_real_alpha_beta_bytes(
num_bands: u32,
start_band: u32,
quant_mode: crate::acpl::AcplQuantMode,
alpha_q_per_band: &[i32],
beta_q_per_band: &[i32],
) -> Vec<u8> {
let mut bw = BitWriter::new();
write_acpl_data_1ch_real_alpha_beta(
&mut bw,
num_bands,
start_band,
quant_mode,
alpha_q_per_band,
Some(beta_q_per_band),
);
bw.finish()
}
pub fn extract_beta_q_per_band(
coeffs_carrier: &[f32],
coeffs_surround: &[f32],
transform_length: u32,
num_param_bands: u32,
start_pb: u32,
alpha_q: &[i32],
qm: crate::acpl::AcplQuantMode,
) -> Vec<i32> {
let (e_c, e_s) = compute_per_band_energies(
coeffs_carrier,
coeffs_surround,
transform_length,
num_param_bands,
start_pb,
);
let alpha_dq: Vec<f32> = alpha_q
.iter()
.map(|&q| crate::acpl_synth::dequantize_alpha_index(qm, q).0)
.collect();
let beta = analytic_beta_per_band(&e_c, &e_s, &alpha_dq, qm);
beta.iter()
.map(|&b| quantise_beta_magnitude(b, qm))
.collect()
}
pub fn extract_alpha_q_per_band(
coeffs_carrier: &[f32],
coeffs_surround: &[f32],
transform_length: u32,
num_param_bands: u32,
start_pb: u32,
qm: crate::acpl::AcplQuantMode,
) -> Vec<i32> {
let (num, den) = compute_per_band_correlations(
coeffs_carrier,
coeffs_surround,
transform_length,
num_param_bands,
start_pb,
);
let alpha = analytic_alpha_per_band(&num, &den, qm);
alpha.iter().map(|&a| quantise_alpha(a, qm)).collect()
}
pub fn extract_beta_q_per_band_carrier_energy(
coeffs_carrier: &[f32],
transform_length: u32,
num_param_bands: u32,
start_pb: u32,
scale: f32,
qm: crate::acpl::AcplQuantMode,
) -> Vec<i32> {
let (e_c, _e_zero) = compute_per_band_energies(
coeffs_carrier,
coeffs_carrier,
transform_length,
num_param_bands,
start_pb,
);
e_c.iter()
.map(|&e| {
if e <= 0.0 || !e.is_finite() {
0
} else {
let rms = e.sqrt();
let beta_mag = (scale * rms).max(0.0);
quantise_beta_magnitude(beta_mag, qm)
}
})
.collect()
}
fn compute_per_band_energies(
coeffs_carrier: &[f32],
coeffs_surround: &[f32],
transform_length: u32,
num_param_bands: u32,
start_pb: u32,
) -> (Vec<f32>, Vec<f32>) {
let n = num_param_bands as usize;
let mut e_c = vec![0.0f32; n];
let mut e_s = vec![0.0f32; n];
let len = coeffs_carrier.len().min(coeffs_surround.len());
for bin in 0..len {
let pb = mdct_bin_to_param_band(bin as u32, transform_length, num_param_bands) as usize;
if (pb as u32) < start_pb {
continue;
}
let xc = coeffs_carrier[bin];
let xs = coeffs_surround[bin];
e_c[pb] += xc * xc;
e_s[pb] += xs * xs;
}
(e_c, e_s)
}
fn analytic_beta_per_band(
energy_c: &[f32],
energy_s: &[f32],
alpha_dq: &[f32],
qm: crate::acpl::AcplQuantMode,
) -> Vec<f32> {
let max_abs: f32 = match qm {
crate::acpl::AcplQuantMode::Fine => 4.0,
crate::acpl::AcplQuantMode::Coarse => 4.0,
};
let n = energy_c.len().min(energy_s.len()).min(alpha_dq.len());
let mut out = Vec::with_capacity(n);
for i in 0..n {
let ec = energy_c[i];
let es = energy_s[i];
if ec <= 0.0 || !ec.is_finite() {
out.push(0.0);
continue;
}
let one_minus_a = 1.0 - alpha_dq[i];
let beta_sq = 2.0 * es / ec - one_minus_a * one_minus_a;
let beta = if beta_sq <= 0.0 || !beta_sq.is_finite() {
0.0
} else {
beta_sq.sqrt()
};
out.push(beta.clamp(0.0, max_abs));
}
out
}
fn quantise_beta_magnitude(beta_mag: f32, qm: crate::acpl::AcplQuantMode) -> i32 {
let table: &[f32] = match qm {
crate::acpl::AcplQuantMode::Fine => {
&[0.0, 0.2375, 0.55, 0.9375, 1.4, 1.9375, 2.55, 3.2375, 4.0]
}
crate::acpl::AcplQuantMode::Coarse => &[0.0, 0.55, 1.4, 2.55, 4.0],
};
let mut best_lane = 0usize;
let mut best_err = f32::INFINITY;
for (lane, &v) in table.iter().enumerate() {
let err = (v - beta_mag).abs();
if err < best_err {
best_err = err;
best_lane = lane;
}
}
best_lane as i32
}
fn write_acpl_beta_f0_value(bw: &mut BitWriter, qm: crate::acpl::AcplQuantMode, beta_q: i32) {
let (len, cw, cb_off) = acpl_hcb_arrays(
crate::acpl::AcplDataType::Beta,
qm,
crate::acpl::AcplHcbType::F0,
);
let idx = (beta_q + cb_off).clamp(0, (len.len() as i32) - 1) as usize;
bw.write_u32(cw[idx], len[idx] as u32);
}
fn write_acpl_beta_df_value(bw: &mut BitWriter, qm: crate::acpl::AcplQuantMode, delta_q: i32) {
let (len, cw, cb_off) = acpl_hcb_arrays(
crate::acpl::AcplDataType::Beta,
qm,
crate::acpl::AcplHcbType::Df,
);
let idx = (delta_q + cb_off).clamp(0, (len.len() as i32) - 1) as usize;
bw.write_u32(cw[idx], len[idx] as u32);
}
fn write_acpl_alpha_f0_value(bw: &mut BitWriter, qm: crate::acpl::AcplQuantMode, alpha_q: i32) {
let (len, cw, cb_off) = acpl_hcb_arrays(
crate::acpl::AcplDataType::Alpha,
qm,
crate::acpl::AcplHcbType::F0,
);
let idx = (alpha_q + cb_off).clamp(0, (len.len() as i32) - 1) as usize;
bw.write_u32(cw[idx], len[idx] as u32);
}
fn write_acpl_alpha_df_value(bw: &mut BitWriter, qm: crate::acpl::AcplQuantMode, delta_q: i32) {
let (len, cw, cb_off) = acpl_hcb_arrays(
crate::acpl::AcplDataType::Alpha,
qm,
crate::acpl::AcplHcbType::Df,
);
let idx = (delta_q + cb_off).clamp(0, (len.len() as i32) - 1) as usize;
bw.write_u32(cw[idx], len[idx] as u32);
}
fn write_acpl_data_1ch_real_alpha(
bw: &mut BitWriter,
num_bands: u32,
start_band: u32,
quant_mode: crate::acpl::AcplQuantMode,
alpha_q_per_band: &[i32],
) {
write_acpl_data_1ch_real_alpha_beta(
bw,
num_bands,
start_band,
quant_mode,
alpha_q_per_band,
None,
);
}
fn write_acpl_data_1ch_real_alpha_beta(
bw: &mut BitWriter,
num_bands: u32,
start_band: u32,
quant_mode: crate::acpl::AcplQuantMode,
alpha_q_per_band: &[i32],
beta_q_per_band: Option<&[i32]>,
) {
bw.write_bit(false);
bw.write_bit(false);
bw.write_bit(false); let mut prev_q: i32 = 0;
let mut first = true;
for pb in start_band..num_bands {
let a_q = alpha_q_per_band.get(pb as usize).copied().unwrap_or(0);
if first {
write_acpl_alpha_f0_value(bw, quant_mode, a_q);
first = false;
} else {
let delta = a_q - prev_q;
write_acpl_alpha_df_value(bw, quant_mode, delta);
}
prev_q = a_q;
}
bw.write_bit(false); if let Some(beta_q) = beta_q_per_band {
let mut prev_q: i32 = 0;
let mut first = true;
for pb in start_band..num_bands {
let b_q = beta_q.get(pb as usize).copied().unwrap_or(0);
if first {
write_acpl_beta_f0_value(bw, quant_mode, b_q);
first = false;
} else {
let delta = b_q - prev_q;
write_acpl_beta_df_value(bw, quant_mode, delta);
}
prev_q = b_q;
}
} else {
if num_bands > start_band {
write_acpl_f0_zero(bw, crate::acpl::AcplDataType::Beta, quant_mode);
}
for _ in (start_band + 1)..num_bands {
write_acpl_df_zero(bw, crate::acpl::AcplDataType::Beta, quant_mode);
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn build_5_x_acpl1_body_from_pcm_spectra_real_alpha(
transform_length: u32,
max_sfb: u32,
max_sfb_master: u32,
b_iframe: bool,
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_c: &[f32],
coeffs_ls: &[f32],
coeffs_rs: &[f32],
aspx_cfg: &aspx::AspxConfig,
acpl_num_param_bands_id: u8,
acpl_quant_mode: crate::acpl::AcplQuantMode,
acpl_qmf_band_minus1: u8,
pad_target_bytes: usize,
) -> Vec<u8> {
let acpl_num_bands = crate::acpl::num_param_bands_from_id(acpl_num_param_bands_id as u32);
let qmf_band = (acpl_qmf_band_minus1 as u32 & 0b111) + 1;
let start_band = crate::acpl::sb_to_pb(qmf_band, acpl_num_bands);
let (num_l, den_l) = compute_per_band_correlations(
coeffs_l,
coeffs_ls,
transform_length,
acpl_num_bands,
start_band,
);
let (num_r, den_r) = compute_per_band_correlations(
coeffs_r,
coeffs_rs,
transform_length,
acpl_num_bands,
start_band,
);
let alpha_l_real = analytic_alpha_per_band(&num_l, &den_l, acpl_quant_mode);
let alpha_r_real = analytic_alpha_per_band(&num_r, &den_r, acpl_quant_mode);
let alpha_l_q: Vec<i32> = alpha_l_real
.iter()
.map(|&a| quantise_alpha(a, acpl_quant_mode))
.collect();
let alpha_r_q: Vec<i32> = alpha_r_real
.iter()
.map(|&a| quantise_alpha(a, acpl_quant_mode))
.collect();
let mut bw = BitWriter::new();
let audio_size = pad_target_bytes as u32;
bw.write_u32(audio_size & 0x7FFF, 15);
bw.write_bit(false);
bw.align_to_byte();
bw.write_u32(2, 3);
if b_iframe {
write_aspx_config(&mut bw, aspx_cfg);
write_acpl_config_1ch_partial(
&mut bw,
acpl_num_param_bands_id,
acpl_quant_mode,
acpl_qmf_band_minus1,
);
}
write_companding_control_2ch_sync_on(&mut bw);
bw.write_bit(false); write_two_channel_data(&mut bw, transform_length, max_sfb, coeffs_l, coeffs_r);
write_acpl_1_residual_layer(
&mut bw,
transform_length,
max_sfb_master,
coeffs_ls,
coeffs_rs,
);
write_mono_data_centre(&mut bw, transform_length, max_sfb, coeffs_c);
if b_iframe {
write_aspx_data_2ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_aspx_data_1ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_acpl_data_1ch_real_alpha(
&mut bw,
acpl_num_bands,
start_band,
acpl_quant_mode,
&alpha_l_q,
);
write_acpl_data_1ch_real_alpha(
&mut bw,
acpl_num_bands,
start_band,
acpl_quant_mode,
&alpha_r_q,
);
}
bw.align_to_byte();
while bw.byte_len() < pad_target_bytes {
bw.write_u32(0, 8);
}
let mut bytes = bw.finish();
if bytes.len() > pad_target_bytes {
bytes.truncate(pad_target_bytes);
}
bytes
}
#[allow(clippy::too_many_arguments)]
pub fn build_5_x_acpl1_body_from_pcm_spectra_real_alpha_beta(
transform_length: u32,
max_sfb: u32,
max_sfb_master: u32,
b_iframe: bool,
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_c: &[f32],
coeffs_ls: &[f32],
coeffs_rs: &[f32],
aspx_cfg: &aspx::AspxConfig,
acpl_num_param_bands_id: u8,
acpl_quant_mode: crate::acpl::AcplQuantMode,
acpl_qmf_band_minus1: u8,
pad_target_bytes: usize,
) -> Vec<u8> {
let acpl_num_bands = crate::acpl::num_param_bands_from_id(acpl_num_param_bands_id as u32);
let qmf_band = (acpl_qmf_band_minus1 as u32 & 0b111) + 1;
let start_band = crate::acpl::sb_to_pb(qmf_band, acpl_num_bands);
let (num_l, den_l) = compute_per_band_correlations(
coeffs_l,
coeffs_ls,
transform_length,
acpl_num_bands,
start_band,
);
let (num_r, den_r) = compute_per_band_correlations(
coeffs_r,
coeffs_rs,
transform_length,
acpl_num_bands,
start_band,
);
let alpha_l_real = analytic_alpha_per_band(&num_l, &den_l, acpl_quant_mode);
let alpha_r_real = analytic_alpha_per_band(&num_r, &den_r, acpl_quant_mode);
let alpha_l_q: Vec<i32> = alpha_l_real
.iter()
.map(|&a| quantise_alpha(a, acpl_quant_mode))
.collect();
let alpha_r_q: Vec<i32> = alpha_r_real
.iter()
.map(|&a| quantise_alpha(a, acpl_quant_mode))
.collect();
let (e_c_l, e_s_l) = compute_per_band_energies(
coeffs_l,
coeffs_ls,
transform_length,
acpl_num_bands,
start_band,
);
let (e_c_r, e_s_r) = compute_per_band_energies(
coeffs_r,
coeffs_rs,
transform_length,
acpl_num_bands,
start_band,
);
let alpha_l_dq: Vec<f32> = alpha_l_q
.iter()
.map(|&q| crate::acpl_synth::dequantize_alpha_index(acpl_quant_mode, q).0)
.collect();
let alpha_r_dq: Vec<f32> = alpha_r_q
.iter()
.map(|&q| crate::acpl_synth::dequantize_alpha_index(acpl_quant_mode, q).0)
.collect();
let beta_l_real = analytic_beta_per_band(&e_c_l, &e_s_l, &alpha_l_dq, acpl_quant_mode);
let beta_r_real = analytic_beta_per_band(&e_c_r, &e_s_r, &alpha_r_dq, acpl_quant_mode);
let beta_l_q: Vec<i32> = beta_l_real
.iter()
.map(|&b| quantise_beta_magnitude(b, acpl_quant_mode))
.collect();
let beta_r_q: Vec<i32> = beta_r_real
.iter()
.map(|&b| quantise_beta_magnitude(b, acpl_quant_mode))
.collect();
let mut bw = BitWriter::new();
let audio_size = pad_target_bytes as u32;
bw.write_u32(audio_size & 0x7FFF, 15);
bw.write_bit(false);
bw.align_to_byte();
bw.write_u32(2, 3);
if b_iframe {
write_aspx_config(&mut bw, aspx_cfg);
write_acpl_config_1ch_partial(
&mut bw,
acpl_num_param_bands_id,
acpl_quant_mode,
acpl_qmf_band_minus1,
);
}
write_companding_control_2ch_sync_on(&mut bw);
bw.write_bit(false); write_two_channel_data(&mut bw, transform_length, max_sfb, coeffs_l, coeffs_r);
write_acpl_1_residual_layer(
&mut bw,
transform_length,
max_sfb_master,
coeffs_ls,
coeffs_rs,
);
write_mono_data_centre(&mut bw, transform_length, max_sfb, coeffs_c);
if b_iframe {
write_aspx_data_2ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_aspx_data_1ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_acpl_data_1ch_real_alpha_beta(
&mut bw,
acpl_num_bands,
start_band,
acpl_quant_mode,
&alpha_l_q,
Some(&beta_l_q),
);
write_acpl_data_1ch_real_alpha_beta(
&mut bw,
acpl_num_bands,
start_band,
acpl_quant_mode,
&alpha_r_q,
Some(&beta_r_q),
);
}
bw.align_to_byte();
while bw.byte_len() < pad_target_bytes {
bw.write_u32(0, 8);
}
let mut bytes = bw.finish();
if bytes.len() > pad_target_bytes {
bytes.truncate(pad_target_bytes);
}
bytes
}
#[allow(clippy::too_many_arguments)]
pub fn build_7_x_acpl2_body_from_pcm_spectra(
transform_length: u32,
max_sfb: u32,
max_sfb_lfe: Option<u32>,
b_iframe: bool,
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_ls: &[f32],
coeffs_rs: &[f32],
coeffs_c: &[f32],
coeffs_lfe: Option<&[f32]>,
aspx_cfg: &aspx::AspxConfig,
acpl_num_param_bands_id: u8,
acpl_quant_mode: crate::acpl::AcplQuantMode,
pad_target_bytes: usize,
) -> Vec<u8> {
let acpl_num_bands = crate::acpl::num_param_bands_from_id(acpl_num_param_bands_id as u32);
let mut bw = BitWriter::new();
let audio_size = pad_target_bytes as u32;
bw.write_u32(audio_size & 0x7FFF, 15);
bw.write_bit(false);
bw.align_to_byte();
bw.write_u32(3, 2);
if b_iframe {
write_aspx_config(&mut bw, aspx_cfg);
write_acpl_config_1ch_full(&mut bw, acpl_num_param_bands_id, acpl_quant_mode);
}
if let (Some(lfe), Some(m_lfe)) = (coeffs_lfe, max_sfb_lfe) {
write_lfe_mono_data(&mut bw, transform_length, m_lfe, lfe);
}
write_companding_control_2ch_sync_on(&mut bw);
bw.write_u32(0, 2);
bw.write_bit(false); write_two_channel_data(&mut bw, transform_length, max_sfb, coeffs_l, coeffs_r);
write_two_channel_data(&mut bw, transform_length, max_sfb, coeffs_ls, coeffs_rs);
write_mono_data_centre(&mut bw, transform_length, max_sfb, coeffs_c);
if b_iframe {
write_aspx_data_2ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_aspx_data_2ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_aspx_data_1ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_acpl_data_1ch_minimal(&mut bw, acpl_num_bands, 0, acpl_quant_mode);
write_acpl_data_1ch_minimal(&mut bw, acpl_num_bands, 0, acpl_quant_mode);
}
bw.align_to_byte();
while bw.byte_len() < pad_target_bytes {
bw.write_u32(0, 8);
}
let mut bytes = bw.finish();
if bytes.len() > pad_target_bytes {
bytes.truncate(pad_target_bytes);
}
bytes
}
#[allow(clippy::too_many_arguments)]
pub fn build_7_x_acpl1_body_from_pcm_spectra(
transform_length: u32,
max_sfb: u32,
max_sfb_master: u32,
max_sfb_lfe: Option<u32>,
b_iframe: bool,
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_ls: &[f32],
coeffs_rs: &[f32],
coeffs_c: &[f32],
coeffs_lfe: Option<&[f32]>,
aspx_cfg: &aspx::AspxConfig,
acpl_num_param_bands_id: u8,
acpl_quant_mode: crate::acpl::AcplQuantMode,
acpl_qmf_band_minus1: u8,
pad_target_bytes: usize,
) -> Vec<u8> {
let acpl_num_bands = crate::acpl::num_param_bands_from_id(acpl_num_param_bands_id as u32);
let mut bw = BitWriter::new();
let audio_size = pad_target_bytes as u32;
bw.write_u32(audio_size & 0x7FFF, 15);
bw.write_bit(false);
bw.align_to_byte();
bw.write_u32(2, 2);
if b_iframe {
write_aspx_config(&mut bw, aspx_cfg);
write_acpl_config_1ch_partial(
&mut bw,
acpl_num_param_bands_id,
acpl_quant_mode,
acpl_qmf_band_minus1,
);
}
if let (Some(lfe), Some(m_lfe)) = (coeffs_lfe, max_sfb_lfe) {
write_lfe_mono_data(&mut bw, transform_length, m_lfe, lfe);
}
write_companding_control_2ch_sync_on(&mut bw);
bw.write_u32(0, 2);
bw.write_bit(false); write_two_channel_data(&mut bw, transform_length, max_sfb, coeffs_l, coeffs_r);
write_two_channel_data(&mut bw, transform_length, max_sfb, coeffs_ls, coeffs_rs);
write_acpl_1_residual_layer(
&mut bw,
transform_length,
max_sfb_master,
coeffs_ls,
coeffs_rs,
);
write_mono_data_centre(&mut bw, transform_length, max_sfb, coeffs_c);
if b_iframe {
write_aspx_data_2ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_aspx_data_2ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_aspx_data_1ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
let qmf_band = (acpl_qmf_band_minus1 as u32 & 0b111) + 1;
let start_band = crate::acpl::sb_to_pb(qmf_band, acpl_num_bands);
write_acpl_data_1ch_minimal(&mut bw, acpl_num_bands, start_band, acpl_quant_mode);
write_acpl_data_1ch_minimal(&mut bw, acpl_num_bands, start_band, acpl_quant_mode);
}
bw.align_to_byte();
while bw.byte_len() < pad_target_bytes {
bw.write_u32(0, 8);
}
let mut bytes = bw.finish();
if bytes.len() > pad_target_bytes {
bytes.truncate(pad_target_bytes);
}
bytes
}
#[allow(clippy::too_many_arguments)]
pub fn build_7_x_acpl1_body_from_pcm_spectra_real_alpha_beta(
transform_length: u32,
max_sfb: u32,
max_sfb_master: u32,
max_sfb_lfe: Option<u32>,
b_iframe: bool,
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_ls: &[f32],
coeffs_rs: &[f32],
coeffs_c: &[f32],
coeffs_lfe: Option<&[f32]>,
aspx_cfg: &aspx::AspxConfig,
acpl_num_param_bands_id: u8,
acpl_quant_mode: crate::acpl::AcplQuantMode,
acpl_qmf_band_minus1: u8,
pad_target_bytes: usize,
) -> Vec<u8> {
let acpl_num_bands = crate::acpl::num_param_bands_from_id(acpl_num_param_bands_id as u32);
let qmf_band = (acpl_qmf_band_minus1 as u32 & 0b111) + 1;
let start_band = crate::acpl::sb_to_pb(qmf_band, acpl_num_bands);
let alpha_l_q = extract_alpha_q_per_band(
coeffs_l,
coeffs_ls,
transform_length,
acpl_num_bands,
start_band,
acpl_quant_mode,
);
let alpha_r_q = extract_alpha_q_per_band(
coeffs_r,
coeffs_rs,
transform_length,
acpl_num_bands,
start_band,
acpl_quant_mode,
);
let beta_l_q = extract_beta_q_per_band(
coeffs_l,
coeffs_ls,
transform_length,
acpl_num_bands,
start_band,
&alpha_l_q,
acpl_quant_mode,
);
let beta_r_q = extract_beta_q_per_band(
coeffs_r,
coeffs_rs,
transform_length,
acpl_num_bands,
start_band,
&alpha_r_q,
acpl_quant_mode,
);
let mut bw = BitWriter::new();
let audio_size = pad_target_bytes as u32;
bw.write_u32(audio_size & 0x7FFF, 15);
bw.write_bit(false);
bw.align_to_byte();
bw.write_u32(2, 2);
if b_iframe {
write_aspx_config(&mut bw, aspx_cfg);
write_acpl_config_1ch_partial(
&mut bw,
acpl_num_param_bands_id,
acpl_quant_mode,
acpl_qmf_band_minus1,
);
}
if let (Some(lfe), Some(m_lfe)) = (coeffs_lfe, max_sfb_lfe) {
write_lfe_mono_data(&mut bw, transform_length, m_lfe, lfe);
}
write_companding_control_2ch_sync_on(&mut bw);
bw.write_u32(0, 2);
bw.write_bit(false); write_two_channel_data(&mut bw, transform_length, max_sfb, coeffs_l, coeffs_r);
write_two_channel_data(&mut bw, transform_length, max_sfb, coeffs_ls, coeffs_rs);
write_acpl_1_residual_layer(
&mut bw,
transform_length,
max_sfb_master,
coeffs_ls,
coeffs_rs,
);
write_mono_data_centre(&mut bw, transform_length, max_sfb, coeffs_c);
if b_iframe {
write_aspx_data_2ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_aspx_data_2ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_aspx_data_1ch_minimal(&mut bw, aspx_cfg).expect("encoder: aspx config invalid");
write_acpl_data_1ch_real_alpha_beta(
&mut bw,
acpl_num_bands,
start_band,
acpl_quant_mode,
&alpha_l_q,
Some(&beta_l_q),
);
write_acpl_data_1ch_real_alpha_beta(
&mut bw,
acpl_num_bands,
start_band,
acpl_quant_mode,
&alpha_r_q,
Some(&beta_r_q),
);
}
bw.align_to_byte();
while bw.byte_len() < pad_target_bytes {
bw.write_u32(0, 8);
}
let mut bytes = bw.finish();
if bytes.len() > pad_target_bytes {
bytes.truncate(pad_target_bytes);
}
bytes
}
#[cfg(test)]
mod tests {
use super::*;
use oxideav_core::bits::BitReader;
#[test]
fn pick_min_len_cw_finds_smallest_length() {
let (cw, len) = pick_min_len_cw(
aspx_huffman::ASPX_HCB_ENV_LEVEL_15_F0_LEN,
aspx_huffman::ASPX_HCB_ENV_LEVEL_15_F0_CW,
);
assert_eq!(len, 4);
assert_eq!(cw, 0x00000);
}
#[test]
fn pick_zero_delta_cw_returns_cb_off_entry() {
let (cw, len) = pick_zero_delta_cw(
aspx_huffman::ASPX_HCB_ENV_LEVEL_15_DF_LEN,
aspx_huffman::ASPX_HCB_ENV_LEVEL_15_DF_CW,
70,
);
assert_eq!(len, 2);
assert_eq!(cw, 0x00000);
}
#[test]
fn write_aspx_config_round_trips_through_parser() {
let cfg = aspx::AspxConfig {
quant_mode_env: aspx::AspxQuantStep::Coarse,
start_freq: 5,
stop_freq: 2,
master_freq_scale: aspx::AspxMasterFreqScale::HighRes,
interpolation: true,
preflat: false,
limiter: true,
noise_sbg: 3,
num_env_bits_fixfix: 1,
freq_res_mode: aspx::AspxFreqResMode::Low,
};
let mut bw = BitWriter::new();
write_aspx_config(&mut bw, &cfg);
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let parsed = aspx::parse_aspx_config(&mut br).unwrap();
assert_eq!(parsed, cfg);
}
#[test]
fn write_acpl_config_2ch_round_trips_through_parser() {
let mut bw = BitWriter::new();
write_acpl_config_2ch(
&mut bw,
3,
crate::acpl::AcplQuantMode::Fine,
crate::acpl::AcplQuantMode::Coarse,
);
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let parsed = crate::acpl::parse_acpl_config_2ch(&mut br).unwrap();
assert_eq!(parsed.num_param_bands_id, 3);
assert_eq!(parsed.num_param_bands, 7);
assert!(matches!(
parsed.quant_mode_0,
crate::acpl::AcplQuantMode::Fine
));
assert!(matches!(
parsed.quant_mode_1,
crate::acpl::AcplQuantMode::Coarse
));
}
#[test]
fn companding_control_2ch_sync_on_round_trips() {
let mut bw = BitWriter::new();
write_companding_control_2ch_sync_on(&mut bw);
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let cc = aspx::parse_companding_control(&mut br, 2).unwrap();
assert_eq!(cc.sync_flag, Some(true));
assert_eq!(cc.compand_on, vec![true]);
assert!(cc.compand_avg.is_none());
}
#[test]
fn acpl_data_2ch_minimal_round_trips() {
let num_bands: u32 = 7; let qm0 = crate::acpl::AcplQuantMode::Fine;
let qm1 = crate::acpl::AcplQuantMode::Fine;
let mut bw = BitWriter::new();
write_acpl_data_2ch_minimal(&mut bw, num_bands, qm0, qm1);
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let parsed = crate::acpl::parse_acpl_data_2ch(&mut br, num_bands, 0, qm0, qm1).unwrap();
assert_eq!(parsed.framing.num_param_sets, 1);
assert_eq!(parsed.alpha1.len(), 1);
assert_eq!(parsed.alpha1[0].values.len(), num_bands as usize);
for v in &parsed.alpha1[0].values[1..] {
assert_eq!(*v, 0);
}
for v in &parsed.gamma1[0].values[1..] {
assert_eq!(*v, 0);
}
}
#[test]
fn aspx_data_2ch_minimal_round_trips_through_parser() {
let cfg = aspx::AspxConfig {
quant_mode_env: aspx::AspxQuantStep::Fine,
start_freq: 0,
stop_freq: 0,
master_freq_scale: aspx::AspxMasterFreqScale::LowRes,
interpolation: false,
preflat: false,
limiter: false,
noise_sbg: 0, num_env_bits_fixfix: 0,
freq_res_mode: aspx::AspxFreqResMode::DurationDependent,
};
let mut bw = BitWriter::new();
write_aspx_data_2ch_minimal(&mut bw, &cfg).unwrap();
bw.align_to_byte();
let bytes = bw.finish();
let _ = bytes;
}
#[test]
fn write_acpl_config_1ch_full_round_trips_through_parser() {
let mut bw = BitWriter::new();
write_acpl_config_1ch_full(&mut bw, 3, crate::acpl::AcplQuantMode::Fine);
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let parsed =
crate::acpl::parse_acpl_config_1ch(&mut br, crate::acpl::Acpl1chMode::Full).unwrap();
assert_eq!(parsed.num_param_bands_id, 3);
assert_eq!(parsed.num_param_bands, 7);
assert!(matches!(
parsed.quant_mode,
crate::acpl::AcplQuantMode::Fine
));
assert_eq!(parsed.qmf_band, 0);
}
#[test]
fn write_two_channel_data_round_trips_through_parser() {
let tl = 1920u32;
let max_sfb = 8u32;
let coeffs_l = vec![0.0f32; tl as usize / 2];
let coeffs_r = vec![0.0f32; tl as usize / 2];
let mut bw = BitWriter::new();
write_two_channel_data(&mut bw, tl, max_sfb, &coeffs_l, &coeffs_r);
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let d = crate::mch::parse_two_channel_data(&mut br, tl).unwrap();
assert_eq!(d.transform_info.as_ref().unwrap().transform_length_0, tl);
assert_eq!(d.psy_info.as_ref().unwrap().max_sfb_0, max_sfb);
assert_eq!(d.chparam.as_ref().unwrap().sap_mode, 0);
assert_eq!(d.scaled_spec_per_channel.len(), 2);
assert!(d.scaled_spec_per_channel.iter().all(|c| c.is_some()));
}
#[test]
fn write_mono_data_centre_round_trips_through_parser() {
let tl = 1920u32;
let max_sfb = 6u32;
let coeffs = vec![0.0f32; tl as usize / 2];
let mut bw = BitWriter::new();
write_mono_data_centre(&mut bw, tl, max_sfb, &coeffs);
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let m = crate::mch::parse_mono_data(&mut br, false, tl).unwrap();
assert!(!m.b_lfe);
assert_eq!(m.spec_frontend_bit, 0);
assert_eq!(m.psy_info.as_ref().unwrap().max_sfb_0, max_sfb);
assert!(m.scaled_spec.is_some());
}
#[test]
fn acpl_data_1ch_minimal_round_trips() {
let num_bands: u32 = 7; let qm = crate::acpl::AcplQuantMode::Fine;
let mut bw = BitWriter::new();
write_acpl_data_1ch_minimal(&mut bw, num_bands, 0, qm);
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let parsed = crate::acpl::parse_acpl_data_1ch(&mut br, num_bands, 0, qm).unwrap();
assert_eq!(parsed.framing.num_param_sets, 1);
assert_eq!(parsed.alpha1.len(), 1);
assert_eq!(parsed.alpha1[0].values.len(), num_bands as usize);
assert_eq!(parsed.beta1[0].values.len(), num_bands as usize);
for v in &parsed.alpha1[0].values[1..] {
assert_eq!(*v, 0);
}
for v in &parsed.beta1[0].values[1..] {
assert_eq!(*v, 0);
}
}
#[test]
fn aspx_data_1ch_minimal_emits_without_error() {
let cfg = aspx::AspxConfig {
quant_mode_env: aspx::AspxQuantStep::Fine,
start_freq: 0,
stop_freq: 0,
master_freq_scale: aspx::AspxMasterFreqScale::LowRes,
interpolation: false,
preflat: false,
limiter: false,
noise_sbg: 0,
num_env_bits_fixfix: 0,
freq_res_mode: aspx::AspxFreqResMode::DurationDependent,
};
let mut bw = BitWriter::new();
write_aspx_data_1ch_minimal(&mut bw, &cfg).unwrap();
bw.align_to_byte();
assert!(!bw.finish().is_empty());
}
#[test]
fn write_acpl_config_1ch_partial_round_trips_through_parser() {
let mut bw = BitWriter::new();
write_acpl_config_1ch_partial(&mut bw, 3, crate::acpl::AcplQuantMode::Fine, 2);
bw.align_to_byte();
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let parsed =
crate::acpl::parse_acpl_config_1ch(&mut br, crate::acpl::Acpl1chMode::Partial).unwrap();
assert_eq!(parsed.num_param_bands_id, 3);
assert_eq!(parsed.num_param_bands, 7);
assert!(matches!(
parsed.quant_mode,
crate::acpl::AcplQuantMode::Fine
));
assert_eq!(parsed.qmf_band, 3);
}
#[test]
fn write_acpl_1_residual_layer_clamps_and_emits() {
let tl = 1920u32;
let coeffs_ls = vec![0.0f32; tl as usize / 2];
let coeffs_rs = vec![0.0f32; tl as usize / 2];
let mut bw = BitWriter::new();
let used = write_acpl_1_residual_layer(&mut bw, tl, 40, &coeffs_ls, &coeffs_rs);
bw.align_to_byte();
assert_eq!(used, 31, "max_sfb_master clamped to n_side cap (5 b → 31)");
assert!(!bw.finish().is_empty());
let mut bw2 = BitWriter::new();
let used2 = write_acpl_1_residual_layer(&mut bw2, tl, 0, &coeffs_ls, &coeffs_rs);
assert_eq!(
used2, 1,
"max_sfb_master clamped up to 1 (decoder bails on 0)"
);
}
#[test]
fn build_5_x_acpl1_body_decoder_resolves_full_body() {
let tl = 1920u32;
let half = tl as usize / 2;
let zeros = vec![0.0f32; half];
let cfg = aspx::AspxConfig {
quant_mode_env: aspx::AspxQuantStep::Fine,
start_freq: 0,
stop_freq: 0,
master_freq_scale: aspx::AspxMasterFreqScale::LowRes,
interpolation: false,
preflat: false,
limiter: false,
noise_sbg: 0,
num_env_bits_fixfix: 0,
freq_res_mode: aspx::AspxFreqResMode::DurationDependent,
};
let body = build_5_x_acpl1_body_from_pcm_spectra(
tl,
16,
8,
true,
&zeros,
&zeros,
&zeros,
&zeros,
&zeros,
&cfg,
3,
crate::acpl::AcplQuantMode::Fine,
0,
4096,
);
let mut br = BitReader::new(&body[2..]);
let mut tools = crate::asf::SubstreamTools::default();
crate::mch::parse_5x_audio_data_outer(&mut br, &mut tools, false, true, tl).unwrap();
assert_eq!(
tools.five_x_mode,
Some(crate::mch::FiveXCodecMode::AspxAcpl1)
);
let cfg_partial = tools
.acpl_config_1ch_partial
.expect("PARTIAL config parsed");
assert_eq!(cfg_partial.qmf_band, 1); assert_eq!(tools.two_channel_data.len(), 1);
assert!(tools.cfg0_centre_mono.is_some());
assert_eq!(tools.acpl_1_residual_max_sfb_master, Some(8));
assert!(tools.acpl_1_residual_pair[0].is_some());
assert!(tools.acpl_1_residual_pair[1].is_some());
assert!(tools.acpl_data_1ch_pair[0].is_some());
assert!(tools.acpl_data_1ch_pair[1].is_some());
}
#[test]
fn build_7_x_acpl2_body_decoder_resolves_full_body() {
let tl = 1920u32;
let half = tl as usize / 2;
let zeros = vec![0.0f32; half];
let cfg = aspx::AspxConfig {
quant_mode_env: aspx::AspxQuantStep::Fine,
start_freq: 0,
stop_freq: 0,
master_freq_scale: aspx::AspxMasterFreqScale::LowRes,
interpolation: false,
preflat: false,
limiter: false,
noise_sbg: 0,
num_env_bits_fixfix: 0,
freq_res_mode: aspx::AspxFreqResMode::DurationDependent,
};
let body = build_7_x_acpl2_body_from_pcm_spectra(
tl,
16,
None, true,
&zeros, &zeros, &zeros, &zeros, &zeros, None, &cfg,
3,
crate::acpl::AcplQuantMode::Fine,
4096,
);
let mut br = BitReader::new(&body[2..]);
let mut tools = crate::asf::SubstreamTools::default();
crate::mch::parse_7x_audio_data_outer(&mut br, &mut tools, false, true, tl).unwrap();
assert_eq!(
tools.seven_x_mode,
Some(crate::mch::SevenXCodecMode::AspxAcpl2)
);
assert!(tools.acpl_config_1ch_full.is_some());
assert_eq!(tools.two_channel_data.len(), 2);
assert!(tools.cfg0_centre_mono.is_some());
assert!(tools.acpl_data_1ch_pair[0].is_some());
assert!(tools.acpl_data_1ch_pair[1].is_some());
assert!(tools.acpl_1_residual_pair[0].is_none());
assert!(tools.acpl_1_residual_pair[1].is_none());
assert!(tools.lfe_mono_data.is_none());
}
#[test]
fn build_7_x_acpl2_body_with_lfe_decoder_resolves_lfe() {
let tl = 1920u32;
let half = tl as usize / 2;
let zeros = vec![0.0f32; half];
let cfg = aspx::AspxConfig {
quant_mode_env: aspx::AspxQuantStep::Fine,
start_freq: 0,
stop_freq: 0,
master_freq_scale: aspx::AspxMasterFreqScale::LowRes,
interpolation: false,
preflat: false,
limiter: false,
noise_sbg: 0,
num_env_bits_fixfix: 0,
freq_res_mode: aspx::AspxFreqResMode::DurationDependent,
};
let body = build_7_x_acpl2_body_from_pcm_spectra(
tl,
16,
Some(7), true,
&zeros, &zeros, &zeros, &zeros, &zeros, Some(&zeros), &cfg,
3,
crate::acpl::AcplQuantMode::Fine,
4096,
);
let mut br = BitReader::new(&body[2..]);
let mut tools = crate::asf::SubstreamTools::default();
crate::mch::parse_7x_audio_data_outer(&mut br, &mut tools, true, true, tl).unwrap();
assert_eq!(
tools.seven_x_mode,
Some(crate::mch::SevenXCodecMode::AspxAcpl2)
);
assert!(tools.seven_x_b_has_lfe);
assert!(tools.lfe_mono_data.is_some());
assert!(tools.acpl_config_1ch_full.is_some());
assert_eq!(tools.two_channel_data.len(), 2);
assert!(tools.cfg0_centre_mono.is_some());
assert!(tools.acpl_data_1ch_pair[0].is_some());
assert!(tools.acpl_data_1ch_pair[1].is_some());
assert!(tools.acpl_1_residual_pair[0].is_none());
}
#[test]
fn analytic_beta_positive_when_surround_energy_exceeds_alpha_model() {
let qm = crate::acpl::AcplQuantMode::Fine;
let e_c = vec![0.0f32, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0];
let e_s = vec![0.0f32, 0.0, 0.0, 0.0, 2.5, 0.0, 0.0]; let alpha_dq = vec![0.0f32; 7]; let beta = analytic_beta_per_band(&e_c, &e_s, &alpha_dq, qm);
assert!(
(beta[4] - 2.0).abs() < 1e-5,
"expected β=2.0 at band 4, got {}",
beta[4]
);
let q = quantise_beta_magnitude(beta[4], qm);
assert_eq!(q, 5, "expected beta_q lane 5 (=1.9375), got {q}");
}
#[test]
fn analytic_beta_zero_when_alpha_fully_explains_surround() {
let qm = crate::acpl::AcplQuantMode::Fine;
let e_c = vec![1.0f32; 1];
let alpha_dq = vec![0.5f32; 1];
let e_s = vec![0.125f32; 1];
let beta = analytic_beta_per_band(&e_c, &e_s, &alpha_dq, qm);
assert!((beta[0]).abs() < 1e-5, "expected β=0, got {}", beta[0]);
assert_eq!(quantise_beta_magnitude(beta[0], qm), 0);
}
#[test]
fn analytic_beta_zero_when_carrier_silent() {
let qm = crate::acpl::AcplQuantMode::Fine;
let e_c = vec![0.0f32; 1];
let e_s = vec![1.0f32; 1];
let alpha_dq = vec![0.0f32; 1];
let beta = analytic_beta_per_band(&e_c, &e_s, &alpha_dq, qm);
assert_eq!(beta[0], 0.0);
}
#[test]
fn write_beta_f0_df_round_trips() {
use crate::acpl::{get_acpl_hcb, AcplDataType, AcplHcbType, AcplQuantMode};
let qm = AcplQuantMode::Fine;
let beta_seq = [5i32, 5, 3, 0, 0, 0];
let mut bw = BitWriter::new();
let mut prev = 0i32;
for (i, &b) in beta_seq.iter().enumerate() {
if i == 0 {
write_acpl_beta_f0_value(&mut bw, qm, b);
} else {
write_acpl_beta_df_value(&mut bw, qm, b - prev);
}
prev = b;
}
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let hcb_f0 = get_acpl_hcb(AcplDataType::Beta, qm, AcplHcbType::F0);
let hcb_df = get_acpl_hcb(AcplDataType::Beta, qm, AcplHcbType::Df);
let mut got = vec![hcb_f0.decode_delta(&mut br).unwrap()];
for _ in 1..beta_seq.len() {
got.push(hcb_df.decode_delta(&mut br).unwrap());
}
let mut absvals = Vec::with_capacity(got.len());
let mut acc = 0;
for (i, &v) in got.iter().enumerate() {
if i == 0 {
acc = v;
} else {
acc += v;
}
absvals.push(acc);
}
assert_eq!(absvals, beta_seq);
}
}