Maria@4
|
1 # -*- coding: utf-8 -*-
|
Maria@4
|
2 """
|
Maria@4
|
3 Created on Tue Feb 2 22:26:10 2016
|
Maria@4
|
4
|
Maria@4
|
5 @author: mariapanteli
|
Maria@4
|
6 """
|
Maria@36
|
7 import numpy as np
|
Maria@36
|
8 import os
|
Maria@36
|
9 import scipy.signal
|
Maria@36
|
10
|
Maria@4
|
11
|
Maria@4
|
12 class PitchBihist:
|
Maria@4
|
13 def __init__(self, win2sec=8):
|
Maria@4
|
14 self.win2sec = win2sec
|
Maria@37
|
15 self.hop2sec = 0.5
|
Maria@37
|
16 self.melody_sr = 44100. / 128.
|
Maria@4
|
17
|
Maria@36
|
18
|
Maria@36
|
19 def hz_to_cents(self, freq_Hz, ref_Hz=32.703, n_cents=1200):
|
Maria@36
|
20 """ convert frequency values from Hz to cents
|
Maria@36
|
21 reference frequency at C1
|
Maria@36
|
22 """
|
Maria@36
|
23 freq_cents = np.round(n_cents * np.log2(freq_Hz/ref_Hz))
|
Maria@36
|
24 return freq_cents
|
Maria@36
|
25
|
Maria@36
|
26
|
Maria@36
|
27 def wrap_to_octave(self, cents, octave_length=1200):
|
Maria@36
|
28 """ wrap to a single octave 0-1200
|
Maria@36
|
29 """
|
Maria@36
|
30 octave_cents = cents % octave_length
|
Maria@36
|
31 return octave_cents
|
Maria@36
|
32
|
Maria@36
|
33
|
Maria@36
|
34 def get_melody_from_file(self, melodia_file, stop_sec=None):
|
Maria@36
|
35 if not os.path.exists(melodia_file):
|
Maria@36
|
36 return []
|
Maria@36
|
37 data = np.loadtxt(melodia_file, delimiter=',')
|
Maria@36
|
38 times, freqs = (data[:, 0], data[:, 1])
|
Maria@36
|
39 if stop_sec is not None:
|
Maria@36
|
40 stop_idx = np.where(times < stop_sec)[0]
|
Maria@36
|
41 times, freqs = times[stop_idx], freqs[stop_idx]
|
Maria@36
|
42 freqs[freqs<=0] = np.nan
|
Maria@36
|
43 melody = freqs
|
Maria@38
|
44 return melody
|
Maria@36
|
45
|
Maria@36
|
46
|
Maria@36
|
47 def get_melody_matrix(self, melody):
|
Maria@36
|
48 n_bins = 60
|
Maria@36
|
49 n_frames = len(melody)
|
Maria@36
|
50 melody_cents = self.hz_to_cents(melody, n_cents=n_bins)
|
Maria@36
|
51 melody_octave = self.wrap_to_octave(melody_cents, octave_length=n_bins)
|
Maria@36
|
52 melody_matrix = np.zeros((n_bins, n_frames))
|
Maria@36
|
53 for time, pitch in enumerate(melody_octave):
|
Maria@36
|
54 if not np.isnan(pitch):
|
Maria@36
|
55 melody_matrix[int(pitch), time] = 1
|
Maria@36
|
56 return melody_matrix
|
Maria@36
|
57
|
Maria@36
|
58
|
Maria@37
|
59 def bihistogram(self, spec, spec_sr=None, winsec=0.5, align=True):
|
Maria@37
|
60 if spec_sr is None:
|
Maria@37
|
61 # assume spec is melody_matrix with default sr
|
Maria@37
|
62 spec_sr = self.melody_sr
|
Maria@37
|
63 win = int(round(winsec * spec_sr))
|
Maria@36
|
64 ker = np.concatenate([np.zeros((win, 1)), np.ones((win+1, 1))], axis=0)
|
Maria@37
|
65 spec = spec.T # transpose to have frames as rows in convolution
|
Maria@36
|
66
|
Maria@36
|
67 # energy threshold
|
Maria@36
|
68 thr = 0.3*np.max(spec)
|
Maria@36
|
69 spec[spec < max(thr, 0)] = 0
|
Maria@36
|
70
|
Maria@36
|
71 # transitions via convolution
|
Maria@36
|
72 tra = scipy.signal.convolve2d(spec, ker, mode='same')
|
Maria@36
|
73 tra[spec > 0] = 0
|
Maria@36
|
74
|
Maria@36
|
75 # multiply with original
|
Maria@36
|
76 B = np.dot(tra.T, spec)
|
Maria@36
|
77
|
Maria@36
|
78 # normalize to [0, 1]
|
Maria@36
|
79 mxB = np.max(B)
|
Maria@36
|
80 mnB = np.min(B)
|
Maria@36
|
81 if mxB != mnB:
|
Maria@36
|
82 B = (B - mnB)/float(mxB-mnB)
|
Maria@36
|
83
|
Maria@36
|
84 # circshift to highest?
|
Maria@36
|
85 if align:
|
Maria@36
|
86 ref = np.argmax(np.sum(spec, axis=0))
|
Maria@36
|
87 B = np.roll(B, -ref, axis=0)
|
Maria@36
|
88 B = np.roll(B, -ref, axis=1)
|
Maria@36
|
89 return B
|
Maria@36
|
90
|
Maria@36
|
91
|
Maria@39
|
92 def second_frame_decomposition(self, norigframes):
|
Maria@39
|
93 win2 = int(round(self.win2sec * self.melody_sr))
|
Maria@39
|
94 hop2 = int(round(self.hop2sec * self.melody_sr))
|
Maria@39
|
95 print win2, hop2, norigframes
|
Maria@39
|
96 if norigframes<=win2:
|
Maria@39
|
97 nframes = 1
|
Maria@39
|
98 win2 = norigframes
|
Maria@39
|
99 else:
|
Maria@39
|
100 nframes = np.int(1+np.floor((norigframes-win2)/float(hop2)))
|
Maria@39
|
101 return nframes, win2, hop2
|
Maria@39
|
102
|
Maria@39
|
103
|
Maria@38
|
104 def bihist_from_melodia(self, filename='sample_melodia.csv', secondframedecomp=True, stop_sec=None):
|
Maria@38
|
105 melody = self.get_melody_from_file(filename, stop_sec=stop_sec)
|
Maria@38
|
106 if len(melody) == 0:
|
Maria@38
|
107 return []
|
Maria@38
|
108 melody_matrix = self.get_melody_matrix(melody)
|
Maria@38
|
109 bihist = []
|
Maria@38
|
110 if secondframedecomp:
|
Maria@38
|
111 nbins, norigframes = melody_matrix.shape
|
Maria@39
|
112 nframes, win2, hop2 = self.second_frame_decomposition(norigframes)
|
Maria@38
|
113 bihistframes = np.empty((nbins*nbins, nframes))
|
Maria@38
|
114 for i in range(nframes): # loop over all 8-sec frames
|
Maria@38
|
115 frame = melody_matrix[:, (i*hop2):(i*hop2+win2)]
|
Maria@38
|
116 bihist = self.bihistogram(frame)
|
Maria@38
|
117 bihist = np.reshape(bihist, -1)
|
Maria@38
|
118 bihistframes[:, i] = bihist
|
Maria@38
|
119 bihist = bihistframes
|
Maria@38
|
120 else:
|
Maria@38
|
121 bihist = self.bihistogram(melody_matrix)
|
Maria@38
|
122 return bihist
|
Maria@38
|
123
|
Maria@38
|
124
|
Maria@4
|
125 if __name__ == '__main__':
|
Maria@4
|
126 pb = PitchBihist()
|
Maria@38
|
127 melody = np.concatenate([660 * np.ones(250), 440 * np.ones(500)])
|
Maria@38
|
128 melody_matrix = pb.get_melody_matrix(melody)
|
Maria@38
|
129 pb.bihistogram(melody_matrix, align=True)
|
Maria@38
|
130 |