Spaces:
Sleeping
Sleeping
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
|