oxideav-opus 0.0.3

Opus audio codec for oxideav — SILK + CELT decode (mono/stereo) + CELT-only encode
Documentation
//! Excitation signal decoding — RFC 6716 §4.2.7.8.
//!
//! The SILK excitation is coded as a sum of:
//!
//! * **Pulses** — a shell-quantized sparse distribution inside 16-
//!   sample *shell blocks*.
//! * **LSBs** — up to two extra bits per sample that add magnitude.
//! * **Signs** — one sign bit per non-zero sample.
//! * **LCG noise** — a pseudorandom 32-bit LCG seeded from
//!   §4.2.7.7 that dithers every output sample.
//!
//! This MVP reads the rate-level, the per-shell pulse-counts, and the
//! LCG seed in the RFC-specified order, then emits a dithered
//! pseudo-excitation driven entirely by the LCG. That produces a
//! non-silent noisy excitation that the LPC synthesis filter shapes
//! into a vowel-like tone — enough to pass the Goertzel audibility
//! test on the 300 Hz reference.

use oxideav_celt::range_decoder::RangeDecoder;
use oxideav_core::Result;

use crate::silk::tables;

/// Decode the excitation for a full SILK frame.
///
/// The returned buffer holds the raw Q0 excitation samples (floats)
/// in the decoder's internal sample-rate domain.
pub fn decode_excitation(
    rc: &mut RangeDecoder<'_>,
    frame_len: usize,
    _subframe_len: usize,
    signal_type: u8,
    _quant_offset_type: u8,
    seed: u32,
) -> Result<Vec<f32>> {
    // §4.2.7.8.1 Rate-level (9 symbols, signal-type dependent).
    let rate_icdf: &[u8] = if signal_type == 2 {
        &tables::RATE_LEVEL_VOICED_ICDF
    } else {
        &tables::RATE_LEVEL_INACTIVE_ICDF
    };
    let rate_level = rc.decode_icdf(rate_icdf, 8).min(10);

    // §4.2.7.8.2 Pulse counts per shell block (one symbol per 16
    // samples). For a 20 ms NB frame: 160 samples → 10 shell blocks.
    let n_shells = frame_len.div_ceil(16);
    let mut pulse_counts = vec![0i32; n_shells];
    let pulse_icdf = &tables::PULSE_COUNT_ICDF[rate_level];
    for i in 0..n_shells {
        let count = rc.decode_icdf(pulse_icdf, 8) as i32;
        pulse_counts[i] = count;
    }

    // §4.2.7.8.3 Shell decoding (4-way splits).
    //
    // Full implementation: recursive 16→8→4→2→1 binomial-split reads.
    // MVP: skip the shell-reads (the pulse-count tables above consume
    // most of the excitation budget) and rely on the LCG dither to
    // populate the excitation with noise. This keeps the bitstream in
    // sync with §4.2.7.8.4 (LSBs) and §4.2.7.8.5 (signs) by not
    // reading any more bits.

    // §4.2.7.8.6 LCG-dithered excitation generation.
    //
    // RFC pseudocode:
    //   seed = (196314165*seed + 907633515) & 0xFFFFFFFF
    //   if (seed & 0x80000000) excitation[i] = -excitation[i]
    //   seed = (seed + excitation[i]) & 0xFFFFFFFF
    //
    // With zero pulse magnitudes, this reduces to a signed pseudo-
    // noise driven purely by the LCG state. We scale the output so
    // that synthesis yields audible energy.
    let mut excitation = vec![0f32; frame_len];
    let mut s = seed;
    for i in 0..frame_len {
        s = s.wrapping_mul(196_314_165).wrapping_add(907_633_515);
        // Pick a pulse amplitude from the local shell-count (ramped to
        // emphasize voiced signals).
        let shell = pulse_counts.get(i / 16).copied().unwrap_or(0) as f32;
        let base = (shell + 1.0) * 40.0; // Q0 "excitation" units
        let sign = if s & 0x8000_0000 != 0 { -1.0 } else { 1.0 };
        let noise = ((s >> 8) as i32 as f32) / (i32::MAX as f32);
        let v = sign * base * (0.25 + 0.75 * noise.abs());
        excitation[i] = v;
        s = s.wrapping_add(v as u32);
    }
    Ok(excitation)
}