Spaces:
Runtime error
Runtime error
Upload __init__.py
Browse files- autochord/__init__.py +142 -0
autochord/__init__.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Main functions"""
|
| 2 |
+
import os
|
| 3 |
+
from shutil import copy
|
| 4 |
+
import pkg_resources
|
| 5 |
+
import tf_keras as k3
|
| 6 |
+
import numpy as np
|
| 7 |
+
from scipy.signal import resample
|
| 8 |
+
import gdown
|
| 9 |
+
import librosa
|
| 10 |
+
import vamp
|
| 11 |
+
import lazycats.np as catnp
|
| 12 |
+
from tensorflow import keras
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
_CHROMA_VAMP_LIB = pkg_resources.resource_filename('autochord', 'res/nnls-chroma.so')
|
| 16 |
+
_CHROMA_VAMP_KEY = 'nnls-chroma:nnls-chroma'
|
| 17 |
+
|
| 18 |
+
_CHORD_MODEL_URL = 'https://drive.google.com/uc?id=1XBn7FyYjF8Ff6EuC7PjwwPzFBLRXGP7n'
|
| 19 |
+
_EXT_RES_DIR = os.path.join(os.path.expanduser('~'), '.autochord')
|
| 20 |
+
_CHORD_MODEL_DIR = "/content/chroma-seq-bilstm-crf-v1"
|
| 21 |
+
_CHORD_MODEL = None
|
| 22 |
+
|
| 23 |
+
_SAMPLE_RATE = 44100 # operating sample rate for all audio
|
| 24 |
+
_SEQ_LEN = 128 # LSTM model sequence length
|
| 25 |
+
_BATCH_SIZE = 128 # arbitrary inference batch size
|
| 26 |
+
_STEP_SIZE = 2048/_SAMPLE_RATE # chroma vectors step size
|
| 27 |
+
|
| 28 |
+
_CHROMA_NOTES = ['C','Db','D','Eb','E','F','Gb','G','Ab','A','Bb','B']
|
| 29 |
+
_NO_CHORD = 'N'
|
| 30 |
+
_MAJMIN_CLASSES = [_NO_CHORD, *[f'{note}:maj' for note in _CHROMA_NOTES],
|
| 31 |
+
*[f'{note}:min' for note in _CHROMA_NOTES]]
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
##############
|
| 35 |
+
# Intializers
|
| 36 |
+
##############
|
| 37 |
+
def _setup_chroma_vamp():
|
| 38 |
+
# pylint: disable=c-extension-no-member
|
| 39 |
+
vamp_paths = vamp.vampyhost.get_plugin_path()
|
| 40 |
+
vamp_lib_fn = os.path.basename(_CHROMA_VAMP_LIB)
|
| 41 |
+
for path in vamp_paths:
|
| 42 |
+
try:
|
| 43 |
+
if not os.path.exists(os.path.join(path, vamp_lib_fn)):
|
| 44 |
+
os.makedirs(path, exist_ok=True)
|
| 45 |
+
copy(_CHROMA_VAMP_LIB, path)
|
| 46 |
+
# try to load to confirm if configured correctly
|
| 47 |
+
vamp.vampyhost.load_plugin(_CHROMA_VAMP_KEY, _SAMPLE_RATE,
|
| 48 |
+
vamp.vampyhost.ADAPT_NONE)
|
| 49 |
+
print(f'autochord: Using NNLS-Chroma VAMP plugin in {path}')
|
| 50 |
+
return
|
| 51 |
+
except Exception as e:
|
| 52 |
+
continue
|
| 53 |
+
|
| 54 |
+
print(f'autochord WARNING: NNLS-Chroma VAMP plugin not setup properly. '
|
| 55 |
+
f'Try copying `{_CHROMA_VAMP_LIB}` in any of following directories: {vamp_paths}')
|
| 56 |
+
|
| 57 |
+
def _download_model():
|
| 58 |
+
os.makedirs(_EXT_RES_DIR, exist_ok=True)
|
| 59 |
+
model_zip = os.path.join(_EXT_RES_DIR, 'model.zip')
|
| 60 |
+
gdown.download(_CHORD_MODEL_URL, model_zip, quiet=False)
|
| 61 |
+
|
| 62 |
+
model_files = gdown.extractall(model_zip)
|
| 63 |
+
model_files.sort()
|
| 64 |
+
os.remove(model_zip)
|
| 65 |
+
print(f'autochord: Chord model downloaded in {model_files[0]}')
|
| 66 |
+
return model_files[0]
|
| 67 |
+
|
| 68 |
+
def _load_model():
|
| 69 |
+
global _CHORD_MODEL_DIR, _CHORD_MODEL
|
| 70 |
+
try:
|
| 71 |
+
if not os.path.exists(_CHORD_MODEL_DIR):
|
| 72 |
+
_CHORD_MODEL_DIR = _download_model()
|
| 73 |
+
|
| 74 |
+
_CHORD_MODEL = k3.models.load_model(_CHORD_MODEL_DIR)
|
| 75 |
+
print(f'autochord: Loaded model from {_CHORD_MODEL_DIR}')
|
| 76 |
+
except Exception as e:
|
| 77 |
+
raise Exception(f'autochord: Error in loading model: {e}')
|
| 78 |
+
|
| 79 |
+
def _init_module():
|
| 80 |
+
print('autochord: Initializing...')
|
| 81 |
+
_setup_chroma_vamp()
|
| 82 |
+
_load_model()
|
| 83 |
+
|
| 84 |
+
_init_module()
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
#################
|
| 88 |
+
# Core Functions
|
| 89 |
+
#################
|
| 90 |
+
def generate_chroma(audio_fn, rollon=1.0):
|
| 91 |
+
""" Generate chroma from raw audio using NNLS-chroma VAMP plugin """
|
| 92 |
+
|
| 93 |
+
samples, fs = librosa.load(audio_fn, sr=None, mono=True)
|
| 94 |
+
if fs != _SAMPLE_RATE:
|
| 95 |
+
samples = resample(samples, num=int(len(samples)*_SAMPLE_RATE/fs))
|
| 96 |
+
|
| 97 |
+
out = vamp.collect(samples, _SAMPLE_RATE, 'nnls-chroma:nnls-chroma',
|
| 98 |
+
output='bothchroma', parameters={'rollon': rollon})
|
| 99 |
+
|
| 100 |
+
chroma = out['matrix'][1]
|
| 101 |
+
return chroma
|
| 102 |
+
|
| 103 |
+
def predict_chord_labels(chroma_vectors):
|
| 104 |
+
""" Predict (numeric) chord labels from sequence of chroma vectors """
|
| 105 |
+
|
| 106 |
+
chordseq_vectors = catnp.divide_to_subsequences(chroma_vectors, sub_len=_SEQ_LEN)
|
| 107 |
+
pred_labels, _, _, _ = _CHORD_MODEL.predict(chordseq_vectors, batch_size=_BATCH_SIZE)
|
| 108 |
+
pred_labels = pred_labels.flatten()
|
| 109 |
+
if len(chroma_vectors) < len(pred_labels): # remove pad
|
| 110 |
+
pad_st = len(pred_labels)-_SEQ_LEN
|
| 111 |
+
pad_ed = pad_st+len(pred_labels)-len(chroma_vectors)
|
| 112 |
+
pred_labels = np.append(pred_labels[:pad_st], pred_labels[pad_ed:])
|
| 113 |
+
|
| 114 |
+
assert len(pred_labels)==len(chroma_vectors)
|
| 115 |
+
return pred_labels
|
| 116 |
+
|
| 117 |
+
def recognize(audio_fn, lab_fn=None):
|
| 118 |
+
"""
|
| 119 |
+
Perform chord recognition on provided audio file. Optionally,
|
| 120 |
+
you may dump the labels on a LAB file (MIREX format) through `lab_fn`.
|
| 121 |
+
"""
|
| 122 |
+
|
| 123 |
+
chroma_vectors = generate_chroma(audio_fn)
|
| 124 |
+
pred_labels = predict_chord_labels(chroma_vectors)
|
| 125 |
+
|
| 126 |
+
chord_labels = catnp.squash_consecutive_duplicates(pred_labels)
|
| 127 |
+
chord_lengths = [0] + list(catnp.contiguous_lengths(pred_labels))
|
| 128 |
+
chord_timestamps = np.cumsum(chord_lengths)
|
| 129 |
+
|
| 130 |
+
chord_labels = [_MAJMIN_CLASSES[label] for label in chord_labels]
|
| 131 |
+
out_labels = [(_STEP_SIZE*st, _STEP_SIZE*ed, chord_name)
|
| 132 |
+
for st, ed, chord_name in
|
| 133 |
+
zip(chord_timestamps[:-1], chord_timestamps[1:], chord_labels)]
|
| 134 |
+
|
| 135 |
+
if lab_fn: # dump labels to file
|
| 136 |
+
str_labels = [f'{st}\t{ed}\t{chord_name}'
|
| 137 |
+
for st, ed, chord_name in out_labels]
|
| 138 |
+
with open(lab_fn, 'w') as f:
|
| 139 |
+
for line in str_labels:
|
| 140 |
+
f.write("%s\n" % line)
|
| 141 |
+
|
| 142 |
+
return out_labels
|