Music information retrieval with librosa: tempo, chroma, MFCCs, spectral features, onset detection, harmonic-percussive separation, pitch, and k-NN similarity search.
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Music information retrieval with librosa: tempo, chroma, MFCCs, spectral features, onset detection, harmonic-percussive separation, pitch, and k-NN similarity search.
This skill covers end-to-end music information retrieval (MIR) using the librosa
library. You will learn how to load audio, extract a rich set of acoustic features,
detect musical events, and build a similarity-search index over a corpus of tracks.
librosa.load decodes any format supported by soundfile / audioread and resamples
to the target sample rate.
import librosa
import numpy as np
defload_audio(path: str, sr: int = 22050, mono: bool = True):
"""
Load an audio file and return the signal and sample rate.
Parameters
----------
path : str
Path to the audio file (WAV, FLAC, MP3, OGG, …).
sr : int
Target sample rate in Hz. Set to None to keep native rate.
mono : bool
Mix down to mono when True.
Returns
-------
y : np.ndarray shape (n_samples,)
sr : int
"""
y, sr_out = librosa.load(path, sr=sr, mono=mono)
duration = len(y) / sr_out
print(f"Loaded '{path}': {duration:.2f}s @ {sr_out} Hz")
return y, sr_out
2. Core Feature Extraction
2.1 Tempo and Beat Tracking
defextract_tempo_beats(y: np.ndarray, sr: int):
"""
Estimate global tempo and beat frame positions.
Returns
-------
tempo : float — estimated BPM
beat_times : np.ndarray — beat positions in seconds
"""
tempo, beat_frames = librosa.beat.beat_track(y=y, sr=sr)
beat_times = librosa.frames_to_time(beat_frames, sr=sr)
(tempo), beat_times
return
float
2.2 Chroma Features
Chroma (pitch-class profile) captures harmonic content independent of timbre.
defextract_mfcc(
y: np.ndarray,
sr: int,
n_mfcc: int = 13,
hop_length: int = 512,
) -> dict:
"""
Compute MFCCs, delta, and delta-delta coefficients.
Returns a dict with keys 'mfcc', 'delta', 'delta2', each shape (n_mfcc, T).
"""
mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=n_mfcc, hop_length=hop_length)
delta = librosa.feature.delta(mfcc)
delta2 = librosa.feature.delta(mfcc, order=2)
return {"mfcc": mfcc, "delta": delta, "delta2": delta2}
2.4 Spectral Features
defextract_spectral_features(y: np.ndarray, sr: int, hop_length: int = 512) -> dict:
"""
Compute spectral centroid, bandwidth, rolloff, and zero-crossing rate.
All features returned as 1-D arrays (mean over time) plus raw frames.
"""
centroid = librosa.feature.spectral_centroid(y=y, sr=sr, hop_length=hop_length)
bandwidth = librosa.feature.spectral_bandwidth(y=y, sr=sr, hop_length=hop_length)
rolloff = librosa.feature.spectral_rolloff(y=y, sr=sr, hop_length=hop_length)
zcr = librosa.feature.zero_crossing_rate(y, hop_length=hop_length)
return {
"centroid_mean": float(np.mean(centroid)),
"centroid_std": float(np.std(centroid)),
"bandwidth_mean": float(np.mean(bandwidth)),
"bandwidth_std": float(np.std(bandwidth)),
"rolloff_mean": float(np.mean(rolloff)),
"rolloff_std": float(np.std(rolloff)),
"zcr_mean": float(np.mean(zcr)),
"zcr_std": float(np.std(zcr)),
# raw frames kept for downstream use"_centroid": centroid,
"_bandwidth": bandwidth,
"_rolloff": rolloff,
"_zcr": zcr,
}
3. Advanced Features
3.1 Onset Detection
defdetect_onsets(y: np.ndarray, sr: int) -> np.ndarray:
"""
Detect note onsets and return their times in seconds.
"""
onset_frames = librosa.onset.onset_detect(y=y, sr=sr, units="frames")
onset_times = librosa.frames_to_time(onset_frames, sr=sr)
return onset_times
3.2 Harmonic-Percussive Separation
defseparate_harmonic_percussive(y: np.ndarray):
"""
Decompose a signal into harmonic and percussive components using median filtering.
Returns
-------
y_harmonic : np.ndarray
y_percussive : np.ndarray
"""
y_harmonic, y_percussive = librosa.effects.hpss(y)
return y_harmonic, y_percussive
3.3 Pitch Detection with YIN
defestimate_pitch(y: np.ndarray, sr: int, fmin: float = 50.0, fmax: float = 2000.0):
"""
Frame-wise fundamental frequency estimation using the YIN algorithm.
Returns
-------
f0 : np.ndarray — F0 in Hz per frame (NaN where unvoiced)
"""
f0 = librosa.yin(y, fmin=fmin, fmax=fmax, sr=sr)
return f0
3.4 Mel Spectrogram
defcompute_mel_spectrogram(
y: np.ndarray,
sr: int,
n_mels: int = 128,
hop_length: int = 512,
) -> np.ndarray:
"""
Compute a log-power mel spectrogram.
Returns
-------
mel_db : np.ndarray shape (n_mels, T) — values in dB
"""
S = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=n_mels, hop_length=hop_length)
mel_db = librosa.power_to_db(S, ref=np.max)
return mel_db
4. Compound Functions
4.1 Full Feature Extraction Pipeline
import pandas as pd
import pathlib
defextract_audio_features(path: str, sr: int = 22050) -> dict:
"""
Extract a flat feature vector from an audio file.
Covers: tempo, chroma (mean per pitch class), MFCCs + deltas (mean + std),
spectral centroid/bandwidth/rolloff/zcr, onset rate, and pitch statistics.
Returns
-------
dict with scalar feature values and 'path' key.
"""
y, sr = load_audio(path, sr=sr)
features = {"path": path}
# Tempo
tempo, beat_times = extract_tempo_beats(y, sr)
features["tempo_bpm"] = tempo
features["n_beats"] = len(beat_times)
# Chroma — 12 pitch classes
chroma = extract_chroma(y, sr)
for i, note inenumerate(["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"]):
features[f"chroma_{note}"] = float(np.mean(chroma[i]))
# MFCCs
mfcc_dict = extract_mfcc(y, sr, n_mfcc=13)
for k in ["mfcc", "delta", "delta2"]:
arr = mfcc_dict[k]
for i inrange(arr.shape[0]):
features[f"{k}_{i+1}_mean"] = float(np.mean(arr[i]))
features[f"{k}_{i+1}_std"] = float(np.std(arr[i]))
# Spectral
spec = extract_spectral_features(y, sr)
for k, v in spec.items():
ifnot k.startswith("_"):
features[k] = v
# Onsets
onsets = detect_onsets(y, sr)
duration = len(y) / sr
features["onset_rate_hz"] = len(onsets) / duration if duration > 0else0.0# Pitch (voiced frames only)
f0 = estimate_pitch(y, sr)
voiced = f0[~np.isnan(f0) & (f0 > 0)]
features["pitch_mean_hz"] = float(np.mean(voiced)) iflen(voiced) > 0else0.0
features["pitch_std_hz"] = float(np.std(voiced)) iflen(voiced) > 0else0.0return features
defbatch_extract_features(corpus_dir: str, sr: int = 22050, output_path: str = None) -> pd.DataFrame:
"""
Extract features for every audio file in corpus_dir and return a DataFrame.
Supports WAV, FLAC, MP3, OGG. Saves to Parquet if output_path is given.
"""
audio_extensions = {".wav", ".flac", ".mp3", ".ogg", ".aif", ".aiff"}
paths = [
p for p in pathlib.Path(corpus_dir).rglob("*")
if p.suffix.lower() in audio_extensions
]
print(f"Found {len(paths)} audio files in '{corpus_dir}'")
records = []
for p in paths:
try:
rec = extract_audio_features(str(p), sr=sr)
records.append(rec)
except Exception as exc:
print(f" WARN: skipping {p.name} — {exc}")
df = pd.DataFrame(records)
if output_path:
if output_path.endswith(".parquet"):
df.to_parquet(output_path, index=False)
else:
df.to_csv(output_path, index=False)
print(f"Saved features to '{output_path}'")
return df
4.2 Similarity Matrix and k-NN Search
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import NearestNeighbors
FEATURE_COLS_EXCLUDE = {"path", "tempo_bpm", "n_beats"} # keep for referencedef_feature_matrix(df: pd.DataFrame) -> np.ndarray:
"""Return numeric feature matrix with NaN filled to column mean."""
numeric_cols = [c for c in df.select_dtypes("number").columns
if c notin FEATURE_COLS_EXCLUDE]
X = df[numeric_cols].fillna(df[numeric_cols].mean()).values
return X
defcompute_similarity_matrix(df: pd.DataFrame) -> np.ndarray:
"""
Compute a pairwise cosine similarity matrix over the audio feature space.
Parameters
----------
df : pd.DataFrame — output of batch_extract_features
Returns
-------
sim_matrix : np.ndarray shape (n_tracks, n_tracks)
"""from sklearn.metrics.pairwise import cosine_similarity
X = _feature_matrix(df)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
sim_matrix = cosine_similarity(X_scaled)
return sim_matrix
deffind_similar_tracks(
query_path: str,
corpus_dir: str,
k: int = 5,
sr: int = 22050,
) -> pd.DataFrame:
"""
Find the k most similar tracks in corpus_dir to a query audio file.
Steps
-----
1. Extract features for all tracks in corpus_dir.
2. Extract features for the query file.
3. Scale features and run k-NN search.
Returns
-------
pd.DataFrame with columns ['path', 'distance', 'rank']
"""
df_corpus = batch_extract_features(corpus_dir, sr=sr)
query_feats = extract_audio_features(query_path, sr=sr)
df_query = pd.DataFrame([query_feats])
X_corpus = _feature_matrix(df_corpus)
X_query = _feature_matrix(df_query)
scaler = StandardScaler().fit(X_corpus)
X_corpus_s = scaler.transform(X_corpus)
X_query_s = scaler.transform(X_query)
nn = NearestNeighbors(n_neighbors=min(k, len(df_corpus)), metric="euclidean")
nn.fit(X_corpus_s)
distances, indices = nn.kneighbors(X_query_s)
results = df_corpus.iloc[indices[0]].copy()
results["distance"] = distances[0]
results["rank"] = range(1, len(results) + 1)
return results[["path", "distance", "rank"]]
5. Examples
Example A — Batch Feature Extraction and k-NN Music Similarity Search
import os
# --- Configuration ---
CORPUS_DIR = "/data/music_corpus"# directory with audio files
QUERY_FILE = "/data/query/my_song.wav"# song to match against corpus
FEATURES_CACHE = "/tmp/corpus_features.parquet"
K_NEIGHBORS = 5# Step 1: Extract and cache features (skip if cache exists)if os.path.exists(FEATURES_CACHE):
import pandas as pd
df_corpus = pd.read_parquet(FEATURES_CACHE)
print(f"Loaded cached features: {len(df_corpus)} tracks")
else:
df_corpus = batch_extract_features(
corpus_dir=CORPUS_DIR,
output_path=FEATURES_CACHE,
)
# Step 2: Compute full similarity matrix (optional — useful for playlist generation)
sim_matrix = compute_similarity_matrix(df_corpus)
print(f"Similarity matrix shape: {sim_matrix.shape}")
# Step 3: Find similar tracks
results = find_similar_tracks(
query_path=QUERY_FILE,
corpus_dir=CORPUS_DIR,
k=K_NEIGHBORS,
)
print("\nTop similar tracks:")
print(results.to_string(index=False))
Example B — Tempo and Key Estimation for a Music Collection
import pathlib
import pandas as pd
import numpy as np
import librosa
defestimate_key(chroma_mean: np.ndarray) -> str:
"""
Estimate musical key using the Krumhansl-Schmuckler key-finding algorithm
applied to a 12-element chroma mean vector.
"""# Major and minor key profiles (Krumhansl 1990)
major_profile = np.array([6.35, 2.23, 3.48, 2.33, 4.38, 4.09,
2.52, 5.19, 2.39, 3.66, 2.29, 2.88])
minor_profile = np.array([6.33, 2.68, 3.52, 5.38, 2.60, 3.53,
2.54, 4.75, 3.98, 2.69, 3.34, 3.17])
note_names = ["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"]
best_corr, best_key = -np.inf, "C major"for shift inrange(12):
rotated = np.roll(chroma_mean, -shift)
for profile, mode in [(major_profile, "major"), (minor_profile, "minor")]:
corr = np.corrcoef(rotated, profile)[0, 1]
if corr > best_corr:
best_corr = corr
best_key = f"{note_names[shift]}{mode}"return best_key
defanalyze_collection(music_dir: str, sr: int = 22050) -> pd.DataFrame:
"""
Estimate tempo and key for every track in music_dir.
Returns a DataFrame with columns: path, tempo_bpm, key, duration_s.
"""
audio_ext = {".wav", ".flac", ".mp3", ".ogg"}
paths = [p for p in pathlib.Path(music_dir).rglob("*") if p.suffix.lower() in audio_ext]
records = []
for p in paths:
try:
y, sr_out = librosa.load(str(p), sr=sr, mono=True)
duration = len(y) / sr_out
tempo, _ = librosa.beat.beat_track(y=y, sr=sr_out)
chroma = librosa.feature.chroma_cqt(y=y, sr=sr_out)
key = estimate_key(np.mean(chroma, axis=1))
records.append({
"path": str(p),
"filename": p.name,
"tempo_bpm": round(float(tempo), 1),
"key": key,
"duration_s": round(duration, 2),
})
print(f" {p.name}: {tempo:.1f} BPM, {key}")
except Exception as exc:
print(f" WARN {p.name}: {exc}")
df = pd.DataFrame(records)
# Summary statisticsifnot df.empty:
print(f"\nCollection summary ({len(df)} tracks):")
print(f" Avg tempo : {df['tempo_bpm'].mean():.1f} BPM")
print(f" Most common key: {df['key'].value_counts().index[0]}")
print(f" Total duration : {df['duration_s'].sum()/60:.1f} minutes")
return df
if __name__ == "__main__":
MUSIC_DIR = "/data/my_collection"
df_collection = analyze_collection(MUSIC_DIR)
df_collection.to_csv("/tmp/collection_analysis.csv", index=False)
print("Saved to /tmp/collection_analysis.csv")
6. Tips and Gotchas
Sample rate: Always specify sr explicitly. Default sr=22050 is fine for most
MIR tasks; use sr=44100 when you need full bandwidth (e.g., audio quality assessment).
MP3 support: Requires audioread (bundled) or ffmpeg on PATH. FLAC/WAV work
out-of-the-box via soundfile.
Memory: For long files, use librosa.load(..., offset=start, duration=length) to
process in chunks.
Normalization: Always StandardScaler before k-NN — raw MFCC values dwarf chroma
in magnitude.
Beat tracking: Works best for music with a clear rhythmic pulse. Noisy or ambient
recordings will yield unreliable BPM estimates.
YIN pitch detection: fmin and fmax must bracket the expected fundamental.
For singing voice use fmin=80, fmax=600.
HPSS margin: Increase margin parameter in librosa.effects.hpss for cleaner
separation at the cost of computation: librosa.effects.hpss(y, margin=3.0).
7. References
McFee et al. (2015). librosa: Audio and Music Signal Analysis in Python.
SciPy Proceedings. https://librosa.org
Krumhansl, C. L. (1990). Cognitive Foundations of Musical Pitch. Oxford UP.
De Cheveigné & Kawahara (2002). YIN, a fundamental frequency estimator. JASA.