use std::num::{NonZeroU32, NonZeroUsize};
use non_empty_slice::NonEmptyVec;
use spectrograms::WindowType;
use crate::codecs::perceptual::{BandLayout, PsychoacousticConfig};
use crate::{AudioSampleResult, AudioSamples};
use super::celt::{CeltEncodedFrame, celt_decode_frame, celt_encode_frame};
use super::silk::{
SilkEncodedFrame, SilkState, silk_decode_frame_stateful, silk_encode_frame_stateful,
};
pub const HYBRID_CROSSOVER_HZ: f32 = 8_000.0;
#[derive(Debug, Clone)]
pub struct HybridEncodedFrame {
pub silk_frame: SilkEncodedFrame,
pub celt_frame: CeltEncodedFrame,
pub n_samples: usize,
}
fn crossover_alpha(crossover_hz: f32, sample_rate_hz: f32) -> f32 {
let omega = std::f32::consts::TAU * crossover_hz / sample_rate_hz;
let c = 2.0_f32 - omega.cos();
(c - (c * c - 1.0_f32).sqrt()).clamp(0.0, 1.0)
}
fn crossover_split(samples: &[f32], alpha: f32) -> (Vec<f32>, Vec<f32>) {
let one_minus = 1.0 - alpha;
let mut lp = Vec::with_capacity(samples.len());
let mut hp = Vec::with_capacity(samples.len());
let mut prev = 0.0_f32;
for &x in samples {
let y = alpha * prev + one_minus * x;
lp.push(y);
hp.push(x - y);
prev = y;
}
(lp, hp)
}
pub fn hybrid_encode_frame(
frame_samples: &[f32],
sample_rate: u32,
band_layout: &BandLayout,
psych_config: &PsychoacousticConfig,
window: WindowType,
bit_budget: u32,
min_bits: u8,
silk_state: &mut SilkState,
) -> AudioSampleResult<HybridEncodedFrame> {
let n_samples = frame_samples.len();
let alpha = crossover_alpha(HYBRID_CROSSOVER_HZ, sample_rate as f32);
let (low_band, high_band) = crossover_split(frame_samples, alpha);
let silk_frame = silk_encode_frame_stateful(&low_band, sample_rate, silk_state)?;
let sr =
NonZeroU32::new(sample_rate).expect("sample_rate > 0 — guaranteed by OpusCodec dispatch");
let ne = NonEmptyVec::new(high_band)
.expect("high_band non-empty — frame_samples non-empty guaranteed by caller");
let frame_audio: AudioSamples<'static, f32> = AudioSamples::from_mono_vec(ne, sr);
let window_size = NonZeroUsize::new((n_samples / 2) * 2)
.expect("n_samples >= 4 guaranteed by OpusCodec dispatch");
let celt_frame = celt_encode_frame(
&frame_audio,
band_layout,
psych_config,
window,
Some(window_size),
bit_budget,
min_bits,
)?;
Ok(HybridEncodedFrame {
silk_frame,
celt_frame,
n_samples,
})
}
pub fn hybrid_decode_frame(
frame: HybridEncodedFrame,
sample_rate: NonZeroU32,
silk_state: &mut SilkState,
) -> AudioSampleResult<Vec<f32>> {
let n = frame.n_samples;
let low = silk_decode_frame_stateful(&frame.silk_frame, silk_state);
let high = celt_decode_frame(frame.celt_frame, sample_rate)?;
let len = n.min(low.len()).min(high.len());
Ok((0..len).map(|i| low[i] + high[i]).collect())
}