use crate::core::io::AudioData;
use crate::features::phase_recovery::griffinlim;
use crate::utils::frequency::{fft_frequencies_impl, mel_frequencies_impl};
use ndarray::{Array2, Axis};
use rayon::prelude::*;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MfccError {
#[error("Invalid dimensions: {0}")]
InvalidDimensions(String),
#[error("Invalid parameter: {0}")]
InvalidInput(String),
#[error("Computation failed: {0}")]
ComputationFailed(String),
}
pub fn compute_delta(mfcc: &Array2<f32>) -> ComputeDeltaBuilder<'_> {
ComputeDeltaBuilder { mfcc, width: 9, axis: -1 }
}
#[derive(Debug, Clone)]
pub struct ComputeDeltaBuilder<'a> {
mfcc: &'a Array2<f32>,
width: usize,
axis: isize,
}
impl ComputeDeltaBuilder<'_> {
#[must_use]
pub fn width(mut self, width: usize) -> Self {
self.width = width;
self
}
#[must_use]
pub fn axis(mut self, axis: isize) -> Self {
self.axis = axis;
self
}
pub fn compute(self) -> Result<Array2<f32>, MfccError> {
compute_delta_impl(self.mfcc, Some(self.width), Some(self.axis))
}
}
fn compute_delta_impl(
mfcc: &Array2<f32>,
width: Option<usize>,
axis: Option<isize>,
) -> Result<Array2<f32>, MfccError> {
let width = width.unwrap_or(9);
let axis = axis.unwrap_or(-1);
if width == 0 || width % 2 == 0 {
return Err(MfccError::InvalidInput(
"Width must be a positive odd integer".to_string(),
));
}
let ax = usize::from(axis < 0);
let (n_mfcc, n_frames) = if ax == 1 {
mfcc.dim()
} else {
(mfcc.shape()[1], mfcc.shape()[0])
};
if n_frames == 0 || n_mfcc == 0 {
return Err(MfccError::InvalidDimensions(
"MFCC matrix is empty".to_string(),
));
}
if n_frames < width {
return Err(MfccError::InvalidDimensions(format!(
"Time axis length {n_frames} less than width {width}"
)));
}
let half_width = width / 2;
let weights: Vec<f32> = (-(half_width as isize)..=half_width as isize)
.map(|i| i as f32)
.collect();
let norm = weights.iter().map(|x| x.powi(2)).sum::<f32>();
if norm == 0.0 {
return Err(MfccError::ComputationFailed(
"Normalization factor is zero".to_string(),
));
}
let mut delta = Array2::zeros(mfcc.dim());
delta
.axis_iter_mut(Axis(ax))
.into_par_iter()
.enumerate()
.for_each(|(i, mut slice)| {
let row = mfcc.index_axis(Axis(ax), i);
for j in 0..row.len() {
let mut sum = 0.0;
for (w_idx, &w) in weights.iter().enumerate() {
let offset = w_idx as isize - half_width as isize;
let idx = (j as isize + offset).clamp(0, row.len() as isize - 1) as usize;
sum += w * row[idx];
}
slice[j] = sum / norm;
}
});
Ok(delta)
}
pub fn mel_to_stft(m: &Array2<f32>) -> MelToStftBuilder<'_> {
MelToStftBuilder { m, sr: 44100, n_fft: 2048, power: 2.0 }
}
#[derive(Debug, Clone)]
pub struct MelToStftBuilder<'a> {
m: &'a Array2<f32>,
sr: u32,
n_fft: usize,
power: f32,
}
impl MelToStftBuilder<'_> {
#[must_use]
pub fn sample_rate(mut self, sr: u32) -> Self {
self.sr = sr;
self
}
#[must_use]
pub fn n_fft(mut self, n_fft: usize) -> Self {
self.n_fft = n_fft;
self
}
#[must_use]
pub fn power(mut self, power: f32) -> Self {
self.power = power;
self
}
pub fn compute(self) -> Result<Array2<f32>, MfccError> {
mel_to_stft_impl(self.m, Some(self.sr), Some(self.n_fft), Some(self.power))
}
}
fn mel_to_stft_impl(
m: &Array2<f32>,
sr: Option<u32>,
n_fft: Option<usize>,
power: Option<f32>,
) -> Result<Array2<f32>, MfccError> {
let sr = sr.unwrap_or(44100);
let n_fft = n_fft.unwrap_or(2048);
let power = power.unwrap_or(2.0);
if m.is_empty() {
return Err(MfccError::InvalidDimensions(
"Mel spectrogram is empty".to_string(),
));
}
if n_fft < 2 {
return Err(MfccError::InvalidInput(
"n_fft must be at least 2".to_string(),
));
}
if power <= 0.0 {
return Err(MfccError::InvalidInput(
"Power must be positive".to_string(),
));
}
let n_mels = m.shape()[0];
let n_frames = m.shape()[1];
let mel_f = mel_frequencies_impl(n_mels + 2, 0.0, sr as f32 / 2.0);
let fft_f = fft_frequencies_impl(sr, n_fft);
let n_bins = n_fft / 2 + 1;
let mut s = Array2::zeros((n_bins, n_frames));
s.axis_iter_mut(Axis(1))
.into_par_iter()
.enumerate()
.for_each(|(t, mut col)| {
for mel in 0..n_mels {
let f_low = mel_f[mel];
let f_center = mel_f[mel + 1];
let f_high = mel_f[mel + 2];
for (bin, &f) in fft_f.iter().enumerate().take(n_bins) {
let weight = if f >= f_low && f <= f_high {
if f <= f_center {
(f - f_low) / (f_center - f_low)
} else {
(f_high - f) / (f_high - f_center)
}
} else {
0.0
}
.max(0.0);
col[bin] += m[[mel, t]].max(0.0) * weight;
}
}
});
Ok(s.mapv(|x: f32| x.powf(1.0 / power)))
}
pub fn mel_to_audio(m: &Array2<f32>) -> MelToAudioBuilder<'_> {
MelToAudioBuilder { m, sr: 44100, n_fft: 2048, hop_length: None }
}
#[derive(Debug, Clone)]
pub struct MelToAudioBuilder<'a> {
m: &'a Array2<f32>,
sr: u32,
n_fft: usize,
hop_length: Option<usize>,
}
impl MelToAudioBuilder<'_> {
#[must_use]
pub fn sample_rate(mut self, sr: u32) -> Self {
self.sr = sr;
self
}
#[must_use]
pub fn n_fft(mut self, n_fft: usize) -> Self {
self.n_fft = n_fft;
self
}
#[must_use]
pub fn hop_length(mut self, hop_length: usize) -> Self {
self.hop_length = Some(hop_length);
self
}
pub fn compute(self) -> Result<AudioData, MfccError> {
mel_to_audio_impl(self.m, Some(self.sr), Some(self.n_fft), self.hop_length)
}
}
fn mel_to_audio_impl(
m: &Array2<f32>,
sr: Option<u32>,
n_fft: Option<usize>,
hop_length: Option<usize>,
) -> Result<AudioData, MfccError> {
let n_fft = n_fft.unwrap_or(2048);
let hop = hop_length.unwrap_or(n_fft / 4);
let sr = sr.unwrap_or(44100);
if hop == 0 {
return Err(MfccError::InvalidInput(
"Hop length must be positive".to_string(),
));
}
let s = mel_to_stft_impl(m, Some(sr), Some(n_fft), None)?;
let samples = griffinlim(&s)
.hop_length(hop)
.compute()
.map_err(|e| MfccError::ComputationFailed(format!("Griffin-Lim failed: {e}")))?;
if samples.is_empty() {
return Err(MfccError::ComputationFailed(
"Griffin-Lim returned empty samples".to_string(),
));
}
if samples.iter().any(|&x| !x.is_finite()) {
return Err(MfccError::ComputationFailed(
"Non-finite samples in reconstruction".to_string(),
));
}
AudioData::new(samples, sr, 1).map_err(|e| MfccError::ComputationFailed(e.to_string()))
}
pub fn mfcc_to_mel(mfcc: &Array2<f32>) -> MfccToMelBuilder<'_> {
MfccToMelBuilder { mfcc, n_mels: 128, dct_type: 2 }
}
#[derive(Debug, Clone)]
pub struct MfccToMelBuilder<'a> {
mfcc: &'a Array2<f32>,
n_mels: usize,
dct_type: i32,
}
impl MfccToMelBuilder<'_> {
#[must_use]
pub fn n_mels(mut self, n_mels: usize) -> Self {
self.n_mels = n_mels;
self
}
#[must_use]
pub fn dct_type(mut self, dct_type: i32) -> Self {
self.dct_type = dct_type;
self
}
pub fn compute(self) -> Result<Array2<f32>, MfccError> {
mfcc_to_mel_impl(self.mfcc, Some(self.n_mels), Some(self.dct_type))
}
}
fn mfcc_to_mel_impl(
mfcc: &Array2<f32>,
n_mels: Option<usize>,
dct_type: Option<i32>,
) -> Result<Array2<f32>, MfccError> {
let n_mels = n_mels.unwrap_or(128);
let dct_type = dct_type.unwrap_or(2);
if mfcc.is_empty() {
return Err(MfccError::InvalidDimensions(
"MFCC matrix is empty".to_string(),
));
}
if ![1, 2, 3, 4].contains(&dct_type) {
return Err(MfccError::InvalidInput(format!(
"Unsupported DCT type: {dct_type}"
)));
}
let n_frames = mfcc.shape()[1];
let n_mfcc = mfcc.shape()[0];
let mut mel = Array2::zeros((n_mels, n_frames));
mel.axis_iter_mut(Axis(1))
.into_par_iter()
.enumerate()
.for_each(|(t, mut col)| {
for n in 0..n_mels {
let mut sum = 0.0;
for k in 0..n_mfcc {
let scale = if k == 0 {
1.0 / (n_mels as f32).sqrt()
} else {
(2.0 / n_mels as f32).sqrt()
};
let theta = std::f32::consts::PI * k as f32 * (n as f32 + 0.5) / n_mels as f32;
sum += scale * mfcc[[k, t]] * theta.cos();
}
col[n] = sum.max(0.0);
}
});
Ok(mel.mapv(f32::exp))
}
pub fn mfcc_to_audio(mfcc: &Array2<f32>) -> MfccToAudioBuilder<'_> {
MfccToAudioBuilder { mfcc, n_mels: 128, sr: 44100, n_fft: 2048, hop_length: None }
}
#[derive(Debug, Clone)]
pub struct MfccToAudioBuilder<'a> {
mfcc: &'a Array2<f32>,
n_mels: usize,
sr: u32,
n_fft: usize,
hop_length: Option<usize>,
}
impl MfccToAudioBuilder<'_> {
#[must_use]
pub fn n_mels(mut self, n_mels: usize) -> Self {
self.n_mels = n_mels;
self
}
#[must_use]
pub fn sample_rate(mut self, sr: u32) -> Self {
self.sr = sr;
self
}
#[must_use]
pub fn n_fft(mut self, n_fft: usize) -> Self {
self.n_fft = n_fft;
self
}
#[must_use]
pub fn hop_length(mut self, hop_length: usize) -> Self {
self.hop_length = Some(hop_length);
self
}
pub fn compute(self) -> Result<AudioData, MfccError> {
let mel = mfcc_to_mel_impl(self.mfcc, Some(self.n_mels), Some(2))?;
mel_to_audio_impl(&mel, Some(self.sr), Some(self.n_fft), self.hop_length)
}
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::array;
#[test]
fn test_compute_delta_invalid_width() {
let mfcc = array![[0.1, 0.2], [0.3, 0.4]];
let result = compute_delta(&mfcc).width(2).compute(); assert!(matches!(result, Err(MfccError::InvalidInput(_))));
}
#[test]
fn test_compute_delta_empty_input() {
let mfcc = array![[]]; let result = compute_delta(&mfcc).width(3).compute();
assert!(matches!(result, Err(MfccError::InvalidDimensions(_))));
}
#[test]
fn test_compute_delta_insufficient_frames() {
let mfcc = array![[0.1, 0.2], [0.3, 0.4]]; let result = compute_delta(&mfcc).width(5).compute(); assert!(matches!(result, Err(MfccError::InvalidDimensions(_))));
}
#[test]
fn test_mfcc_to_mel() {
let mfcc = array![[0.1, 0.2], [0.3, 0.4]];
let mel = mfcc_to_mel(&mfcc).n_mels(4).compute().unwrap();
assert_eq!(mel.shape(), &[4, 2]);
assert!(mel[[0, 0]] > 0.0);
}
#[test]
fn test_invalid_input() {
let empty = array![[]];
assert!(matches!(
compute_delta(&empty).compute(),
Err(MfccError::InvalidDimensions(_))
));
assert!(matches!(
mel_to_stft(&empty).compute(),
Err(MfccError::InvalidDimensions(_))
));
}
}