Skip to main content

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}