Jaman commited on
Commit
6b55c2e
·
verified ·
1 Parent(s): f7ae39c

Upload __init__.py

Browse files
Files changed (1) hide show
  1. 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