Spaces:
Sleeping
Sleeping
| import librosa | |
| import numpy as np | |
| from scipy import stats | |
| import warnings | |
| # Suppress librosa's PySoundFile warnings for mp3s | |
| warnings.filterwarnings('ignore') | |
| def compute_statistics(feature_matrix): | |
| """ | |
| Calculates the 7 FMA statistics across a feature matrix. | |
| Order: kurtosis, max, mean, median, min, skew, std. | |
| """ | |
| kurtosis = stats.kurtosis(feature_matrix, axis=1) | |
| maximum = np.max(feature_matrix, axis=1) | |
| mean = np.mean(feature_matrix, axis=1) | |
| median = np.median(feature_matrix, axis=1) | |
| minimum = np.min(feature_matrix, axis=1) | |
| skewness = stats.skew(feature_matrix, axis=1) | |
| std = np.std(feature_matrix, axis=1) | |
| return np.concatenate([kurtosis, maximum, mean, median, minimum, skewness, std]) | |
| def extract_audio_features(file_path): | |
| """ | |
| Loads audio, extracts 518 features, and returns frontend metrics. | |
| """ | |
| # 1. Load exactly 30 seconds at 22050 Hz | |
| y, sr = librosa.load(file_path, sr=22050, duration=30.0) | |
| # 2. Extract Base Features | |
| stft = np.abs(librosa.stft(y, n_fft=2048, hop_length=512)) | |
| chroma_stft = librosa.feature.chroma_stft(S=stft, sr=sr) | |
| chroma_cqt = librosa.feature.chroma_cqt(y=y, sr=sr) | |
| chroma_cens = librosa.feature.chroma_cens(y=y, sr=sr) | |
| mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=20) | |
| rmse = librosa.feature.rms(S=stft) | |
| spec_cent = librosa.feature.spectral_centroid(S=stft, sr=sr) | |
| spec_bw = librosa.feature.spectral_bandwidth(S=stft, sr=sr) | |
| spec_contrast = librosa.feature.spectral_contrast(S=stft, sr=sr) | |
| spec_rolloff = librosa.feature.spectral_rolloff(S=stft, sr=sr) | |
| tonnetz = librosa.feature.tonnetz(y=librosa.effects.harmonic(y), sr=sr) | |
| zcr = librosa.feature.zero_crossing_rate(y) | |
| # 3. Compute Statistics (Strict Order Required) | |
| features = [] | |
| feature_list = [chroma_cens, chroma_cqt, chroma_stft, mfcc, rmse, | |
| spec_bw, spec_cent, spec_contrast, spec_rolloff, tonnetz, zcr] | |
| for feature in feature_list: | |
| features.append(compute_statistics(feature)) | |
| final_feature_vector = np.concatenate(features) | |
| # 4. Extract clean frontend metrics for the p5.js Mandala | |
| # 4. Extract clean frontend metrics for the p5.js Mandala | |
| bpm, _ = librosa.beat.beat_track(y=y, sr=sr) | |
| # SAFELY handle the Librosa version difference (extracts the number if it's trapped in an array) | |
| bpm_value = bpm[0] if isinstance(bpm, np.ndarray) else bpm | |
| frontend_metrics = { | |
| "bpm": round(float(bpm_value), 2), | |
| "energy_rmse": round(float(np.mean(rmse)), 4), | |
| "brightness_centroid": round(float(np.mean(spec_cent)), 2), | |
| "mfcc_vector": [round(float(x), 2) for x in np.mean(mfcc, axis=1)[:3]] | |
| } | |
| return final_feature_vector, frontend_metrics |