annotate examples/FixedTempoEstimator.cpp @ 484:ea23f1201155

Copyright dates
author Chris Cannam
date Thu, 23 Feb 2017 13:59:51 +0000
parents 7d59dd1ba5de
children d129bf797f24
rev   line source
cannam@198 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
cannam@198 2
cannam@198 3 /*
cannam@198 4 Vamp
cannam@198 5
cannam@198 6 An API for audio analysis and feature extraction plugins.
cannam@198 7
cannam@198 8 Centre for Digital Music, Queen Mary, University of London.
cannam@290 9 Copyright 2006-2009 Chris Cannam and QMUL.
cannam@198 10
cannam@198 11 Permission is hereby granted, free of charge, to any person
cannam@198 12 obtaining a copy of this software and associated documentation
cannam@198 13 files (the "Software"), to deal in the Software without
cannam@198 14 restriction, including without limitation the rights to use, copy,
cannam@198 15 modify, merge, publish, distribute, sublicense, and/or sell copies
cannam@198 16 of the Software, and to permit persons to whom the Software is
cannam@198 17 furnished to do so, subject to the following conditions:
cannam@198 18
cannam@198 19 The above copyright notice and this permission notice shall be
cannam@198 20 included in all copies or substantial portions of the Software.
cannam@198 21
cannam@198 22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
cannam@198 23 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
cannam@198 24 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
cannam@198 25 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
cannam@198 26 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
cannam@198 27 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
cannam@198 28 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
cannam@198 29
cannam@198 30 Except as contained in this notice, the names of the Centre for
cannam@198 31 Digital Music; Queen Mary, University of London; and Chris Cannam
cannam@198 32 shall not be used in advertising or otherwise to promote the sale,
cannam@198 33 use or other dealings in this Software without prior written
cannam@198 34 authorization.
cannam@198 35 */
cannam@198 36
cannam@198 37 #include "FixedTempoEstimator.h"
cannam@198 38
cannam@198 39 using std::string;
cannam@198 40 using std::vector;
cannam@198 41 using std::cerr;
cannam@198 42 using std::endl;
cannam@198 43
cannam@198 44 using Vamp::RealTime;
cannam@198 45
cannam@198 46 #include <cmath>
cannam@301 47 #include <cstdio>
cannam@198 48
cannam@198 49
cannam@243 50 class FixedTempoEstimator::D
cannam@255 51 // this class just avoids us having to declare any data members in the header
cannam@243 52 {
cannam@243 53 public:
cannam@243 54 D(float inputSampleRate);
cannam@243 55 ~D();
cannam@243 56
cannam@243 57 size_t getPreferredStepSize() const { return 64; }
cannam@243 58 size_t getPreferredBlockSize() const { return 256; }
cannam@243 59
cannam@243 60 ParameterList getParameterDescriptors() const;
cannam@243 61 float getParameter(string id) const;
cannam@243 62 void setParameter(string id, float value);
cannam@243 63
cannam@243 64 OutputList getOutputDescriptors() const;
cannam@243 65
cannam@243 66 bool initialise(size_t channels, size_t stepSize, size_t blockSize);
cannam@243 67 void reset();
cannam@243 68 FeatureSet process(const float *const *, RealTime);
cannam@243 69 FeatureSet getRemainingFeatures();
cannam@243 70
cannam@243 71 private:
cannam@243 72 void calculate();
cannam@243 73 FeatureSet assembleFeatures();
cannam@243 74
cannam@243 75 float lag2tempo(int);
cannam@243 76 int tempo2lag(float);
cannam@243 77
cannam@243 78 float m_inputSampleRate;
cannam@243 79 size_t m_stepSize;
cannam@243 80 size_t m_blockSize;
cannam@243 81
cannam@243 82 float m_minbpm;
cannam@243 83 float m_maxbpm;
cannam@243 84 float m_maxdflen;
cannam@243 85
cannam@243 86 float *m_priorMagnitudes;
cannam@243 87
cannam@243 88 size_t m_dfsize;
cannam@243 89 float *m_df;
cannam@243 90 float *m_r;
cannam@243 91 float *m_fr;
cannam@243 92 float *m_t;
cannam@243 93 size_t m_n;
cannam@243 94
cannam@243 95 Vamp::RealTime m_start;
cannam@243 96 Vamp::RealTime m_lasttime;
cannam@243 97 };
cannam@243 98
cannam@243 99 FixedTempoEstimator::D::D(float inputSampleRate) :
cannam@243 100 m_inputSampleRate(inputSampleRate),
cannam@198 101 m_stepSize(0),
cannam@198 102 m_blockSize(0),
cannam@243 103 m_minbpm(50),
cannam@243 104 m_maxbpm(190),
cannam@243 105 m_maxdflen(10),
cannam@198 106 m_priorMagnitudes(0),
cannam@200 107 m_df(0),
cannam@200 108 m_r(0),
cannam@200 109 m_fr(0),
cannam@204 110 m_t(0),
cannam@200 111 m_n(0)
cannam@198 112 {
cannam@198 113 }
cannam@198 114
cannam@243 115 FixedTempoEstimator::D::~D()
cannam@198 116 {
cannam@198 117 delete[] m_priorMagnitudes;
cannam@198 118 delete[] m_df;
cannam@200 119 delete[] m_r;
cannam@200 120 delete[] m_fr;
cannam@204 121 delete[] m_t;
cannam@198 122 }
cannam@198 123
cannam@198 124 FixedTempoEstimator::ParameterList
cannam@243 125 FixedTempoEstimator::D::getParameterDescriptors() const
cannam@198 126 {
cannam@198 127 ParameterList list;
cannam@243 128
cannam@243 129 ParameterDescriptor d;
cannam@243 130 d.identifier = "minbpm";
cannam@243 131 d.name = "Minimum estimated tempo";
cannam@243 132 d.description = "Minimum beat-per-minute value which the tempo estimator is able to return";
cannam@243 133 d.unit = "bpm";
cannam@243 134 d.minValue = 10;
cannam@243 135 d.maxValue = 360;
cannam@243 136 d.defaultValue = 50;
cannam@243 137 d.isQuantized = false;
cannam@243 138 list.push_back(d);
cannam@243 139
cannam@243 140 d.identifier = "maxbpm";
cannam@243 141 d.name = "Maximum estimated tempo";
cannam@243 142 d.description = "Maximum beat-per-minute value which the tempo estimator is able to return";
cannam@243 143 d.defaultValue = 190;
cannam@243 144 list.push_back(d);
cannam@243 145
cannam@243 146 d.identifier = "maxdflen";
cannam@243 147 d.name = "Input duration to study";
cannam@243 148 d.description = "Length of audio input, in seconds, which should be taken into account when estimating tempo. There is no need to supply the plugin with any further input once this time has elapsed since the start of the audio. The tempo estimator may use only the first part of this, up to eight times the slowest beat duration: increasing this value further than that is unlikely to improve results.";
cannam@243 149 d.unit = "s";
cannam@243 150 d.minValue = 2;
cannam@243 151 d.maxValue = 40;
cannam@243 152 d.defaultValue = 10;
cannam@243 153 list.push_back(d);
cannam@243 154
cannam@198 155 return list;
cannam@198 156 }
cannam@198 157
cannam@198 158 float
cannam@243 159 FixedTempoEstimator::D::getParameter(string id) const
cannam@198 160 {
cannam@243 161 if (id == "minbpm") {
cannam@243 162 return m_minbpm;
cannam@243 163 } else if (id == "maxbpm") {
cannam@243 164 return m_maxbpm;
cannam@243 165 } else if (id == "maxdflen") {
cannam@243 166 return m_maxdflen;
cannam@243 167 }
cannam@198 168 return 0.f;
cannam@198 169 }
cannam@198 170
cannam@198 171 void
cannam@243 172 FixedTempoEstimator::D::setParameter(string id, float value)
cannam@198 173 {
cannam@243 174 if (id == "minbpm") {
cannam@243 175 m_minbpm = value;
cannam@243 176 } else if (id == "maxbpm") {
cannam@243 177 m_maxbpm = value;
cannam@243 178 } else if (id == "maxdflen") {
cannam@243 179 m_maxdflen = value;
cannam@243 180 }
cannam@198 181 }
cannam@198 182
cannam@200 183 static int TempoOutput = 0;
cannam@200 184 static int CandidatesOutput = 1;
cannam@200 185 static int DFOutput = 2;
cannam@200 186 static int ACFOutput = 3;
cannam@200 187 static int FilteredACFOutput = 4;
cannam@200 188
cannam@198 189 FixedTempoEstimator::OutputList
cannam@243 190 FixedTempoEstimator::D::getOutputDescriptors() const
cannam@198 191 {
cannam@198 192 OutputList list;
cannam@198 193
cannam@198 194 OutputDescriptor d;
cannam@198 195 d.identifier = "tempo";
cannam@198 196 d.name = "Tempo";
cannam@198 197 d.description = "Estimated tempo";
cannam@198 198 d.unit = "bpm";
cannam@198 199 d.hasFixedBinCount = true;
cannam@198 200 d.binCount = 1;
cannam@198 201 d.hasKnownExtents = false;
cannam@198 202 d.isQuantized = false;
cannam@198 203 d.sampleType = OutputDescriptor::VariableSampleRate;
cannam@198 204 d.sampleRate = m_inputSampleRate;
cannam@198 205 d.hasDuration = true; // our returned tempo spans a certain range
cannam@198 206 list.push_back(d);
cannam@198 207
cannam@200 208 d.identifier = "candidates";
cannam@200 209 d.name = "Tempo candidates";
cannam@200 210 d.description = "Possible tempo estimates, one per bin with the most likely in the first bin";
cannam@200 211 d.unit = "bpm";
cannam@200 212 d.hasFixedBinCount = false;
cannam@200 213 list.push_back(d);
cannam@200 214
cannam@198 215 d.identifier = "detectionfunction";
cannam@198 216 d.name = "Detection Function";
cannam@198 217 d.description = "Onset detection function";
cannam@198 218 d.unit = "";
cannam@198 219 d.hasFixedBinCount = 1;
cannam@198 220 d.binCount = 1;
cannam@198 221 d.hasKnownExtents = true;
cannam@198 222 d.minValue = 0.0;
cannam@198 223 d.maxValue = 1.0;
cannam@198 224 d.isQuantized = false;
cannam@198 225 d.quantizeStep = 0.0;
cannam@198 226 d.sampleType = OutputDescriptor::FixedSampleRate;
cannam@198 227 if (m_stepSize) {
cannam@198 228 d.sampleRate = m_inputSampleRate / m_stepSize;
cannam@198 229 } else {
cannam@198 230 d.sampleRate = m_inputSampleRate / (getPreferredBlockSize()/2);
cannam@198 231 }
cannam@198 232 d.hasDuration = false;
cannam@198 233 list.push_back(d);
cannam@198 234
cannam@198 235 d.identifier = "acf";
cannam@198 236 d.name = "Autocorrelation Function";
cannam@198 237 d.description = "Autocorrelation of onset detection function";
cannam@198 238 d.hasKnownExtents = false;
cannam@201 239 d.unit = "r";
cannam@198 240 list.push_back(d);
cannam@198 241
cannam@198 242 d.identifier = "filtered_acf";
cannam@198 243 d.name = "Filtered Autocorrelation";
cannam@198 244 d.description = "Filtered autocorrelation of onset detection function";
cannam@201 245 d.unit = "r";
cannam@198 246 list.push_back(d);
cannam@198 247
cannam@198 248 return list;
cannam@198 249 }
cannam@198 250
cannam@243 251 bool
cannam@271 252 FixedTempoEstimator::D::initialise(size_t, size_t stepSize, size_t blockSize)
cannam@243 253 {
cannam@243 254 m_stepSize = stepSize;
cannam@243 255 m_blockSize = blockSize;
cannam@243 256
cannam@243 257 float dfLengthSecs = m_maxdflen;
cannam@243 258 m_dfsize = (dfLengthSecs * m_inputSampleRate) / m_stepSize;
cannam@243 259
cannam@243 260 m_priorMagnitudes = new float[m_blockSize/2];
cannam@243 261 m_df = new float[m_dfsize];
cannam@243 262
cannam@243 263 for (size_t i = 0; i < m_blockSize/2; ++i) {
cannam@243 264 m_priorMagnitudes[i] = 0.f;
cannam@243 265 }
cannam@243 266 for (size_t i = 0; i < m_dfsize; ++i) {
cannam@243 267 m_df[i] = 0.f;
cannam@243 268 }
cannam@243 269
cannam@243 270 m_n = 0;
cannam@243 271
cannam@243 272 return true;
cannam@243 273 }
cannam@243 274
cannam@243 275 void
cannam@243 276 FixedTempoEstimator::D::reset()
cannam@243 277 {
cannam@243 278 if (!m_priorMagnitudes) return;
cannam@243 279
cannam@243 280 for (size_t i = 0; i < m_blockSize/2; ++i) {
cannam@243 281 m_priorMagnitudes[i] = 0.f;
cannam@243 282 }
cannam@243 283 for (size_t i = 0; i < m_dfsize; ++i) {
cannam@243 284 m_df[i] = 0.f;
cannam@243 285 }
cannam@243 286
cannam@243 287 delete[] m_r;
cannam@243 288 m_r = 0;
cannam@243 289
cannam@243 290 delete[] m_fr;
cannam@243 291 m_fr = 0;
cannam@243 292
cannam@243 293 delete[] m_t;
cannam@243 294 m_t = 0;
cannam@243 295
cannam@243 296 m_n = 0;
cannam@243 297
cannam@243 298 m_start = RealTime::zeroTime;
cannam@243 299 m_lasttime = RealTime::zeroTime;
cannam@243 300 }
cannam@243 301
cannam@198 302 FixedTempoEstimator::FeatureSet
cannam@243 303 FixedTempoEstimator::D::process(const float *const *inputBuffers, RealTime ts)
cannam@198 304 {
cannam@198 305 FeatureSet fs;
cannam@198 306
cannam@198 307 if (m_stepSize == 0) {
cannam@198 308 cerr << "ERROR: FixedTempoEstimator::process: "
cannam@198 309 << "FixedTempoEstimator has not been initialised"
cannam@198 310 << endl;
cannam@198 311 return fs;
cannam@198 312 }
cannam@198 313
cannam@198 314 if (m_n == 0) m_start = ts;
cannam@198 315 m_lasttime = ts;
cannam@198 316
cannam@198 317 if (m_n == m_dfsize) {
cannam@255 318 // If we have seen enough input, do the estimation and return
cannam@200 319 calculate();
cannam@200 320 fs = assembleFeatures();
cannam@198 321 ++m_n;
cannam@198 322 return fs;
cannam@198 323 }
cannam@198 324
cannam@255 325 // If we have seen more than enough, just discard and return!
cannam@198 326 if (m_n > m_dfsize) return FeatureSet();
cannam@198 327
cannam@207 328 float value = 0.f;
cannam@207 329
cannam@255 330 // m_df will contain an onset detection function based on the rise
cannam@255 331 // in overall power from one spectral frame to the next --
cannam@255 332 // simplistic but reasonably effective for our purposes.
cannam@255 333
cannam@198 334 for (size_t i = 1; i < m_blockSize/2; ++i) {
cannam@198 335
cannam@198 336 float real = inputBuffers[0][i*2];
cannam@198 337 float imag = inputBuffers[0][i*2 + 1];
cannam@198 338
cannam@198 339 float sqrmag = real * real + imag * imag;
cannam@207 340 value += fabsf(sqrmag - m_priorMagnitudes[i]);
cannam@198 341
cannam@198 342 m_priorMagnitudes[i] = sqrmag;
cannam@198 343 }
cannam@198 344
cannam@207 345 m_df[m_n] = value;
cannam@207 346
cannam@198 347 ++m_n;
cannam@198 348 return fs;
cannam@243 349 }
cannam@198 350
cannam@198 351 FixedTempoEstimator::FeatureSet
cannam@243 352 FixedTempoEstimator::D::getRemainingFeatures()
cannam@198 353 {
cannam@198 354 FeatureSet fs;
cannam@198 355 if (m_n > m_dfsize) return fs;
cannam@200 356 calculate();
cannam@200 357 fs = assembleFeatures();
cannam@198 358 ++m_n;
cannam@198 359 return fs;
cannam@198 360 }
cannam@198 361
cannam@198 362 float
cannam@243 363 FixedTempoEstimator::D::lag2tempo(int lag)
cannam@199 364 {
cannam@198 365 return 60.f / ((lag * m_stepSize) / m_inputSampleRate);
cannam@198 366 }
cannam@198 367
cannam@207 368 int
cannam@243 369 FixedTempoEstimator::D::tempo2lag(float tempo)
cannam@207 370 {
cannam@207 371 return ((60.f / tempo) * m_inputSampleRate) / m_stepSize;
cannam@207 372 }
cannam@207 373
cannam@200 374 void
cannam@243 375 FixedTempoEstimator::D::calculate()
cannam@200 376 {
cannam@200 377 if (m_r) {
cannam@207 378 cerr << "FixedTempoEstimator::calculate: calculation already happened?" << endl;
cannam@200 379 return;
cannam@200 380 }
cannam@200 381
cannam@243 382 if (m_n < m_dfsize / 9 &&
cannam@243 383 m_n < (1.0 * m_inputSampleRate) / m_stepSize) { // 1 second
cannam@243 384 cerr << "FixedTempoEstimator::calculate: Input is too short" << endl;
cannam@243 385 return;
cannam@200 386 }
cannam@200 387
cannam@255 388 // This function takes m_df (the detection function array filled
cannam@255 389 // out in process()) and calculates m_r (the raw autocorrelation)
cannam@255 390 // and m_fr (the filtered autocorrelation from whose peaks tempo
cannam@255 391 // estimates will be taken).
cannam@200 392
cannam@255 393 int n = m_n; // length of actual df array (m_dfsize is the theoretical max)
cannam@255 394
cannam@255 395 m_r = new float[n/2]; // raw autocorrelation
cannam@255 396 m_fr = new float[n/2]; // filtered autocorrelation
cannam@255 397 m_t = new float[n/2]; // averaged tempo estimate for each lag value
cannam@200 398
cannam@200 399 for (int i = 0; i < n/2; ++i) {
cannam@255 400 m_r[i] = 0.f;
cannam@200 401 m_fr[i] = 0.f;
cannam@255 402 m_t[i] = lag2tempo(i);
cannam@200 403 }
cannam@200 404
cannam@255 405 // Calculate the raw autocorrelation of the detection function
cannam@255 406
cannam@200 407 for (int i = 0; i < n/2; ++i) {
cannam@200 408
cannam@271 409 for (int j = i; j < n; ++j) {
cannam@200 410 m_r[i] += m_df[j] * m_df[j - i];
cannam@200 411 }
cannam@200 412
cannam@200 413 m_r[i] /= n - i - 1;
cannam@200 414 }
cannam@200 415
cannam@255 416 // Filter the autocorrelation and average out the tempo estimates
cannam@255 417
cannam@246 418 float related[] = { 0.5, 2, 4, 8 };
cannam@208 419
cannam@209 420 for (int i = 1; i < n/2-1; ++i) {
cannam@204 421
cannam@209 422 m_fr[i] = m_r[i];
cannam@204 423
cannam@200 424 int div = 1;
cannam@200 425
cannam@215 426 for (int j = 0; j < int(sizeof(related)/sizeof(related[0])); ++j) {
cannam@204 427
cannam@255 428 // Check for an obvious peak at each metrically related lag
cannam@255 429
cannam@215 430 int k0 = int(i * related[j] + 0.5);
cannam@209 431
cannam@215 432 if (k0 >= 0 && k0 < int(n/2)) {
cannam@204 433
Chris@398 434 int kmax = 0;
cannam@207 435 float kvmax = 0, kvmin = 0;
cannam@209 436 bool have = false;
cannam@204 437
cannam@209 438 for (int k = k0 - 1; k <= k0 + 1; ++k) {
cannam@204 439
cannam@209 440 if (k < 0 || k >= n/2) continue;
cannam@209 441
Chris@398 442 if (!have || (m_r[k] > kvmax)) { kvmax = m_r[k]; kmax = k; }
Chris@398 443 if (!have || (m_r[k] < kvmin)) { kvmin = m_r[k]; }
cannam@209 444
cannam@209 445 have = true;
cannam@204 446 }
cannam@209 447
cannam@255 448 // Boost the original lag according to the strongest
cannam@255 449 // value found close to this related lag
cannam@255 450
cannam@215 451 m_fr[i] += m_r[kmax] / 5;
cannam@209 452
cannam@209 453 if ((kmax == 0 || m_r[kmax] > m_r[kmax-1]) &&
cannam@209 454 (kmax == n/2-1 || m_r[kmax] > m_r[kmax+1]) &&
cannam@207 455 kvmax > kvmin * 1.05) {
cannam@255 456
cannam@255 457 // The strongest value close to the related lag is
cannam@255 458 // also a pretty good looking peak, so use it to
cannam@255 459 // improve our tempo estimate for the original lag
cannam@209 460
cannam@207 461 m_t[i] = m_t[i] + lag2tempo(kmax) * related[j];
cannam@207 462 ++div;
cannam@207 463 }
cannam@204 464 }
cannam@204 465 }
cannam@209 466
cannam@204 467 m_t[i] /= div;
cannam@204 468
cannam@255 469 // Finally apply a primitive perceptual weighting (to prefer
cannam@255 470 // tempi of around 120-130)
cannam@255 471
cannam@255 472 float weight = 1.f - fabsf(128.f - lag2tempo(i)) * 0.005;
cannam@255 473 if (weight < 0.f) weight = 0.f;
cannam@255 474 weight = weight * weight * weight;
cannam@255 475
cannam@215 476 m_fr[i] += m_fr[i] * (weight / 3);
cannam@207 477 }
cannam@200 478 }
cannam@200 479
cannam@198 480 FixedTempoEstimator::FeatureSet
cannam@243 481 FixedTempoEstimator::D::assembleFeatures()
cannam@198 482 {
cannam@198 483 FeatureSet fs;
cannam@255 484 if (!m_r) return fs; // No autocorrelation: no results
cannam@200 485
cannam@198 486 Feature feature;
cannam@198 487 feature.hasTimestamp = true;
cannam@198 488 feature.hasDuration = false;
cannam@198 489 feature.label = "";
cannam@198 490 feature.values.clear();
cannam@198 491 feature.values.push_back(0.f);
cannam@198 492
cannam@200 493 char buffer[40];
cannam@198 494
cannam@198 495 int n = m_n;
cannam@198 496
cannam@198 497 for (int i = 0; i < n; ++i) {
cannam@255 498
cannam@255 499 // Return the detection function in the DF output
cannam@255 500
cannam@208 501 feature.timestamp = m_start +
cannam@208 502 RealTime::frame2RealTime(i * m_stepSize, m_inputSampleRate);
cannam@200 503 feature.values[0] = m_df[i];
cannam@198 504 feature.label = "";
cannam@200 505 fs[DFOutput].push_back(feature);
cannam@198 506 }
cannam@198 507
cannam@199 508 for (int i = 1; i < n/2; ++i) {
cannam@255 509
cannam@255 510 // Return the raw autocorrelation in the ACF output, each
cannam@255 511 // value labelled according to its corresponding tempo
cannam@255 512
cannam@208 513 feature.timestamp = m_start +
cannam@208 514 RealTime::frame2RealTime(i * m_stepSize, m_inputSampleRate);
cannam@200 515 feature.values[0] = m_r[i];
cannam@199 516 sprintf(buffer, "%.1f bpm", lag2tempo(i));
cannam@200 517 if (i == n/2-1) feature.label = "";
cannam@200 518 else feature.label = buffer;
cannam@200 519 fs[ACFOutput].push_back(feature);
cannam@198 520 }
cannam@198 521
cannam@243 522 float t0 = m_minbpm; // our minimum detected tempo
cannam@243 523 float t1 = m_maxbpm; // our maximum detected tempo
cannam@216 524
cannam@207 525 int p0 = tempo2lag(t1);
cannam@207 526 int p1 = tempo2lag(t0);
cannam@198 527
cannam@200 528 std::map<float, int> candidates;
cannam@198 529
cannam@271 530 for (int i = p0; i <= p1 && i+1 < n/2; ++i) {
cannam@198 531
cannam@209 532 if (m_fr[i] > m_fr[i-1] &&
cannam@209 533 m_fr[i] > m_fr[i+1]) {
cannam@255 534
cannam@255 535 // This is a peak in the filtered autocorrelation: stick
cannam@255 536 // it into the map from filtered autocorrelation to lag
cannam@255 537 // index -- this sorts our peaks by filtered acf value
cannam@255 538
cannam@209 539 candidates[m_fr[i]] = i;
cannam@209 540 }
cannam@198 541
cannam@255 542 // Also return the filtered autocorrelation in its own output
cannam@255 543
cannam@208 544 feature.timestamp = m_start +
cannam@208 545 RealTime::frame2RealTime(i * m_stepSize, m_inputSampleRate);
cannam@200 546 feature.values[0] = m_fr[i];
cannam@199 547 sprintf(buffer, "%.1f bpm", lag2tempo(i));
cannam@200 548 if (i == p1 || i == n/2-2) feature.label = "";
cannam@200 549 else feature.label = buffer;
cannam@200 550 fs[FilteredACFOutput].push_back(feature);
cannam@198 551 }
cannam@198 552
cannam@200 553 if (candidates.empty()) {
cannam@207 554 cerr << "No tempo candidates!" << endl;
cannam@200 555 return fs;
cannam@200 556 }
cannam@198 557
cannam@198 558 feature.hasTimestamp = true;
cannam@198 559 feature.timestamp = m_start;
cannam@198 560
cannam@198 561 feature.hasDuration = true;
cannam@198 562 feature.duration = m_lasttime - m_start;
cannam@198 563
cannam@255 564 // The map contains only peaks and is sorted by filtered acf
cannam@255 565 // value, so the final element in it is our "best" tempo guess
cannam@255 566
cannam@200 567 std::map<float, int>::const_iterator ci = candidates.end();
cannam@200 568 --ci;
cannam@200 569 int maxpi = ci->second;
cannam@198 570
cannam@204 571 if (m_t[maxpi] > 0) {
cannam@255 572
cannam@255 573 // This lag has an adjusted tempo from the averaging process:
cannam@255 574 // use it
cannam@255 575
cannam@204 576 feature.values[0] = m_t[maxpi];
cannam@255 577
cannam@204 578 } else {
cannam@255 579
cannam@255 580 // shouldn't happen -- it would imply that this high value was
cannam@255 581 // not a peak!
cannam@255 582
cannam@204 583 feature.values[0] = lag2tempo(maxpi);
cannam@207 584 cerr << "WARNING: No stored tempo for index " << maxpi << endl;
cannam@204 585 }
cannam@204 586
cannam@204 587 sprintf(buffer, "%.1f bpm", feature.values[0]);
cannam@199 588 feature.label = buffer;
cannam@199 589
cannam@255 590 // Return the best tempo in the main output
cannam@255 591
cannam@200 592 fs[TempoOutput].push_back(feature);
cannam@198 593
cannam@255 594 // And return the other estimates (up to the arbitrarily chosen
cannam@255 595 // number of 10 of them) in the candidates output
cannam@255 596
cannam@200 597 feature.values.clear();
cannam@200 598 feature.label = "";
cannam@200 599
cannam@255 600 while (feature.values.size() < 10) {
cannam@207 601 if (m_t[ci->second] > 0) {
cannam@207 602 feature.values.push_back(m_t[ci->second]);
cannam@207 603 } else {
cannam@207 604 feature.values.push_back(lag2tempo(ci->second));
cannam@207 605 }
cannam@200 606 if (ci == candidates.begin()) break;
cannam@200 607 --ci;
cannam@200 608 }
cannam@200 609
cannam@200 610 fs[CandidatesOutput].push_back(feature);
cannam@200 611
cannam@198 612 return fs;
cannam@198 613 }
cannam@243 614
cannam@243 615
cannam@243 616
cannam@243 617 FixedTempoEstimator::FixedTempoEstimator(float inputSampleRate) :
cannam@243 618 Plugin(inputSampleRate),
cannam@243 619 m_d(new D(inputSampleRate))
cannam@243 620 {
cannam@243 621 }
cannam@243 622
cannam@243 623 FixedTempoEstimator::~FixedTempoEstimator()
cannam@243 624 {
cannam@271 625 delete m_d;
cannam@243 626 }
cannam@243 627
cannam@243 628 string
cannam@243 629 FixedTempoEstimator::getIdentifier() const
cannam@243 630 {
cannam@243 631 return "fixedtempo";
cannam@243 632 }
cannam@243 633
cannam@243 634 string
cannam@243 635 FixedTempoEstimator::getName() const
cannam@243 636 {
cannam@243 637 return "Simple Fixed Tempo Estimator";
cannam@243 638 }
cannam@243 639
cannam@243 640 string
cannam@243 641 FixedTempoEstimator::getDescription() const
cannam@243 642 {
cannam@243 643 return "Study a short section of audio and estimate its tempo, assuming the tempo is constant";
cannam@243 644 }
cannam@243 645
cannam@243 646 string
cannam@243 647 FixedTempoEstimator::getMaker() const
cannam@243 648 {
cannam@243 649 return "Vamp SDK Example Plugins";
cannam@243 650 }
cannam@243 651
cannam@243 652 int
cannam@243 653 FixedTempoEstimator::getPluginVersion() const
cannam@243 654 {
cannam@243 655 return 1;
cannam@243 656 }
cannam@243 657
cannam@243 658 string
cannam@243 659 FixedTempoEstimator::getCopyright() const
cannam@243 660 {
cannam@243 661 return "Code copyright 2008 Queen Mary, University of London. Freely redistributable (BSD license)";
cannam@243 662 }
cannam@243 663
cannam@243 664 size_t
cannam@243 665 FixedTempoEstimator::getPreferredStepSize() const
cannam@243 666 {
cannam@243 667 return m_d->getPreferredStepSize();
cannam@243 668 }
cannam@243 669
cannam@243 670 size_t
cannam@243 671 FixedTempoEstimator::getPreferredBlockSize() const
cannam@243 672 {
cannam@243 673 return m_d->getPreferredBlockSize();
cannam@243 674 }
cannam@243 675
cannam@243 676 bool
cannam@243 677 FixedTempoEstimator::initialise(size_t channels, size_t stepSize, size_t blockSize)
cannam@243 678 {
cannam@243 679 if (channels < getMinChannelCount() ||
cannam@243 680 channels > getMaxChannelCount()) return false;
cannam@243 681
cannam@243 682 return m_d->initialise(channels, stepSize, blockSize);
cannam@243 683 }
cannam@243 684
cannam@243 685 void
cannam@243 686 FixedTempoEstimator::reset()
cannam@243 687 {
cannam@243 688 return m_d->reset();
cannam@243 689 }
cannam@243 690
cannam@243 691 FixedTempoEstimator::ParameterList
cannam@243 692 FixedTempoEstimator::getParameterDescriptors() const
cannam@243 693 {
cannam@243 694 return m_d->getParameterDescriptors();
cannam@243 695 }
cannam@243 696
cannam@243 697 float
cannam@243 698 FixedTempoEstimator::getParameter(std::string id) const
cannam@243 699 {
cannam@243 700 return m_d->getParameter(id);
cannam@243 701 }
cannam@243 702
cannam@243 703 void
cannam@243 704 FixedTempoEstimator::setParameter(std::string id, float value)
cannam@243 705 {
cannam@243 706 m_d->setParameter(id, value);
cannam@243 707 }
cannam@243 708
cannam@243 709 FixedTempoEstimator::OutputList
cannam@243 710 FixedTempoEstimator::getOutputDescriptors() const
cannam@243 711 {
cannam@243 712 return m_d->getOutputDescriptors();
cannam@243 713 }
cannam@243 714
cannam@243 715 FixedTempoEstimator::FeatureSet
cannam@243 716 FixedTempoEstimator::process(const float *const *inputBuffers, RealTime ts)
cannam@243 717 {
cannam@243 718 return m_d->process(inputBuffers, ts);
cannam@243 719 }
cannam@243 720
cannam@243 721 FixedTempoEstimator::FeatureSet
cannam@243 722 FixedTempoEstimator::getRemainingFeatures()
cannam@243 723 {
cannam@243 724 return m_d->getRemainingFeatures();
cannam@243 725 }