rlx_voxtral/audio.rs
1// RLX — versatile ML compiler + runtime.
2// Copyright (C) 2026 Eugene Hauptmann, Nataliya Kosmyna.
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, version 3.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
16//! Native log-mel frontend — Whisper-style features at 16 kHz, fully in Rust/RLX.
17//!
18//! Matches HF `VoxtralProcessor` (= `WhisperFeatureExtractor`): pad/trim to 30 s,
19//! 128 mel bins, n_fft 400, hop 160, log10 + `max - 8` floor + `(x + 4) / 4` scale.
20//! Implemented by reusing [`rlx_whisper::pcm_to_log_mel`] — no Python, no `transformers`.
21
22use crate::config::VoxtralAudioConfig;
23use anyhow::{Result, ensure};
24pub use rlx_whisper::{
25 N_SAMPLES, SAMPLE_RATE, SpeechSegment, load_wav_mono_f32, parse_wav_mono_f32,
26 pcm_segments_by_vad,
27};
28
29/// Default mel frames for a 30 s chunk (`preprocessor_config.json`: `nb_max_frames = 3000`).
30pub const N_FRAMES: usize = 3_000;
31
32#[derive(Debug, Clone)]
33pub struct MelSpectrogram {
34 pub n_mels: usize,
35 pub n_frames: usize,
36 pub data: Vec<f32>,
37}
38
39/// Native Whisper log-mel features for one ≤30 s utterance.
40///
41/// PCM is padded (or trimmed) to exactly 30 s before the STFT so the geometry is
42/// always `[num_mel_bins, N_FRAMES]` — matching the HF processor's `pad_to_multiple_of`
43/// behaviour. Input must be 16 kHz mono f32 (see [`load_wav_mono_f32`]).
44pub fn pcm_to_mel(cfg: &VoxtralAudioConfig, pcm: &[f32]) -> Result<MelSpectrogram> {
45 ensure!(cfg.num_mel_bins > 0, "num_mel_bins must be > 0");
46 let padded = rlx_whisper::audio::pad_or_trim_pcm(pcm);
47 let mel = rlx_whisper::pcm_to_log_mel(&padded, cfg.num_mel_bins, N_FRAMES);
48 Ok(MelSpectrogram {
49 n_mels: mel.n_mels,
50 n_frames: mel.n_frames,
51 data: mel.data,
52 })
53}
54
55pub fn mel_from_flat(n_mels: usize, n_frames: usize, data: Vec<f32>) -> Result<MelSpectrogram> {
56 ensure!(
57 data.len() == n_mels * n_frames,
58 "mel flat len {} != {n_mels}×{n_frames}",
59 data.len()
60 );
61 Ok(MelSpectrogram {
62 n_mels,
63 n_frames,
64 data,
65 })
66}