c@116
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
c@116
|
2 /*
|
c@116
|
3 Constant-Q library
|
c@116
|
4 Copyright (c) 2013-2014 Queen Mary, University of London
|
c@116
|
5
|
c@116
|
6 Permission is hereby granted, free of charge, to any person
|
c@116
|
7 obtaining a copy of this software and associated documentation
|
c@116
|
8 files (the "Software"), to deal in the Software without
|
c@116
|
9 restriction, including without limitation the rights to use, copy,
|
c@116
|
10 modify, merge, publish, distribute, sublicense, and/or sell copies
|
c@116
|
11 of the Software, and to permit persons to whom the Software is
|
c@116
|
12 furnished to do so, subject to the following conditions:
|
c@116
|
13
|
c@116
|
14 The above copyright notice and this permission notice shall be
|
c@116
|
15 included in all copies or substantial portions of the Software.
|
c@116
|
16
|
c@116
|
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
c@116
|
18 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
c@116
|
19 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
c@116
|
20 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
c@116
|
21 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
c@116
|
22 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
c@116
|
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
c@116
|
24
|
c@116
|
25 Except as contained in this notice, the names of the Centre for
|
c@116
|
26 Digital Music; Queen Mary, University of London; and Chris Cannam
|
c@116
|
27 shall not be used in advertising or otherwise to promote the sale,
|
c@116
|
28 use or other dealings in this Software without prior written
|
c@116
|
29 authorization.
|
c@116
|
30 */
|
c@116
|
31
|
c@116
|
32 #include "CQKernel.h"
|
c@116
|
33
|
c@121
|
34 #include "dsp/MathUtilities.h"
|
c@121
|
35 #include "dsp/FFT.h"
|
c@121
|
36 #include "dsp/Window.h"
|
c@116
|
37
|
c@116
|
38 #include <cmath>
|
c@116
|
39 #include <cassert>
|
c@116
|
40 #include <vector>
|
c@116
|
41 #include <iostream>
|
c@116
|
42 #include <algorithm>
|
c@116
|
43
|
c@164
|
44 #include <cmath>
|
c@164
|
45
|
c@116
|
46 using std::vector;
|
c@116
|
47 using std::complex;
|
c@116
|
48 using std::cerr;
|
c@116
|
49 using std::endl;
|
c@116
|
50
|
c@116
|
51 typedef std::complex<double> C;
|
c@116
|
52
|
c@138
|
53 //#define DEBUG_CQ_KERNEL 1
|
c@138
|
54
|
c@127
|
55 CQKernel::CQKernel(CQParameters params) :
|
c@127
|
56 m_inparams(params),
|
c@147
|
57 m_valid(false),
|
c@116
|
58 m_fft(0)
|
c@116
|
59 {
|
c@127
|
60 m_p.sampleRate = params.sampleRate;
|
c@127
|
61 m_p.maxFrequency = params.maxFrequency;
|
c@127
|
62 m_p.binsPerOctave = params.binsPerOctave;
|
c@147
|
63 m_valid = generateKernel();
|
c@116
|
64 }
|
c@116
|
65
|
c@116
|
66 CQKernel::~CQKernel()
|
c@116
|
67 {
|
c@116
|
68 delete m_fft;
|
c@116
|
69 }
|
c@116
|
70
|
c@127
|
71 vector<double>
|
c@127
|
72 CQKernel::makeWindow(int len) const
|
c@127
|
73 {
|
c@127
|
74 // The MATLAB version uses a symmetric window, but our windows
|
c@127
|
75 // are periodic. A symmetric window of size N is a periodic
|
c@127
|
76 // one of size N-1 with the first element stuck on the end.
|
c@127
|
77
|
c@127
|
78 WindowType wt(BlackmanHarrisWindow);
|
c@127
|
79
|
c@127
|
80 switch (m_inparams.window) {
|
c@127
|
81 case CQParameters::SqrtBlackmanHarris:
|
c@127
|
82 case CQParameters::BlackmanHarris:
|
c@127
|
83 wt = BlackmanHarrisWindow;
|
c@127
|
84 break;
|
c@127
|
85 case CQParameters::SqrtBlackman:
|
c@127
|
86 case CQParameters::Blackman:
|
c@127
|
87 wt = BlackmanWindow;
|
c@127
|
88 break;
|
c@127
|
89 case CQParameters::SqrtHann:
|
c@127
|
90 case CQParameters::Hann:
|
c@127
|
91 wt = HanningWindow;
|
c@127
|
92 break;
|
c@127
|
93 }
|
c@127
|
94
|
c@127
|
95 Window<double> w(wt, len-1);
|
c@127
|
96 vector<double> win = w.getWindowData();
|
c@127
|
97 win.push_back(win[0]);
|
c@127
|
98
|
c@127
|
99 switch (m_inparams.window) {
|
c@127
|
100 case CQParameters::SqrtBlackmanHarris:
|
c@127
|
101 case CQParameters::SqrtBlackman:
|
c@127
|
102 case CQParameters::SqrtHann:
|
c@127
|
103 for (int i = 0; i < (int)win.size(); ++i) {
|
c@127
|
104 win[i] = sqrt(win[i]) / len;
|
c@127
|
105 }
|
c@127
|
106 break;
|
c@127
|
107 case CQParameters::BlackmanHarris:
|
c@127
|
108 case CQParameters::Blackman:
|
c@127
|
109 case CQParameters::Hann:
|
c@127
|
110 for (int i = 0; i < (int)win.size(); ++i) {
|
c@127
|
111 win[i] = win[i] / len;
|
c@127
|
112 }
|
c@127
|
113 break;
|
c@127
|
114 }
|
c@127
|
115
|
c@127
|
116 return win;
|
c@127
|
117 }
|
c@127
|
118
|
c@147
|
119 bool
|
c@116
|
120 CQKernel::generateKernel()
|
c@116
|
121 {
|
c@127
|
122 double q = m_inparams.q;
|
c@127
|
123 double atomHopFactor = m_inparams.atomHopFactor;
|
c@127
|
124 double thresh = m_inparams.threshold;
|
c@116
|
125
|
c@116
|
126 double bpo = m_p.binsPerOctave;
|
c@116
|
127
|
c@116
|
128 m_p.minFrequency = (m_p.maxFrequency / 2) * pow(2, 1.0/bpo);
|
c@116
|
129 m_p.Q = q / (pow(2, 1.0/bpo) - 1.0);
|
c@116
|
130
|
c@164
|
131 double maxNK = int(m_p.Q * m_p.sampleRate / m_p.minFrequency + 0.5);
|
c@164
|
132 double minNK = int
|
c@116
|
133 (m_p.Q * m_p.sampleRate /
|
c@164
|
134 (m_p.minFrequency * pow(2, (bpo - 1.0) / bpo)) + 0.5);
|
c@116
|
135
|
c@116
|
136 if (minNK == 0 || maxNK == 0) {
|
c@116
|
137 // most likely pathological parameters of some sort
|
c@116
|
138 cerr << "WARNING: CQKernel::generateKernel: minNK or maxNK is zero (minNK == " << minNK << ", maxNK == " << maxNK << "), not generating a kernel" << endl;
|
c@116
|
139 m_p.atomSpacing = 0;
|
c@116
|
140 m_p.firstCentre = 0;
|
c@116
|
141 m_p.fftSize = 0;
|
c@116
|
142 m_p.atomsPerFrame = 0;
|
c@116
|
143 m_p.lastCentre = 0;
|
c@116
|
144 m_p.fftHop = 0;
|
c@147
|
145 return false;
|
c@116
|
146 }
|
c@116
|
147
|
c@164
|
148 m_p.atomSpacing = int(minNK * atomHopFactor + 0.5);
|
c@116
|
149 m_p.firstCentre = m_p.atomSpacing * ceil(ceil(maxNK / 2.0) / m_p.atomSpacing);
|
c@116
|
150 m_p.fftSize = MathUtilities::nextPowerOfTwo
|
c@116
|
151 (m_p.firstCentre + ceil(maxNK / 2.0));
|
c@116
|
152
|
c@116
|
153 m_p.atomsPerFrame = floor
|
c@116
|
154 (1.0 + (m_p.fftSize - ceil(maxNK / 2.0) - m_p.firstCentre) / m_p.atomSpacing);
|
c@116
|
155
|
c@138
|
156 #ifdef DEBUG_CQ_KERNEL
|
c@127
|
157 cerr << "atomsPerFrame = " << m_p.atomsPerFrame << " (q = " << q << ", Q = " << m_p.Q << ", atomHopFactor = " << atomHopFactor << ", atomSpacing = " << m_p.atomSpacing << ", fftSize = " << m_p.fftSize << ", maxNK = " << maxNK << ", firstCentre = " << m_p.firstCentre << ")" << endl;
|
c@138
|
158 #endif
|
c@116
|
159
|
c@116
|
160 m_p.lastCentre = m_p.firstCentre + (m_p.atomsPerFrame - 1) * m_p.atomSpacing;
|
c@116
|
161
|
c@116
|
162 m_p.fftHop = (m_p.lastCentre + m_p.atomSpacing) - m_p.firstCentre;
|
c@116
|
163
|
c@138
|
164 #ifdef DEBUG_CQ_KERNEL
|
c@116
|
165 cerr << "fftHop = " << m_p.fftHop << endl;
|
c@138
|
166 #endif
|
c@116
|
167
|
c@116
|
168 m_fft = new FFT(m_p.fftSize);
|
c@116
|
169
|
c@116
|
170 for (int k = 1; k <= m_p.binsPerOctave; ++k) {
|
c@116
|
171
|
c@164
|
172 int nk = int(m_p.Q * m_p.sampleRate /
|
c@164
|
173 (m_p.minFrequency * pow(2, ((k-1.0) / bpo))) + 0.5);
|
c@116
|
174
|
c@127
|
175 vector<double> win = makeWindow(nk);
|
c@116
|
176
|
c@116
|
177 double fk = m_p.minFrequency * pow(2, ((k-1.0) / bpo));
|
c@116
|
178
|
c@116
|
179 vector<double> reals, imags;
|
c@116
|
180
|
c@116
|
181 for (int i = 0; i < nk; ++i) {
|
c@116
|
182 double arg = (2.0 * M_PI * fk * i) / m_p.sampleRate;
|
c@116
|
183 reals.push_back(win[i] * cos(arg));
|
c@116
|
184 imags.push_back(win[i] * sin(arg));
|
c@116
|
185 }
|
c@116
|
186
|
c@116
|
187 int atomOffset = m_p.firstCentre - int(ceil(nk/2.0));
|
c@116
|
188
|
c@116
|
189 for (int i = 0; i < m_p.atomsPerFrame; ++i) {
|
c@116
|
190
|
c@116
|
191 int shift = atomOffset + (i * m_p.atomSpacing);
|
c@116
|
192
|
c@116
|
193 vector<double> rin(m_p.fftSize, 0.0);
|
c@116
|
194 vector<double> iin(m_p.fftSize, 0.0);
|
c@116
|
195
|
c@116
|
196 for (int j = 0; j < nk; ++j) {
|
c@116
|
197 rin[j + shift] = reals[j];
|
c@116
|
198 iin[j + shift] = imags[j];
|
c@116
|
199 }
|
c@116
|
200
|
c@116
|
201 vector<double> rout(m_p.fftSize, 0.0);
|
c@116
|
202 vector<double> iout(m_p.fftSize, 0.0);
|
c@116
|
203
|
c@116
|
204 m_fft->process(false,
|
c@116
|
205 rin.data(), iin.data(),
|
c@116
|
206 rout.data(), iout.data());
|
c@116
|
207
|
c@116
|
208 // Keep this dense for the moment (until after
|
c@116
|
209 // normalisation calculations)
|
c@116
|
210
|
c@116
|
211 vector<C> row;
|
c@116
|
212
|
c@116
|
213 for (int j = 0; j < m_p.fftSize; ++j) {
|
c@116
|
214 if (sqrt(rout[j] * rout[j] + iout[j] * iout[j]) < thresh) {
|
c@116
|
215 row.push_back(C(0, 0));
|
c@116
|
216 } else {
|
c@116
|
217 row.push_back(C(rout[j] / m_p.fftSize,
|
c@116
|
218 iout[j] / m_p.fftSize));
|
c@116
|
219 }
|
c@116
|
220 }
|
c@116
|
221
|
c@116
|
222 m_kernel.origin.push_back(0);
|
c@116
|
223 m_kernel.data.push_back(row);
|
c@116
|
224 }
|
c@116
|
225 }
|
c@116
|
226
|
c@116
|
227 assert((int)m_kernel.data.size() == m_p.binsPerOctave * m_p.atomsPerFrame);
|
c@116
|
228
|
c@116
|
229 // print density as diagnostic
|
c@116
|
230
|
c@116
|
231 int nnz = 0;
|
c@116
|
232 for (int i = 0; i < (int)m_kernel.data.size(); ++i) {
|
c@116
|
233 for (int j = 0; j < (int)m_kernel.data[i].size(); ++j) {
|
c@116
|
234 if (m_kernel.data[i][j] != C(0, 0)) {
|
c@116
|
235 ++nnz;
|
c@116
|
236 }
|
c@116
|
237 }
|
c@116
|
238 }
|
c@116
|
239
|
c@138
|
240 #ifdef DEBUG_CQ_KERNEL
|
c@116
|
241 cerr << "size = " << m_kernel.data.size() << "*" << m_kernel.data[0].size() << " (fft size = " << m_p.fftSize << ")" << endl;
|
c@138
|
242 #endif
|
c@116
|
243
|
c@116
|
244 assert((int)m_kernel.data.size() == m_p.binsPerOctave * m_p.atomsPerFrame);
|
c@116
|
245 assert((int)m_kernel.data[0].size() == m_p.fftSize);
|
c@116
|
246
|
c@138
|
247 #ifdef DEBUG_CQ_KERNEL
|
c@116
|
248 cerr << "density = " << double(nnz) / double(m_p.binsPerOctave * m_p.atomsPerFrame * m_p.fftSize) << " (" << nnz << " of " << m_p.binsPerOctave * m_p.atomsPerFrame * m_p.fftSize << ")" << endl;
|
c@138
|
249 #endif
|
c@116
|
250
|
c@116
|
251 finaliseKernel();
|
c@147
|
252 return true;
|
c@116
|
253 }
|
c@116
|
254
|
c@116
|
255 static bool ccomparator(C &c1, C &c2)
|
c@116
|
256 {
|
c@116
|
257 return abs(c1) < abs(c2);
|
c@116
|
258 }
|
c@116
|
259
|
c@116
|
260 static int maxidx(vector<C> &v)
|
c@116
|
261 {
|
c@116
|
262 return std::max_element(v.begin(), v.end(), ccomparator) - v.begin();
|
c@116
|
263 }
|
c@116
|
264
|
c@116
|
265 void
|
c@116
|
266 CQKernel::finaliseKernel()
|
c@116
|
267 {
|
c@116
|
268 // calculate weight for normalisation
|
c@116
|
269
|
c@116
|
270 int wx1 = maxidx(m_kernel.data[0]);
|
c@116
|
271 int wx2 = maxidx(m_kernel.data[m_kernel.data.size()-1]);
|
c@116
|
272
|
c@116
|
273 vector<vector<C> > subset(m_kernel.data.size());
|
c@116
|
274 for (int j = wx1; j <= wx2; ++j) {
|
c@116
|
275 for (int i = 0; i < (int)m_kernel.data.size(); ++i) {
|
c@116
|
276 subset[i].push_back(m_kernel.data[i][j]);
|
c@116
|
277 }
|
c@116
|
278 }
|
c@116
|
279
|
c@116
|
280 int nrows = subset.size();
|
c@116
|
281 int ncols = subset[0].size();
|
c@116
|
282 vector<vector<C> > square(ncols); // conjugate transpose of subset * subset
|
c@116
|
283
|
c@116
|
284 for (int i = 0; i < nrows; ++i) {
|
c@116
|
285 assert((int)subset[i].size() == ncols);
|
c@116
|
286 }
|
c@116
|
287
|
c@116
|
288 for (int j = 0; j < ncols; ++j) {
|
c@116
|
289 for (int i = 0; i < ncols; ++i) {
|
c@116
|
290 C v(0, 0);
|
c@116
|
291 for (int k = 0; k < nrows; ++k) {
|
c@116
|
292 v += subset[k][i] * conj(subset[k][j]);
|
c@116
|
293 }
|
c@116
|
294 square[i].push_back(v);
|
c@116
|
295 }
|
c@116
|
296 }
|
c@116
|
297
|
c@116
|
298 vector<double> wK;
|
c@127
|
299 double q = m_inparams.q;
|
c@164
|
300 for (int i = int(1.0/q + 0.5); i < ncols - int(1.0/q + 0.5) - 2; ++i) {
|
c@116
|
301 wK.push_back(abs(square[i][i]));
|
c@116
|
302 }
|
c@116
|
303
|
c@116
|
304 double weight = double(m_p.fftHop) / m_p.fftSize;
|
c@147
|
305 if (!wK.empty()) {
|
c@147
|
306 weight /= MathUtilities::mean(wK.data(), wK.size());
|
c@147
|
307 }
|
c@116
|
308 weight = sqrt(weight);
|
c@138
|
309
|
c@138
|
310 #ifdef DEBUG_CQ_KERNEL
|
c@147
|
311 cerr << "weight = " << weight << " (from " << wK.size() << " elements in wK, ncols = " << ncols << ", q = " << q << ")" << endl;
|
c@138
|
312 #endif
|
c@116
|
313
|
c@116
|
314 // apply normalisation weight, make sparse, and store conjugate
|
c@116
|
315 // (we use the adjoint or conjugate transpose of the kernel matrix
|
c@116
|
316 // for the forward transform, the plain kernel for the inverse
|
c@116
|
317 // which we expect to be less common)
|
c@116
|
318
|
c@116
|
319 KernelMatrix sk;
|
c@116
|
320
|
c@116
|
321 for (int i = 0; i < (int)m_kernel.data.size(); ++i) {
|
c@116
|
322
|
c@116
|
323 sk.origin.push_back(0);
|
c@116
|
324 sk.data.push_back(vector<C>());
|
c@116
|
325
|
c@116
|
326 int lastNZ = 0;
|
c@116
|
327 for (int j = (int)m_kernel.data[i].size()-1; j >= 0; --j) {
|
c@116
|
328 if (abs(m_kernel.data[i][j]) != 0.0) {
|
c@116
|
329 lastNZ = j;
|
c@116
|
330 break;
|
c@116
|
331 }
|
c@116
|
332 }
|
c@116
|
333
|
c@116
|
334 bool haveNZ = false;
|
c@116
|
335 for (int j = 0; j <= lastNZ; ++j) {
|
c@116
|
336 if (haveNZ || abs(m_kernel.data[i][j]) != 0.0) {
|
c@116
|
337 if (!haveNZ) sk.origin[i] = j;
|
c@116
|
338 haveNZ = true;
|
c@116
|
339 sk.data[i].push_back(conj(m_kernel.data[i][j]) * weight);
|
c@116
|
340 }
|
c@116
|
341 }
|
c@116
|
342 }
|
c@116
|
343
|
c@116
|
344 m_kernel = sk;
|
c@116
|
345 }
|
c@116
|
346
|
c@116
|
347 vector<C>
|
c@116
|
348 CQKernel::processForward(const vector<C> &cv)
|
c@116
|
349 {
|
c@116
|
350 // straightforward matrix multiply (taking into account m_kernel's
|
c@116
|
351 // slightly-sparse representation)
|
c@116
|
352
|
c@116
|
353 if (m_kernel.data.empty()) return vector<C>();
|
c@116
|
354
|
c@116
|
355 int nrows = m_p.binsPerOctave * m_p.atomsPerFrame;
|
c@116
|
356
|
c@116
|
357 vector<C> rv(nrows, C());
|
c@116
|
358
|
c@116
|
359 for (int i = 0; i < nrows; ++i) {
|
c@116
|
360 int len = m_kernel.data[i].size();
|
c@116
|
361 for (int j = 0; j < len; ++j) {
|
c@116
|
362 rv[i] += cv[j + m_kernel.origin[i]] * m_kernel.data[i][j];
|
c@116
|
363 }
|
c@116
|
364 }
|
c@116
|
365
|
c@116
|
366 return rv;
|
c@116
|
367 }
|
c@116
|
368
|
c@116
|
369 vector<C>
|
c@116
|
370 CQKernel::processInverse(const vector<C> &cv)
|
c@116
|
371 {
|
c@116
|
372 // matrix multiply by conjugate transpose of m_kernel. This is
|
c@116
|
373 // actually the original kernel as calculated, we just stored the
|
c@116
|
374 // conjugate-transpose of the kernel because we expect to be doing
|
c@116
|
375 // more forward transforms than inverse ones.
|
c@116
|
376
|
c@116
|
377 if (m_kernel.data.empty()) return vector<C>();
|
c@116
|
378
|
c@116
|
379 int ncols = m_p.binsPerOctave * m_p.atomsPerFrame;
|
c@116
|
380 int nrows = m_p.fftSize;
|
c@116
|
381
|
c@116
|
382 vector<C> rv(nrows, C());
|
c@116
|
383
|
c@116
|
384 for (int j = 0; j < ncols; ++j) {
|
c@116
|
385 int i0 = m_kernel.origin[j];
|
c@116
|
386 int i1 = i0 + m_kernel.data[j].size();
|
c@116
|
387 for (int i = i0; i < i1; ++i) {
|
c@116
|
388 rv[i] += cv[j] * conj(m_kernel.data[j][i - i0]);
|
c@116
|
389 }
|
c@116
|
390 }
|
c@116
|
391
|
c@116
|
392 return rv;
|
c@116
|
393 }
|
c@116
|
394
|
c@116
|
395
|