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")