rosa 0.1.0

Audio feature extraction library matching librosa output
Documentation
//! Spectral bandwidth matching `librosa.feature.spectral_bandwidth`.

use crate::matrix::{Axis, Matrix};

/// Compute p'th-order spectral bandwidth from a magnitude spectrogram.
///
/// Matches `librosa.feature.spectral_bandwidth(S=S, freq=freq, norm=norm, p=p)`.
///
/// - `s`: magnitude spectrogram (rows = frequency bins, cols = frames)
/// - `freq`: center frequencies for each bin (length = s.rows())
/// - `centroid`: pre-computed centroid per frame (length = s.cols())
/// - `p`: power for the deviation (default: 2.0)
/// - `norm`: if true, L1-normalize the spectrogram per frame (default: true)
///
/// # Panics
///
/// Panics if `freq.len() != s.rows()` or `centroid.len() != s.cols()`.
pub fn spectral_bandwidth(
    s: &Matrix,
    freq: &[f64],
    centroid: &[f64],
    p: f64,
    norm: bool,
) -> Vec<f64> {
    assert_eq!(
        s.rows(),
        freq.len(),
        "freq length {} != spectrogram rows {}",
        freq.len(),
        s.rows()
    );
    assert_eq!(
        s.cols(),
        centroid.len(),
        "centroid length {} != spectrogram cols {}",
        centroid.len(),
        s.cols()
    );

    let s_work = if norm {
        s.normalize_axis(Axis::Row, 1.0)
    } else {
        s.clone()
    };

    let n_frames = s_work.cols();
    let n_bins = s_work.rows();
    let inv_p = 1.0 / p;
    let mut result = Vec::with_capacity(n_frames);

    for (t, &c) in centroid.iter().enumerate().take(n_frames) {
        let mut sum = 0.0;
        for (k, &f) in freq.iter().enumerate().take(n_bins) {
            let deviation = (f - c).abs();
            sum += s_work.get(k, t) * deviation.powf(p);
        }
        result.push(sum.powf(inv_p));
    }

    result
}