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);
}
type AspxHcbArrays = (&'static [u8], &'static [u32], i32);
fn aspx_sig_hcb_arrays(
quant: aspx::AspxQuantStep,
stereo: aspx::AspxStereoMode,
hcb: aspx::AspxHcbType,
) -> AspxHcbArrays {
use aspx::AspxHcbType::*;
use aspx::AspxQuantStep::*;
use aspx::AspxStereoMode::*;
match (quant, stereo, hcb) {
(Fine, Level, F0) => (
aspx_huffman::ASPX_HCB_ENV_LEVEL_15_F0_LEN,
aspx_huffman::ASPX_HCB_ENV_LEVEL_15_F0_CW,
0,
),
(Fine, Level, Df) => (
aspx_huffman::ASPX_HCB_ENV_LEVEL_15_DF_LEN,
aspx_huffman::ASPX_HCB_ENV_LEVEL_15_DF_CW,
70,
),
(Fine, Level, Dt) => (
aspx_huffman::ASPX_HCB_ENV_LEVEL_15_DT_LEN,
aspx_huffman::ASPX_HCB_ENV_LEVEL_15_DT_CW,
70,
),
(Fine, Balance, F0) => (
aspx_huffman::ASPX_HCB_ENV_BALANCE_15_F0_LEN,
aspx_huffman::ASPX_HCB_ENV_BALANCE_15_F0_CW,
0,
),
(Fine, Balance, Df) => (
aspx_huffman::ASPX_HCB_ENV_BALANCE_15_DF_LEN,
aspx_huffman::ASPX_HCB_ENV_BALANCE_15_DF_CW,
24,
),
(Fine, Balance, Dt) => (
aspx_huffman::ASPX_HCB_ENV_BALANCE_15_DT_LEN,
aspx_huffman::ASPX_HCB_ENV_BALANCE_15_DT_CW,
24,
),
(Coarse, Level, F0) => (
aspx_huffman::ASPX_HCB_ENV_LEVEL_30_F0_LEN,
aspx_huffman::ASPX_HCB_ENV_LEVEL_30_F0_CW,
0,
),
(Coarse, Level, Df) => (
aspx_huffman::ASPX_HCB_ENV_LEVEL_30_DF_LEN,
aspx_huffman::ASPX_HCB_ENV_LEVEL_30_DF_CW,
35,
),
(Coarse, Level, Dt) => (
aspx_huffman::ASPX_HCB_ENV_LEVEL_30_DT_LEN,
aspx_huffman::ASPX_HCB_ENV_LEVEL_30_DT_CW,
35,
),
(Coarse, Balance, F0) => (
aspx_huffman::ASPX_HCB_ENV_BALANCE_30_F0_LEN,
aspx_huffman::ASPX_HCB_ENV_BALANCE_30_F0_CW,
0,
),
(Coarse, Balance, Df) => (
aspx_huffman::ASPX_HCB_ENV_BALANCE_30_DF_LEN,
aspx_huffman::ASPX_HCB_ENV_BALANCE_30_DF_CW,
12,
),
(Coarse, Balance, Dt) => (
aspx_huffman::ASPX_HCB_ENV_BALANCE_30_DT_LEN,
aspx_huffman::ASPX_HCB_ENV_BALANCE_30_DT_CW,
12,
),
}
}
fn aspx_noise_hcb_arrays(stereo: aspx::AspxStereoMode, hcb: aspx::AspxHcbType) -> AspxHcbArrays {
use aspx::AspxHcbType::*;
use aspx::AspxStereoMode::*;
match (stereo, hcb) {
(Level, F0) => (
aspx_huffman::ASPX_HCB_NOISE_LEVEL_F0_LEN,
aspx_huffman::ASPX_HCB_NOISE_LEVEL_F0_CW,
0,
),
(Level, Df) => (
aspx_huffman::ASPX_HCB_NOISE_LEVEL_DF_LEN,
aspx_huffman::ASPX_HCB_NOISE_LEVEL_DF_CW,
29,
),
(Level, Dt) => (
aspx_huffman::ASPX_HCB_NOISE_LEVEL_DT_LEN,
aspx_huffman::ASPX_HCB_NOISE_LEVEL_DT_CW,
29,
),
(Balance, F0) => (
aspx_huffman::ASPX_HCB_NOISE_BALANCE_F0_LEN,
aspx_huffman::ASPX_HCB_NOISE_BALANCE_F0_CW,
0,
),
(Balance, Df) => (
aspx_huffman::ASPX_HCB_NOISE_BALANCE_DF_LEN,
aspx_huffman::ASPX_HCB_NOISE_BALANCE_DF_CW,
12,
),
(Balance, Dt) => (
aspx_huffman::ASPX_HCB_NOISE_BALANCE_DT_LEN,
aspx_huffman::ASPX_HCB_NOISE_BALANCE_DT_CW,
12,
),
}
}
pub fn write_aspx_sig_f0_value(
bw: &mut BitWriter,
quant: aspx::AspxQuantStep,
stereo: aspx::AspxStereoMode,
v: i32,
) {
let (len, cw, _cb_off) = aspx_sig_hcb_arrays(quant, stereo, aspx::AspxHcbType::F0);
let idx = v.clamp(0, (len.len() as i32) - 1) as usize;
bw.write_u32(cw[idx], len[idx] as u32);
}
pub fn write_aspx_sig_df_value(
bw: &mut BitWriter,
quant: aspx::AspxQuantStep,
stereo: aspx::AspxStereoMode,
delta_q: i32,
) {
let (len, cw, cb_off) = aspx_sig_hcb_arrays(quant, stereo, aspx::AspxHcbType::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);
}
pub fn write_aspx_sig_dt_value(
bw: &mut BitWriter,
quant: aspx::AspxQuantStep,
stereo: aspx::AspxStereoMode,
delta_q: i32,
) {
let (len, cw, cb_off) = aspx_sig_hcb_arrays(quant, stereo, aspx::AspxHcbType::Dt);
let idx = (delta_q + cb_off).clamp(0, (len.len() as i32) - 1) as usize;
bw.write_u32(cw[idx], len[idx] as u32);
}
pub fn write_aspx_noise_f0_value(bw: &mut BitWriter, stereo: aspx::AspxStereoMode, v: i32) {
let (len, cw, _cb_off) = aspx_noise_hcb_arrays(stereo, aspx::AspxHcbType::F0);
let idx = v.clamp(0, (len.len() as i32) - 1) as usize;
bw.write_u32(cw[idx], len[idx] as u32);
}
pub fn write_aspx_noise_df_value(bw: &mut BitWriter, stereo: aspx::AspxStereoMode, delta_q: i32) {
let (len, cw, cb_off) = aspx_noise_hcb_arrays(stereo, aspx::AspxHcbType::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);
}
pub fn write_aspx_noise_dt_value(bw: &mut BitWriter, stereo: aspx::AspxStereoMode, delta_q: i32) {
let (len, cw, cb_off) = aspx_noise_hcb_arrays(stereo, aspx::AspxHcbType::Dt);
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 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)]
fn write_acpl_data_2ch_real_alpha_beta(
bw: &mut BitWriter,
num_bands: u32,
quant_mode_0: crate::acpl::AcplQuantMode,
quant_mode_1: crate::acpl::AcplQuantMode,
alpha1_q_per_band: &[i32],
alpha2_q_per_band: &[i32],
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_alpha = |bw: &mut BitWriter, qm: crate::acpl::AcplQuantMode, alpha_q: &[i32]| {
bw.write_bit(false); let mut prev_q: i32 = 0;
let mut first = true;
for pb in 0..num_bands {
let a_q = alpha_q.get(pb as usize).copied().unwrap_or(0);
if first {
write_acpl_alpha_f0_value(bw, qm, a_q);
first = false;
} else {
let delta = a_q - prev_q;
write_acpl_alpha_df_value(bw, qm, delta);
}
prev_q = a_q;
}
};
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_real_alpha(bw, quant_mode_0, alpha1_q_per_band);
emit_real_alpha(bw, quant_mode_0, alpha2_q_per_band);
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)]
fn write_acpl_data_2ch_real_alpha_beta_gamma(
bw: &mut BitWriter,
num_bands: u32,
quant_mode_0: crate::acpl::AcplQuantMode,
quant_mode_1: crate::acpl::AcplQuantMode,
alpha1_q_per_band: &[i32],
alpha2_q_per_band: &[i32],
beta1_q_per_band: &[i32],
beta2_q_per_band: &[i32],
gamma5_q_per_band: &[i32],
gamma6_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_alpha = |bw: &mut BitWriter, qm: crate::acpl::AcplQuantMode, alpha_q: &[i32]| {
bw.write_bit(false); let mut prev_q: i32 = 0;
let mut first = true;
for pb in 0..num_bands {
let a_q = alpha_q.get(pb as usize).copied().unwrap_or(0);
if first {
write_acpl_alpha_f0_value(bw, qm, a_q);
first = false;
} else {
let delta = a_q - prev_q;
write_acpl_alpha_df_value(bw, qm, delta);
}
prev_q = a_q;
}
};
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;
}
};
let emit_real_gamma = |bw: &mut BitWriter, qm: crate::acpl::AcplQuantMode, gamma_q: &[i32]| {
bw.write_bit(false); let mut prev_q: i32 = 0;
let mut first = true;
for pb in 0..num_bands {
let g_q = gamma_q.get(pb as usize).copied().unwrap_or(0);
if first {
write_acpl_gamma_f0_value(bw, qm, g_q);
first = false;
} else {
let delta = g_q - prev_q;
write_acpl_gamma_df_value(bw, qm, delta);
}
prev_q = g_q;
}
};
emit_real_alpha(bw, quant_mode_0, alpha1_q_per_band);
emit_real_alpha(bw, quant_mode_0, alpha2_q_per_band);
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..4 {
emit_zero(bw, crate::acpl::AcplDataType::Gamma, quant_mode_1);
}
emit_real_gamma(bw, quant_mode_1, gamma5_q_per_band);
emit_real_gamma(bw, quant_mode_1, gamma6_q_per_band);
}
#[allow(clippy::too_many_arguments)]
fn write_acpl_data_2ch_real_alpha_beta_full_gamma(
bw: &mut BitWriter,
num_bands: u32,
quant_mode_0: crate::acpl::AcplQuantMode,
quant_mode_1: crate::acpl::AcplQuantMode,
alpha1_q_per_band: &[i32],
alpha2_q_per_band: &[i32],
beta1_q_per_band: &[i32],
beta2_q_per_band: &[i32],
gamma1_q_per_band: &[i32],
gamma2_q_per_band: &[i32],
gamma3_q_per_band: &[i32],
gamma4_q_per_band: &[i32],
gamma5_q_per_band: &[i32],
gamma6_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_alpha = |bw: &mut BitWriter, qm: crate::acpl::AcplQuantMode, alpha_q: &[i32]| {
bw.write_bit(false); let mut prev_q: i32 = 0;
let mut first = true;
for pb in 0..num_bands {
let a_q = alpha_q.get(pb as usize).copied().unwrap_or(0);
if first {
write_acpl_alpha_f0_value(bw, qm, a_q);
first = false;
} else {
let delta = a_q - prev_q;
write_acpl_alpha_df_value(bw, qm, delta);
}
prev_q = a_q;
}
};
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;
}
};
let emit_real_gamma = |bw: &mut BitWriter, qm: crate::acpl::AcplQuantMode, gamma_q: &[i32]| {
bw.write_bit(false); let mut prev_q: i32 = 0;
let mut first = true;
for pb in 0..num_bands {
let g_q = gamma_q.get(pb as usize).copied().unwrap_or(0);
if first {
write_acpl_gamma_f0_value(bw, qm, g_q);
first = false;
} else {
let delta = g_q - prev_q;
write_acpl_gamma_df_value(bw, qm, delta);
}
prev_q = g_q;
}
};
emit_real_alpha(bw, quant_mode_0, alpha1_q_per_band);
emit_real_alpha(bw, quant_mode_0, alpha2_q_per_band);
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);
emit_real_gamma(bw, quant_mode_1, gamma1_q_per_band);
emit_real_gamma(bw, quant_mode_1, gamma2_q_per_band);
emit_real_gamma(bw, quant_mode_1, gamma3_q_per_band);
emit_real_gamma(bw, quant_mode_1, gamma4_q_per_band);
emit_real_gamma(bw, quant_mode_1, gamma5_q_per_band);
emit_real_gamma(bw, quant_mode_1, gamma6_q_per_band);
}
#[allow(clippy::too_many_arguments)]
fn write_acpl_data_2ch_real_alpha_beta_full_gamma_beta3(
bw: &mut BitWriter,
num_bands: u32,
quant_mode_0: crate::acpl::AcplQuantMode,
quant_mode_1: crate::acpl::AcplQuantMode,
alpha1_q_per_band: &[i32],
alpha2_q_per_band: &[i32],
beta1_q_per_band: &[i32],
beta2_q_per_band: &[i32],
beta3_q_per_band: &[i32],
gamma1_q_per_band: &[i32],
gamma2_q_per_band: &[i32],
gamma3_q_per_band: &[i32],
gamma4_q_per_band: &[i32],
gamma5_q_per_band: &[i32],
gamma6_q_per_band: &[i32],
) {
bw.write_bit(false);
bw.write_bit(false);
let emit_real_alpha = |bw: &mut BitWriter, qm: crate::acpl::AcplQuantMode, alpha_q: &[i32]| {
bw.write_bit(false); let mut prev_q: i32 = 0;
let mut first = true;
for pb in 0..num_bands {
let a_q = alpha_q.get(pb as usize).copied().unwrap_or(0);
if first {
write_acpl_alpha_f0_value(bw, qm, a_q);
first = false;
} else {
let delta = a_q - prev_q;
write_acpl_alpha_df_value(bw, qm, delta);
}
prev_q = a_q;
}
};
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;
}
};
let emit_real_beta3 = |bw: &mut BitWriter, qm: crate::acpl::AcplQuantMode, beta3_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 = beta3_q.get(pb as usize).copied().unwrap_or(0);
if first {
write_acpl_beta3_f0_value(bw, qm, b_q);
first = false;
} else {
let delta = b_q - prev_q;
write_acpl_beta3_df_value(bw, qm, delta);
}
prev_q = b_q;
}
};
let emit_real_gamma = |bw: &mut BitWriter, qm: crate::acpl::AcplQuantMode, gamma_q: &[i32]| {
bw.write_bit(false); let mut prev_q: i32 = 0;
let mut first = true;
for pb in 0..num_bands {
let g_q = gamma_q.get(pb as usize).copied().unwrap_or(0);
if first {
write_acpl_gamma_f0_value(bw, qm, g_q);
first = false;
} else {
let delta = g_q - prev_q;
write_acpl_gamma_df_value(bw, qm, delta);
}
prev_q = g_q;
}
};
emit_real_alpha(bw, quant_mode_0, alpha1_q_per_band);
emit_real_alpha(bw, quant_mode_0, alpha2_q_per_band);
emit_real_beta(bw, quant_mode_0, beta1_q_per_band);
emit_real_beta(bw, quant_mode_0, beta2_q_per_band);
emit_real_beta3(bw, quant_mode_0, beta3_q_per_band);
emit_real_gamma(bw, quant_mode_1, gamma1_q_per_band);
emit_real_gamma(bw, quant_mode_1, gamma2_q_per_band);
emit_real_gamma(bw, quant_mode_1, gamma3_q_per_band);
emit_real_gamma(bw, quant_mode_1, gamma4_q_per_band);
emit_real_gamma(bw, quant_mode_1, gamma5_q_per_band);
emit_real_gamma(bw, quant_mode_1, gamma6_q_per_band);
}
#[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
}
#[allow(clippy::too_many_arguments)]
pub fn build_5_x_acpl3_body_from_pcm_spectra_real_alpha_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,
alpha_scale: f32,
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 alpha_q = extract_alpha_q_per_band_carrier_correlation(
coeffs_l,
coeffs_r,
transform_length,
acpl_num_bands,
0,
alpha_scale,
acpl_qm0,
);
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_alpha_beta(
&mut bw,
acpl_num_bands,
acpl_qm0,
acpl_qm1,
&alpha_q,
&alpha_q,
&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
}
#[allow(clippy::too_many_arguments)]
pub fn build_5_x_acpl3_body_from_pcm_spectra_real_alpha_beta_gamma(
transform_length: u32,
max_sfb: u32,
max_sfb_lfe: Option<u32>,
b_iframe: bool,
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_c: Option<&[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,
alpha_scale: f32,
beta_scale: f32,
gamma_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 alpha_q = extract_alpha_q_per_band_carrier_correlation(
coeffs_l,
coeffs_r,
transform_length,
acpl_num_bands,
0,
alpha_scale,
acpl_qm0,
);
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 (g5_q, g6_q) = if let Some(coeffs_c_buf) = coeffs_c {
extract_gamma_5_6_q_per_band_centre_least_squares(
coeffs_l,
coeffs_r,
coeffs_c_buf,
transform_length,
acpl_num_bands,
0,
gamma_scale,
acpl_qm1,
)
} else {
(
vec![0i32; acpl_num_bands as usize],
vec![0i32; acpl_num_bands as usize],
)
};
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_alpha_beta_gamma(
&mut bw,
acpl_num_bands,
acpl_qm0,
acpl_qm1,
&alpha_q,
&alpha_q,
&beta1_q,
&beta2_q,
&g5_q,
&g6_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_acpl3_body_from_pcm_spectra_real_alpha_beta_full_gamma(
transform_length: u32,
max_sfb: u32,
max_sfb_lfe: Option<u32>,
b_iframe: bool,
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_c: Option<&[f32]>,
coeffs_ls: Option<&[f32]>,
coeffs_rs: Option<&[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,
alpha_scale: f32,
beta_scale: f32,
gamma_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 alpha_q = extract_alpha_q_per_band_carrier_correlation(
coeffs_l,
coeffs_r,
transform_length,
acpl_num_bands,
0,
alpha_scale,
acpl_qm0,
);
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 (g1_q, g2_q) = if let Some(coeffs_ls_buf) = coeffs_ls {
extract_gamma_1_2_q_per_band_surround_least_squares(
coeffs_l,
coeffs_r,
coeffs_ls_buf,
transform_length,
acpl_num_bands,
0,
gamma_scale,
acpl_qm1,
)
} else {
(
vec![0i32; acpl_num_bands as usize],
vec![0i32; acpl_num_bands as usize],
)
};
let (g3_q, g4_q) = if let Some(coeffs_rs_buf) = coeffs_rs {
extract_gamma_3_4_q_per_band_surround_least_squares(
coeffs_l,
coeffs_r,
coeffs_rs_buf,
transform_length,
acpl_num_bands,
0,
gamma_scale,
acpl_qm1,
)
} else {
(
vec![0i32; acpl_num_bands as usize],
vec![0i32; acpl_num_bands as usize],
)
};
let (g5_q, g6_q) = if let Some(coeffs_c_buf) = coeffs_c {
extract_gamma_5_6_q_per_band_centre_least_squares(
coeffs_l,
coeffs_r,
coeffs_c_buf,
transform_length,
acpl_num_bands,
0,
gamma_scale,
acpl_qm1,
)
} else {
(
vec![0i32; acpl_num_bands as usize],
vec![0i32; acpl_num_bands as usize],
)
};
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_alpha_beta_full_gamma(
&mut bw,
acpl_num_bands,
acpl_qm0,
acpl_qm1,
&alpha_q,
&alpha_q,
&beta1_q,
&beta2_q,
&g1_q,
&g2_q,
&g3_q,
&g4_q,
&g5_q,
&g6_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_acpl3_body_from_pcm_spectra_real_alpha_beta_full_gamma_beta3(
transform_length: u32,
max_sfb: u32,
max_sfb_lfe: Option<u32>,
b_iframe: bool,
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_c: Option<&[f32]>,
coeffs_ls: Option<&[f32]>,
coeffs_rs: Option<&[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,
alpha_scale: f32,
beta_scale: f32,
gamma_scale: f32,
beta3_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 alpha_q = extract_alpha_q_per_band_carrier_correlation(
coeffs_l,
coeffs_r,
transform_length,
acpl_num_bands,
0,
alpha_scale,
acpl_qm0,
);
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 (g1_q, g2_q) = if let Some(coeffs_ls_buf) = coeffs_ls {
extract_gamma_1_2_q_per_band_surround_least_squares(
coeffs_l,
coeffs_r,
coeffs_ls_buf,
transform_length,
acpl_num_bands,
0,
gamma_scale,
acpl_qm1,
)
} else {
(
vec![0i32; acpl_num_bands as usize],
vec![0i32; acpl_num_bands as usize],
)
};
let (g3_q, g4_q) = if let Some(coeffs_rs_buf) = coeffs_rs {
extract_gamma_3_4_q_per_band_surround_least_squares(
coeffs_l,
coeffs_r,
coeffs_rs_buf,
transform_length,
acpl_num_bands,
0,
gamma_scale,
acpl_qm1,
)
} else {
(
vec![0i32; acpl_num_bands as usize],
vec![0i32; acpl_num_bands as usize],
)
};
let (g5_q, g6_q) = if let Some(coeffs_c_buf) = coeffs_c {
extract_gamma_5_6_q_per_band_centre_least_squares(
coeffs_l,
coeffs_r,
coeffs_c_buf,
transform_length,
acpl_num_bands,
0,
gamma_scale,
acpl_qm1,
)
} else {
(
vec![0i32; acpl_num_bands as usize],
vec![0i32; acpl_num_bands as usize],
)
};
let beta3_q = if let Some(coeffs_c_buf) = coeffs_c {
extract_beta3_q_per_band_centre_residual(
coeffs_l,
coeffs_r,
coeffs_c_buf,
&g1_q,
&g2_q,
&g3_q,
&g4_q,
&g5_q,
&g6_q,
transform_length,
acpl_num_bands,
0,
beta3_scale,
acpl_qm1,
acpl_qm0,
)
} else {
vec![0i32; acpl_num_bands as usize]
};
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_alpha_beta_full_gamma_beta3(
&mut bw,
acpl_num_bands,
acpl_qm0,
acpl_qm1,
&alpha_q,
&alpha_q,
&beta1_q,
&beta2_q,
&beta3_q,
&g1_q,
&g2_q,
&g3_q,
&g4_q,
&g5_q,
&g6_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);
}
#[derive(Debug, Clone, Default)]
pub struct AspxHfgenIwc1ChPayload<'a> {
pub tna_mode: &'a [u8],
pub add_harmonic: &'a [bool],
pub fic_used_in_sfb: &'a [bool],
pub tic_used_in_slot: &'a [bool],
}
fn write_bool_run(bw: &mut BitWriter, src: &[bool], n: u32) {
for i in 0..n as usize {
bw.write_bit(src.get(i).copied().unwrap_or(false));
}
}
fn any_set(src: &[bool], n: u32) -> bool {
src.iter().take(n as usize).any(|&b| b)
}
pub fn write_aspx_hfgen_iwc_1ch(
bw: &mut BitWriter,
payload: &AspxHfgenIwc1ChPayload<'_>,
num_sbg_noise: u32,
num_sbg_sig_highres: u32,
num_aspx_timeslots: u32,
) {
for i in 0..num_sbg_noise as usize {
let mode = payload.tna_mode.get(i).copied().unwrap_or(0);
bw.write_u32((mode & 0x3) as u32, 2);
}
let ah_present = any_set(payload.add_harmonic, num_sbg_sig_highres);
bw.write_bit(ah_present);
if ah_present {
write_bool_run(bw, payload.add_harmonic, num_sbg_sig_highres);
}
let fic_present = any_set(payload.fic_used_in_sfb, num_sbg_sig_highres);
bw.write_bit(fic_present);
if fic_present {
write_bool_run(bw, payload.fic_used_in_sfb, num_sbg_sig_highres);
}
let tic_present = any_set(payload.tic_used_in_slot, num_aspx_timeslots);
bw.write_bit(tic_present);
if tic_present {
write_bool_run(bw, payload.tic_used_in_slot, num_aspx_timeslots);
}
}
#[derive(Debug, Clone, Default)]
pub struct AspxHfgenIwc2ChPayload<'a> {
pub tna_mode: [&'a [u8]; 2],
pub add_harmonic: [&'a [bool]; 2],
pub fic_used_in_sfb: [&'a [bool]; 2],
pub tic_used_in_slot: [&'a [bool]; 2],
}
pub fn write_aspx_hfgen_iwc_2ch(
bw: &mut BitWriter,
payload: &AspxHfgenIwc2ChPayload<'_>,
aspx_balance: bool,
num_sbg_noise: u32,
num_sbg_sig_highres: u32,
num_aspx_timeslots: u32,
) {
for i in 0..num_sbg_noise as usize {
let mode = payload.tna_mode[0].get(i).copied().unwrap_or(0);
bw.write_u32((mode & 0x3) as u32, 2);
}
if !aspx_balance {
for i in 0..num_sbg_noise as usize {
let mode = payload.tna_mode[1].get(i).copied().unwrap_or(0);
bw.write_u32((mode & 0x3) as u32, 2);
}
}
let ah_left = any_set(payload.add_harmonic[0], num_sbg_sig_highres);
bw.write_bit(ah_left);
if ah_left {
write_bool_run(bw, payload.add_harmonic[0], num_sbg_sig_highres);
}
let ah_right = any_set(payload.add_harmonic[1], num_sbg_sig_highres);
bw.write_bit(ah_right);
if ah_right {
write_bool_run(bw, payload.add_harmonic[1], num_sbg_sig_highres);
}
let fic_left = any_set(payload.fic_used_in_sfb[0], num_sbg_sig_highres);
let fic_right = any_set(payload.fic_used_in_sfb[1], num_sbg_sig_highres);
let fic_present = fic_left || fic_right;
bw.write_bit(fic_present);
if fic_present {
bw.write_bit(fic_left);
if fic_left {
write_bool_run(bw, payload.fic_used_in_sfb[0], num_sbg_sig_highres);
}
bw.write_bit(fic_right);
if fic_right {
write_bool_run(bw, payload.fic_used_in_sfb[1], num_sbg_sig_highres);
}
}
let tic_left_active = any_set(payload.tic_used_in_slot[0], num_aspx_timeslots);
let tic_right_active = any_set(payload.tic_used_in_slot[1], num_aspx_timeslots);
let tic_present = tic_left_active || tic_right_active;
bw.write_bit(tic_present);
if tic_present {
let patterns_equal = (0..num_aspx_timeslots as usize).all(|i| {
payload.tic_used_in_slot[0].get(i).copied().unwrap_or(false)
== payload.tic_used_in_slot[1].get(i).copied().unwrap_or(false)
});
let tic_copy = patterns_equal && tic_left_active;
bw.write_bit(tic_copy);
if !tic_copy {
bw.write_bit(tic_left_active);
bw.write_bit(tic_right_active);
}
if tic_copy || tic_left_active {
write_bool_run(bw, payload.tic_used_in_slot[0], num_aspx_timeslots);
}
if !tic_copy && tic_right_active {
write_bool_run(bw, payload.tic_used_in_slot[1], num_aspx_timeslots);
}
}
}
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;
write_aspx_hfgen_iwc_1ch(
bw,
&AspxHfgenIwc1ChPayload::default(),
counts.num_sbg_noise,
counts.num_sbg_sig_highres,
0,
);
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(())
}
#[derive(Debug, Clone, Default)]
pub struct AspxRealEnvelopeChannel<'a> {
pub sig: &'a [i32],
pub noise: &'a [i32],
}
pub fn write_aspx_data_2ch_real_envelope(
bw: &mut BitWriter,
cfg: &aspx::AspxConfig,
ch0: AspxRealEnvelopeChannel<'_>,
ch1: AspxRealEnvelopeChannel<'_>,
) -> 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_sig = if cfg.fixfix_tmp_num_env_bits() == 1 {
aspx::AspxQuantStep::Fine
} else {
cfg.quant_mode_env
};
write_aspx_sig_envelope_values(
bw,
qmode_sig,
aspx::AspxStereoMode::Level,
ch0.sig,
num_sbg_sig,
);
write_aspx_sig_envelope_values(
bw,
qmode_sig,
aspx::AspxStereoMode::Balance,
ch1.sig,
num_sbg_sig,
);
write_aspx_noise_envelope_values(bw, aspx::AspxStereoMode::Level, ch0.noise, num_sbg_noise);
write_aspx_noise_envelope_values(bw, aspx::AspxStereoMode::Balance, ch1.noise, num_sbg_noise);
Ok(())
}
pub fn write_aspx_data_1ch_real_envelope(
bw: &mut BitWriter,
cfg: &aspx::AspxConfig,
ch: AspxRealEnvelopeChannel<'_>,
) -> 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 = 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_sig = if cfg.fixfix_tmp_num_env_bits() == 1 {
aspx::AspxQuantStep::Fine
} else {
cfg.quant_mode_env
};
write_aspx_sig_envelope_values(
bw,
qmode_sig,
aspx::AspxStereoMode::Level,
ch.sig,
num_sbg_sig,
);
write_aspx_noise_envelope_values(bw, aspx::AspxStereoMode::Level, ch.noise, num_sbg_noise);
Ok(())
}
fn write_aspx_sig_envelope_values(
bw: &mut BitWriter,
quant: aspx::AspxQuantStep,
stereo: aspx::AspxStereoMode,
values: &[i32],
num_sbg: u32,
) {
if num_sbg == 0 {
return;
}
let f0 = values.first().copied().unwrap_or(0);
write_aspx_sig_f0_value(bw, quant, stereo, f0);
for i in 1..num_sbg as usize {
let delta = values.get(i).copied().unwrap_or(0);
write_aspx_sig_df_value(bw, quant, stereo, delta);
}
}
fn write_aspx_noise_envelope_values(
bw: &mut BitWriter,
stereo: aspx::AspxStereoMode,
values: &[i32],
num_sbg: u32,
) {
if num_sbg == 0 {
return;
}
let f0 = values.first().copied().unwrap_or(0);
write_aspx_noise_f0_value(bw, stereo, f0);
for i in 1..num_sbg as usize {
let delta = values.get(i).copied().unwrap_or(0);
write_aspx_noise_df_value(bw, stereo, delta);
}
}
fn write_aspx_sig_envelope_directional(
bw: &mut BitWriter,
quant: aspx::AspxQuantStep,
stereo: aspx::AspxStereoMode,
env: &AspxEncodedEnvelope,
num_sbg: u32,
) {
if num_sbg == 0 {
return;
}
if env.direction_time {
for i in 0..num_sbg as usize {
let d = env.values.get(i).copied().unwrap_or(0);
write_aspx_sig_dt_value(bw, quant, stereo, d);
}
} else {
let f0 = env.values.first().copied().unwrap_or(0);
write_aspx_sig_f0_value(bw, quant, stereo, f0);
for i in 1..num_sbg as usize {
let d = env.values.get(i).copied().unwrap_or(0);
write_aspx_sig_df_value(bw, quant, stereo, d);
}
}
}
fn write_aspx_noise_envelope_directional(
bw: &mut BitWriter,
stereo: aspx::AspxStereoMode,
env: &AspxEncodedEnvelope,
num_sbg: u32,
) {
if num_sbg == 0 {
return;
}
if env.direction_time {
for i in 0..num_sbg as usize {
let d = env.values.get(i).copied().unwrap_or(0);
write_aspx_noise_dt_value(bw, stereo, d);
}
} else {
let f0 = env.values.first().copied().unwrap_or(0);
write_aspx_noise_f0_value(bw, stereo, f0);
for i in 1..num_sbg as usize {
let d = env.values.get(i).copied().unwrap_or(0);
write_aspx_noise_df_value(bw, stereo, d);
}
}
}
#[derive(Debug, Clone, Default)]
pub struct AspxMultiEnvelopeChannel<'a> {
pub sig: &'a [AspxEncodedEnvelope],
pub noise: &'a [AspxEncodedEnvelope],
}
fn zero_freq_env() -> AspxEncodedEnvelope {
AspxEncodedEnvelope {
values: Vec::new(),
direction_time: false,
}
}
pub fn write_aspx_data_2ch_multi_envelope(
bw: &mut BitWriter,
cfg: &aspx::AspxConfig,
num_env: u32,
ch0: AspxMultiEnvelopeChannel<'_>,
ch1: AspxMultiEnvelopeChannel<'_>,
) -> Result<(), &'static str> {
let tmp_num_env = check_multi_env_cfg(cfg, num_env)?;
let num_noise = if num_env > 1 { 2 } else { 1 };
let xover: u32 = 0;
bw.write_u32(xover, 3);
bw.write_bit(false);
let envbits = cfg.fixfix_tmp_num_env_bits();
bw.write_u32(tmp_num_env, envbits);
bw.write_bit(true);
write_delta_dir_bits(bw, num_env, num_noise, &ch0);
write_delta_dir_bits(bw, num_env, num_noise, &ch1);
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 = counts.num_sbg_sig_highres;
let num_sbg_noise = counts.num_sbg_noise;
let qmode_sig = cfg.quant_mode_env;
write_sig_envelopes(
bw,
qmode_sig,
aspx::AspxStereoMode::Level,
ch0.sig,
num_env,
num_sbg_sig,
);
write_sig_envelopes(
bw,
qmode_sig,
aspx::AspxStereoMode::Balance,
ch1.sig,
num_env,
num_sbg_sig,
);
write_noise_envelopes(
bw,
aspx::AspxStereoMode::Level,
ch0.noise,
num_noise,
num_sbg_noise,
);
write_noise_envelopes(
bw,
aspx::AspxStereoMode::Balance,
ch1.noise,
num_noise,
num_sbg_noise,
);
Ok(())
}
pub fn write_aspx_data_1ch_multi_envelope(
bw: &mut BitWriter,
cfg: &aspx::AspxConfig,
num_env: u32,
ch: AspxMultiEnvelopeChannel<'_>,
) -> Result<(), &'static str> {
let tmp_num_env = check_multi_env_cfg(cfg, num_env)?;
let num_noise = if num_env > 1 { 2 } else { 1 };
let xover: u32 = 0;
bw.write_u32(xover, 3);
bw.write_bit(false);
let envbits = cfg.fixfix_tmp_num_env_bits();
bw.write_u32(tmp_num_env, envbits);
write_delta_dir_bits(bw, num_env, num_noise, &ch);
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 = cfg.quant_mode_env;
write_sig_envelopes(
bw,
qmode_sig,
aspx::AspxStereoMode::Level,
ch.sig,
num_env,
num_sbg_sig,
);
write_noise_envelopes(
bw,
aspx::AspxStereoMode::Level,
ch.noise,
num_noise,
num_sbg_noise,
);
Ok(())
}
fn check_multi_env_cfg(cfg: &aspx::AspxConfig, num_env: u32) -> Result<u32, &'static str> {
if cfg.signals_freq_res() {
return Err("encoder: multi-envelope writer requires !signals_freq_res()");
}
if num_env == 0 || !num_env.is_power_of_two() {
return Err("encoder: num_env must be a positive power of two");
}
let tmp_num_env = num_env.trailing_zeros();
let envbits = cfg.fixfix_tmp_num_env_bits();
if tmp_num_env >= (1u32 << envbits) {
return Err("encoder: num_env exceeds fixfix_tmp_num_env_bits() capacity");
}
Ok(tmp_num_env)
}
fn write_delta_dir_bits(
bw: &mut BitWriter,
num_env: u32,
num_noise: u32,
ch: &AspxMultiEnvelopeChannel<'_>,
) {
for e in 0..num_env as usize {
let dir = ch.sig.get(e).map(|r| r.direction_time).unwrap_or(false);
bw.write_bit(dir);
}
for e in 0..num_noise as usize {
let dir = ch.noise.get(e).map(|r| r.direction_time).unwrap_or(false);
bw.write_bit(dir);
}
}
fn write_sig_envelopes(
bw: &mut BitWriter,
quant: aspx::AspxQuantStep,
stereo: aspx::AspxStereoMode,
rows: &[AspxEncodedEnvelope],
num_env: u32,
num_sbg: u32,
) {
let pad = zero_freq_env();
for e in 0..num_env as usize {
let env = rows.get(e).unwrap_or(&pad);
write_aspx_sig_envelope_directional(bw, quant, stereo, env, num_sbg);
}
}
fn write_noise_envelopes(
bw: &mut BitWriter,
stereo: aspx::AspxStereoMode,
rows: &[AspxEncodedEnvelope],
num_noise: u32,
num_sbg: u32,
) {
let pad = zero_freq_env();
for e in 0..num_noise as usize {
let env = rows.get(e).unwrap_or(&pad);
write_aspx_noise_envelope_directional(bw, stereo, env, num_sbg);
}
}
pub fn quantize_sig_scf(scf: f32, qmode_env: aspx::AspxQuantStep, num_qmf_subbands: u32) -> i32 {
let a: f32 = match qmode_env {
aspx::AspxQuantStep::Fine => 2.0,
aspx::AspxQuantStep::Coarse => 1.0,
};
let n_subbands = num_qmf_subbands as f32;
let scf_clamped = scf.max(f32::MIN_POSITIVE);
let ratio = (scf_clamped / n_subbands).max(f32::MIN_POSITIVE);
let q = a * ratio.log2();
q.round() as i32
}
pub fn quantize_noise_scf(scf: f32) -> i32 {
const NOISE_FLOOR_OFFSET: i32 = 6;
let scf_clamped = scf.max(f32::MIN_POSITIVE);
let q = (NOISE_FLOOR_OFFSET as f32) - scf_clamped.log2();
q.round() as i32
}
pub fn freq_dpcm_encode_qscf(qscf: &[i32]) -> Vec<i32> {
if qscf.is_empty() {
return Vec::new();
}
let mut out = Vec::with_capacity(qscf.len());
out.push(qscf[0]);
for sbg in 1..qscf.len() {
out.push(qscf[sbg] - qscf[sbg - 1]);
}
out
}
pub fn time_dpcm_encode_qscf(qscf: &[i32], prev: &[i32], delta: i32) -> Vec<i32> {
if qscf.is_empty() {
return Vec::new();
}
let step = if delta == 0 { 1 } else { delta };
qscf.iter()
.enumerate()
.map(|(sbg, &q)| {
let p = prev.get(sbg).copied().unwrap_or(0);
(q - p) / step
})
.collect()
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct AspxEncodedEnvelope {
pub values: Vec<i32>,
pub direction_time: bool,
}
pub fn dpcm_encode_qscf_envelopes(
qscf: &[Vec<i32>],
qscf_prev_last: &[i32],
delta: i32,
force_freq: bool,
) -> Vec<AspxEncodedEnvelope> {
let num_sbg = qscf.len();
if num_sbg == 0 {
return Vec::new();
}
let num_env = qscf[0].len();
let mut out: Vec<AspxEncodedEnvelope> = Vec::with_capacity(num_env);
for atsg in 0..num_env {
let col: Vec<i32> = (0..num_sbg)
.map(|sbg| qscf[sbg].get(atsg).copied().unwrap_or(0))
.collect();
let freq = freq_dpcm_encode_qscf(&col);
if force_freq {
out.push(AspxEncodedEnvelope {
values: freq,
direction_time: false,
});
continue;
}
let prev: Vec<i32> = if atsg == 0 {
qscf_prev_last.to_vec()
} else {
(0..num_sbg)
.map(|sbg| qscf[sbg].get(atsg - 1).copied().unwrap_or(0))
.collect()
};
let time = time_dpcm_encode_qscf(&col, &prev, delta);
let cost_freq: i64 = freq.iter().map(|&v| (v as i64).abs()).sum();
let cost_time: i64 = time.iter().map(|&v| (v as i64).abs()).sum();
if cost_time < cost_freq {
out.push(AspxEncodedEnvelope {
values: time,
direction_time: true,
});
} else {
out.push(AspxEncodedEnvelope {
values: freq,
direction_time: false,
});
}
}
out
}
pub fn extract_aspx_sig_envelope_indices(
scf_sig: &[f32],
qmode_env: aspx::AspxQuantStep,
num_qmf_subbands: u32,
) -> Vec<i32> {
let qscf: Vec<i32> = scf_sig
.iter()
.map(|&v| quantize_sig_scf(v, qmode_env, num_qmf_subbands))
.collect();
freq_dpcm_encode_qscf(&qscf)
}
pub fn extract_aspx_noise_envelope_indices(scf_noise: &[f32]) -> Vec<i32> {
let qscf: Vec<i32> = scf_noise.iter().map(|&v| quantize_noise_scf(v)).collect();
freq_dpcm_encode_qscf(&qscf)
}
#[derive(Debug, Clone, Default)]
pub struct AspxEnvelopeScfChannel<'a> {
pub sig: &'a [f32],
pub noise: &'a [f32],
}
pub fn build_aspx_real_envelope_channel(
ch: &AspxEnvelopeScfChannel<'_>,
qmode_env: aspx::AspxQuantStep,
num_qmf_subbands: u32,
) -> (Vec<i32>, Vec<i32>) {
let sig = extract_aspx_sig_envelope_indices(ch.sig, qmode_env, num_qmf_subbands);
let noise = extract_aspx_noise_envelope_indices(ch.noise);
(sig, noise)
}
pub fn aggregate_qmf_to_sbg_atsg(
q_high: &[Vec<(f32, f32)>],
sbg_borders: &[u32],
atsg_borders: &[u32],
num_ts_in_ats: u32,
sbx: u32,
) -> Vec<Vec<f32>> {
let num_sbg = sbg_borders.len().saturating_sub(1);
let num_atsg = atsg_borders.len().saturating_sub(1);
let mut result: Vec<Vec<f32>> = vec![vec![0.0_f32; num_atsg]; num_sbg];
if num_sbg == 0 || num_atsg == 0 {
return result;
}
for atsg in 0..num_atsg {
let tsa = atsg_borders[atsg] * num_ts_in_ats;
let tsz = atsg_borders[atsg + 1] * num_ts_in_ats;
let ts_span = tsz.saturating_sub(tsa) as f64;
if ts_span <= 0.0 {
continue;
}
for sbg in 0..num_sbg {
let lo = sbg_borders[sbg].max(sbx) as usize;
let hi = sbg_borders[sbg + 1].max(sbx) as usize;
if hi <= lo {
continue;
}
let band_span = (hi - lo) as f64;
let mut acc: f64 = 0.0;
for sb_abs in lo..hi {
if sb_abs >= q_high.len() {
continue;
}
let row = &q_high[sb_abs];
for ts in tsa..tsz {
let ts = ts as usize;
if ts < row.len() {
let (re, im) = row[ts];
acc += (re as f64) * (re as f64) + (im as f64) * (im as f64);
}
}
}
result[sbg][atsg] = (acc / (band_span * ts_span)) as f32;
}
}
result
}
pub fn extract_aspx_sig_envelope_scf_from_qmf(
q_high: &[Vec<(f32, f32)>],
sbg_sig_borders: &[u32],
num_ts_in_ats: u32,
aspx_frame_ts_count: u32,
sbx: u32,
) -> Vec<f32> {
if sbg_sig_borders.len() < 2 || aspx_frame_ts_count == 0 || num_ts_in_ats == 0 {
return Vec::new();
}
let atsg_borders = [0u32, aspx_frame_ts_count];
let agg = aggregate_qmf_to_sbg_atsg(q_high, sbg_sig_borders, &atsg_borders, num_ts_in_ats, sbx);
agg.into_iter()
.map(|row| row.first().copied().unwrap_or(0.0))
.collect()
}
pub fn extract_aspx_noise_envelope_scf_from_qmf(
q_high: &[Vec<(f32, f32)>],
sbg_noise_borders: &[u32],
num_ts_in_ats: u32,
aspx_frame_ts_count: u32,
sbx: u32,
) -> Vec<f32> {
if sbg_noise_borders.len() < 2 || aspx_frame_ts_count == 0 || num_ts_in_ats == 0 {
return Vec::new();
}
let atsg_borders = [0u32, aspx_frame_ts_count];
let agg =
aggregate_qmf_to_sbg_atsg(q_high, sbg_noise_borders, &atsg_borders, num_ts_in_ats, sbx);
agg.into_iter()
.map(|row| row.first().copied().unwrap_or(0.0))
.collect()
}
#[derive(Debug, Clone)]
pub struct AspxQmfEnvelopeChannel<'a> {
pub q_high: &'a [Vec<(f32, f32)>],
pub sbg_sig_borders: &'a [u32],
pub sbg_noise_borders: &'a [u32],
}
pub fn build_aspx_real_envelope_channel_from_qmf(
ch: &AspxQmfEnvelopeChannel<'_>,
qmode_env: aspx::AspxQuantStep,
num_qmf_subbands: u32,
num_ts_in_ats: u32,
aspx_frame_ts_count: u32,
sbx: u32,
) -> (Vec<i32>, Vec<i32>) {
let sig_scf = extract_aspx_sig_envelope_scf_from_qmf(
ch.q_high,
ch.sbg_sig_borders,
num_ts_in_ats,
aspx_frame_ts_count,
sbx,
);
let noise_scf = extract_aspx_noise_envelope_scf_from_qmf(
ch.q_high,
ch.sbg_noise_borders,
num_ts_in_ats,
aspx_frame_ts_count,
sbx,
);
let scf_ch = AspxEnvelopeScfChannel {
sig: &sig_scf,
noise: &noise_scf,
};
build_aspx_real_envelope_channel(&scf_ch, qmode_env, num_qmf_subbands)
}
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)]
fn write_acpl_1_residual_layer_sap(
bw: &mut BitWriter,
transform_length: u32,
max_sfb_master: u32,
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_ls: &[f32],
coeffs_rs: &[f32],
chparam_pair: Option<&[crate::asf::ChparamInfo; 2]>,
) -> 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);
let identity_pair = [
crate::asf::ChparamInfo {
sap_mode: 0,
ms_used: vec![],
sap_data: None,
},
crate::asf::ChparamInfo {
sap_mode: 0,
ms_used: vec![],
sap_data: None,
},
];
let pair: &[crate::asf::ChparamInfo; 2] = chparam_pair.unwrap_or(&identity_pair);
let max_sfb_per_group = [max_sfb_master];
crate::encoder_asf::write_chparam_info(bw, &pair[0], &max_sfb_per_group);
crate::encoder_asf::write_chparam_info(bw, &pair[1], &max_sfb_per_group);
let n = transform_length as usize;
let pad = |src: &[f32]| -> Vec<f32> {
let mut v = vec![0.0f32; n];
let take = src.len().min(n);
v[..take].copy_from_slice(&src[..take]);
v
};
let l_pad = pad(coeffs_l);
let r_pad = pad(coeffs_r);
let ls_pad = pad(coeffs_ls);
let rs_pad = pad(coeffs_rs);
let (s3, s4) = match crate::asf::invert_sap_table_181(
&l_pad,
&r_pad,
&ls_pad,
&rs_pad,
pair,
max_sfb_master,
transform_length,
) {
Some((_a, _b, s3, s4)) => (s3, s4),
None => (ls_pad.clone(), rs_pad.clone()),
};
for coeffs in [&s3, &s4] {
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
}
#[allow(clippy::too_many_arguments)]
pub fn build_5_x_acpl1_body_from_pcm_spectra_sap(
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],
chparam_pair: Option<&[crate::asf::ChparamInfo; 2]>,
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_sap(
&mut bw,
transform_length,
max_sfb_master,
coeffs_l,
coeffs_r,
coeffs_ls,
coeffs_rs,
chparam_pair,
);
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
}
pub fn select_acpl1_residual_chparam_pair(
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_ls: &[f32],
coeffs_rs: &[f32],
max_sfb_master: u32,
transform_length: u32,
) -> [crate::asf::ChparamInfo; 2] {
let Some(sfbo) = crate::sfb_offset::sfb_offset_48(transform_length) else {
return [
crate::asf::build_chparam_info_none(),
crate::asf::build_chparam_info_none(),
];
};
let max_sfb_per_group = [max_sfb_master];
let build_row = |front: &[f32], surround: &[f32]| -> crate::asf::ChparamInfo {
let (mut alpha_q, used) = crate::asf::select_alpha_q_for_pair(
&[front.to_vec()],
&[surround.to_vec()],
sfbo,
&max_sfb_per_group,
);
for row in alpha_q.iter_mut() {
for v in row.iter_mut() {
*v = (*v).clamp(-30, 30);
}
}
let any_used = used.iter().any(|row| row.iter().any(|&b| b));
if any_used {
crate::asf::build_chparam_info_sap_data_from_alpha_q(
&alpha_q,
&used,
false,
&max_sfb_per_group,
)
} else {
crate::asf::build_chparam_info_none()
}
};
[
build_row(coeffs_l, coeffs_ls),
build_row(coeffs_r, coeffs_rs),
]
}
#[allow(clippy::too_many_arguments)]
pub fn build_5_x_acpl1_body_from_pcm_spectra_sap_auto(
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 (_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));
let chparam_pair = select_acpl1_residual_chparam_pair(
coeffs_l,
coeffs_r,
coeffs_ls,
coeffs_rs,
max_sfb_master,
transform_length,
);
let n = transform_length as usize;
let pad = |src: &[f32]| -> Vec<f32> {
let mut v = vec![0.0f32; n];
let take = src.len().min(n);
v[..take].copy_from_slice(&src[..take]);
v
};
let l_pad = pad(coeffs_l);
let r_pad = pad(coeffs_r);
let ls_pad = pad(coeffs_ls);
let rs_pad = pad(coeffs_rs);
let (carrier_a, carrier_b) = match crate::asf::invert_sap_table_181(
&l_pad,
&r_pad,
&ls_pad,
&rs_pad,
&chparam_pair,
max_sfb_master,
transform_length,
) {
Some((a, b, _s3, _s4)) => (a, b),
None => (l_pad.clone(), r_pad.clone()),
};
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, &carrier_a, &carrier_b);
write_acpl_1_residual_layer_sap(
&mut bw,
transform_length,
max_sfb_master,
coeffs_l,
coeffs_r,
coeffs_ls,
coeffs_rs,
Some(&chparam_pair),
);
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()
}
pub fn extract_alpha_q_per_band_carrier_correlation(
coeffs_l: &[f32],
coeffs_r: &[f32],
transform_length: u32,
num_param_bands: u32,
start_pb: u32,
alpha_scale: f32,
qm: crate::acpl::AcplQuantMode,
) -> Vec<i32> {
let n = num_param_bands as usize;
let mut e_lr = vec![0.0f32; n];
let mut e_ll = vec![0.0f32; n];
let mut e_rr = vec![0.0f32; n];
let len = coeffs_l.len().min(coeffs_r.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 xl = coeffs_l[bin];
let xr = coeffs_r[bin];
e_lr[pb] += xl * xr;
e_ll[pb] += xl * xl;
e_rr[pb] += xr * xr;
}
let max_abs: f32 = match qm {
crate::acpl::AcplQuantMode::Fine => 2.0,
crate::acpl::AcplQuantMode::Coarse => 2.0,
};
(0..n)
.map(|pb| {
let denom_sq = e_ll[pb] * e_rr[pb];
if denom_sq <= 0.0 || !denom_sq.is_finite() {
return 0;
}
let denom = denom_sq.sqrt();
let rho = e_lr[pb] / denom;
let mut a = alpha_scale * rho;
if !a.is_finite() {
a = 0.0;
}
quantise_alpha(a.clamp(-max_abs, max_abs), qm)
})
.collect()
}
fn quantise_gamma(gamma: f32, qm: crate::acpl::AcplQuantMode) -> i32 {
let delta = crate::acpl_synth::gamma_delta(qm);
let cb_off: i32 = match qm {
crate::acpl::AcplQuantMode::Fine => 20,
crate::acpl::AcplQuantMode::Coarse => 10,
};
let max_abs = (cb_off as f32) * delta;
let g = gamma.clamp(-max_abs, max_abs);
let raw = (g / delta).round() as i32;
raw.clamp(-cb_off, cb_off)
}
fn quantise_beta3(beta3: f32, qm: crate::acpl::AcplQuantMode) -> i32 {
let delta = crate::acpl_synth::beta3_delta(qm);
let cb_off: i32 = match qm {
crate::acpl::AcplQuantMode::Fine => 8,
crate::acpl::AcplQuantMode::Coarse => 4,
};
let max_abs = (cb_off as f32) * delta;
let b = beta3.clamp(-max_abs, max_abs);
let raw = (b / delta).round() as i32;
raw.clamp(-cb_off, cb_off)
}
#[allow(clippy::too_many_arguments)]
pub fn extract_gamma_5_6_q_per_band_centre_least_squares(
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_c: &[f32],
transform_length: u32,
num_param_bands: u32,
start_pb: u32,
gamma_scale: f32,
qm: crate::acpl::AcplQuantMode,
) -> (Vec<i32>, Vec<i32>) {
let n = num_param_bands as usize;
let mut e_ll = vec![0.0f32; n];
let mut e_rr = vec![0.0f32; n];
let mut e_lr = vec![0.0f32; n];
let mut e_lc = vec![0.0f32; n];
let mut e_rc = vec![0.0f32; n];
let len = coeffs_l.len().min(coeffs_r.len()).min(coeffs_c.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 xl = coeffs_l[bin];
let xr = coeffs_r[bin];
let xc = coeffs_c[bin];
e_ll[pb] += xl * xl;
e_rr[pb] += xr * xr;
e_lr[pb] += xl * xr;
e_lc[pb] += xl * xc;
e_rc[pb] += xr * xc;
}
let k = 1.0 + (0.5f32).sqrt();
let inv_k = 1.0 / k;
let mut g5_q = vec![0i32; n];
let mut g6_q = vec![0i32; n];
for pb in 0..n {
if (pb as u32) < start_pb {
continue;
}
let a = e_ll[pb];
let b = e_lr[pb];
let c = e_rr[pb];
let det = a * c - b * b;
if !det.is_finite() || det.abs() <= f32::EPSILON * (a.abs() + c.abs() + 1.0) {
continue;
}
let rhs0 = e_lc[pb] * inv_k;
let rhs1 = e_rc[pb] * inv_k;
let g5_raw = (c * rhs0 - b * rhs1) / det;
let g6_raw = (-b * rhs0 + a * rhs1) / det;
let g5 = gamma_scale * g5_raw;
let g6 = gamma_scale * g6_raw;
if !g5.is_finite() || !g6.is_finite() {
continue;
}
g5_q[pb] = quantise_gamma(g5, qm);
g6_q[pb] = quantise_gamma(g6, qm);
}
(g5_q, g6_q)
}
#[allow(clippy::too_many_arguments)]
fn extract_gamma_pair_q_per_band_surround_least_squares(
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_front: &[f32],
coeffs_back: &[f32],
transform_length: u32,
num_param_bands: u32,
start_pb: u32,
gamma_scale: f32,
qm: crate::acpl::AcplQuantMode,
) -> (Vec<i32>, Vec<i32>) {
let n = num_param_bands as usize;
let mut e_ll = vec![0.0f32; n];
let mut e_rr = vec![0.0f32; n];
let mut e_lr = vec![0.0f32; n];
let mut e_lt = vec![0.0f32; n];
let mut e_rt = vec![0.0f32; n];
let k = 1.0 + (2.0f32).sqrt();
let inv_k = 1.0 / k;
let inv_sqrt2 = 1.0 / (2.0f32).sqrt();
let len = coeffs_l
.len()
.min(coeffs_r.len())
.min(coeffs_front.len())
.min(coeffs_back.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 xl = coeffs_l[bin];
let xr = coeffs_r[bin];
let t = (coeffs_front[bin] + coeffs_back[bin] * inv_sqrt2) * inv_k;
e_ll[pb] += xl * xl;
e_rr[pb] += xr * xr;
e_lr[pb] += xl * xr;
e_lt[pb] += xl * t;
e_rt[pb] += xr * t;
}
let mut g_q = vec![0i32; n];
let mut gp_q = vec![0i32; n];
for pb in 0..n {
if (pb as u32) < start_pb {
continue;
}
let a = e_ll[pb];
let b = e_lr[pb];
let c = e_rr[pb];
let det = a * c - b * b;
if !det.is_finite() || det.abs() <= f32::EPSILON * (a.abs() + c.abs() + 1.0) {
continue;
}
let g_raw = (c * e_lt[pb] - b * e_rt[pb]) / det;
let gp_raw = (-b * e_lt[pb] + a * e_rt[pb]) / det;
let g = gamma_scale * g_raw;
let gp = gamma_scale * gp_raw;
if !g.is_finite() || !gp.is_finite() {
continue;
}
g_q[pb] = quantise_gamma(g, qm);
gp_q[pb] = quantise_gamma(gp, qm);
}
(g_q, gp_q)
}
#[allow(clippy::too_many_arguments)]
pub fn extract_gamma_1_2_q_per_band_surround_least_squares(
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_ls: &[f32],
transform_length: u32,
num_param_bands: u32,
start_pb: u32,
gamma_scale: f32,
qm: crate::acpl::AcplQuantMode,
) -> (Vec<i32>, Vec<i32>) {
extract_gamma_pair_q_per_band_surround_least_squares(
coeffs_l,
coeffs_r,
coeffs_l,
coeffs_ls,
transform_length,
num_param_bands,
start_pb,
gamma_scale,
qm,
)
}
#[allow(clippy::too_many_arguments)]
pub fn extract_gamma_3_4_q_per_band_surround_least_squares(
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_rs: &[f32],
transform_length: u32,
num_param_bands: u32,
start_pb: u32,
gamma_scale: f32,
qm: crate::acpl::AcplQuantMode,
) -> (Vec<i32>, Vec<i32>) {
extract_gamma_pair_q_per_band_surround_least_squares(
coeffs_l,
coeffs_r,
coeffs_r,
coeffs_rs,
transform_length,
num_param_bands,
start_pb,
gamma_scale,
qm,
)
}
#[allow(clippy::too_many_arguments)]
pub fn extract_beta3_q_per_band_centre_residual(
coeffs_l: &[f32],
coeffs_r: &[f32],
coeffs_c: &[f32],
gamma1_q: &[i32],
gamma2_q: &[i32],
gamma3_q: &[i32],
gamma4_q: &[i32],
gamma5_q: &[i32],
gamma6_q: &[i32],
transform_length: u32,
num_param_bands: u32,
start_pb: u32,
beta3_scale: f32,
qm_gamma: crate::acpl::AcplQuantMode,
qm_beta3: crate::acpl::AcplQuantMode,
) -> Vec<i32> {
let n = num_param_bands as usize;
let mut e_ll = vec![0.0f32; n];
let mut e_rr = vec![0.0f32; n];
let mut e_lr = vec![0.0f32; n];
let mut e_lc = vec![0.0f32; n];
let mut e_rc = vec![0.0f32; n];
let mut e_cc = vec![0.0f32; n];
let len = coeffs_l.len().min(coeffs_r.len()).min(coeffs_c.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 xl = coeffs_l[bin];
let xr = coeffs_r[bin];
let xc = coeffs_c[bin];
e_ll[pb] += xl * xl;
e_rr[pb] += xr * xr;
e_lr[pb] += xl * xr;
e_lc[pb] += xl * xc;
e_rc[pb] += xr * xc;
e_cc[pb] += xc * xc;
}
let gd = crate::acpl_synth::gamma_delta(qm_gamma);
let k = 1.0 + (0.5f32).sqrt();
let s2 = {
let s = 1.0 + (2.0f32).sqrt();
s * s
};
let mut beta3_q = vec![0i32; n];
for pb in 0..n {
if (pb as u32) < start_pb {
continue;
}
let g_at = |g: &[i32]| g.get(pb).copied().unwrap_or(0) as f32 * gd;
let g1 = g_at(gamma1_q);
let g2 = g_at(gamma2_q);
let g3 = g_at(gamma3_q);
let g4 = g_at(gamma4_q);
let g5 = g_at(gamma5_q);
let g6 = g_at(gamma6_q);
let e_res = (e_cc[pb] - 2.0 * k * (g5 * e_lc[pb] + g6 * e_rc[pb])
+ k * k * (g5 * g5 * e_ll[pb] + 2.0 * g5 * g6 * e_lr[pb] + g6 * g6 * e_rr[pb]))
.max(0.0);
let big_g1 = g1 + g3 + g5;
let big_g2 = g2 + g4 + g6;
let e_v3 = s2
* (big_g1 * big_g1 * e_ll[pb]
+ 2.0 * big_g1 * big_g2 * e_lr[pb]
+ big_g2 * big_g2 * e_rr[pb]);
if !e_res.is_finite() || !e_v3.is_finite() || e_res <= 0.0 || e_v3 <= f32::EPSILON {
continue;
}
let b3 = beta3_scale * (2.0 * e_res / e_v3).sqrt();
if !b3.is_finite() {
continue;
}
beta3_q[pb] = quantise_beta3(b3, qm_beta3);
}
beta3_q
}
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_gamma_f0_value(bw: &mut BitWriter, qm: crate::acpl::AcplQuantMode, gamma_q: i32) {
let (len, cw, cb_off) = acpl_hcb_arrays(
crate::acpl::AcplDataType::Gamma,
qm,
crate::acpl::AcplHcbType::F0,
);
let idx = (gamma_q + cb_off).clamp(0, (len.len() as i32) - 1) as usize;
bw.write_u32(cw[idx], len[idx] as u32);
}
fn write_acpl_gamma_df_value(bw: &mut BitWriter, qm: crate::acpl::AcplQuantMode, delta_q: i32) {
let (len, cw, cb_off) = acpl_hcb_arrays(
crate::acpl::AcplDataType::Gamma,
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_beta3_f0_value(bw: &mut BitWriter, qm: crate::acpl::AcplQuantMode, beta3_q: i32) {
let (len, cw, cb_off) = acpl_hcb_arrays(
crate::acpl::AcplDataType::Beta3,
qm,
crate::acpl::AcplHcbType::F0,
);
let idx = (beta3_q + cb_off).clamp(0, (len.len() as i32) - 1) as usize;
bw.write_u32(cw[idx], len[idx] as u32);
}
fn write_acpl_beta3_df_value(bw: &mut BitWriter, qm: crate::acpl::AcplQuantMode, delta_q: i32) {
let (len, cw, cb_off) = acpl_hcb_arrays(
crate::acpl::AcplDataType::Beta3,
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_acpl2_body_from_pcm_spectra_real_alpha_beta(
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 start_band = 0u32;
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(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_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_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 write_acpl_1_residual_layer_sap_none_matches_legacy() {
let tl = 1920u32;
let n = tl as usize;
let coeffs_ls: Vec<f32> = (0..n).map(|i| 0.10 + 1e-4 * i as f32).collect();
let coeffs_rs: Vec<f32> = (0..n).map(|i| -0.05 + 2e-4 * i as f32).collect();
let coeffs_l = vec![0.0f32; n];
let coeffs_r = vec![0.0f32; n];
let mut bw_legacy = BitWriter::new();
let used_legacy =
write_acpl_1_residual_layer(&mut bw_legacy, tl, 8, &coeffs_ls, &coeffs_rs);
bw_legacy.align_to_byte();
let bytes_legacy = bw_legacy.finish();
let mut bw_sap = BitWriter::new();
let used_sap = write_acpl_1_residual_layer_sap(
&mut bw_sap,
tl,
8,
&coeffs_l,
&coeffs_r,
&coeffs_ls,
&coeffs_rs,
None,
);
bw_sap.align_to_byte();
let bytes_sap = bw_sap.finish();
assert_eq!(used_legacy, used_sap);
assert_eq!(bytes_legacy, bytes_sap,
"SAP-aware writer with chparam_pair = None must be bit-equal to the legacy identity-SAP writer");
}
#[test]
fn write_acpl_1_residual_layer_sap_ms_row_roundtrips_through_decoder() {
let tl = 256u32;
let n = tl as usize;
let max_sfb_master = 2u32;
let l_spec = vec![4.0f32; n];
let r_spec = vec![6.0f32; n];
let ls_spec = vec![-2.0f32; n];
let rs_spec = vec![-2.0f32; n];
let cp_ms = crate::asf::ChparamInfo {
sap_mode: 1,
ms_used: vec![vec![true, true]],
sap_data: None,
};
let pair = [cp_ms.clone(), cp_ms];
let mut bw = BitWriter::new();
let used = write_acpl_1_residual_layer_sap(
&mut bw,
tl,
max_sfb_master,
&l_spec,
&r_spec,
&ls_spec,
&rs_spec,
Some(&pair),
);
bw.align_to_byte();
let bytes = bw.finish();
assert_eq!(used, max_sfb_master);
let mut br = BitReader::new(&bytes);
let (_, n_side, _) = crate::tables::n_msfb_bits_48(tl).unwrap();
let parsed_max_sfb_master = br.read_u32(n_side).unwrap();
assert_eq!(parsed_max_sfb_master, max_sfb_master);
let cp0 = crate::asf::parse_chparam_info(&mut br, &[max_sfb_master]).unwrap();
let cp1 = crate::asf::parse_chparam_info(&mut br, &[max_sfb_master]).unwrap();
assert_eq!(cp0.sap_mode, 1);
assert_eq!(cp1.sap_mode, 1);
assert_eq!(cp0.ms_used, vec![vec![true, true]]);
assert_eq!(cp1.ms_used, vec![vec![true, true]]);
}
#[test]
fn write_acpl_1_residual_layer_sap_identity_explicit_matches_default() {
let tl = 1920u32;
let n = tl as usize;
let coeffs_ls = vec![0.25f32; n];
let coeffs_rs = vec![-0.25f32; n];
let coeffs_l = vec![0.0f32; n];
let coeffs_r = vec![0.0f32; n];
let cp_id = crate::asf::ChparamInfo {
sap_mode: 0,
ms_used: vec![],
sap_data: None,
};
let pair = [cp_id.clone(), cp_id];
let mut bw_default = BitWriter::new();
write_acpl_1_residual_layer_sap(
&mut bw_default,
tl,
8,
&coeffs_l,
&coeffs_r,
&coeffs_ls,
&coeffs_rs,
None,
);
bw_default.align_to_byte();
let mut bw_explicit = BitWriter::new();
write_acpl_1_residual_layer_sap(
&mut bw_explicit,
tl,
8,
&coeffs_l,
&coeffs_r,
&coeffs_ls,
&coeffs_rs,
Some(&pair),
);
bw_explicit.align_to_byte();
assert_eq!(bw_default.finish(), bw_explicit.finish());
}
#[test]
fn build_5_x_acpl1_body_sap_none_matches_legacy() {
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_legacy = 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 body_sap = build_5_x_acpl1_body_from_pcm_spectra_sap(
tl,
16,
8,
true,
&zeros,
&zeros,
&zeros,
&zeros,
&zeros,
None,
&cfg,
3,
crate::acpl::AcplQuantMode::Fine,
0,
4096,
);
assert_eq!(body_legacy, body_sap);
}
#[test]
fn build_5_x_acpl1_body_sap_ms_decoder_recovers_chparam() {
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 max_sfb_master = 8u32;
let ms_row = (0..max_sfb_master).map(|i| i % 2 == 0).collect::<Vec<_>>();
let cp_ms = crate::asf::ChparamInfo {
sap_mode: 1,
ms_used: vec![ms_row.clone()],
sap_data: None,
};
let pair = [cp_ms.clone(), cp_ms];
let body = build_5_x_acpl1_body_from_pcm_spectra_sap(
tl,
16,
max_sfb_master,
true,
&zeros,
&zeros,
&zeros,
&zeros,
&zeros,
Some(&pair),
&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)
);
assert_eq!(tools.acpl_1_residual_max_sfb_master, Some(max_sfb_master));
let cp0 = tools.acpl_1_residual_chparam[0]
.as_ref()
.expect("residual chparam[0] parsed");
let cp1 = tools.acpl_1_residual_chparam[1]
.as_ref()
.expect("residual chparam[1] parsed");
assert_eq!(cp0.sap_mode, 1);
assert_eq!(cp1.sap_mode, 1);
assert_eq!(cp0.ms_used, vec![ms_row.clone()]);
assert_eq!(cp1.ms_used, vec![ms_row]);
assert!(tools.acpl_1_residual_pair[0].is_some());
assert!(tools.acpl_1_residual_pair[1].is_some());
}
fn const_spectrum_1920(amp: f32, n_sfb: usize) -> Vec<f32> {
let sfbo = crate::sfb_offset::sfb_offset_48(1920).unwrap();
let hi = sfbo[n_sfb] as usize;
let mut v = vec![0.0f32; 1920];
for s in v.iter_mut().take(hi) {
*s = amp;
}
v
}
#[test]
fn select_acpl1_residual_chparam_correlated_pair_picks_sap_row() {
let l = const_spectrum_1920(1.0, 4);
let ls = const_spectrum_1920(0.2, 4);
let zeros = vec![0.0f32; 1920];
let max_sfb_master = 4u32;
let pair =
select_acpl1_residual_chparam_pair(&l, &zeros, &ls, &zeros, max_sfb_master, 1920);
assert_eq!(pair[0].sap_mode, 3, "correlated pair must be SAP-coded");
assert_eq!(pair[1].sap_mode, 0, "silent pair must fall back to None");
let coeffs = crate::asf::extract_sap_abcd(&pair[0], &[max_sfb_master]);
for sfb in 0..max_sfb_master as usize {
let (a, b, c, d) = coeffs.abcd[0][sfb];
assert!(
(a - 1.7).abs() < 1e-6 && (b - 1.0).abs() < 1e-6,
"sfb {sfb}: expected a = 1.7, b = 1, got ({a}, {b})"
);
assert!(
(c - 0.3).abs() < 1e-6 && (d + 1.0).abs() < 1e-6,
"sfb {sfb}: expected c = 0.3, d = -1, got ({c}, {d})"
);
}
}
#[test]
fn select_acpl1_residual_chparam_equal_pair_falls_back_to_none() {
let l = const_spectrum_1920(0.7, 4);
let r = const_spectrum_1920(0.4, 4);
let pair = select_acpl1_residual_chparam_pair(&l, &r, &l, &r, 4, 1920);
assert_eq!(pair[0].sap_mode, 0);
assert_eq!(pair[1].sap_mode, 0);
assert!(pair[0].sap_data.is_none());
assert!(pair[1].sap_data.is_none());
}
#[test]
fn select_acpl1_residual_chparam_clamps_alpha_q_to_30() {
let l = const_spectrum_1920(1.0, 4);
let ls: Vec<f32> = l.iter().map(|v| v * -0.9).collect();
let zeros = vec![0.0f32; 1920];
let pair = select_acpl1_residual_chparam_pair(&l, &zeros, &ls, &zeros, 4, 1920);
assert_eq!(pair[0].sap_mode, 3);
let coeffs = crate::asf::extract_sap_abcd(&pair[0], &[4]);
let (a, _b, c, _d) = coeffs.abcd[0][0];
assert!(
(a - 4.0).abs() < 1e-6 && (c + 2.0).abs() < 1e-6,
"alpha_q must clamp to +30 (g = 3.0): got a = {a}, c = {c}"
);
}
#[test]
fn build_5_x_acpl1_body_sap_auto_identity_matches_legacy() {
let tl = 1920u32;
let l = const_spectrum_1920(0.6, 6);
let r = const_spectrum_1920(0.3, 6);
let c = const_spectrum_1920(0.2, 6);
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 legacy = build_5_x_acpl1_body_from_pcm_spectra(
tl,
16,
8,
true,
&l,
&r,
&c,
&l,
&r,
&cfg,
3,
crate::acpl::AcplQuantMode::Fine,
0,
4096,
);
let auto = build_5_x_acpl1_body_from_pcm_spectra_sap_auto(
tl,
16,
8,
true,
&l,
&r,
&c,
&l,
&r,
&cfg,
3,
crate::acpl::AcplQuantMode::Fine,
0,
4096,
);
assert_eq!(
legacy, auto,
"no-SAP-band input must produce a bit-identical body"
);
}
#[test]
fn build_5_x_acpl1_body_sap_auto_round_trips_and_shrinks_residual() {
let tl = 1920u32;
let max_sfb_master = 8u32;
let l = const_spectrum_1920(1.0, 4);
let ls = const_spectrum_1920(0.5, 4); let r = const_spectrum_1920(0.8, 4);
let rs = const_spectrum_1920(0.2, 4); let c = vec![0.0f32; 1920];
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_sap_auto(
tl,
16,
max_sfb_master,
true,
&l,
&r,
&c,
&ls,
&rs,
&cfg,
3,
crate::acpl::AcplQuantMode::Fine,
0,
8192,
);
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)
);
assert_eq!(tools.acpl_1_residual_max_sfb_master, Some(max_sfb_master));
let cp0 = tools.acpl_1_residual_chparam[0]
.as_ref()
.expect("residual chparam[0] parsed");
let cp1 = tools.acpl_1_residual_chparam[1]
.as_ref()
.expect("residual chparam[1] parsed");
assert_eq!(cp0.sap_mode, 3, "pair 0 must be SAP-coded");
assert_eq!(cp1.sap_mode, 3, "pair 1 must be SAP-coded");
let (_tl3, s3) = tools.acpl_1_residual_pair[0]
.as_ref()
.expect("residual pair[0] parsed");
let e_s3: f64 = s3.iter().map(|v| (*v as f64) * (*v as f64)).sum();
let e_ls: f64 = ls.iter().map(|v| (*v as f64) * (*v as f64)).sum();
assert!(
e_s3 < 0.05 * e_ls,
"SAP residual energy must collapse: e_s3 = {e_s3}, e_ls = {e_ls}"
);
let tcd = tools
.two_channel_data
.first()
.expect("two_channel_data parsed");
let a_spec = tcd.scaled_spec_per_channel[0]
.as_ref()
.expect("carrier A spectrum");
let b_spec = tcd.scaled_spec_per_channel[1]
.as_ref()
.expect("carrier B spectrum");
let (_tl4, s4) = tools.acpl_1_residual_pair[1]
.as_ref()
.expect("residual pair[1] parsed");
let pad = |src: &[f32]| -> Vec<f32> {
let mut v = vec![0.0f32; tl as usize];
let take = src.len().min(tl as usize);
v[..take].copy_from_slice(&src[..take]);
v
};
let (l_out, r_out, ls_out, rs_out) = crate::asf::apply_sap_table_181(
&pad(a_spec),
&pad(b_spec),
&pad(s3),
&pad(s4),
&[cp0.clone(), cp1.clone()],
max_sfb_master,
tl,
)
.expect("forward SAP mix");
let rel_err = |got: &[f32], want: &[f32]| -> f64 {
let mut num = 0.0f64;
let mut den = 0.0f64;
for (g, w) in got.iter().zip(want.iter()) {
num += ((*g - *w) as f64).powi(2);
den += (*w as f64).powi(2);
}
if den == 0.0 {
num.sqrt()
} else {
(num / den).sqrt()
}
};
assert!(rel_err(&l_out, &l) < 0.2, "L: {}", rel_err(&l_out, &l));
assert!(rel_err(&r_out, &r) < 0.2, "R: {}", rel_err(&r_out, &r));
assert!(rel_err(&ls_out, &ls) < 0.2, "Ls: {}", rel_err(&ls_out, &ls));
assert!(rel_err(&rs_out, &rs) < 0.2, "Rs: {}", rel_err(&rs_out, &rs));
}
#[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);
}
#[test]
fn quantise_beta3_grid_and_clamp() {
use crate::acpl::AcplQuantMode::{Coarse, Fine};
assert_eq!(quantise_beta3(0.0, Fine), 0);
assert_eq!(quantise_beta3(0.25, Fine), 2);
assert_eq!(quantise_beta3(-0.5, Fine), -4);
assert_eq!(quantise_beta3(1.0, Fine), 8);
assert_eq!(quantise_beta3(7.5, Fine), 8);
assert_eq!(quantise_beta3(-99.0, Fine), -8);
assert_eq!(quantise_beta3(0.5, Coarse), 2);
assert_eq!(quantise_beta3(1.0, Coarse), 4);
assert_eq!(quantise_beta3(2.0, Coarse), 4);
assert_eq!(quantise_beta3(-2.0, Coarse), -4);
}
#[test]
fn write_beta3_f0_df_round_trips_through_parse_acpl_huff_data() {
use crate::acpl::{parse_acpl_huff_data, AcplDataType, AcplQuantMode};
let qm = AcplQuantMode::Fine;
let beta3_seq = [3i32, 3, -2, 0, 8, -8];
let mut bw = BitWriter::new();
bw.write_bit(false); let mut prev = 0i32;
for (i, &b) in beta3_seq.iter().enumerate() {
if i == 0 {
write_acpl_beta3_f0_value(&mut bw, qm, b);
} else {
write_acpl_beta3_df_value(&mut bw, qm, b - prev);
}
prev = b;
}
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let param =
parse_acpl_huff_data(&mut br, AcplDataType::Beta3, beta3_seq.len() as u32, 0, qm)
.expect("parse");
assert!(!param.direction_time);
let mut state = crate::acpl_synth::AcplDiffState::new();
let rows = crate::acpl_synth::differential_decode(
std::slice::from_ref(¶m),
beta3_seq.len() as u32,
&mut state,
);
assert_eq!(rows[0], beta3_seq);
}
#[test]
fn extract_beta3_zero_residual_vs_uncaptured_centre() {
use crate::acpl::AcplQuantMode::Fine;
let tl = 1920u32;
let nb = 12u32;
let gd = crate::acpl_synth::gamma_delta(Fine);
let k = 1.0 + (0.5f32).sqrt();
let mut l = vec![0.0f32; tl as usize];
let mut r = vec![0.0f32; tl as usize];
for (i, v) in l.iter_mut().enumerate() {
*v = if i % 2 == 0 { 1.0 } else { 0.5 };
}
for (i, v) in r.iter_mut().enumerate() {
*v = if i % 3 == 0 { 0.8 } else { -0.4 };
}
let c_exact: Vec<f32> = l.iter().map(|&x| k * 10.0 * gd * x).collect();
let (g5_q, g6_q) = extract_gamma_5_6_q_per_band_centre_least_squares(
&l, &r, &c_exact, tl, nb, 0, 1.0, Fine,
);
assert!(g5_q.iter().all(|&q| q == 10), "γ₅_q = 10: {g5_q:?}");
let zeros = vec![0i32; nb as usize];
let b3_exact = extract_beta3_q_per_band_centre_residual(
&l, &r, &c_exact, &zeros, &zeros, &zeros, &zeros, &g5_q, &g6_q, tl, nb, 0, 1.0, Fine,
Fine,
);
assert!(
b3_exact.iter().all(|&q| q == 0),
"exact dry fit ⇒ β₃_q = 0 everywhere: {b3_exact:?}"
);
let c_orth: Vec<f32> = (0..tl as usize)
.map(|i| if i % 5 == 1 { 2.0 } else { -1.0 })
.collect();
let (g5o, g6o) = extract_gamma_5_6_q_per_band_centre_least_squares(
&l, &r, &c_orth, tl, nb, 0, 1.0, Fine,
);
let b3_orth = extract_beta3_q_per_band_centre_residual(
&l, &r, &c_orth, &zeros, &zeros, &zeros, &zeros, &g5o, &g6o, tl, nb, 0, 1.0, Fine, Fine,
);
assert!(
b3_orth.iter().any(|&q| q > 0),
"uncaptured centre ⇒ β₃_q > 0 in ≥ 1 band: {b3_orth:?}"
);
assert!(b3_orth.iter().all(|&q| q >= 0));
}
#[test]
fn build_acpl3_beta3_zero_scale_matches_round215_full_gamma_builder() {
let tl = 1920u32;
let n = tl as usize;
let l: Vec<f32> = (0..n).map(|i| ((i % 17) as f32 - 8.0) * 0.1).collect();
let r: Vec<f32> = (0..n).map(|i| ((i % 23) as f32 - 11.0) * 0.07).collect();
let c: Vec<f32> = (0..n).map(|i| ((i % 13) as f32 - 6.0) * 0.05).collect();
let ls: Vec<f32> = (0..n).map(|i| ((i % 7) as f32 - 3.0) * 0.04).collect();
let rs: Vec<f32> = (0..n).map(|i| ((i % 11) as f32 - 5.0) * 0.03).collect();
let aspx_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 qm = crate::acpl::AcplQuantMode::Fine;
let legacy = build_5_x_acpl3_body_from_pcm_spectra_real_alpha_beta_full_gamma(
tl,
40,
None,
true,
&l,
&r,
Some(&c),
Some(&ls),
Some(&rs),
None,
&aspx_cfg,
3,
qm,
qm,
0.5,
0.1,
1.0,
8192,
);
let with_beta3_off = build_5_x_acpl3_body_from_pcm_spectra_real_alpha_beta_full_gamma_beta3(
tl,
40,
None,
true,
&l,
&r,
Some(&c),
Some(&ls),
Some(&rs),
None,
&aspx_cfg,
3,
qm,
qm,
0.5,
0.1,
1.0,
0.0,
8192,
);
assert_eq!(legacy, with_beta3_off);
}
}