btc-chord / btc_src /features.py
puar-playground's picture
Upload BTC chord recognition model (weights + trust_remote_code loader)
304e1da verified
Raw
History Blame Contribute Delete
2.46 kB
"""CQT feature extraction + chord-index maps for BTC.
Trimmed from the original utils/mir_eval_modules.py — the `mir_eval` dependency
(only used for evaluation metrics, not inference) is removed.
"""
from __future__ import annotations
import numpy as np
import librosa
# --- chord index -> label maps -------------------------------------------------
idx2chord = ['C', 'C:min', 'C#', 'C#:min', 'D', 'D:min', 'D#', 'D#:min', 'E', 'E:min',
'F', 'F:min', 'F#', 'F#:min', 'G', 'G:min', 'G#', 'G#:min', 'A', 'A:min',
'A#', 'A#:min', 'B', 'B:min', 'N']
root_list = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
quality_list = ['min', 'maj', 'dim', 'aug', 'min6', 'maj6', 'min7', 'minmaj7', 'maj7',
'7', 'dim7', 'hdim7', 'sus2', 'sus4']
def idx2voca_chord():
"""169-index large-vocabulary map (168 root×quality + 'X' + 'N')."""
m = {169: 'N', 168: 'X'}
for i in range(168):
root = root_list[i // 14]
quality = quality_list[i % 14]
m[i] = root if (i % 14) == 1 else root + ':' + quality
return m
def audio_to_features(audio_path_or_array, sr_target=22050, inst_len=10.0,
n_bins=144, bins_per_octave=24, hop_length=2048):
"""Load audio (path or 1-D np array) and compute the log-CQT feature matrix.
Returns (feature [n_bins, T], feature_per_frame_seconds).
Mirrors the original audio_file_to_features windowing exactly.
"""
if isinstance(audio_path_or_array, (str, bytes)) or hasattr(audio_path_or_array, "__fspath__"):
wav, sr = librosa.load(str(audio_path_or_array), sr=sr_target, mono=True)
else:
wav = np.asarray(audio_path_or_array, dtype=np.float32)
if wav.ndim == 2:
wav = wav.mean(axis=1)
sr = sr_target
def _cqt(y):
return librosa.cqt(y, sr=sr, n_bins=n_bins,
bins_per_octave=bins_per_octave, hop_length=hop_length)
win = int(sr_target * inst_len)
feature = None
cur = 0
while len(wav) > cur + win:
tmp = _cqt(wav[cur:cur + win])
feature = tmp if feature is None else np.concatenate((feature, tmp), axis=1)
cur += win
tmp = _cqt(wav[cur:])
feature = tmp if feature is None else np.concatenate((feature, tmp), axis=1)
feature = np.log(np.abs(feature) + 1e-6)
# timestep is fixed at 108 for BTC; feature_per_second = inst_len / timestep
return feature