Skip to main content

beat_this/
mel.rs

1use anyhow::{anyhow, ensure, Result};
2
3use crate::runtime::{InferenceSession, Tensor};
4
5/// Computes log-mel spectrograms via an ONNX model.
6///
7/// The model takes raw PCM audio and returns a mel spectrogram,
8/// guaranteeing exact numerical parity with the Python training pipeline.
9pub struct MelProcessor<S: InferenceSession> {
10    session: S,
11}
12
13impl<S: InferenceSession> MelProcessor<S> {
14    /// Wrap an already-loaded inference session for the mel spectrogram model.
15    pub fn new(session: S) -> Self {
16        Self { session }
17    }
18
19    /// Get a mutable reference to the underlying session.
20    pub fn session_mut(&mut self) -> &mut S {
21        &mut self.session
22    }
23
24    /// Compute mel spectrogram from mono PCM samples at 22050 Hz.
25    ///
26    /// Input: mono f32 samples (any length).
27    /// Output: Tensor with shape `[1, time_frames, 128]`.
28    ///
29    /// The number of time frames depends on sample count:
30    /// `time_frames ≈ samples.len() / 441` (hop_length=441 for 50 fps at 22050 Hz).
31    pub fn process(&mut self, samples: &[f32]) -> Result<Tensor> {
32        let input = Tensor {
33            shape: vec![1, samples.len()],
34            data: samples.to_vec(),
35        };
36
37        let mut outputs = self.session.run(&[("audio_pcm", &input)])?;
38
39        let mel = outputs
40            .remove("mel_spectrogram")
41            .ok_or_else(|| anyhow!("Model missing 'mel_spectrogram' output"))?;
42
43        ensure!(
44            mel.shape.len() == 3 && mel.shape[0] == 1 && mel.shape[2] == 128,
45            "Unexpected mel shape: {:?}",
46            mel.shape
47        );
48
49        Ok(mel)
50    }
51}
52
53/// Number of time frames in a mel spectrogram tensor.
54/// Assumes shape `[1, T, 128]`.
55pub fn num_frames(mel: &Tensor) -> usize {
56    mel.shape[1]
57}