1use crate::audio::resample_linear;
23use anyhow::Result;
24use rlx_runtime::Device;
25use serde::{Deserialize, Serialize};
26use std::path::Path;
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct RvqCodes {
35 pub frames: Vec<Vec<u32>>,
37 pub num_quantizers: usize,
39}
40
41impl RvqCodes {
42 pub fn new(frames: Vec<Vec<u32>>, num_quantizers: usize) -> Self {
43 Self {
44 frames,
45 num_quantizers,
46 }
47 }
48
49 pub fn num_frames(&self) -> usize {
50 self.frames.len()
51 }
52
53 pub fn is_empty(&self) -> bool {
54 self.frames.is_empty()
55 }
56
57 pub fn to_quantizer_major(&self) -> Vec<Vec<u32>> {
60 let nq = self.num_quantizers;
61 let t = self.num_frames();
62 let mut out = vec![Vec::with_capacity(t); nq];
63 for frame in &self.frames {
64 for (q, &code) in frame.iter().enumerate().take(nq) {
65 out[q].push(code);
66 }
67 }
68 out
69 }
70
71 pub fn from_quantizer_major(rows: &[Vec<u32>]) -> Self {
73 let nq = rows.len();
74 let t = rows.iter().map(|r| r.len()).max().unwrap_or(0);
75 let mut frames = Vec::with_capacity(t);
76 for ti in 0..t {
77 let mut frame = Vec::with_capacity(nq);
78 for row in rows {
79 frame.push(row.get(ti).copied().unwrap_or(0));
80 }
81 frames.push(frame);
82 }
83 Self {
84 frames,
85 num_quantizers: nq,
86 }
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
94pub struct HierarchicalCodes {
95 pub levels: Vec<Vec<u32>>,
97}
98
99impl HierarchicalCodes {
100 pub fn new(levels: Vec<Vec<u32>>) -> Self {
101 Self { levels }
102 }
103
104 pub fn num_levels(&self) -> usize {
105 self.levels.len()
106 }
107
108 pub fn level_lengths(&self) -> Vec<usize> {
109 self.levels.iter().map(|l| l.len()).collect()
110 }
111
112 pub fn total_tokens(&self) -> usize {
113 self.levels.iter().map(|l| l.len()).sum()
114 }
115
116 pub fn is_empty(&self) -> bool {
117 self.levels.iter().all(|l| l.is_empty())
118 }
119}
120
121#[derive(Debug, Clone, Copy, PartialEq)]
124pub struct CodecInfo {
125 pub sample_rate: u32,
127 pub frame_rate: f32,
129 pub hop_length: usize,
131 pub channels: usize,
133 pub max_quantizers: usize,
135 pub codebook_size: usize,
137}
138
139impl CodecInfo {
140 pub fn bits_per_codebook(&self) -> f32 {
142 (self.codebook_size.max(2) as f32).log2()
143 }
144
145 pub fn bits_per_second_per_codebook(&self) -> f32 {
147 self.bits_per_codebook() * self.frame_rate
148 }
149
150 pub fn bitrate_bps(&self, num_quantizers: Option<usize>) -> f32 {
152 let q = num_quantizers
153 .unwrap_or(self.max_quantizers)
154 .min(self.max_quantizers);
155 q as f32 * self.bits_per_second_per_codebook()
156 }
157
158 pub fn quantizers_for_bitrate(&self, target_bps: f32) -> usize {
162 let per_q = self.bits_per_second_per_codebook();
163 if per_q <= 0.0 {
164 return 1;
165 }
166 let q = (target_bps / per_q).floor() as i64;
167 q.clamp(1, self.max_quantizers as i64) as usize
168 }
169
170 pub fn frames_for_samples(&self, num_samples: usize) -> usize {
172 if self.hop_length == 0 {
173 return 0;
174 }
175 num_samples.div_ceil(self.hop_length)
176 }
177}
178
179pub trait AudioCodec {
186 fn info(&self) -> CodecInfo;
188
189 fn device(&self) -> Device;
191
192 fn encode_pcm(&self, pcm: &[f32], num_quantizers: Option<usize>) -> Result<RvqCodes>;
195
196 fn decode_codes(&self, codes: &RvqCodes) -> Result<Vec<f32>>;
198
199 fn sample_rate(&self) -> u32 {
201 self.info().sample_rate
202 }
203
204 fn encode_pcm_bitrate(&self, pcm: &[f32], target_bps: f32) -> Result<RvqCodes> {
207 let nq = self.info().quantizers_for_bitrate(target_bps);
208 self.encode_pcm(pcm, Some(nq))
209 }
210
211 fn encode_pcm_resampled(
214 &self,
215 pcm: &[f32],
216 input_rate: u32,
217 num_quantizers: Option<usize>,
218 ) -> Result<RvqCodes> {
219 let sr = self.info().sample_rate;
220 if input_rate == sr {
221 self.encode_pcm(pcm, num_quantizers)
222 } else {
223 let resampled = resample_linear(pcm, input_rate, sr);
224 self.encode_pcm(&resampled, num_quantizers)
225 }
226 }
227
228 fn decode_codes_resampled(&self, codes: &RvqCodes, output_rate: u32) -> Result<Vec<f32>> {
230 let pcm = self.decode_codes(codes)?;
231 let sr = self.info().sample_rate;
232 Ok(if output_rate == sr {
233 pcm
234 } else {
235 resample_linear(&pcm, sr, output_rate)
236 })
237 }
238
239 fn roundtrip_pcm(
241 &self,
242 pcm: &[f32],
243 num_quantizers: Option<usize>,
244 ) -> Result<(RvqCodes, Vec<f32>)> {
245 let codes = self.encode_pcm(pcm, num_quantizers)?;
246 let recon = self.decode_codes(&codes)?;
247 Ok((codes, recon))
248 }
249}
250
251#[derive(Debug, Clone, Copy, PartialEq)]
253pub struct CompressStats {
254 pub compressed_bytes: u64,
256 pub encode_ms: f64,
258 pub decode_ms: f64,
260}
261
262impl CompressStats {
263 pub fn compression_ratio(&self, raw_pcm_bytes: u64) -> f64 {
265 if self.compressed_bytes == 0 {
266 return 0.0;
267 }
268 raw_pcm_bytes as f64 / self.compressed_bytes as f64
269 }
270
271 pub fn bitrate_bps(&self, duration_secs: f64) -> f64 {
273 if duration_secs <= 0.0 {
274 return 0.0;
275 }
276 self.compressed_bytes as f64 * 8.0 / duration_secs
277 }
278}
279
280pub trait FileCodec {
286 fn device(&self) -> Device;
288
289 fn sample_rate(&self) -> u32;
291
292 fn encode_file(&self, in_audio: &Path, out_compressed: &Path) -> Result<CompressStats>;
294
295 fn decode_file(&self, in_compressed: &Path, out_wav: &Path) -> Result<()>;
297
298 fn roundtrip_file(&self, in_audio: &Path, out_wav: &Path) -> Result<CompressStats> {
301 let stem = in_audio
302 .file_stem()
303 .and_then(|s| s.to_str())
304 .unwrap_or("audio");
305 let tmp =
306 std::env::temp_dir().join(format!("rlx-filecodec-{}-{stem}.bin", std::process::id()));
307 let enc = self.encode_file(in_audio, &tmp)?;
308 let t0 = std::time::Instant::now();
309 let dec = self.decode_file(&tmp, out_wav);
310 let decode_ms = t0.elapsed().as_secs_f64() * 1000.0;
311 let _ = std::fs::remove_file(&tmp);
312 dec?;
313 Ok(CompressStats {
314 compressed_bytes: enc.compressed_bytes,
315 encode_ms: enc.encode_ms,
316 decode_ms,
317 })
318 }
319}
320
321pub struct ChunkStreamer<'a, C: AudioCodec + ?Sized> {
332 codec: &'a C,
333 num_quantizers: Option<usize>,
334 frames_emitted: usize,
335 samples_emitted: usize,
336}
337
338impl<'a, C: AudioCodec + ?Sized> ChunkStreamer<'a, C> {
339 pub fn new(codec: &'a C, num_quantizers: Option<usize>) -> Self {
340 Self {
341 codec,
342 num_quantizers,
343 frames_emitted: 0,
344 samples_emitted: 0,
345 }
346 }
347
348 pub fn with_bitrate(codec: &'a C, target_bps: f32) -> Self {
350 let nq = codec.info().quantizers_for_bitrate(target_bps);
351 Self::new(codec, Some(nq))
352 }
353
354 pub fn encode_chunk(&mut self, pcm: &[f32]) -> Result<RvqCodes> {
356 let codes = self.codec.encode_pcm(pcm, self.num_quantizers)?;
357 self.frames_emitted += codes.num_frames();
358 Ok(codes)
359 }
360
361 pub fn decode_chunk(&mut self, codes: &RvqCodes) -> Result<Vec<f32>> {
363 let pcm = self.codec.decode_codes(codes)?;
364 self.samples_emitted += pcm.len();
365 Ok(pcm)
366 }
367
368 pub fn frames_emitted(&self) -> usize {
369 self.frames_emitted
370 }
371
372 pub fn samples_emitted(&self) -> usize {
373 self.samples_emitted
374 }
375
376 pub fn reset(&mut self) {
377 self.frames_emitted = 0;
378 self.samples_emitted = 0;
379 }
380}
381
382#[cfg(test)]
383mod tests {
384 use super::*;
385
386 fn mimi_info() -> CodecInfo {
387 CodecInfo {
388 sample_rate: 24_000,
389 frame_rate: 12.5,
390 hop_length: 1920,
391 channels: 1,
392 max_quantizers: 32,
393 codebook_size: 2048,
394 }
395 }
396
397 #[test]
398 fn bitrate_monotonic_in_quantizers() {
399 let info = mimi_info();
400 let b8 = info.bitrate_bps(Some(8));
401 let b16 = info.bitrate_bps(Some(16));
402 assert!(b16 > b8);
403 assert!((b8 - 1100.0).abs() < 1.0, "got {b8}");
405 }
406
407 #[test]
408 fn quantizers_for_bitrate_roundtrips() {
409 let info = mimi_info();
410 for q in 1..=info.max_quantizers {
411 let bps = info.bitrate_bps(Some(q));
412 let got = info.quantizers_for_bitrate(bps);
414 assert!(got == q || got == q.saturating_sub(1), "q={q} -> {got}");
415 }
416 }
417
418 #[test]
419 fn quantizers_for_bitrate_clamps() {
420 let info = mimi_info();
421 assert_eq!(info.quantizers_for_bitrate(0.0), 1);
422 assert_eq!(info.quantizers_for_bitrate(1.0), 1);
423 assert_eq!(info.quantizers_for_bitrate(1e9), info.max_quantizers);
424 }
425
426 #[test]
427 fn compress_stats_ratio_and_bitrate() {
428 let s = CompressStats {
429 compressed_bytes: 1_000,
430 encode_ms: 5.0,
431 decode_ms: 3.0,
432 };
433 assert!((s.compression_ratio(8_000) - 8.0).abs() < 1e-9);
435 assert!((s.bitrate_bps(1.0) - 8_000.0).abs() < 1e-6);
437 let z = CompressStats {
439 compressed_bytes: 0,
440 encode_ms: 0.0,
441 decode_ms: 0.0,
442 };
443 assert_eq!(z.compression_ratio(1000), 0.0);
444 assert_eq!(s.bitrate_bps(0.0), 0.0);
445 }
446
447 #[test]
448 fn rvq_codes_transpose_roundtrip() {
449 let codes = RvqCodes::new(vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]], 3);
450 let qm = codes.to_quantizer_major();
451 assert_eq!(qm, vec![vec![1, 4, 7], vec![2, 5, 8], vec![3, 6, 9]]);
452 assert_eq!(RvqCodes::from_quantizer_major(&qm), codes);
453 }
454}