annotate src/CQKernel.cpp @ 196:da283326bcd3 tip master

Update plugin versions in RDF
author Chris Cannam <cannam@all-day-breakfast.com>
date Fri, 28 Feb 2020 09:43:02 +0000
parents 1081c73fbbe3
children
rev   line source
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