Music Fingerprinting using Locality Sensitive Hashing#
import librosa
import os.path
import numpy as np
from mirdotcom import mirdotcom
mirdotcom.init()
This notebook shows a simple system for performing retrieval of musical tracks using LSH.
Select training data:
training_dir = mirdotcom.AUDIO_DIRECTORY + "drum_samples/train/"
training_files = [os.path.join(training_dir, f) for f in os.listdir(training_dir)]
Define a hash function:
def hash_func(vecs, projections):
bools = np.dot(vecs, projections.T) > 0
return [bool2int(bool_vec) for bool_vec in bools]
def bool2int(x):
y = 0
for i, j in enumerate(x):
if j:
y += 1 << i
return y
bool2int([False, True, False, True])
10
X = np.random.randn(10, 100)
P = np.random.randn(3, 100)
hash_func(X, P)
[0, 6, 1, 5, 0, 1, 4, 6, 4, 5]
Create three LSH structures: Table, LSH, and MusicSearch:#
class Table:
def __init__(self, hash_size, dim):
self.table = dict()
self.hash_size = hash_size
self.projections = np.random.randn(self.hash_size, dim)
def add(self, vecs, label):
entry = {"label": label}
hashes = hash_func(vecs, self.projections)
for h in hashes:
if h in self.table.keys():
self.table[h].append(entry)
else:
self.table[h] = [entry]
def query(self, vecs):
hashes = hash_func(vecs, self.projections)
results = list()
for h in hashes:
if h in self.table.keys():
results.extend(self.table[h])
return results
class LSH:
def __init__(self, dim):
self.num_tables = 4
self.hash_size = 8
self.tables = list()
for i in range(self.num_tables):
self.tables.append(Table(self.hash_size, dim))
def add(self, vecs, label):
for table in self.tables:
table.add(vecs, label)
def query(self, vecs):
results = list()
for table in self.tables:
results.extend(table.query(vecs))
return results
def describe(self):
for table in self.tables:
print(table.table)
class MusicSearch:
def __init__(self, training_files):
self.frame_size = 4096
self.hop_size = 4000
self.fv_size = 12
self.lsh = LSH(self.fv_size)
self.training_files = training_files
self.num_features_in_file = dict()
for f in self.training_files:
self.num_features_in_file[f] = 0
def train(self):
for filepath in self.training_files:
x, fs = librosa.load(filepath)
features = librosa.feature.chroma_stft(
y=x, sr=fs, n_fft=self.frame_size, hop_length=self.hop_size
).T
self.lsh.add(features, filepath)
self.num_features_in_file[filepath] += len(features)
def query(self, filepath):
x, fs = librosa.load(filepath)
features = librosa.feature.chroma_stft(
y=x, sr=fs, n_fft=self.frame_size, hop_length=self.hop_size
).T
results = self.lsh.query(features)
print("num results", len(results))
counts = dict()
for r in results:
if r["label"] in counts.keys():
counts[r["label"]] += 1
else:
counts[r["label"]] = 1
for k in counts:
counts[k] = float(counts[k]) / self.num_features_in_file[k]
return counts
Train:
ms = MusicSearch(training_files)
ms.train()
Test:
test_file = mirdotcom.get_audio("drum_samples/test/kick_00.mp3")
results = ms.query(test_file)
num results 373
Display the results:
for r in sorted(results, key=results.get, reverse=True):
print(r, results[r])
assets/audio/drum_samples/train/kick_09.mp3 6.0
assets/audio/drum_samples/train/snare_06.mp3 6.0
assets/audio/drum_samples/train/kick_02.mp3 4.666666666666667
assets/audio/drum_samples/train/kick_03.mp3 4.375
assets/audio/drum_samples/train/kick_01.mp3 4.0
assets/audio/drum_samples/train/snare_07.mp3 4.0
assets/audio/drum_samples/train/kick_10.mp3 3.7777777777777777
assets/audio/drum_samples/train/snare_03.mp3 3.75
assets/audio/drum_samples/train/kick_06.mp3 3.625
assets/audio/drum_samples/train/snare_01.mp3 3.25
assets/audio/drum_samples/train/snare_10.mp3 2.75
assets/audio/drum_samples/train/kick_08.mp3 2.5
assets/audio/drum_samples/train/kick_05.mp3 2.111111111111111
assets/audio/drum_samples/train/snare_04.mp3 1.8
assets/audio/drum_samples/train/kick_04.mp3 1.75
assets/audio/drum_samples/train/snare_05.mp3 1.75
assets/audio/drum_samples/train/snare_08.mp3 1.75
assets/audio/drum_samples/train/kick_07.mp3 1.6666666666666667
assets/audio/drum_samples/train/snare_02.mp3 1.0
assets/audio/drum_samples/train/snare_09.mp3 0.25