File size: 3,176 Bytes
c942b33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import numpy as np
import librosa
from scipy import stats as scipy_stats
import warnings

warnings.filterwarnings('ignore')

# ==========================================
# CONFIGURATION
# ==========================================
TARGET_SR = 48000
TARGET_DURATION = 10.0
TARGET_LENGTH = int(TARGET_SR * TARGET_DURATION)
TRIM_TOP_DB = 20
N_MFCC = 20

# ==========================================
# PREPROCESSING
# ==========================================
def preprocess_audio(file_path):
    y, sr = librosa.load(file_path, sr=TARGET_SR)

    # Trim silence
    y_trimmed, _ = librosa.effects.trim(y, top_db=TRIM_TOP_DB)
    if len(y_trimmed) == 0:
        y_trimmed = y

    # DC offset removal
    y_centered = y_trimmed - np.mean(y_trimmed)

    # Z-score normalization
    std = np.std(y_centered)
    if std > 1e-8:
        y_norm = y_centered / std
    else:
        y_norm = y_centered

    # Fixed length
    if len(y_norm) < TARGET_LENGTH:
        y_norm = np.pad(y_norm, (0, TARGET_LENGTH - len(y_norm)), mode='constant')
    else:
        y_norm = y_norm[:TARGET_LENGTH]

    return y_norm


# ==========================================
# FEATURE EXTRACTION
# ==========================================
def compute_stats(feature_array, prefix):
    result = {}
    if feature_array.ndim == 1:
        feature_array = feature_array.reshape(1, -1)

    for i in range(feature_array.shape[0]):
        row = feature_array[i]
        suffix = f"_{i}" if feature_array.shape[0] > 1 else ""
        result[f"{prefix}{suffix}_mean"] = np.mean(row)
        result[f"{prefix}{suffix}_std"]  = np.std(row)
        result[f"{prefix}{suffix}_min"]  = np.min(row)
        result[f"{prefix}{suffix}_max"]  = np.max(row)
    return result


def extract_ml_features(y, sr=TARGET_SR, n_mfcc=N_MFCC):
    features = {}

    # 1. MFCCs (20 × 4 = 80)
    mfccs = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=n_mfcc)
    features.update(compute_stats(mfccs, "mfcc"))

    # 2. Spectral Centroid (4)
    features.update(compute_stats(librosa.feature.spectral_centroid(y=y, sr=sr), "spectral_centroid"))

    # 3. Spectral Flatness (4)
    features.update(compute_stats(librosa.feature.spectral_flatness(y=y), "spectral_flatness"))

    # 4. Zero-Crossing Rate (4)
    features.update(compute_stats(librosa.feature.zero_crossing_rate(y), "zcr"))

    # 5. RMS Energy (4)
    features.update(compute_stats(librosa.feature.rms(y=y), "rms"))

    # 6. Spectral Rolloff at 85% (4)
    features.update(compute_stats(librosa.feature.spectral_rolloff(y=y, sr=sr, roll_percent=0.85), "spectral_rolloff"))

    # 7. Spectral Bandwidth (4)
    features.update(compute_stats(librosa.feature.spectral_bandwidth(y=y, sr=sr), "spectral_bandwidth"))

    # 8. Spectral Contrast (7 bands × 4 = 28)
    features.update(compute_stats(librosa.feature.spectral_contrast(y=y, sr=sr, n_bands=6), "spectral_contrast"))

    # 9. Chroma STFT (12 × 4 = 48)
    features.update(compute_stats(librosa.feature.chroma_stft(y=y, sr=sr), "chroma"))

    # 10. Spectral Kurtosis (1)
    S = np.abs(librosa.stft(y))
    features["spectral_kurtosis"] = float(scipy_stats.kurtosis(np.mean(S, axis=1)))

    return features