use crate::celt_band_layout::{
celt_band_bins_per_channel, celt_end_coded_band, celt_first_coded_band, CeltFrameSize,
CELT_NUM_BANDS,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DenormaliseError {
BandOutOfRange {
band: usize,
},
OutputBufferTooSmall {
required: usize,
provided: usize,
},
ShapeLengthMismatch {
shape_len: usize,
expected: usize,
},
}
impl core::fmt::Display for DenormaliseError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
DenormaliseError::BandOutOfRange { band } => write!(
f,
"band index {band} out of range (must be < {CELT_NUM_BANDS}) \
per RFC 6716 §4.3 Table 55"
),
DenormaliseError::OutputBufferTooSmall { required, provided } => write!(
f,
"denormalisation output buffer too small: need {required} \
coefficients, got {provided}"
),
DenormaliseError::ShapeLengthMismatch {
shape_len,
expected,
} => write!(
f,
"shape length {shape_len} does not match output region length \
{expected} for the §4.3.6 element-wise multiply"
),
}
}
}
impl std::error::Error for DenormaliseError {}
#[inline]
#[must_use]
pub fn denormalise_gain(log2_energy: f64) -> f64 {
(log2_energy * 0.5).exp2()
}
pub fn denormalise_band(
shape: &[f64],
log2_energy: f64,
out: &mut [f64],
) -> Result<(), DenormaliseError> {
if shape.len() != out.len() {
return Err(DenormaliseError::ShapeLengthMismatch {
shape_len: shape.len(),
expected: out.len(),
});
}
let gain = denormalise_gain(log2_energy);
for (dst, &s) in out.iter_mut().zip(shape.iter()) {
*dst = s * gain;
}
Ok(())
}
pub fn denormalise_bands(
shapes: &[&[f64]],
log2_energy: &[f64],
frame_size: CeltFrameSize,
is_hybrid: bool,
out: &mut [f64],
) -> Result<usize, DenormaliseError> {
let first = celt_first_coded_band(is_hybrid);
let end = celt_end_coded_band();
let coded = end - first;
if shapes.len() != coded {
return Err(DenormaliseError::ShapeLengthMismatch {
shape_len: shapes.len(),
expected: coded,
});
}
if log2_energy.len() != coded {
return Err(DenormaliseError::ShapeLengthMismatch {
shape_len: log2_energy.len(),
expected: coded,
});
}
let mut offset = 0usize;
for (k, band) in (first..end).enumerate() {
let bins = celt_band_bins_per_channel(band, frame_size)
.expect("coded band index < CELT_NUM_BANDS by loop bound") as usize;
let shape = shapes[k];
if shape.len() != bins {
return Err(DenormaliseError::ShapeLengthMismatch {
shape_len: shape.len(),
expected: bins,
});
}
let end_off = offset + bins;
if end_off > out.len() {
return Err(DenormaliseError::OutputBufferTooSmall {
required: end_off,
provided: out.len(),
});
}
denormalise_band(shape, log2_energy[k], &mut out[offset..end_off])?;
offset = end_off;
}
Ok(offset)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::celt_band_layout::{celt_total_bins_per_channel, CeltFrameSize};
#[test]
fn gain_is_sqrt_of_linear_energy() {
for &l in &[-8.0, -2.0, -1.0, 0.0, 0.5, 1.0, 3.0, 7.5, 12.0] {
let expected = 2.0_f64.powf(l).sqrt();
assert!(
(denormalise_gain(l) - expected).abs() < 1e-12,
"L={l}: {} vs {}",
denormalise_gain(l),
expected
);
}
}
#[test]
fn gain_zero_energy_is_one() {
assert_eq!(denormalise_gain(0.0), 1.0);
}
#[test]
fn denormalised_band_has_target_energy() {
let raw = [3.0_f64, 4.0]; let norm = (raw[0] * raw[0] + raw[1] * raw[1]).sqrt();
let shape = [raw[0] / norm, raw[1] / norm]; let l = 3.0_f64;
let mut out = [0.0_f64; 2];
denormalise_band(&shape, l, &mut out).unwrap();
let energy = out[0] * out[0] + out[1] * out[1];
assert!(
(energy - 2.0_f64.powf(l)).abs() < 1e-9,
"band energy {energy} != 2**{l} = {}",
2.0_f64.powf(l)
);
}
#[test]
fn zero_shape_stays_zero() {
let shape = [0.0_f64; 4];
let mut out = [9.9_f64; 4];
denormalise_band(&shape, 5.0, &mut out).unwrap();
assert_eq!(out, [0.0; 4]);
}
#[test]
fn band_length_mismatch_errors() {
let shape = [1.0_f64; 3];
let mut out = [0.0_f64; 4];
assert_eq!(
denormalise_band(&shape, 0.0, &mut out),
Err(DenormaliseError::ShapeLengthMismatch {
shape_len: 3,
expected: 4,
})
);
}
#[test]
fn bands_celt_only_2p5ms_writes_full_buffer() {
let fs = CeltFrameSize::from_frame_tenths_ms(25).unwrap();
let coded = celt_end_coded_band() - celt_first_coded_band(false);
assert_eq!(coded, CELT_NUM_BANDS);
let mut owned: Vec<Vec<f64>> = Vec::with_capacity(coded);
for band in 0..coded {
let bins = celt_band_bins_per_channel(band, fs).unwrap() as usize;
let mut v = vec![0.0_f64; bins];
v[0] = 1.0; owned.push(v);
}
let shapes: Vec<&[f64]> = owned.iter().map(|v| v.as_slice()).collect();
let energies = vec![2.0_f64; coded];
let total = celt_total_bins_per_channel(fs, false) as usize;
assert_eq!(total, 100);
let mut out = vec![0.0_f64; total];
let written = denormalise_bands(&shapes, &energies, fs, false, &mut out).unwrap();
assert_eq!(written, total);
let mut offset = 0;
for band in 0..coded {
let bins = celt_band_bins_per_channel(band, fs).unwrap() as usize;
assert!((out[offset] - 2.0).abs() < 1e-12, "band {band} first bin");
for j in 1..bins {
assert_eq!(out[offset + j], 0.0, "band {band} bin {j}");
}
offset += bins;
}
}
#[test]
fn bands_hybrid_20ms_starts_at_band_17() {
let fs = CeltFrameSize::from_frame_tenths_ms(200).unwrap();
let first = celt_first_coded_band(true);
assert_eq!(first, 17);
let coded = celt_end_coded_band() - first;
assert_eq!(coded, 4);
let mut owned: Vec<Vec<f64>> = Vec::with_capacity(coded);
for (k, band) in (first..celt_end_coded_band()).enumerate() {
let bins = celt_band_bins_per_channel(band, fs).unwrap() as usize;
let mut v = vec![0.0_f64; bins];
v[0] = 1.0;
owned.push(v);
let _ = k;
}
let shapes: Vec<&[f64]> = owned.iter().map(|v| v.as_slice()).collect();
let energies = vec![0.0_f64; coded];
let total = celt_total_bins_per_channel(fs, true) as usize;
let mut out = vec![0.0_f64; total];
let written = denormalise_bands(&shapes, &energies, fs, true, &mut out).unwrap();
assert_eq!(written, total);
}
#[test]
fn bands_output_too_small_errors() {
let fs = CeltFrameSize::from_frame_tenths_ms(25).unwrap();
let coded = CELT_NUM_BANDS;
let mut owned: Vec<Vec<f64>> = Vec::with_capacity(coded);
for band in 0..coded {
let bins = celt_band_bins_per_channel(band, fs).unwrap() as usize;
owned.push(vec![0.0_f64; bins]);
}
let shapes: Vec<&[f64]> = owned.iter().map(|v| v.as_slice()).collect();
let energies = vec![0.0_f64; coded];
let mut out = vec![0.0_f64; 10]; let r = denormalise_bands(&shapes, &energies, fs, false, &mut out);
assert!(matches!(
r,
Err(DenormaliseError::OutputBufferTooSmall { .. })
));
}
#[test]
fn bands_wrong_shape_count_errors() {
let fs = CeltFrameSize::from_frame_tenths_ms(25).unwrap();
let shapes: Vec<&[f64]> = vec![&[1.0][..]; 3]; let energies = vec![0.0_f64; 3];
let mut out = vec![0.0_f64; 120];
let r = denormalise_bands(&shapes, &energies, fs, false, &mut out);
assert!(matches!(
r,
Err(DenormaliseError::ShapeLengthMismatch { .. })
));
}
#[test]
fn bands_wrong_per_band_shape_len_errors() {
let fs = CeltFrameSize::from_frame_tenths_ms(25).unwrap();
let coded = CELT_NUM_BANDS;
let mut owned: Vec<Vec<f64>> = Vec::with_capacity(coded);
for band in 0..coded {
let bins = celt_band_bins_per_channel(band, fs).unwrap() as usize;
owned.push(vec![0.0_f64; bins]);
}
owned[5].push(0.0);
let shapes: Vec<&[f64]> = owned.iter().map(|v| v.as_slice()).collect();
let energies = vec![0.0_f64; coded];
let mut out = vec![0.0_f64; 121];
let r = denormalise_bands(&shapes, &energies, fs, false, &mut out);
assert!(matches!(
r,
Err(DenormaliseError::ShapeLengthMismatch { .. })
));
}
#[test]
fn negative_energy_attenuates() {
let shape = [1.0_f64];
let mut out = [0.0_f64; 1];
denormalise_band(&shape, -4.0, &mut out).unwrap();
assert!((out[0] - 0.25).abs() < 1e-12);
}
}