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