comparison constant-q-cpp/src/CQKernel.cpp @ 366:5d0a2ebb4d17

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