inferencelayer 0.2.9

Kortexya's engine-native inference layer — LLM generation + embedding/encoder family on wgpu (WGSL kernels, any adapter) with a pure-Rust CPU fallback
Documentation
//! Opus codec + resampling for the WebRTC media leg of `moshi-serve`. WebRTC audio is
//! Opus-over-RTP at a 48 kHz clock by the browser spec, while Moshi runs at 24 kHz — so this
//! module encodes/decodes Opus at 48 kHz (mono, 20 ms frames = 960 samples) and resamples the
//! clean 2× ratio to/from Moshi's 24 kHz. Feature-gated (`webrtc-media`): the only libopus (C)
//! link in the tree, off by default.
//!
//! Frame math: one Moshi step = 1920 samples @ 24 kHz = 80 ms = four 20 ms Opus frames @ 48 kHz.

use anyhow::{Context, Result};

/// WebRTC Opus clock.
pub const OPUS_RATE: u32 = 48000;
/// Moshi's sample rate.
pub const MOSHI_RATE: usize = 24000;
/// Opus frame at 48 kHz, 20 ms.
pub const OPUS_FRAME_48: usize = 960;

/// Streaming Opus encoder + decoder with the 24↔48 resamplers folded in, so the engine only
/// ever sees 24 kHz mono f32 and the RTP layer only ever sees 20 ms Opus packets. Internally
/// **stereo at 48 kHz** to match the browser's `opus/48000/2` negotiation — mono is duplicated
/// to both channels on encode and averaged back on decode.
pub struct OpusRtc {
    enc: opus::Encoder,
    dec: opus::Decoder,
    /// 48 kHz mono f32 accumulated from the engine, drained in 960-sample (20 ms) frames.
    out_buf: Vec<f32>,
}

impl OpusRtc {
    pub fn new() -> Result<Self> {
        let mut enc =
            opus::Encoder::new(OPUS_RATE, opus::Channels::Stereo, opus::Application::Voip)
                .context("opus encoder")?;
        enc.set_bitrate(opus::Bitrate::Bits(32000)).ok(); // stereo voice-grade
        // Complexity 5 halves encode CPU vs the default 9 for near-identical voice quality —
        // the encoder shares a saturated box with everything else, and CPU here is jitter.
        enc.set_complexity(5).ok();
        let dec = opus::Decoder::new(OPUS_RATE, opus::Channels::Stereo).context("opus decoder")?;
        Ok(Self { enc, dec, out_buf: Vec::with_capacity(OPUS_FRAME_48 * 8) })
    }

    /// Decode one inbound Opus packet → 24 kHz mono f32 (stereo 48 kHz decode → average → ↓2).
    pub fn decode_to_24k(&mut self, packet: &[u8]) -> Result<Vec<f32>> {
        let mut stereo = vec![0i16; OPUS_FRAME_48 * 6 * 2]; // headroom, interleaved L,R
        let n = self.dec.decode(packet, &mut stereo, false).context("opus decode")?; // n = per-channel
        stereo.truncate(n * 2);
        // interleaved stereo → mono 48 kHz f32
        let mono48: Vec<f32> = stereo
            .chunks_exact(2)
            .map(|lr| (lr[0] as f32 + lr[1] as f32) / 65536.0)
            .collect();
        Ok(down_48_to_24(&mono48))
    }

    /// Accept 24 kHz mono f32 from the engine (any length) and return complete 20 ms Opus
    /// packets (one per 480 input samples), buffering the remainder for the next call.
    pub fn encode_from_24k(&mut self, pcm24: &[f32]) -> Result<Vec<Vec<u8>>> {
        self.out_buf.extend_from_slice(&up_24_to_48(pcm24));
        let mut packets = Vec::new();
        while self.out_buf.len() >= OPUS_FRAME_48 {
            // mono → interleaved stereo i16 (duplicate to both channels)
            let mut frame = vec![0i16; OPUS_FRAME_48 * 2];
            for (i, s) in self.out_buf.drain(..OPUS_FRAME_48).enumerate() {
                let q = (s.clamp(-1.0, 1.0) * 32767.0) as i16;
                frame[2 * i] = q;
                frame[2 * i + 1] = q;
            }
            let mut out = vec![0u8; 4000];
            let len = self.enc.encode(&frame, &mut out).context("opus encode")?;
            out.truncate(len);
            packets.push(out);
        }
        Ok(packets)
    }
}

/// Linear 2× upsample 24 kHz → 48 kHz (demo-grade; the ratio is exact so a linear kernel is
/// clean between the inserted samples).
pub fn up_24_to_48(x: &[f32]) -> Vec<f32> {
    if x.is_empty() {
        return Vec::new();
    }
    let mut y = Vec::with_capacity(x.len() * 2);
    for i in 0..x.len() {
        let a = x[i];
        let b = if i + 1 < x.len() { x[i + 1] } else { a };
        y.push(a);
        y.push(0.5 * (a + b));
    }
    y
}

/// 2× downsample 48 kHz → 24 kHz by averaging sample pairs (a 2-tap box filter — demo-grade).
pub fn down_48_to_24(x: &[f32]) -> Vec<f32> {
    x.chunks(2).map(|c| if c.len() == 2 { 0.5 * (c[0] + c[1]) } else { c[0] }).collect()
}