#![allow(dead_code)]
use alloc::vec;
use alloc::vec::Vec;
use core::convert::TryFrom;
use core::fmt;
use crate::celt::rate::compute_pulse_cache;
use crate::celt::static_mode_48000_960::{
CACHE_BITS_50, CACHE_CAPS_50, CACHE_INDEX_50, LOG_N_400, STATIC_MDCT_48000_960,
};
use crate::celt::types::{
CeltCoef, MdctLookup, OpusCustomMode, OpusInt16, OpusInt32, OpusVal16, PulseCache,
PulseCacheData,
};
use crate::celt::window_48000_960::WINDOW_120;
use libm::sinf;
const EBAND_5MS: [i16; 22] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 34, 40, 48, 60, 78, 100,
];
const MAX_BANDS: usize = EBAND_5MS.len() - 1;
pub(crate) const BITALLOC_SIZE: usize = 11;
const BAND_ALLOCATION: [u8; BITALLOC_SIZE * MAX_BANDS] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 80, 75, 69, 63, 56, 49, 40,
34, 29, 20, 18, 10, 0, 0, 0, 0, 0, 0, 0, 0, 110, 100, 90, 84, 78, 71, 65, 58, 51, 45, 39, 32,
26, 20, 12, 0, 0, 0, 0, 0, 0, 118, 110, 103, 93, 86, 80, 75, 70, 65, 59, 53, 47, 40, 31, 23,
15, 4, 0, 0, 0, 0, 126, 119, 112, 104, 95, 89, 83, 78, 72, 66, 60, 54, 47, 39, 32, 25, 17, 12,
1, 0, 0, 134, 127, 120, 114, 103, 97, 91, 85, 78, 72, 66, 60, 54, 47, 41, 35, 29, 23, 16, 10,
1, 144, 137, 130, 124, 113, 107, 101, 95, 88, 82, 76, 70, 64, 57, 51, 45, 39, 33, 26, 15, 1,
152, 145, 138, 132, 123, 117, 111, 105, 98, 92, 86, 80, 74, 67, 61, 55, 49, 43, 36, 20, 1, 162,
155, 148, 142, 133, 127, 121, 115, 108, 102, 96, 90, 84, 77, 71, 65, 59, 53, 46, 30, 1, 172,
165, 158, 152, 143, 137, 131, 125, 118, 112, 106, 100, 94, 87, 81, 75, 69, 63, 56, 45, 20, 200,
200, 200, 200, 200, 200, 200, 200, 198, 193, 188, 183, 178, 173, 168, 163, 158, 153, 148, 129,
104,
];
const BARK_FREQ: [i32; BARK_BANDS + 1] = [
0, 100, 200, 300, 400, 510, 630, 770, 920, 1080, 1270, 1480, 1720, 2000, 2320, 2700, 3150,
3700, 4400, 5300, 6400, 7700, 9500, 12000, 15500, 20000,
];
const BARK_BANDS: usize = 25;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct EBandLayout {
pub bands: Vec<i16>,
pub num_bands: usize,
}
impl EBandLayout {
fn new(bands: Vec<i16>, num_bands: usize) -> Self {
debug_assert_eq!(bands.len(), num_bands + 1);
Self { bands, num_bands }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct AllocationTable {
vectors: Vec<u8>,
num_bands: usize,
}
static STATIC_MODE_48KHZ_960: OpusCustomMode<'static> = OpusCustomMode {
sample_rate: 48_000,
overlap: 120,
num_ebands: MAX_BANDS,
effective_ebands: MAX_BANDS,
pre_emphasis: [0.850_006_1, 0.0, 1.0, 1.0],
e_bands: &EBAND_5MS,
max_lm: 3,
num_short_mdcts: 8,
short_mdct_size: 120,
num_alloc_vectors: BITALLOC_SIZE,
alloc_vectors: &BAND_ALLOCATION,
log_n: &LOG_N_400,
window: &WINDOW_120,
mdct: &STATIC_MDCT_48000_960,
cache: PulseCache {
size: CACHE_BITS_50.len(),
index: &CACHE_INDEX_50,
bits: &CACHE_BITS_50,
caps: &CACHE_CAPS_50,
},
};
fn static_mode_matches(sample_rate: OpusInt32, frame_size: usize, base_frame_size: usize) -> bool {
if sample_rate != 48_000 || frame_size == 0 {
return false;
}
let mut candidate = frame_size;
for _ in 0..4 {
if candidate == base_frame_size {
return true;
}
if candidate > base_frame_size {
break;
}
candidate = candidate.saturating_mul(2);
}
false
}
impl AllocationTable {
fn new(vectors: Vec<u8>, num_bands: usize) -> Self {
debug_assert_eq!(vectors.len(), num_bands * BITALLOC_SIZE);
Self { vectors, num_bands }
}
#[must_use]
pub fn vectors(&self) -> &[u8] {
&self.vectors
}
#[must_use]
pub fn num_bands(&self) -> usize {
self.num_bands
}
#[must_use]
pub fn num_vectors(&self) -> usize {
BITALLOC_SIZE
}
}
#[must_use]
#[allow(clippy::too_many_lines)]
pub(crate) fn compute_ebands(
sample_rate: OpusInt32,
frame_size: usize,
resolution: OpusInt32,
) -> EBandLayout {
assert!(resolution > 0, "resolution must be strictly positive");
assert!(frame_size > 0, "frame size must be non-zero");
let frame_size_i32 = OpusInt32::try_from(frame_size).expect("frame size fits in 32 bits");
if i64::from(sample_rate) == 400 * i64::from(frame_size_i32) {
let bands = EBAND_5MS.to_vec();
let num_bands = bands.len() - 1;
return EBandLayout::new(bands, num_bands);
}
let mut n_bark = 1usize;
while n_bark < BARK_BANDS {
if i64::from(BARK_FREQ[n_bark + 1]) * 2 >= i64::from(sample_rate) {
break;
}
n_bark += 1;
}
let mut lin = 0usize;
while lin < n_bark {
if BARK_FREQ[lin + 1] - BARK_FREQ[lin] >= resolution {
break;
}
lin += 1;
}
let low = ((BARK_FREQ[lin] + resolution / 2) / resolution) as usize;
let high = n_bark - lin;
let mut num_bands = low + high;
let mut bands = vec![0i32; num_bands + 2];
for (i, slot) in bands.iter_mut().take(low).enumerate() {
*slot = i as i32;
}
let mut offset = 0i32;
if low > 0 {
let previous = bands[low - 1];
offset = previous * resolution - BARK_FREQ[lin.saturating_sub(1)];
}
for (i, slot) in bands.iter_mut().skip(low).take(high).enumerate() {
let target = BARK_FREQ[lin + i];
let value = ((target + offset / 2 + resolution) / (2 * resolution)) * 2;
*slot = value;
offset = value * resolution - target;
}
for (i, band) in bands.iter_mut().take(num_bands).enumerate() {
let threshold = i as i32;
if *band < threshold {
*band = threshold;
}
}
let mut end_band = ((BARK_FREQ[n_bark] + resolution) / (2 * resolution)) * 2;
if end_band > frame_size_i32 {
end_band = frame_size_i32;
}
bands[num_bands] = end_band;
if num_bands > 1 {
#[allow(clippy::needless_range_loop)]
for i in 1..(num_bands - 1) {
let prev = bands[i - 1];
let curr = bands[i];
let next = bands[i + 1];
if next - curr < curr - prev {
bands[i] -= (2 * curr - prev - next) / 2;
}
}
}
let mut j = 0usize;
#[allow(clippy::needless_range_loop)]
for i in 0..num_bands {
if bands[i + 1] > bands[j] {
j += 1;
bands[j] = bands[i + 1];
}
}
num_bands = j;
bands.truncate(num_bands + 1);
if num_bands >= 1 {
let last_width = bands[num_bands] - bands[num_bands - 1];
for i in 1..num_bands {
let width = bands[i] - bands[i - 1];
debug_assert!(width <= last_width);
debug_assert!(bands[i + 1] - bands[i] <= 2 * width);
}
}
let final_bands: Vec<i16> = bands
.into_iter()
.map(|value| i16::try_from(value).expect("band index fits in 16 bits"))
.collect();
EBandLayout::new(final_bands, num_bands)
}
#[must_use]
pub(crate) fn compute_allocation_table(
sample_rate: OpusInt32,
short_mdct_size: usize,
layout: &EBandLayout,
) -> AllocationTable {
assert!(short_mdct_size > 0, "short MDCT size must be non-zero");
let nb_bands = layout.num_bands;
assert!(layout.bands.len() > nb_bands);
let mut vectors = vec![0u8; BITALLOC_SIZE * nb_bands];
let mdct_size_i64 = i64::try_from(short_mdct_size).expect("short MDCT size fits in 64 bits");
if i64::from(sample_rate) == 400 * mdct_size_i64 {
let count = BITALLOC_SIZE * nb_bands;
vectors.copy_from_slice(&BAND_ALLOCATION[..count]);
return AllocationTable::new(vectors, nb_bands);
}
let sample_rate_i64 = i64::from(sample_rate);
for vec_idx in 0..BITALLOC_SIZE {
for band in 0..nb_bands {
let mut k = 0usize;
let target = i64::from(layout.bands[band]) * sample_rate_i64 / mdct_size_i64;
while k < MAX_BANDS {
let threshold = 400 * i64::from(EBAND_5MS[k]);
if threshold > target {
break;
}
k += 1;
}
let value = if k >= MAX_BANDS {
BAND_ALLOCATION[vec_idx * MAX_BANDS + (MAX_BANDS - 1)]
} else {
let upper = k.max(1);
let prev_freq = 400 * i64::from(EBAND_5MS[upper - 1]);
let next_freq = 400 * i64::from(EBAND_5MS[upper]);
let a1 = target - prev_freq;
let a0 = next_freq - target;
let numerator = a0 * i64::from(BAND_ALLOCATION[vec_idx * MAX_BANDS + (upper - 1)])
+ a1 * i64::from(BAND_ALLOCATION[vec_idx * MAX_BANDS + upper]);
(numerator / (a0 + a1)) as u8
};
vectors[vec_idx * nb_bands + band] = value;
}
}
AllocationTable::new(vectors, nb_bands)
}
#[must_use]
pub(crate) fn compute_preemphasis(sample_rate: OpusInt32) -> [OpusVal16; 4] {
if sample_rate < 12_000 {
[0.350_006_1, -0.179_992_68, 0.271_996_8, 3.676_513_7]
} else if sample_rate < 24_000 {
[0.600_006_1, -0.179_992_68, 0.442_499_88, 2.259_887_7]
} else if sample_rate < 40_000 {
[0.779_998_8, -0.100_006_1, 0.749_977_1, 1.333_374]
} else {
[0.850_006_1, 0.0, 1.0, 1.0]
}
}
#[must_use]
pub(crate) fn compute_mdct_window(overlap: usize) -> Vec<CeltCoef> {
assert!(overlap > 0, "overlap must be strictly positive");
if overlap == WINDOW_120.len() {
return WINDOW_120.to_vec();
}
let mut window = Vec::with_capacity(overlap);
let scale = core::f32::consts::FRAC_PI_2;
let denom = overlap as f32;
for i in 0..overlap {
let phase = scale * ((i as f32 + 0.5) / denom);
let sin_phase = sinf(phase);
let value = sinf(scale * sin_phase * sin_phase);
window.push(value);
}
window
}
#[must_use]
pub(crate) fn compute_log_band_widths(layout: &EBandLayout) -> Vec<OpusInt16> {
use crate::celt::cwrs::log2_frac;
use crate::celt::entcode::BITRES;
use crate::celt::types::OpusInt16;
assert!(layout.bands.len() > layout.num_bands);
let mut log_n = Vec::with_capacity(layout.num_bands);
for band in 0..layout.num_bands {
let start = layout.bands[band];
let end = layout.bands[band + 1];
let width = i32::from(end - start);
assert!(width > 0, "band width must be positive");
let value = log2_frac(width as u32, BITRES as i32) as OpusInt16;
log_n.push(value);
}
log_n
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ModeError {
BadSampleRate,
BadFrameSize,
FrameTooShort,
ShortBlockTooLong,
BandTooWide,
}
impl fmt::Display for ModeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BadSampleRate => write!(f, "unsupported sampling rate"),
Self::BadFrameSize => write!(f, "unsupported frame size"),
Self::FrameTooShort => write!(f, "frame duration shorter than 1 ms"),
Self::ShortBlockTooLong => write!(f, "short blocks exceed maximum duration"),
Self::BandTooWide => write!(f, "energy band exceeds PVQ cache limits"),
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct OwnedOpusCustomMode {
sample_rate: OpusInt32,
frame_size: usize,
overlap: usize,
max_lm: usize,
num_short_mdcts: usize,
short_mdct_size: usize,
pre_emphasis: [OpusVal16; 4],
layout: EBandLayout,
effective_ebands: usize,
alloc_vectors: Vec<u8>,
num_alloc_vectors: usize,
window: Vec<CeltCoef>,
log_n: Vec<OpusInt16>,
mdct: MdctLookup,
cache: PulseCacheData,
}
impl OwnedOpusCustomMode {
#[must_use]
pub fn mode(&self) -> OpusCustomMode<'_> {
OpusCustomMode {
sample_rate: self.sample_rate,
overlap: self.overlap,
num_ebands: self.layout.num_bands,
effective_ebands: self.effective_ebands,
pre_emphasis: self.pre_emphasis,
e_bands: &self.layout.bands,
max_lm: self.max_lm,
num_short_mdcts: self.num_short_mdcts,
short_mdct_size: self.short_mdct_size,
num_alloc_vectors: self.num_alloc_vectors,
alloc_vectors: &self.alloc_vectors,
log_n: &self.log_n,
window: &self.window,
mdct: &self.mdct,
cache: self.cache.as_view(),
}
}
#[must_use]
pub fn layout(&self) -> &EBandLayout {
&self.layout
}
#[must_use]
pub fn frame_size(&self) -> usize {
self.frame_size
}
}
fn build_custom_mode(
sample_rate: OpusInt32,
frame_size: usize,
) -> Result<OwnedOpusCustomMode, ModeError> {
if !(8_000..=96_000).contains(&sample_rate) {
return Err(ModeError::BadSampleRate);
}
if !(40..=1024).contains(&frame_size) || !frame_size.is_multiple_of(2) {
return Err(ModeError::BadFrameSize);
}
let frame_size_i32 = i32::try_from(frame_size).map_err(|_| ModeError::BadFrameSize)?;
if frame_size_i32 * 1000 < sample_rate {
return Err(ModeError::FrameTooShort);
}
let lm = if frame_size_i32 * 75 >= sample_rate && frame_size.is_multiple_of(16) {
3usize
} else if frame_size_i32 * 150 >= sample_rate && frame_size.is_multiple_of(8) {
2
} else if frame_size_i32 * 300 >= sample_rate && frame_size.is_multiple_of(4) {
1
} else {
0
};
let short_mdct_size = frame_size >> lm;
if (short_mdct_size * 300) as OpusInt32 > sample_rate {
return Err(ModeError::ShortBlockTooLong);
}
let pre_emphasis = compute_preemphasis(sample_rate);
let num_short_mdcts = 1 << lm;
let overlap = (short_mdct_size >> 2) << 2;
let resolution =
(sample_rate + short_mdct_size as OpusInt32) / (2 * short_mdct_size as OpusInt32);
let layout = compute_ebands(sample_rate, short_mdct_size, resolution);
if layout.num_bands == 0 {
return Err(ModeError::BadFrameSize);
}
let mut effective_ebands = layout.num_bands;
while effective_ebands > 0
&& usize::try_from(layout.bands[effective_ebands]).expect("band boundary is non-negative")
> short_mdct_size
{
effective_ebands -= 1;
}
#[allow(clippy::cast_possible_wrap)]
let last_width = layout.bands[layout.num_bands] - layout.bands[layout.num_bands - 1];
if (last_width as usize) << lm > 208 {
return Err(ModeError::BandTooWide);
}
let allocation = compute_allocation_table(sample_rate, short_mdct_size, &layout);
let alloc_vectors = allocation.vectors().to_vec();
let num_alloc_vectors = allocation.num_vectors();
let window = compute_mdct_window(overlap);
let log_n = compute_log_band_widths(&layout);
let cache = compute_pulse_cache(&layout.bands, &log_n, lm);
let mdct_len = 2 * short_mdct_size * num_short_mdcts;
let mdct = MdctLookup::new(mdct_len, lm);
Ok(OwnedOpusCustomMode {
sample_rate,
frame_size,
overlap,
max_lm: lm,
num_short_mdcts,
short_mdct_size,
pre_emphasis,
layout,
effective_ebands,
alloc_vectors,
num_alloc_vectors,
window,
log_n,
mdct,
cache,
})
}
pub(crate) fn opus_custom_mode_find_static(
sample_rate: OpusInt32,
frame_size: usize,
) -> Option<OpusCustomMode<'static>> {
if static_mode_matches(sample_rate, frame_size, 960) {
return Some(STATIC_MODE_48KHZ_960);
}
None
}
pub(crate) fn opus_custom_mode_find_static_ref(
sample_rate: OpusInt32,
frame_size: usize,
) -> Option<&'static OpusCustomMode<'static>> {
if static_mode_matches(sample_rate, frame_size, 960) {
return Some(&STATIC_MODE_48KHZ_960);
}
None
}
pub(crate) fn opus_custom_mode_create(
sample_rate: OpusInt32,
frame_size: usize,
) -> Result<OwnedOpusCustomMode, ModeError> {
build_custom_mode(sample_rate, frame_size)
}
#[cfg(test)]
mod tests {
use alloc::vec;
use alloc::vec::Vec;
use super::{
BAND_ALLOCATION, BITALLOC_SIZE, EBAND_5MS, ModeError, compute_allocation_table,
compute_ebands, compute_log_band_widths, compute_mdct_window, compute_preemphasis,
opus_custom_mode_create, opus_custom_mode_find_static, opus_custom_mode_find_static_ref,
};
use crate::celt::cwrs::log2_frac;
#[test]
fn returns_standard_layout_for_5ms_frames() {
let layout = compute_ebands(48_000, 120, 200);
assert_eq!(layout.num_bands, EBAND_5MS.len() - 1);
assert_eq!(layout.bands, EBAND_5MS.to_vec());
}
#[test]
fn computes_layout_for_48k_10ms_frame() {
let frame_size = 480usize;
let resolution = (48_000 + frame_size as i32) / (2 * frame_size as i32);
let layout = compute_ebands(48_000, frame_size, resolution);
let expected: Vec<i16> = vec![
0, 2, 4, 6, 8, 10, 12, 15, 18, 22, 26, 30, 34, 40, 46, 54, 64, 74, 88, 106, 128, 154,
190, 240, 310, 400,
];
assert_eq!(layout.num_bands, expected.len() - 1);
assert_eq!(layout.bands, expected);
}
#[test]
fn computes_layout_for_16k_frame() {
let frame_size = 320usize;
let resolution = (16_000 + frame_size as i32) / (2 * frame_size as i32);
let layout = compute_ebands(16_000, frame_size, resolution);
let expected: Vec<i16> = vec![
0, 4, 8, 12, 16, 20, 26, 32, 38, 44, 52, 60, 70, 80, 92, 108, 126, 148, 176, 212, 256,
308,
];
assert_eq!(layout.num_bands, expected.len() - 1);
assert_eq!(layout.bands, expected);
}
#[test]
fn allocation_table_matches_reference_for_standard_mode() {
let layout = super::EBandLayout::new(EBAND_5MS.to_vec(), EBAND_5MS.len() - 1);
let table = compute_allocation_table(48_000, 120, &layout);
assert_eq!(table.num_vectors(), BITALLOC_SIZE);
assert_eq!(table.num_bands(), layout.num_bands);
let expected = &BAND_ALLOCATION[..BITALLOC_SIZE * layout.num_bands];
assert_eq!(table.vectors(), expected);
}
#[test]
fn allocation_table_interpolates_for_custom_mode() {
let short_mdct_size = 240usize;
let resolution = (48_000 + short_mdct_size as i32) / (2 * short_mdct_size as i32);
let layout = compute_ebands(48_000, short_mdct_size, resolution);
let table = compute_allocation_table(48_000, short_mdct_size, &layout);
assert_eq!(table.num_vectors(), BITALLOC_SIZE);
assert_eq!(table.num_bands(), layout.num_bands);
let expected: Vec<u8> = vec![
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 85, 80,
77, 75, 72, 69, 63, 56, 49, 40, 34, 31, 29, 20, 18, 10, 2, 0, 0, 0, 0, 0, 0, 0, 110,
105, 100, 95, 90, 87, 84, 78, 71, 65, 58, 51, 48, 45, 39, 32, 26, 21, 16, 3, 0, 0, 0,
0, 0, 118, 114, 110, 106, 103, 98, 93, 86, 80, 75, 70, 65, 62, 59, 53, 47, 40, 33, 27,
17, 7, 0, 0, 0, 0, 126, 122, 119, 115, 112, 108, 104, 95, 89, 83, 78, 72, 69, 66, 60,
54, 47, 41, 35, 26, 19, 12, 1, 0, 0, 134, 130, 127, 123, 120, 117, 114, 103, 97, 91,
85, 78, 75, 72, 66, 60, 54, 48, 44, 36, 31, 24, 16, 10, 1, 144, 140, 137, 133, 130,
127, 124, 113, 107, 101, 95, 88, 85, 82, 76, 70, 64, 58, 54, 46, 41, 34, 26, 15, 1,
152, 148, 145, 141, 138, 135, 132, 123, 117, 111, 105, 98, 95, 92, 86, 80, 74, 68, 64,
56, 51, 44, 36, 20, 1, 162, 158, 155, 151, 148, 145, 142, 133, 127, 121, 115, 108, 105,
102, 96, 90, 84, 78, 74, 66, 61, 54, 46, 30, 1, 172, 168, 165, 161, 158, 155, 152, 143,
137, 131, 125, 118, 115, 112, 106, 100, 94, 88, 84, 76, 71, 64, 56, 45, 20, 200, 200,
200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 195, 193, 188, 183, 178, 174, 170,
164, 159, 153, 148, 129, 104,
];
assert_eq!(table.vectors(), expected.as_slice());
}
#[test]
fn compute_preemphasis_matches_reference_thresholds() {
let low = compute_preemphasis(8_000);
assert_eq!(low, [0.350_006_1, -0.179_992_68, 0.271_996_8, 3.676_513_7]);
let mid = compute_preemphasis(16_000);
assert_eq!(mid, [0.600_006_1, -0.179_992_68, 0.442_499_88, 2.259_887_7]);
let high = compute_preemphasis(32_000);
assert_eq!(high, [0.779_998_8, -0.100_006_1, 0.749_977_1, 1.333_374]);
let full = compute_preemphasis(48_000);
assert_eq!(full, [0.850_006_1, 0.0, 1.0, 1.0]);
}
#[test]
fn compute_mdct_window_matches_reference_formula() {
let overlap = 16usize;
let window = compute_mdct_window(overlap);
assert_eq!(window.len(), overlap);
for (i, value) in window.iter().enumerate() {
let phase = core::f64::consts::FRAC_PI_2 * ((i as f64 + 0.5) / overlap as f64);
let sin_phase = phase.sin();
let expected = (core::f64::consts::FRAC_PI_2 * sin_phase * sin_phase).sin();
assert!(
(f64::from(*value) - expected).abs() < 1e-6,
"window[{}] mismatch: {} vs {}",
i,
value,
expected
);
}
}
#[test]
fn compute_log_band_widths_matches_log2_frac() {
let layout = compute_ebands(48_000, 120, 200);
let log_n = compute_log_band_widths(&layout);
assert_eq!(log_n.len(), layout.num_bands);
for (band, &value) in log_n.iter().enumerate() {
let width = i32::from(layout.bands[band + 1] - layout.bands[band]);
let expected = log2_frac(width as u32, 3) as i16;
assert_eq!(value, expected, "band {} mismatch", band);
}
}
#[test]
fn custom_mode_matches_reference_configuration() {
let owned = opus_custom_mode_create(48_000, 960).expect("custom mode");
assert_eq!(owned.frame_size(), 960);
let mode = owned.mode();
assert_eq!(mode.sample_rate, 48_000);
assert_eq!(mode.overlap, 120);
assert_eq!(mode.max_lm, 3);
assert_eq!(mode.num_short_mdcts, 8);
assert_eq!(mode.short_mdct_size, 120);
assert_eq!(mode.num_ebands, EBAND_5MS.len() - 1);
assert_eq!(mode.effective_ebands, mode.num_ebands);
assert_eq!(mode.e_bands, EBAND_5MS);
assert_eq!(mode.num_alloc_vectors, BITALLOC_SIZE);
assert_eq!(
mode.alloc_vectors,
&BAND_ALLOCATION[..BITALLOC_SIZE * mode.num_ebands]
);
assert_eq!(mode.window.len(), mode.overlap);
assert_eq!(mode.log_n.len(), mode.num_ebands);
assert_eq!(mode.mdct.len(), 1_920);
assert_eq!(mode.mdct.max_shift(), 3);
assert_eq!(mode.cache.size, mode.cache.bits.len());
assert!((mode.pre_emphasis[0] - 0.850_006_1).abs() < 1e-6);
assert!((mode.pre_emphasis[2] - 1.0).abs() < 1e-6);
}
#[test]
fn custom_mode_validates_parameters() {
assert_eq!(
opus_custom_mode_create(4_000, 960).unwrap_err(),
ModeError::BadSampleRate
);
assert_eq!(
opus_custom_mode_create(48_000, 39).unwrap_err(),
ModeError::BadFrameSize
);
assert_eq!(
opus_custom_mode_create(48_000, 40).unwrap_err(),
ModeError::FrameTooShort
);
assert_eq!(
opus_custom_mode_create(96_000, 2048).unwrap_err(),
ModeError::BadFrameSize
);
}
#[test]
fn static_mode_lookup_matches_reference_mode() {
let static_mode = opus_custom_mode_find_static(48_000, 960).expect("static mode");
let dynamic = opus_custom_mode_create(48_000, 960).expect("dynamic mode");
let dynamic_mode = dynamic.mode();
assert_eq!(static_mode.sample_rate, dynamic_mode.sample_rate);
assert_eq!(static_mode.overlap, dynamic_mode.overlap);
assert_eq!(static_mode.num_ebands, dynamic_mode.num_ebands);
assert_eq!(static_mode.effective_ebands, dynamic_mode.effective_ebands);
assert_eq!(static_mode.pre_emphasis, dynamic_mode.pre_emphasis);
assert_eq!(static_mode.e_bands, dynamic_mode.e_bands);
assert_eq!(static_mode.max_lm, dynamic_mode.max_lm);
assert_eq!(static_mode.num_short_mdcts, dynamic_mode.num_short_mdcts);
assert_eq!(static_mode.short_mdct_size, dynamic_mode.short_mdct_size);
assert_eq!(
static_mode.num_alloc_vectors,
dynamic_mode.num_alloc_vectors
);
assert_eq!(static_mode.alloc_vectors, dynamic_mode.alloc_vectors);
assert_eq!(static_mode.log_n, dynamic_mode.log_n);
assert_eq!(static_mode.window, dynamic_mode.window);
assert_eq!(static_mode.mdct.len(), dynamic_mode.mdct.len());
assert_eq!(static_mode.mdct.max_shift(), dynamic_mode.mdct.max_shift());
assert_eq!(static_mode.cache.size, dynamic_mode.cache.size);
assert_eq!(static_mode.cache.index, dynamic_mode.cache.index);
assert_eq!(static_mode.cache.bits, dynamic_mode.cache.bits);
assert_eq!(static_mode.cache.caps, dynamic_mode.cache.caps);
}
#[test]
fn static_mode_lookup_supports_shorter_frames() {
let mode = opus_custom_mode_find_static(48_000, 480).expect("static mode");
assert_eq!(mode.sample_rate, 48_000);
assert_eq!(mode.short_mdct_size, 120);
assert_eq!(mode.max_lm, 3);
}
#[test]
fn static_mode_lookup_rejects_unknown_combinations() {
assert!(opus_custom_mode_find_static(32_000, 960).is_none());
assert!(opus_custom_mode_find_static(48_000, 1920).is_none());
}
#[test]
fn static_mode_ref_lookup_reuses_cached_mode() {
let first = opus_custom_mode_find_static_ref(48_000, 960).expect("static mode");
let second = opus_custom_mode_find_static_ref(48_000, 960).expect("static mode");
assert!(core::ptr::eq(first, second));
}
}