annotate plugins/AdaptiveSpectrogram.cpp @ 137:ed55dc6aabf5

* README and version updates, minor Linux build update
author Chris Cannam <c.cannam@qmul.ac.uk>
date Tue, 05 Apr 2011 11:56:34 +0100
parents dcf5800f0f00
children 93355d263f8e
rev   line source
c@92 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
c@92 2
c@92 3 /*
c@92 4 QM Vamp Plugin Set
c@92 5
c@92 6 Centre for Digital Music, Queen Mary, University of London.
c@135 7
c@135 8 This program is free software; you can redistribute it and/or
c@135 9 modify it under the terms of the GNU General Public License as
c@135 10 published by the Free Software Foundation; either version 2 of the
c@135 11 License, or (at your option) any later version. See the file
c@135 12 COPYING included with this distribution for more information.
c@92 13 */
c@92 14
c@92 15 #include "AdaptiveSpectrogram.h"
c@92 16
c@92 17 #include <cstdlib>
c@133 18 #include <cstdio>
c@92 19 #include <cstring>
c@114 20 #include <cfloat>
c@92 21
c@92 22 #include <iostream>
c@92 23
c@92 24 #include <dsp/transforms/FFT.h>
c@92 25
c@92 26 using std::string;
c@92 27 using std::vector;
c@92 28 using std::cerr;
c@92 29 using std::endl;
c@92 30
c@92 31 using Vamp::RealTime;
c@92 32
c@99 33 //#define DEBUG_VERBOSE 1
c@99 34
c@92 35 AdaptiveSpectrogram::AdaptiveSpectrogram(float inputSampleRate) :
c@92 36 Plugin(inputSampleRate),
c@104 37 m_w(8),
c@114 38 m_n(2),
c@114 39 m_coarse(false),
c@109 40 m_threaded(true),
c@109 41 m_threadsInUse(false)
c@92 42 {
c@92 43 }
c@92 44
c@92 45 AdaptiveSpectrogram::~AdaptiveSpectrogram()
c@92 46 {
c@104 47 for (int i = 0; i < m_cutThreads.size(); ++i) {
c@104 48 delete m_cutThreads[i];
c@104 49 }
c@104 50 m_cutThreads.clear();
c@105 51
c@110 52 for (FFTMap::iterator i = m_fftThreads.begin();
c@110 53 i != m_fftThreads.end(); ++i) {
c@106 54 delete i->second;
c@105 55 }
c@105 56 m_fftThreads.clear();
c@92 57 }
c@92 58
c@92 59 string
c@92 60 AdaptiveSpectrogram::getIdentifier() const
c@92 61 {
c@93 62 return "qm-adaptivespectrogram";
c@92 63 }
c@92 64
c@92 65 string
c@92 66 AdaptiveSpectrogram::getName() const
c@92 67 {
c@92 68 return "Adaptive Spectrogram";
c@92 69 }
c@92 70
c@92 71 string
c@92 72 AdaptiveSpectrogram::getDescription() const
c@92 73 {
c@92 74 return "Produce an adaptive spectrogram by adaptive selection from spectrograms at multiple resolutions";
c@92 75 }
c@92 76
c@92 77 string
c@92 78 AdaptiveSpectrogram::getMaker() const
c@92 79 {
c@92 80 return "Queen Mary, University of London";
c@92 81 }
c@92 82
c@92 83 int
c@92 84 AdaptiveSpectrogram::getPluginVersion() const
c@92 85 {
c@92 86 return 1;
c@92 87 }
c@92 88
c@92 89 string
c@92 90 AdaptiveSpectrogram::getCopyright() const
c@92 91 {
c@92 92 return "Plugin by Wen Xue and Chris Cannam. Copyright (c) 2009 Wen Xue and QMUL - All Rights Reserved";
c@92 93 }
c@92 94
c@92 95 size_t
c@92 96 AdaptiveSpectrogram::getPreferredStepSize() const
c@92 97 {
c@92 98 return ((2 << m_w) << m_n) / 2;
c@92 99 }
c@92 100
c@92 101 size_t
c@92 102 AdaptiveSpectrogram::getPreferredBlockSize() const
c@92 103 {
c@92 104 return (2 << m_w) << m_n;
c@92 105 }
c@92 106
c@92 107 bool
c@92 108 AdaptiveSpectrogram::initialise(size_t channels, size_t stepSize, size_t blockSize)
c@92 109 {
c@92 110 if (channels < getMinChannelCount() ||
c@92 111 channels > getMaxChannelCount()) return false;
c@92 112
c@92 113 return true;
c@92 114 }
c@92 115
c@92 116 void
c@92 117 AdaptiveSpectrogram::reset()
c@92 118 {
c@92 119
c@92 120 }
c@92 121
c@92 122 AdaptiveSpectrogram::ParameterList
c@92 123 AdaptiveSpectrogram::getParameterDescriptors() const
c@92 124 {
c@92 125 ParameterList list;
c@92 126
c@92 127 ParameterDescriptor desc;
c@92 128 desc.identifier = "n";
c@92 129 desc.name = "Number of resolutions";
c@114 130 desc.description = "Number of consecutive powers of two in the range to be used as spectrogram resolutions, starting with the minimum resolution specified";
c@92 131 desc.unit = "";
c@114 132 desc.minValue = 2;
c@92 133 desc.maxValue = 10;
c@114 134 desc.defaultValue = 3;
c@92 135 desc.isQuantized = true;
c@92 136 desc.quantizeStep = 1;
c@92 137 list.push_back(desc);
c@92 138
c@92 139 ParameterDescriptor desc2;
c@92 140 desc2.identifier = "w";
c@92 141 desc2.name = "Smallest resolution";
c@92 142 desc2.description = "Smallest of the consecutive powers of two to use as spectrogram resolutions";
c@92 143 desc2.unit = "";
c@92 144 desc2.minValue = 1;
c@92 145 desc2.maxValue = 14;
c@104 146 desc2.defaultValue = 9;
c@92 147 desc2.isQuantized = true;
c@92 148 desc2.quantizeStep = 1;
c@92 149 // I am so lazy
c@92 150 desc2.valueNames.push_back("2");
c@92 151 desc2.valueNames.push_back("4");
c@92 152 desc2.valueNames.push_back("8");
c@92 153 desc2.valueNames.push_back("16");
c@92 154 desc2.valueNames.push_back("32");
c@92 155 desc2.valueNames.push_back("64");
c@92 156 desc2.valueNames.push_back("128");
c@92 157 desc2.valueNames.push_back("256");
c@92 158 desc2.valueNames.push_back("512");
c@92 159 desc2.valueNames.push_back("1024");
c@92 160 desc2.valueNames.push_back("2048");
c@92 161 desc2.valueNames.push_back("4096");
c@92 162 desc2.valueNames.push_back("8192");
c@92 163 desc2.valueNames.push_back("16384");
c@92 164 list.push_back(desc2);
c@92 165
c@109 166 ParameterDescriptor desc3;
c@114 167 desc3.identifier = "coarse";
c@114 168 desc3.name = "Omit alternate resolutions";
c@114 169 desc3.description = "Generate a coarser spectrogram faster by excluding every alternate resolution (first and last resolution are always retained)";
c@114 170 desc3.unit = "";
c@114 171 desc3.minValue = 0;
c@114 172 desc3.maxValue = 1;
c@114 173 desc3.defaultValue = 0;
c@114 174 desc3.isQuantized = true;
c@114 175 desc3.quantizeStep = 1;
c@114 176 list.push_back(desc3);
c@114 177
c@109 178 desc3.identifier = "threaded";
c@109 179 desc3.name = "Multi-threaded processing";
c@110 180 desc3.description = "Perform calculations using several threads in parallel";
c@109 181 desc3.unit = "";
c@109 182 desc3.minValue = 0;
c@109 183 desc3.maxValue = 1;
c@109 184 desc3.defaultValue = 1;
c@109 185 desc3.isQuantized = true;
c@109 186 desc3.quantizeStep = 1;
c@109 187 list.push_back(desc3);
c@109 188
c@92 189 return list;
c@92 190 }
c@92 191
c@92 192 float
c@92 193 AdaptiveSpectrogram::getParameter(std::string id) const
c@92 194 {
c@92 195 if (id == "n") return m_n+1;
c@92 196 else if (id == "w") return m_w+1;
c@109 197 else if (id == "threaded") return (m_threaded ? 1 : 0);
c@114 198 else if (id == "coarse") return (m_coarse ? 1 : 0);
c@92 199 return 0.f;
c@92 200 }
c@92 201
c@92 202 void
c@92 203 AdaptiveSpectrogram::setParameter(std::string id, float value)
c@92 204 {
c@92 205 if (id == "n") {
c@92 206 int n = lrintf(value);
c@92 207 if (n >= 1 && n <= 10) m_n = n-1;
c@92 208 } else if (id == "w") {
c@92 209 int w = lrintf(value);
c@92 210 if (w >= 1 && w <= 14) m_w = w-1;
c@109 211 } else if (id == "threaded") {
c@109 212 m_threaded = (value > 0.5);
c@114 213 } else if (id == "coarse") {
c@114 214 m_coarse = (value > 0.5);
c@109 215 }
c@92 216 }
c@92 217
c@92 218 AdaptiveSpectrogram::OutputList
c@92 219 AdaptiveSpectrogram::getOutputDescriptors() const
c@92 220 {
c@92 221 OutputList list;
c@92 222
c@92 223 OutputDescriptor d;
c@92 224 d.identifier = "output";
c@92 225 d.name = "Output";
c@92 226 d.description = "The output of the plugin";
c@92 227 d.unit = "";
c@92 228 d.hasFixedBinCount = true;
c@114 229 d.binCount = getPreferredBlockSize() / 2;
c@92 230 d.hasKnownExtents = false;
c@92 231 d.isQuantized = false;
c@92 232 d.sampleType = OutputDescriptor::FixedSampleRate;
c@92 233 d.sampleRate = m_inputSampleRate / ((2 << m_w) / 2);
c@92 234 d.hasDuration = false;
c@112 235 char name[20];
c@112 236 for (int i = 0; i < d.binCount; ++i) {
c@114 237 float freq = (m_inputSampleRate / (d.binCount * 2)) * (i + 1); // no DC bin
c@112 238 sprintf(name, "%d Hz", int(freq));
c@112 239 d.binNames.push_back(name);
c@112 240 }
c@92 241 list.push_back(d);
c@92 242
c@92 243 return list;
c@92 244 }
c@92 245
c@92 246 AdaptiveSpectrogram::FeatureSet
c@92 247 AdaptiveSpectrogram::getRemainingFeatures()
c@92 248 {
c@92 249 FeatureSet fs;
c@92 250 return fs;
c@92 251 }
c@92 252
c@100 253 AdaptiveSpectrogram::FeatureSet
c@100 254 AdaptiveSpectrogram::process(const float *const *inputBuffers, RealTime ts)
c@100 255 {
c@100 256 FeatureSet fs;
c@100 257
c@100 258 int minwid = (2 << m_w), maxwid = ((2 << m_w) << m_n);
c@100 259
c@101 260 #ifdef DEBUG_VERBOSE
c@100 261 cerr << "widths from " << minwid << " to " << maxwid << " ("
c@100 262 << minwid/2 << " to " << maxwid/2 << " in real parts)" << endl;
c@101 263 #endif
c@100 264
c@100 265 Spectrograms s(minwid/2, maxwid/2, 1);
c@100 266
c@100 267 int w = minwid;
c@100 268 int index = 0;
c@100 269
c@100 270 while (w <= maxwid) {
c@114 271
c@114 272 if (!isResolutionWanted(s, w/2)) {
c@114 273 w *= 2;
c@114 274 ++index;
c@114 275 continue;
c@114 276 }
c@114 277
c@106 278 if (m_fftThreads.find(w) == m_fftThreads.end()) {
c@106 279 m_fftThreads[w] = new FFTThread(w);
c@106 280 }
c@109 281 if (m_threaded) {
c@114 282 m_fftThreads[w]->startCalculation
c@114 283 (inputBuffers[0], s, index, maxwid);
c@109 284 } else {
c@114 285 m_fftThreads[w]->setParameters
c@114 286 (inputBuffers[0], s, index, maxwid);
c@109 287 m_fftThreads[w]->performTask();
c@109 288 }
c@100 289 w *= 2;
c@100 290 ++index;
c@100 291 }
c@100 292
c@109 293 if (m_threaded) {
c@109 294 w = minwid;
c@114 295 index = 0;
c@109 296 while (w <= maxwid) {
c@114 297 if (!isResolutionWanted(s, w/2)) {
c@114 298 w *= 2;
c@114 299 ++index;
c@114 300 continue;
c@114 301 }
c@109 302 m_fftThreads[w]->await();
c@109 303 w *= 2;
c@114 304 ++index;
c@109 305 }
c@105 306 }
c@102 307
c@109 308 m_threadsInUse = false;
c@104 309
c@114 310 // std::cerr << "maxwid/2 = " << maxwid/2 << ", minwid/2 = " << minwid/2 << ", n+1 = " << m_n+1 << ", 2^(n+1) = " << (2<<m_n) << std::endl;
c@110 311
c@114 312 int cutwid = maxwid/2;
c@114 313 Cutting *cutting = cut(s, cutwid, 0, 0, cutwid, 0);
c@100 314
c@101 315 #ifdef DEBUG_VERBOSE
c@100 316 printCutting(cutting, " ");
c@101 317 #endif
c@100 318
c@100 319 vector<vector<float> > rmat(maxwid/minwid);
c@100 320 for (int i = 0; i < maxwid/minwid; ++i) {
c@100 321 rmat[i] = vector<float>(maxwid/2);
c@100 322 }
c@100 323
c@114 324 assemble(s, cutting, rmat, 0, 0, maxwid/minwid, cutwid);
c@100 325
c@110 326 cutting->erase();
c@100 327
c@100 328 for (int i = 0; i < rmat.size(); ++i) {
c@100 329 Feature f;
c@100 330 f.hasTimestamp = false;
c@100 331 f.values = rmat[i];
c@100 332 fs[0].push_back(f);
c@100 333 }
c@100 334
c@104 335 // std::cerr << "process returning!\n" << std::endl;
c@104 336
c@100 337 return fs;
c@100 338 }
c@100 339
c@100 340 void
c@104 341 AdaptiveSpectrogram::printCutting(Cutting *c, string pfx) const
c@100 342 {
c@100 343 if (c->first) {
c@100 344 if (c->cut == Cutting::Horizontal) {
c@100 345 cerr << pfx << "H" << endl;
c@100 346 } else if (c->cut == Cutting::Vertical) {
c@100 347 cerr << pfx << "V" << endl;
c@100 348 }
c@100 349 printCutting(c->first, pfx + " ");
c@100 350 printCutting(c->second, pfx + " ");
c@100 351 } else {
c@100 352 cerr << pfx << "* " << c->value << endl;
c@100 353 }
c@100 354 }
c@100 355
c@104 356 void
c@104 357 AdaptiveSpectrogram::getSubCuts(const Spectrograms &s,
c@104 358 int res,
c@104 359 int x, int y, int h,
c@114 360 Cutting **top, Cutting **bottom,
c@114 361 Cutting **left, Cutting **right,
c@113 362 BlockAllocator *allocator) const
c@104 363 {
c@109 364 if (m_threaded && !m_threadsInUse) {
c@104 365
c@109 366 m_threadsInUse = true;
c@104 367
c@104 368 if (m_cutThreads.empty()) {
c@104 369 for (int i = 0; i < 4; ++i) {
c@104 370 CutThread *t = new CutThread(this);
c@104 371 m_cutThreads.push_back(t);
c@104 372 }
c@104 373 }
c@104 374
c@109 375 // Cut threads 0 and 1 calculate the top and bottom halves;
c@110 376 // threads 2 and 3 calculate left and right. See notes in
c@110 377 // unthreaded code below for more information.
c@104 378
c@114 379 if (top) m_cutThreads[0]->cut(s, res, x, y + h/2, h/2);
c@114 380 if (bottom) m_cutThreads[1]->cut(s, res, x, y, h/2);
c@104 381
c@114 382 if (left) m_cutThreads[2]->cut(s, res/2, 2 * x, y/2, h/2);
c@114 383 if (right) m_cutThreads[3]->cut(s, res/2, 2 * x + 1, y/2, h/2);
c@114 384
c@114 385 if (top) *top = m_cutThreads[0]->get();
c@114 386 if (bottom) *bottom = m_cutThreads[1]->get();
c@114 387 if (left) *left = m_cutThreads[2]->get();
c@114 388 if (right) *right = m_cutThreads[3]->get();
c@104 389
c@104 390 } else {
c@104 391
c@110 392 // Unthreaded version
c@104 393
c@104 394 // The "vertical" division is a top/bottom split.
c@104 395 // Splitting this way keeps us in the same resolution,
c@104 396 // but with two vertical subregions of height h/2.
c@104 397
c@114 398 if (top) *top = cut(s, res, x, y + h/2, h/2, allocator);
c@114 399 if (bottom) *bottom = cut(s, res, x, y, h/2, allocator);
c@104 400
c@104 401 // The "horizontal" division is a left/right split. Splitting
c@104 402 // this way places us in resolution res/2, which has lower
c@104 403 // vertical resolution but higher horizontal resolution. We
c@104 404 // need to double x accordingly.
c@104 405
c@114 406 if (left) *left = cut(s, res/2, 2 * x, y/2, h/2, allocator);
c@114 407 if (right) *right = cut(s, res/2, 2 * x + 1, y/2, h/2, allocator);
c@104 408 }
c@104 409 }
c@104 410
c@100 411 AdaptiveSpectrogram::Cutting *
c@100 412 AdaptiveSpectrogram::cut(const Spectrograms &s,
c@100 413 int res,
c@110 414 int x, int y, int h,
c@110 415 BlockAllocator *allocator) const
c@100 416 {
c@100 417 // cerr << "res = " << res << ", x = " << x << ", y = " << y << ", h = " << h << endl;
c@100 418
c@110 419 Cutting *cutting;
c@110 420 if (allocator) {
c@110 421 cutting = (Cutting *)(allocator->allocate());
c@110 422 cutting->allocator = allocator;
c@110 423 } else {
c@110 424 cutting = new Cutting;
c@110 425 cutting->allocator = 0;
c@110 426 }
c@110 427
c@100 428 if (h > 1 && res > s.minres) {
c@100 429
c@114 430 if (!isResolutionWanted(s, res)) {
c@100 431
c@114 432 Cutting *left = 0, *right = 0;
c@114 433 getSubCuts(s, res, x, y, h, 0, 0, &left, &right, allocator);
c@114 434
c@114 435 double hcost = left->cost + right->cost;
c@101 436 double henergy = left->value + right->value;
c@114 437 hcost = normalize(hcost, henergy);
c@114 438
c@100 439 cutting->cut = Cutting::Horizontal;
c@100 440 cutting->first = left;
c@100 441 cutting->second = right;
c@100 442 cutting->cost = hcost;
c@111 443 cutting->value = left->value + right->value;
c@100 444
c@114 445 } else if (h == 2 && !isResolutionWanted(s, res/2)) {
c@100 446
c@114 447 Cutting *top = 0, *bottom = 0;
c@114 448 getSubCuts(s, res, x, y, h, &top, &bottom, 0, 0, allocator);
c@114 449
c@114 450 double vcost = top->cost + bottom->cost;
c@114 451 double venergy = top->value + bottom->value;
c@114 452 vcost = normalize(vcost, venergy);
c@114 453
c@100 454 cutting->cut = Cutting::Vertical;
c@100 455 cutting->first = top;
c@100 456 cutting->second = bottom;
c@100 457 cutting->cost = vcost;
c@111 458 cutting->value = top->value + bottom->value;
c@114 459
c@114 460 } else {
c@114 461
c@114 462 Cutting *top = 0, *bottom = 0, *left = 0, *right = 0;
c@114 463 getSubCuts(s, res, x, y, h, &top, &bottom, &left, &right, allocator);
c@114 464
c@114 465 double vcost = top->cost + bottom->cost;
c@114 466 double venergy = top->value + bottom->value;
c@114 467 vcost = normalize(vcost, venergy);
c@114 468
c@114 469 double hcost = left->cost + right->cost;
c@114 470 double henergy = left->value + right->value;
c@114 471 hcost = normalize(hcost, henergy);
c@114 472
c@114 473 if (vcost > hcost) {
c@114 474 cutting->cut = Cutting::Horizontal;
c@114 475 cutting->first = left;
c@114 476 cutting->second = right;
c@114 477 cutting->cost = hcost;
c@114 478 cutting->value = left->value + right->value;
c@114 479 top->erase();
c@114 480 bottom->erase();
c@114 481 return cutting;
c@114 482 } else {
c@114 483 cutting->cut = Cutting::Vertical;
c@114 484 cutting->first = top;
c@114 485 cutting->second = bottom;
c@114 486 cutting->cost = vcost;
c@114 487 cutting->value = top->value + bottom->value;
c@114 488 left->erase();
c@114 489 right->erase();
c@114 490 return cutting;
c@114 491 }
c@100 492 }
c@100 493
c@100 494 } else {
c@100 495
c@100 496 // no cuts possible from this level
c@100 497
c@100 498 cutting->cut = Cutting::Finished;
c@100 499 cutting->first = 0;
c@100 500 cutting->second = 0;
c@100 501
c@100 502 int n = 0;
c@114 503 for (int r = res; r > s.minres; r >>= 1) ++n;
c@100 504 const Spectrogram *spectrogram = s.spectrograms[n];
c@100 505 cutting->cost = cost(*spectrogram, x, y);
c@100 506 cutting->value = value(*spectrogram, x, y);
c@114 507 }
c@100 508
c@114 509 return cutting;
c@100 510 }
c@100 511
c@100 512 void
c@100 513 AdaptiveSpectrogram::assemble(const Spectrograms &s,
c@100 514 const Cutting *cutting,
c@100 515 vector<vector<float> > &rmat,
c@104 516 int x, int y, int w, int h) const
c@100 517 {
c@100 518 switch (cutting->cut) {
c@100 519
c@100 520 case Cutting::Finished:
c@100 521 for (int i = 0; i < w; ++i) {
c@100 522 for (int j = 0; j < h; ++j) {
c@114 523 rmat[x+i][y+j] = cutting->value;
c@100 524 }
c@100 525 }
c@100 526 return;
c@100 527
c@100 528 case Cutting::Horizontal:
c@100 529 assemble(s, cutting->first, rmat, x, y, w/2, h);
c@100 530 assemble(s, cutting->second, rmat, x+w/2, y, w/2, h);
c@100 531 break;
c@100 532
c@100 533 case Cutting::Vertical:
c@100 534 assemble(s, cutting->first, rmat, x, y+h/2, w, h/2);
c@100 535 assemble(s, cutting->second, rmat, x, y, w, h/2);
c@100 536 break;
c@100 537 }
c@100 538 }
c@100 539