use crate::audio::resample_linear;
use anyhow::Result;
use rlx_runtime::Device;
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RvqCodes {
pub frames: Vec<Vec<u32>>,
pub num_quantizers: usize,
}
impl RvqCodes {
pub fn new(frames: Vec<Vec<u32>>, num_quantizers: usize) -> Self {
Self {
frames,
num_quantizers,
}
}
pub fn num_frames(&self) -> usize {
self.frames.len()
}
pub fn is_empty(&self) -> bool {
self.frames.is_empty()
}
pub fn to_quantizer_major(&self) -> Vec<Vec<u32>> {
let nq = self.num_quantizers;
let t = self.num_frames();
let mut out = vec![Vec::with_capacity(t); nq];
for frame in &self.frames {
for (q, &code) in frame.iter().enumerate().take(nq) {
out[q].push(code);
}
}
out
}
pub fn from_quantizer_major(rows: &[Vec<u32>]) -> Self {
let nq = rows.len();
let t = rows.iter().map(|r| r.len()).max().unwrap_or(0);
let mut frames = Vec::with_capacity(t);
for ti in 0..t {
let mut frame = Vec::with_capacity(nq);
for row in rows {
frame.push(row.get(ti).copied().unwrap_or(0));
}
frames.push(frame);
}
Self {
frames,
num_quantizers: nq,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HierarchicalCodes {
pub levels: Vec<Vec<u32>>,
}
impl HierarchicalCodes {
pub fn new(levels: Vec<Vec<u32>>) -> Self {
Self { levels }
}
pub fn num_levels(&self) -> usize {
self.levels.len()
}
pub fn level_lengths(&self) -> Vec<usize> {
self.levels.iter().map(|l| l.len()).collect()
}
pub fn total_tokens(&self) -> usize {
self.levels.iter().map(|l| l.len()).sum()
}
pub fn is_empty(&self) -> bool {
self.levels.iter().all(|l| l.is_empty())
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CodecInfo {
pub sample_rate: u32,
pub frame_rate: f32,
pub hop_length: usize,
pub channels: usize,
pub max_quantizers: usize,
pub codebook_size: usize,
}
impl CodecInfo {
pub fn bits_per_codebook(&self) -> f32 {
(self.codebook_size.max(2) as f32).log2()
}
pub fn bits_per_second_per_codebook(&self) -> f32 {
self.bits_per_codebook() * self.frame_rate
}
pub fn bitrate_bps(&self, num_quantizers: Option<usize>) -> f32 {
let q = num_quantizers
.unwrap_or(self.max_quantizers)
.min(self.max_quantizers);
q as f32 * self.bits_per_second_per_codebook()
}
pub fn quantizers_for_bitrate(&self, target_bps: f32) -> usize {
let per_q = self.bits_per_second_per_codebook();
if per_q <= 0.0 {
return 1;
}
let q = (target_bps / per_q).floor() as i64;
q.clamp(1, self.max_quantizers as i64) as usize
}
pub fn frames_for_samples(&self, num_samples: usize) -> usize {
if self.hop_length == 0 {
return 0;
}
num_samples.div_ceil(self.hop_length)
}
}
pub trait AudioCodec {
fn info(&self) -> CodecInfo;
fn device(&self) -> Device;
fn encode_pcm(&self, pcm: &[f32], num_quantizers: Option<usize>) -> Result<RvqCodes>;
fn decode_codes(&self, codes: &RvqCodes) -> Result<Vec<f32>>;
fn sample_rate(&self) -> u32 {
self.info().sample_rate
}
fn encode_pcm_bitrate(&self, pcm: &[f32], target_bps: f32) -> Result<RvqCodes> {
let nq = self.info().quantizers_for_bitrate(target_bps);
self.encode_pcm(pcm, Some(nq))
}
fn encode_pcm_resampled(
&self,
pcm: &[f32],
input_rate: u32,
num_quantizers: Option<usize>,
) -> Result<RvqCodes> {
let sr = self.info().sample_rate;
if input_rate == sr {
self.encode_pcm(pcm, num_quantizers)
} else {
let resampled = resample_linear(pcm, input_rate, sr);
self.encode_pcm(&resampled, num_quantizers)
}
}
fn decode_codes_resampled(&self, codes: &RvqCodes, output_rate: u32) -> Result<Vec<f32>> {
let pcm = self.decode_codes(codes)?;
let sr = self.info().sample_rate;
Ok(if output_rate == sr {
pcm
} else {
resample_linear(&pcm, sr, output_rate)
})
}
fn roundtrip_pcm(
&self,
pcm: &[f32],
num_quantizers: Option<usize>,
) -> Result<(RvqCodes, Vec<f32>)> {
let codes = self.encode_pcm(pcm, num_quantizers)?;
let recon = self.decode_codes(&codes)?;
Ok((codes, recon))
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CompressStats {
pub compressed_bytes: u64,
pub encode_ms: f64,
pub decode_ms: f64,
}
impl CompressStats {
pub fn compression_ratio(&self, raw_pcm_bytes: u64) -> f64 {
if self.compressed_bytes == 0 {
return 0.0;
}
raw_pcm_bytes as f64 / self.compressed_bytes as f64
}
pub fn bitrate_bps(&self, duration_secs: f64) -> f64 {
if duration_secs <= 0.0 {
return 0.0;
}
self.compressed_bytes as f64 * 8.0 / duration_secs
}
}
pub trait FileCodec {
fn device(&self) -> Device;
fn sample_rate(&self) -> u32;
fn encode_file(&self, in_audio: &Path, out_compressed: &Path) -> Result<CompressStats>;
fn decode_file(&self, in_compressed: &Path, out_wav: &Path) -> Result<()>;
fn roundtrip_file(&self, in_audio: &Path, out_wav: &Path) -> Result<CompressStats> {
let stem = in_audio
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("audio");
let tmp =
std::env::temp_dir().join(format!("rlx-filecodec-{}-{stem}.bin", std::process::id()));
let enc = self.encode_file(in_audio, &tmp)?;
let t0 = std::time::Instant::now();
let dec = self.decode_file(&tmp, out_wav);
let decode_ms = t0.elapsed().as_secs_f64() * 1000.0;
let _ = std::fs::remove_file(&tmp);
dec?;
Ok(CompressStats {
compressed_bytes: enc.compressed_bytes,
encode_ms: enc.encode_ms,
decode_ms,
})
}
}
pub struct ChunkStreamer<'a, C: AudioCodec + ?Sized> {
codec: &'a C,
num_quantizers: Option<usize>,
frames_emitted: usize,
samples_emitted: usize,
}
impl<'a, C: AudioCodec + ?Sized> ChunkStreamer<'a, C> {
pub fn new(codec: &'a C, num_quantizers: Option<usize>) -> Self {
Self {
codec,
num_quantizers,
frames_emitted: 0,
samples_emitted: 0,
}
}
pub fn with_bitrate(codec: &'a C, target_bps: f32) -> Self {
let nq = codec.info().quantizers_for_bitrate(target_bps);
Self::new(codec, Some(nq))
}
pub fn encode_chunk(&mut self, pcm: &[f32]) -> Result<RvqCodes> {
let codes = self.codec.encode_pcm(pcm, self.num_quantizers)?;
self.frames_emitted += codes.num_frames();
Ok(codes)
}
pub fn decode_chunk(&mut self, codes: &RvqCodes) -> Result<Vec<f32>> {
let pcm = self.codec.decode_codes(codes)?;
self.samples_emitted += pcm.len();
Ok(pcm)
}
pub fn frames_emitted(&self) -> usize {
self.frames_emitted
}
pub fn samples_emitted(&self) -> usize {
self.samples_emitted
}
pub fn reset(&mut self) {
self.frames_emitted = 0;
self.samples_emitted = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn mimi_info() -> CodecInfo {
CodecInfo {
sample_rate: 24_000,
frame_rate: 12.5,
hop_length: 1920,
channels: 1,
max_quantizers: 32,
codebook_size: 2048,
}
}
#[test]
fn bitrate_monotonic_in_quantizers() {
let info = mimi_info();
let b8 = info.bitrate_bps(Some(8));
let b16 = info.bitrate_bps(Some(16));
assert!(b16 > b8);
assert!((b8 - 1100.0).abs() < 1.0, "got {b8}");
}
#[test]
fn quantizers_for_bitrate_roundtrips() {
let info = mimi_info();
for q in 1..=info.max_quantizers {
let bps = info.bitrate_bps(Some(q));
let got = info.quantizers_for_bitrate(bps);
assert!(got == q || got == q.saturating_sub(1), "q={q} -> {got}");
}
}
#[test]
fn quantizers_for_bitrate_clamps() {
let info = mimi_info();
assert_eq!(info.quantizers_for_bitrate(0.0), 1);
assert_eq!(info.quantizers_for_bitrate(1.0), 1);
assert_eq!(info.quantizers_for_bitrate(1e9), info.max_quantizers);
}
#[test]
fn compress_stats_ratio_and_bitrate() {
let s = CompressStats {
compressed_bytes: 1_000,
encode_ms: 5.0,
decode_ms: 3.0,
};
assert!((s.compression_ratio(8_000) - 8.0).abs() < 1e-9);
assert!((s.bitrate_bps(1.0) - 8_000.0).abs() < 1e-6);
let z = CompressStats {
compressed_bytes: 0,
encode_ms: 0.0,
decode_ms: 0.0,
};
assert_eq!(z.compression_ratio(1000), 0.0);
assert_eq!(s.bitrate_bps(0.0), 0.0);
}
#[test]
fn rvq_codes_transpose_roundtrip() {
let codes = RvqCodes::new(vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]], 3);
let qm = codes.to_quantizer_major();
assert_eq!(qm, vec![vec![1, 4, 7], vec![2, 5, 8], vec![3, 6, 9]]);
assert_eq!(RvqCodes::from_quantizer_major(&qm), codes);
}
}