Maria@4: # -*- coding: utf-8 -*- Maria@4: """ Maria@4: Created on Tue Feb 2 22:26:10 2016 Maria@4: Maria@4: @author: mariapanteli Maria@4: """ Maria@36: import numpy as np Maria@36: import os Maria@36: import scipy.signal Maria@36: Maria@4: Maria@4: class PitchBihist: Maria@4: def __init__(self, win2sec=8): Maria@4: self.win2sec = win2sec Maria@37: self.hop2sec = 0.5 Maria@37: self.melody_sr = 44100. / 128. Maria@4: Maria@36: Maria@36: def hz_to_cents(self, freq_Hz, ref_Hz=32.703, n_cents=1200): Maria@36: """ convert frequency values from Hz to cents Maria@36: reference frequency at C1 Maria@36: """ Maria@36: freq_cents = np.round(n_cents * np.log2(freq_Hz/ref_Hz)) Maria@36: return freq_cents Maria@36: Maria@36: Maria@36: def wrap_to_octave(self, cents, octave_length=1200): Maria@36: """ wrap to a single octave 0-1200 Maria@36: """ Maria@36: octave_cents = cents % octave_length Maria@36: return octave_cents Maria@36: Maria@36: Maria@36: def get_melody_from_file(self, melodia_file, stop_sec=None): Maria@36: if not os.path.exists(melodia_file): Maria@36: return [] Maria@36: data = np.loadtxt(melodia_file, delimiter=',') Maria@36: times, freqs = (data[:, 0], data[:, 1]) Maria@36: if stop_sec is not None: Maria@36: stop_idx = np.where(times < stop_sec)[0] Maria@36: times, freqs = times[stop_idx], freqs[stop_idx] Maria@36: freqs[freqs<=0] = np.nan Maria@36: melody = freqs Maria@38: return melody Maria@36: Maria@36: Maria@36: def get_melody_matrix(self, melody): Maria@36: n_bins = 60 Maria@36: n_frames = len(melody) Maria@36: melody_cents = self.hz_to_cents(melody, n_cents=n_bins) Maria@36: melody_octave = self.wrap_to_octave(melody_cents, octave_length=n_bins) Maria@36: melody_matrix = np.zeros((n_bins, n_frames)) Maria@36: for time, pitch in enumerate(melody_octave): Maria@36: if not np.isnan(pitch): Maria@36: melody_matrix[int(pitch), time] = 1 Maria@36: return melody_matrix Maria@36: Maria@36: Maria@37: def bihistogram(self, spec, spec_sr=None, winsec=0.5, align=True): Maria@37: if spec_sr is None: Maria@37: # assume spec is melody_matrix with default sr Maria@37: spec_sr = self.melody_sr Maria@37: win = int(round(winsec * spec_sr)) Maria@36: ker = np.concatenate([np.zeros((win, 1)), np.ones((win+1, 1))], axis=0) Maria@37: spec = spec.T # transpose to have frames as rows in convolution Maria@36: Maria@36: # energy threshold Maria@36: thr = 0.3*np.max(spec) Maria@36: spec[spec < max(thr, 0)] = 0 Maria@36: Maria@36: # transitions via convolution Maria@36: tra = scipy.signal.convolve2d(spec, ker, mode='same') Maria@36: tra[spec > 0] = 0 Maria@36: Maria@36: # multiply with original Maria@36: B = np.dot(tra.T, spec) Maria@36: Maria@36: # normalize to [0, 1] Maria@36: mxB = np.max(B) Maria@36: mnB = np.min(B) Maria@36: if mxB != mnB: Maria@36: B = (B - mnB)/float(mxB-mnB) Maria@36: Maria@36: # circshift to highest? Maria@36: if align: Maria@36: ref = np.argmax(np.sum(spec, axis=0)) Maria@36: B = np.roll(B, -ref, axis=0) Maria@36: B = np.roll(B, -ref, axis=1) Maria@36: return B Maria@36: Maria@36: Maria@39: def second_frame_decomposition(self, norigframes): Maria@39: win2 = int(round(self.win2sec * self.melody_sr)) Maria@39: hop2 = int(round(self.hop2sec * self.melody_sr)) Maria@39: print win2, hop2, norigframes Maria@39: if norigframes<=win2: Maria@39: nframes = 1 Maria@39: win2 = norigframes Maria@39: else: Maria@39: nframes = np.int(1+np.floor((norigframes-win2)/float(hop2))) Maria@39: return nframes, win2, hop2 Maria@39: Maria@39: Maria@38: def bihist_from_melodia(self, filename='sample_melodia.csv', secondframedecomp=True, stop_sec=None): Maria@38: melody = self.get_melody_from_file(filename, stop_sec=stop_sec) Maria@38: if len(melody) == 0: Maria@38: return [] Maria@38: melody_matrix = self.get_melody_matrix(melody) Maria@38: bihist = [] Maria@38: if secondframedecomp: Maria@38: nbins, norigframes = melody_matrix.shape Maria@39: nframes, win2, hop2 = self.second_frame_decomposition(norigframes) Maria@38: bihistframes = np.empty((nbins*nbins, nframes)) Maria@38: for i in range(nframes): # loop over all 8-sec frames Maria@38: frame = melody_matrix[:, (i*hop2):(i*hop2+win2)] Maria@38: bihist = self.bihistogram(frame) Maria@38: bihist = np.reshape(bihist, -1) Maria@38: bihistframes[:, i] = bihist Maria@38: bihist = bihistframes Maria@38: else: Maria@38: bihist = self.bihistogram(melody_matrix) Maria@38: return bihist Maria@38: Maria@38: Maria@4: if __name__ == '__main__': Maria@4: pb = PitchBihist() Maria@38: melody = np.concatenate([660 * np.ones(250), 440 * np.ones(500)]) Maria@38: melody_matrix = pb.get_melody_matrix(melody) Maria@38: pb.bihistogram(melody_matrix, align=True) Maria@38: