#2099·librosa

feature.spectral_centroid / bandwidth / rolloff / contrast / poly_features upcast float32 spectrogram to float64

Author: tritsystemCreated Sep 4, 2026Updated Sep 4, 2026

Description

librosa.feature.spectral_centroid, spectral_bandwidth, spectral_rolloff, spectral_contrast and poly_features upcast a float32 magnitude spectrogram to float64, while the rest of librosa.feature (melspectrogram, mfcc, chroma_stft, spectral_flatness, rms) and the core (stft, which has an explicit dtype-preserving dtype= argument) preserve float32.

The cause is the frequency grid: each of these five functions does

python
if freq is None:
    freq = fft_frequencies(sr=sr, n_fft=n_fft)   # always float64

and then multiplies/subtracts it with the spectrogram (freq * S, np.abs(freq - centroid), ind * freq, …), which promotes the whole result to float64.

Steps/Code to Reproduce

python
import numpy as np, librosa

y = np.random.default_rng(0).standard_normal(22050).astype(np.float32)
S = np.abs(librosa.stft(y, n_fft=1024)).astype(np.float32)   # float32

for name in ["melspectrogram", "mfcc", "spectral_flatness",
             "spectral_centroid", "spectral_bandwidth",
             "spectral_rolloff", "spectral_contrast", "poly_features"]:
    fn = getattr(librosa.feature, name)
    kw = {"S": S**2, "sr": 22050} if name == "melspectrogram" else {"S": S}
    print(f"{name:20s} -> {fn(**kw).dtype}")
melspectrogram       -> float32
mfcc                 -> float32
spectral_flatness    -> float32
spectral_centroid    -> float64   # <- upcast
spectral_bandwidth   -> float64   # <- upcast
spectral_rolloff     -> float64   # <- upcast
spectral_contrast    -> float64   # <- upcast
poly_features        -> float64   # <- upcast

Expected Results

float32 in → float32 out, consistent with the other librosa.feature functions and with stft.

Actual Results

float64 out. In a float32 pipeline this doubles the memory of these feature arrays and breaks a downstream assert x.dtype == np.float32 or a float32-only model input, silently.

Versions

librosa 0.11.0, numpy 2.4.6 (also present on main @ f808bac, librosa/feature/spectral.py lines ~182 / 340 / 474 / 670 / 1045).

Suggested fix

Cast the internally-generated grid to the spectrogram dtype right after fft_frequencies, only when librosa generated it (a user-supplied freq is respected as-is):

python
if freq is None:
    freq = fft_frequencies(sr=sr, n_fft=n_fft).astype(S.dtype)

in each of the five functions. Happy to open the PR with a tests/test_features.py case (parametrised float32 / float64, assert out.dtype == S.dtype) if this direction looks right.