use crate::engine::ps_decoder::PsDecoder;
use crate::engine::ps_hybrid::LOOKAHEAD;
use crate::engine::sbr_dequant::{DequantizedSbr, dequant_coupled_into, dequant_single_into};
use crate::engine::sbr_element::EXTENSION_ID_PS;
use crate::engine::sbr_env_adjust::{EnvAdjustState, EnvParams, adjust_into};
use crate::engine::sbr_extension::SbrExtensionData;
use crate::engine::sbr_freq_bands::{HiLoTables, k0 as derive_k0, k2 as derive_k2, master_table};
use crate::engine::sbr_header::SbrHeader;
use crate::engine::sbr_hf_gen::{
Patches, T_HF_ADJ, T_HF_GEN, build_patches, chirp_factors_into, generate_hf_into,
};
use crate::engine::sbr_limiter::limiter_table;
use crate::engine::sbr_qmf::{AnalysisQmf, Complex, SynthesisQmf};
use crate::engine::sbr_reconstruct::{EnvelopeScalefactors, NoiseScalefactors};
use crate::engine::sbr_time_grid::TimeGrid;
use crate::engine::{Error, Result};
pub const NUM_TIME_SLOTS: i32 = 16;
pub const RATE: i32 = 2;
const LF: usize = (NUM_TIME_SLOTS * RATE) as usize;
const COLS: usize = LF + T_HF_GEN;
#[derive(Debug)]
struct ChannelState {
analysis: AnalysisQmf,
synthesis: SynthesisQmf,
w_hist: [[Complex; 32]; T_HF_GEN],
y_prev: Vec<[Complex; 64]>,
t_e_last_prev: i32,
k_x_prev: i32,
m_prev: i32,
env_state: EnvAdjustState,
prev_invf: Vec<u8>,
prev_bw: Vec<f64>,
prev_env: EnvelopeScalefactors,
prev_noise: NoiseScalefactors,
env_cur: EnvelopeScalefactors,
noise_cur: NoiseScalefactors,
dequant: DequantizedSbr,
core: Vec<f32>,
pcm: Vec<f32>,
bw: Vec<f64>,
time_grid: TimeGrid,
}
impl ChannelState {
fn new() -> Self {
ChannelState {
analysis: AnalysisQmf::new(),
synthesis: SynthesisQmf::new(),
w_hist: [[Complex::default(); 32]; T_HF_GEN],
y_prev: vec![[Complex::default(); 64]; COLS],
t_e_last_prev: NUM_TIME_SLOTS,
k_x_prev: 0,
m_prev: 0,
env_state: EnvAdjustState::new(),
prev_invf: Vec::new(),
prev_bw: Vec::new(),
prev_env: EnvelopeScalefactors {
eq: Vec::new(),
freq_res: Vec::new(),
},
prev_noise: NoiseScalefactors { q: Vec::new() },
env_cur: EnvelopeScalefactors {
eq: Vec::new(),
freq_res: Vec::new(),
},
noise_cur: NoiseScalefactors { q: Vec::new() },
dequant: DequantizedSbr {
e_orig: Vec::new(),
q_orig: Vec::new(),
},
core: vec![0.0; 1024],
pcm: Vec::with_capacity(LF * 64),
bw: Vec::new(),
time_grid: TimeGrid {
t_e: Vec::new(),
t_q: Vec::new(),
l_a: -1,
},
}
}
fn analyze(&mut self, x_low: &mut [[Complex; 32]]) -> Result<()> {
if self.core.len() != 1024 || x_low.len() != COLS {
return Err(Error::SbrQmfInvalid);
}
x_low[..T_HF_GEN].copy_from_slice(&self.w_hist);
for slot in 0..LF {
let src = &self.core[slot * 32..(slot + 1) * 32];
x_low[T_HF_GEN + slot] = self.analysis.push_slot(src)?;
}
self.w_hist.copy_from_slice(&x_low[COLS - T_HF_GEN..]);
Ok(())
}
}
#[derive(Debug)]
pub struct SbrDecoder {
fs_sbr: u32,
header: Option<SbrHeader>,
bands: Option<HiLoTables>,
patches: Option<Patches>,
f_table_lim: Vec<i32>,
channels: Vec<ChannelState>,
ps: Option<PsState>,
ps_rendered: bool,
x_low: Vec<[Complex; 32]>,
x_high: Vec<[Complex; 64]>,
prof_an: u64,
prof_hf: u64,
prof_adj: u64,
prof_sy: u64,
prof_frames: u64,
}
#[derive(Debug)]
struct PsState {
dec: PsDecoder,
synthesis_r: SynthesisQmf,
pcm_r: Vec<f32>,
x_input: Vec<[Complex; 64]>,
l_qmf: Vec<[Complex; 64]>,
r_qmf: Vec<[Complex; 64]>,
}
fn take_cols<const N: usize>(v: &mut Vec<[Complex; N]>, n: usize) -> Vec<[Complex; N]> {
let mut m = std::mem::take(v);
m.clear();
m.resize(n, [Complex::default(); N]);
m
}
fn take_cols_dirty<const N: usize>(v: &mut Vec<[Complex; N]>, n: usize) -> Vec<[Complex; N]> {
let mut m = std::mem::take(v);
if m.len() != n {
m.resize(n, [Complex::default(); N]);
}
m
}
impl SbrDecoder {
pub fn new(fs_sbr: u32, num_channels: usize) -> Result<Self> {
if num_channels == 0 || num_channels > 2 || fs_sbr == 0 {
return Err(Error::SbrFreqBandInvalid);
}
Ok(SbrDecoder {
fs_sbr,
header: None,
bands: None,
patches: None,
f_table_lim: Vec::new(),
channels: (0..num_channels).map(|_| ChannelState::new()).collect(),
ps: None,
ps_rendered: false,
x_low: Vec::new(),
x_high: Vec::new(),
prof_an: 0,
prof_hf: 0,
prof_adj: 0,
prof_sy: 0,
prof_frames: 0,
})
}
#[cfg(test)]
fn fill_core_f64(&mut self, core: &[&[f64]]) -> Result<()> {
if core.len() != self.channels.len() {
return Err(Error::SbrQmfInvalid);
}
for (ch, src) in self.channels.iter_mut().zip(core.iter()) {
if src.len() != 1024 {
return Err(Error::SbrQmfInvalid);
}
if ch.core.len() != 1024 {
ch.core.resize(1024, 0.0);
}
for (d, s) in ch.core.iter_mut().zip(src.iter()) {
*d = *s as f32;
}
}
Ok(())
}
pub(crate) fn fill_core_f32(&mut self, planes: &[&[f32]]) -> Result<()> {
if planes.len() != self.channels.len() {
return Err(Error::SbrQmfInvalid);
}
for (ch, src) in self.channels.iter_mut().zip(planes.iter()) {
if src.len() != 1024 {
return Err(Error::SbrQmfInvalid);
}
if ch.core.len() != 1024 {
ch.core.resize(1024, 0.0);
}
ch.core.copy_from_slice(src);
}
Ok(())
}
pub(crate) fn out_planes(&self) -> usize {
if self.ps_rendered {
2
} else {
self.channels.len()
}
}
pub(crate) fn pcm_plane(&self, i: usize) -> &[f32] {
if i == 1 && self.ps_rendered {
self.ps.as_ref().map(|p| p.pcm_r.as_slice()).unwrap_or(&[])
} else {
self.channels
.get(i)
.map(|c| c.pcm.as_slice())
.unwrap_or(&[])
}
}
#[cfg(test)]
pub fn upsample_frame(&mut self, core: &[&[f64]]) -> Result<Vec<Vec<f64>>> {
self.fill_core_f64(core)?;
let n = self.upsample_prepared()?;
Ok((0..n)
.map(|i| self.pcm_plane(i).iter().map(|&x| f64::from(x)).collect())
.collect())
}
pub(crate) fn upsample_prepared(&mut self) -> Result<usize> {
self.ps_rendered = false;
let n_ch = self.channels.len();
for c in 0..n_ch {
let mut x_low = take_cols_dirty(&mut self.x_low, COLS);
self.channels[c].analyze(&mut x_low)?;
let rendered = if n_ch == 1 {
emit_ps_from(
&mut self.channels[0],
self.ps.as_mut(),
None,
&x_low,
None,
0,
None,
32,
)?
} else {
false
};
if rendered {
self.ps_rendered = true;
} else {
synth_low(&mut self.channels[c], &x_low)?;
}
self.channels[c]
.y_prev
.iter_mut()
.for_each(|col| *col = [Complex::default(); 64]);
self.channels[c].t_e_last_prev = NUM_TIME_SLOTS;
self.x_low = x_low;
}
Ok(self.out_planes())
}
#[cfg(test)]
pub fn process_frame(
&mut self,
ext: &SbrExtensionData,
core: &[&[f64]],
) -> Result<Vec<Vec<f64>>> {
self.fill_core_f64(core)?;
let n = self.process_prepared(ext)?;
Ok((0..n)
.map(|i| self.pcm_plane(i).iter().map(|&x| f64::from(x)).collect())
.collect())
}
pub(crate) fn process_prepared(&mut self, ext: &SbrExtensionData) -> Result<usize> {
self.ps_rendered = false;
let n_ch = self.channels.len();
if ext.element.channels.len() != n_ch {
return Err(Error::SbrFreqBandInvalid);
}
let reset = match &self.header {
None => true,
Some(prev) => prev.band_geometry_changed(&ext.header),
};
if reset {
let k0v = derive_k0(self.fs_sbr, ext.header.start_freq)?;
let k2v = derive_k2(self.fs_sbr, ext.header.stop_freq, k0v)?;
let f_master = master_table(k0v, k2v, ext.header.freq_scale, ext.header.alter_scale)?;
let bands =
HiLoTables::derive(&f_master, ext.header.xover_band, ext.header.noise_bands)?;
let patches = build_patches(&f_master, k0v, bands.k_x, bands.m, self.fs_sbr)?;
self.f_table_lim = limiter_table(
&bands,
&patches.borders(bands.k_x),
ext.header.limiter_bands,
)?;
self.bands = Some(bands);
self.patches = Some(patches);
for ch in &mut self.channels {
ch.prev_invf.clear();
ch.prev_bw.clear();
ch.prev_env.eq.clear();
ch.prev_env.freq_res.clear();
ch.prev_noise.q.clear();
}
}
self.header = Some(ext.header);
let coupling = ext.element.coupling;
for (c, sbr_ch) in ext.element.channels.iter().enumerate() {
let ch = &mut self.channels[c];
let prev_env = if reset || ch.prev_env.eq.is_empty() {
None
} else {
Some(&ch.prev_env)
};
let bands = self.bands.as_ref().ok_or(Error::SbrFreqBandInvalid)?;
ch.env_cur.reconstruct_into(
&sbr_ch.envelope,
&sbr_ch.grid,
&sbr_ch.dtdf,
bands,
coupling,
c == 1,
prev_env,
)?;
let prev_noise = if reset || ch.prev_noise.q.is_empty() {
None
} else {
Some(&ch.prev_noise)
};
ch.noise_cur.reconstruct_into(
&sbr_ch.noise,
&sbr_ch.grid,
&sbr_ch.dtdf,
bands.n_q(),
coupling,
c == 1,
prev_noise,
)?;
}
if coupling && n_ch == 2 {
let amp_res = effective_amp_res(&ext.header, &ext.element.channels[0].grid);
let (left, right) = self.channels.split_at_mut(1);
dequant_coupled_into(
&left[0].env_cur,
&left[0].noise_cur,
&right[0].env_cur,
&right[0].noise_cur,
amp_res,
&mut left[0].dequant,
&mut right[0].dequant,
);
} else {
for c in 0..n_ch {
let amp_res = effective_amp_res(&ext.header, &ext.element.channels[c].grid);
let ch = &mut self.channels[c];
dequant_single_into(&ch.env_cur, &ch.noise_cur, amp_res, &mut ch.dequant);
}
}
let ps_payload = if n_ch == 1 {
ext.element
.extension
.as_ref()
.filter(|e| e.id == EXTENSION_ID_PS)
.map(|e| e.data.as_slice())
} else {
None
};
if ps_payload.is_some() && self.ps.is_none() {
self.ps = Some(PsState {
dec: PsDecoder::new(),
synthesis_r: SynthesisQmf::new(),
pcm_r: Vec::with_capacity(LF * 64),
x_input: Vec::new(),
l_qmf: Vec::new(),
r_qmf: Vec::new(),
});
}
for c in 0..n_ch {
let sbr_ch = &ext.element.channels[c];
let invf_modes: &[u8] = if coupling && c == 1 {
&ext.element.channels[0].invf.invf_mode
} else {
&sbr_ch.invf.invf_mode
};
let mut x_low = take_cols_dirty(&mut self.x_low, COLS);
let mut x_high = take_cols_dirty(&mut self.x_high, COLS);
let t_an = super::prof::stamp();
let (an_ns, hf_ns) = {
let ch = &mut self.channels[c];
let bands = self.bands.as_ref().ok_or(Error::SbrFreqBandInvalid)?;
let patches = self.patches.as_ref().ok_or(Error::SbrFreqBandInvalid)?;
ch.time_grid.derive_into(&sbr_ch.grid, NUM_TIME_SLOTS)?;
chirp_factors_into(invf_modes, &ch.prev_invf, &ch.prev_bw, &mut ch.bw);
ch.analyze(&mut x_low)?;
let an_ns = super::prof::ns(t_an);
let t_hf = super::prof::stamp();
let l_range = (RATE * ch.time_grid.t_e[0])
..(RATE * ch.time_grid.t_e[ch.time_grid.t_e.len() - 1]);
generate_hf_into(&x_low, patches, &ch.bw, bands, l_range, LF, &mut x_high)?;
(an_ns, super::prof::ns(t_hf))
};
self.prof_an += an_ns;
self.prof_hf += hf_ns;
let adj_ns = {
let bands = self.bands.as_ref().ok_or(Error::SbrFreqBandInvalid)?;
let ch = &mut self.channels[c];
let params = EnvParams {
bands,
f_table_lim: &self.f_table_lim,
t_e: &ch.time_grid.t_e,
t_q: &ch.time_grid.t_q,
freq_res: &sbr_ch.grid.freq_res,
l_a: ch.time_grid.l_a,
e_orig: &ch.dequant.e_orig,
q_orig: &ch.dequant.q_orig,
add_harmonic: &sbr_ch.add_harmonic,
interpol_freq: ext.header.interpol_freq,
smoothing_mode: ext.header.smoothing_mode,
limiter_gains: ext.header.limiter_gains,
reset,
};
let t_adj = super::prof::stamp();
adjust_into(&mut x_high, ¶ms, &mut ch.env_state)?;
super::prof::ns(t_adj)
};
self.prof_adj += adj_ns;
let bands = self.bands.as_ref().ok_or(Error::SbrFreqBandInvalid)?;
let l_temp =
(RATE * self.channels[c].t_e_last_prev - NUM_TIME_SLOTS * RATE).max(0) as usize;
let kx_plus_m = (bands.k_x + bands.m).max(0) as usize;
let t_sy = super::prof::stamp();
let rendered = emit_ps_from(
&mut self.channels[c],
self.ps.as_mut(),
Some(bands),
&x_low,
Some(&x_high),
l_temp,
ps_payload,
kx_plus_m,
)?;
if rendered {
self.ps_rendered = true;
} else {
synth_assembled(&mut self.channels[c], bands, &x_low, &x_high, l_temp)?;
}
self.prof_sy += super::prof::ns(t_sy);
let ch = &mut self.channels[c];
std::mem::swap(&mut ch.y_prev, &mut x_high);
ch.t_e_last_prev = ch.time_grid.t_e[ch.time_grid.t_e.len() - 1];
ch.k_x_prev = bands.k_x;
ch.m_prev = bands.m;
ch.prev_invf.clear();
ch.prev_invf.extend_from_slice(invf_modes);
ch.prev_bw.clear();
ch.prev_bw.extend_from_slice(&ch.bw);
std::mem::swap(&mut ch.env_cur, &mut ch.prev_env);
std::mem::swap(&mut ch.noise_cur, &mut ch.prev_noise);
self.x_low = x_low;
self.x_high = x_high;
}
if super::prof::on() {
self.prof_frames = self.prof_frames.saturating_add(1);
}
Ok(self.out_planes())
}
}
impl Drop for SbrDecoder {
fn drop(&mut self) {
if self.prof_frames == 0 {
return;
}
let n = self.prof_frames;
eprintln!(
"sbr_prof frames={n} an_ms={:.3} hf_ms={:.3} adj_ms={:.3} sy_ms={:.3}",
self.prof_an as f64 / 1e6,
self.prof_hf as f64 / 1e6,
self.prof_adj as f64 / 1e6,
self.prof_sy as f64 / 1e6
);
}
}
fn assemble_x(
ch: &ChannelState,
x_low: &[[Complex; 32]],
y_cur: Option<&[[Complex; 64]]>,
bands: Option<&HiLoTables>,
l: usize,
l_temp: usize,
x: &mut [Complex; 64],
) {
let Some(bands) = bands else {
x[..32].copy_from_slice(&x_low[l + T_HF_ADJ]);
x[32..].fill(Complex::default());
return;
};
let (kx_cur, m_cur, y_col) = if l < l_temp {
(ch.k_x_prev, ch.m_prev, &ch.y_prev[l + T_HF_ADJ + LF])
} else {
let y = y_cur.unwrap_or(&ch.y_prev);
(bands.k_x, bands.m, &y[l + T_HF_ADJ])
};
let kx_u = (kx_cur.max(0) as usize).min(64);
let low_n = kx_u.min(32);
if low_n > 0 {
x[..low_n].copy_from_slice(&x_low[l + T_HF_ADJ][..low_n]);
}
let hi = ((kx_cur + m_cur).max(0) as usize).min(64);
let mut filled = low_n;
if kx_u > filled {
x[filled..kx_u].fill(Complex::default());
filled = kx_u;
}
if kx_u < hi {
x[kx_u..hi].copy_from_slice(&y_col[kx_u..hi]);
filled = hi;
}
if filled < 64 {
x[filled..].fill(Complex::default());
}
}
fn synth_low(ch: &mut ChannelState, x_low: &[[Complex; 32]]) -> Result<()> {
ch.pcm.clear();
for l in 0..LF {
let mut x = [Complex::default(); 64];
assemble_x(ch, x_low, None, None, l, 0, &mut x);
ch.pcm.extend_from_slice(&ch.synthesis.push_slot(&x)?);
}
Ok(())
}
fn synth_assembled(
ch: &mut ChannelState,
bands: &HiLoTables,
x_low: &[[Complex; 32]],
y_cur: &[[Complex; 64]],
l_temp: usize,
) -> Result<()> {
ch.pcm.clear();
for l in 0..LF {
let mut x = [Complex::default(); 64];
assemble_x(ch, x_low, Some(y_cur), Some(bands), l, l_temp, &mut x);
ch.pcm.extend_from_slice(&ch.synthesis.push_slot(&x)?);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn emit_ps_from(
ch: &mut ChannelState,
ps: Option<&mut PsState>,
bands: Option<&HiLoTables>,
x_low: &[[Complex; 32]],
y_cur: Option<&[[Complex; 64]]>,
l_temp: usize,
payload: Option<&[u8]>,
kx_plus_m: usize,
) -> Result<bool> {
let Some(ps) = ps else {
return Ok(false);
};
let mut x_input = take_cols(&mut ps.x_input, LF + LOOKAHEAD);
for (l, col) in x_input.iter_mut().enumerate().take(LF) {
assemble_x(ch, x_low, y_cur, bands, l, l_temp, col);
}
for l in LF..LF + LOOKAHEAD {
x_input[l][..5].copy_from_slice(&x_low[l + T_HF_ADJ][..5]);
}
let active = ps.dec.process_prepared(payload, &x_input, kx_plus_m);
ps.x_input = x_input;
if !active? {
return Ok(false);
}
let mut l_qmf = take_cols(&mut ps.l_qmf, LF);
let mut r_qmf = take_cols(&mut ps.r_qmf, LF);
ps.dec.mix_qmf(&mut l_qmf, &mut r_qmf)?;
ch.pcm.clear();
ps.pcm_r.clear();
for l in 0..LF {
ch.pcm
.extend_from_slice(&ch.synthesis.push_slot(&l_qmf[l])?);
ps.pcm_r
.extend_from_slice(&ps.synthesis_r.push_slot(&r_qmf[l])?);
}
ps.l_qmf = l_qmf;
ps.r_qmf = r_qmf;
Ok(true)
}
fn effective_amp_res(header: &SbrHeader, grid: &crate::engine::sbr_grid::SbrGrid) -> bool {
if grid.amp_res_override {
false
} else {
header.amp_res
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
use crate::engine::sbr_element::{SbrChannel, SbrElement};
use crate::engine::sbr_envelope::{SbrEnvelopeData, SbrNoiseData};
use crate::engine::sbr_grid::{FrameClass, SbrDtdf, SbrGrid, SbrInvf};
fn sine(freq: f64, n: usize, offset: usize) -> Vec<f64> {
(0..n)
.map(|t| (2.0 * core::f64::consts::PI * freq * (t + offset) as f64).sin())
.collect()
}
#[test]
fn upsample_frames_are_continuous() {
let mut dec = SbrDecoder::new(44_100, 1).unwrap();
let freq = 0.02;
let mut out = Vec::new();
for f in 0..4 {
let core = sine(freq, 1024, f * 1024);
let o = dec.upsample_frame(&[&core]).unwrap();
assert_eq!(o[0].len(), 2048);
out.extend_from_slice(&o[0]);
}
let ideal = |t: f64, d: f64| (2.0 * core::f64::consts::PI * freq * (t - d) / 2.0).sin();
let mut best = f64::INFINITY;
for delay in 0..1500usize {
let mut err = 0.0;
let mut sig = 0.0;
for (t, &o) in out.iter().enumerate().skip(2500) {
let e = o - ideal(t as f64, delay as f64);
err += e * e;
sig += o * o;
}
best = best.min(err / sig.max(1e-30));
}
assert!(best < 1e-4, "upsample error ratio {best}");
}
fn synthetic_ext(fs_sbr: u32, env_start: i32, noise_q: i32) -> SbrExtensionData {
let header = SbrHeader {
amp_res: true,
start_freq: 5,
stop_freq: 3,
xover_band: 0,
reserved: 0,
header_extra_1: false,
header_extra_2: false,
freq_scale: 2,
alter_scale: true,
noise_bands: 2,
limiter_bands: 2,
limiter_gains: 2,
interpol_freq: true,
smoothing_mode: true,
};
let bands = header.derive_bands(fs_sbr).unwrap();
let n_high = bands.n_high();
let n_q = bands.n_q();
let grid = SbrGrid {
frame_class: FrameClass::FixFix,
num_env: 1,
num_noise: 1,
freq_res: vec![true],
var_bord_0: 0,
var_bord_1: 0,
rel_bord_0: vec![],
rel_bord_1: vec![],
pointer: 0,
amp_res_override: true,
};
let dtdf = SbrDtdf {
df_env: vec![false],
df_noise: vec![false],
};
let invf = SbrInvf {
invf_mode: vec![0; n_q],
};
let mut env_row = vec![0i32; n_high];
env_row[0] = env_start;
let envelope = SbrEnvelopeData {
data: vec![env_row],
};
let noise = SbrNoiseData {
data: vec![{
let mut r = vec![0i32; n_q];
r[0] = noise_q;
r
}],
};
SbrExtensionData {
crc: None,
header_present: true,
header,
element: SbrElement {
coupling: false,
channels: vec![SbrChannel {
grid,
dtdf,
invf,
envelope,
noise,
add_harmonic: vec![],
}],
extension: None,
},
num_sbr_bits: 0,
}
}
#[test]
fn synthetic_sbr_frame_produces_high_band() {
let fs_sbr = 44_100;
let ext = synthetic_ext(fs_sbr, 10, 6);
let mut dec = SbrDecoder::new(fs_sbr, 1).unwrap();
let freq = 0.11;
let mut all = Vec::new();
for f in 0..3 {
let core = sine(freq, 1024, f * 1024);
let out = dec.process_frame(&ext, &[&core]).unwrap();
assert_eq!(out.len(), 1);
assert_eq!(out[0].len(), 2048);
assert!(out[0].iter().all(|v| v.is_finite()));
all.extend_from_slice(&out[0]);
}
let energy: f64 = all.iter().map(|v| v * v).sum();
assert!(energy > 1.0, "energy {energy}");
let mut dec2 = SbrDecoder::new(fs_sbr, 1).unwrap();
let mut all2 = Vec::new();
for f in 0..3 {
let core = sine(freq, 1024, f * 1024);
all2.extend_from_slice(&dec2.process_frame(&ext, &[&core]).unwrap()[0]);
}
assert_eq!(all, all2);
}
#[test]
fn envelope_scalefactor_controls_high_band_level() {
let fs_sbr = 44_100;
let mut quiet = SbrDecoder::new(fs_sbr, 1).unwrap();
let mut loud = SbrDecoder::new(fs_sbr, 1).unwrap();
let ext_quiet = synthetic_ext(fs_sbr, 2, 10);
let ext_loud = synthetic_ext(fs_sbr, 12, 10);
let freq = 0.09;
let mut hi_q = 0.0f64;
let mut hi_l = 0.0f64;
for f in 0..3 {
let core = sine(freq, 1024, f * 1024);
let oq = quiet.process_frame(&ext_quiet, &[&core]).unwrap();
let ol = loud.process_frame(&ext_loud, &[&core]).unwrap();
if f > 0 {
for w in oq[0].windows(2) {
hi_q += (w[1] - w[0]) * (w[1] - w[0]);
}
for w in ol[0].windows(2) {
hi_l += (w[1] - w[0]) * (w[1] - w[0]);
}
}
}
assert!(hi_l > hi_q * 4.0, "loud {hi_l} vs quiet {hi_q}");
}
#[test]
fn shape_mismatches_rejected() {
let mut dec = SbrDecoder::new(44_100, 1).unwrap();
let core = vec![0.0; 512];
assert!(dec.upsample_frame(&[&core]).is_err());
let ext = synthetic_ext(44_100, 0, 6);
let short = vec![0.0; 1024];
assert!(dec.process_frame(&ext, &[&short[..], &short[..]]).is_err());
}
}