Spaces:
Sleeping
Sleeping
File size: 2,729 Bytes
c493359 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | """
Audio preprocessing and feature extraction pipeline.
Mirrors the exact training-time pipeline to ensure consistent predictions.
"""
import numpy as np
import librosa
import noisereduce as nr
# ββ Feature extraction config (must match training exactly) ββββββββββββββ
TARGET_SR = 22050
N_MFCC = 20
HOP_LENGTH = 512
TARGET_DURATION = 3.0
MAX_FRAMES = int(np.ceil(TARGET_SR * TARGET_DURATION / HOP_LENGTH))
def preprocess_audio(y: np.ndarray, sr: int) -> np.ndarray:
"""Silence removal, peak normalisation, and noise reduction."""
# Remove silence
intervals = librosa.effects.split(y, top_db=20)
if len(intervals) > 0:
y = np.concatenate([y[start:end] for start, end in intervals])
# Peak normalisation
peak = np.max(np.abs(y))
if peak > 0:
y = y / peak
# Noise reduction
try:
y = nr.reduce_noise(y=y, sr=sr, prop_decrease=0.8)
except Exception:
pass
return y
def extract_and_scale_features(file_path: str, scaler) -> np.ndarray:
"""
Extract MFCC + delta + delta-delta + spectral contrast features,
pad/truncate to MAX_FRAMES, and scale using the saved training scaler.
Returns a float32 array of shape (MAX_FRAMES, 80).
"""
y, sr = librosa.load(file_path, sr=TARGET_SR, mono=True)
y = preprocess_audio(y, sr)
# MFCCs + derivatives
mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=N_MFCC, hop_length=HOP_LENGTH)
delta1 = librosa.feature.delta(mfcc)
delta2 = librosa.feature.delta(mfcc, order=2)
# Spectral contrast (7 bands β pad/trim to N_MFCC rows)
spec_contrast = librosa.feature.spectral_contrast(
y=y, sr=sr, hop_length=HOP_LENGTH
)
if spec_contrast.shape[0] < N_MFCC:
spec_contrast = np.pad(
spec_contrast,
((0, N_MFCC - spec_contrast.shape[0]), (0, 0)),
mode="edge",
)
else:
spec_contrast = spec_contrast[:N_MFCC, :]
# Stack β (time, features)
features = np.vstack([mfcc, delta1, delta2, spec_contrast]).T
# Pad or truncate to fixed length
if features.shape[0] < MAX_FRAMES:
pad_width = MAX_FRAMES - features.shape[0]
features = np.pad(
features, ((0, pad_width), (0, 0)), mode="constant", constant_values=0
)
else:
features = features[:MAX_FRAMES, :]
# Scale using the saved training scaler
n_features = features.shape[1]
features_scaled = scaler.transform(features.reshape(-1, n_features))
features_scaled = features_scaled.reshape(features.shape)
return features_scaled.astype("float32")
|