annotate examples/FixedTempoEstimator.cpp @ 502:d129bf797f24

Avoid buffer underflow
author Chris Cannam
date Tue, 30 Jan 2018 15:28:15 +0000
parents 7d59dd1ba5de
children
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
Chris@502 532 if (i < 1) continue;
Chris@502 533
cannam@209 534 if (m_fr[i] > m_fr[i-1] &&
cannam@209 535 m_fr[i] > m_fr[i+1]) {
cannam@255 536
cannam@255 537 // This is a peak in the filtered autocorrelation: stick
cannam@255 538 // it into the map from filtered autocorrelation to lag
cannam@255 539 // index -- this sorts our peaks by filtered acf value
cannam@255 540
cannam@209 541 candidates[m_fr[i]] = i;
cannam@209 542 }
cannam@198 543
cannam@255 544 // Also return the filtered autocorrelation in its own output
cannam@255 545
cannam@208 546 feature.timestamp = m_start +
cannam@208 547 RealTime::frame2RealTime(i * m_stepSize, m_inputSampleRate);
cannam@200 548 feature.values[0] = m_fr[i];
cannam@199 549 sprintf(buffer, "%.1f bpm", lag2tempo(i));
cannam@200 550 if (i == p1 || i == n/2-2) feature.label = "";
cannam@200 551 else feature.label = buffer;
cannam@200 552 fs[FilteredACFOutput].push_back(feature);
cannam@198 553 }
cannam@198 554
cannam@200 555 if (candidates.empty()) {
cannam@207 556 cerr << "No tempo candidates!" << endl;
cannam@200 557 return fs;
cannam@200 558 }
cannam@198 559
cannam@198 560 feature.hasTimestamp = true;
cannam@198 561 feature.timestamp = m_start;
cannam@198 562
cannam@198 563 feature.hasDuration = true;
cannam@198 564 feature.duration = m_lasttime - m_start;
cannam@198 565
cannam@255 566 // The map contains only peaks and is sorted by filtered acf
cannam@255 567 // value, so the final element in it is our "best" tempo guess
cannam@255 568
cannam@200 569 std::map<float, int>::const_iterator ci = candidates.end();
cannam@200 570 --ci;
cannam@200 571 int maxpi = ci->second;
cannam@198 572
cannam@204 573 if (m_t[maxpi] > 0) {
cannam@255 574
cannam@255 575 // This lag has an adjusted tempo from the averaging process:
cannam@255 576 // use it
cannam@255 577
cannam@204 578 feature.values[0] = m_t[maxpi];
cannam@255 579
cannam@204 580 } else {
cannam@255 581
cannam@255 582 // shouldn't happen -- it would imply that this high value was
cannam@255 583 // not a peak!
cannam@255 584
cannam@204 585 feature.values[0] = lag2tempo(maxpi);
cannam@207 586 cerr << "WARNING: No stored tempo for index " << maxpi << endl;
cannam@204 587 }
cannam@204 588
cannam@204 589 sprintf(buffer, "%.1f bpm", feature.values[0]);
cannam@199 590 feature.label = buffer;
cannam@199 591
cannam@255 592 // Return the best tempo in the main output
cannam@255 593
cannam@200 594 fs[TempoOutput].push_back(feature);
cannam@198 595
cannam@255 596 // And return the other estimates (up to the arbitrarily chosen
cannam@255 597 // number of 10 of them) in the candidates output
cannam@255 598
cannam@200 599 feature.values.clear();
cannam@200 600 feature.label = "";
cannam@200 601
cannam@255 602 while (feature.values.size() < 10) {
cannam@207 603 if (m_t[ci->second] > 0) {
cannam@207 604 feature.values.push_back(m_t[ci->second]);
cannam@207 605 } else {
cannam@207 606 feature.values.push_back(lag2tempo(ci->second));
cannam@207 607 }
cannam@200 608 if (ci == candidates.begin()) break;
cannam@200 609 --ci;
cannam@200 610 }
cannam@200 611
cannam@200 612 fs[CandidatesOutput].push_back(feature);
cannam@200 613
cannam@198 614 return fs;
cannam@198 615 }
cannam@243 616
cannam@243 617
cannam@243 618
cannam@243 619 FixedTempoEstimator::FixedTempoEstimator(float inputSampleRate) :
cannam@243 620 Plugin(inputSampleRate),
cannam@243 621 m_d(new D(inputSampleRate))
cannam@243 622 {
cannam@243 623 }
cannam@243 624
cannam@243 625 FixedTempoEstimator::~FixedTempoEstimator()
cannam@243 626 {
cannam@271 627 delete m_d;
cannam@243 628 }
cannam@243 629
cannam@243 630 string
cannam@243 631 FixedTempoEstimator::getIdentifier() const
cannam@243 632 {
cannam@243 633 return "fixedtempo";
cannam@243 634 }
cannam@243 635
cannam@243 636 string
cannam@243 637 FixedTempoEstimator::getName() const
cannam@243 638 {
cannam@243 639 return "Simple Fixed Tempo Estimator";
cannam@243 640 }
cannam@243 641
cannam@243 642 string
cannam@243 643 FixedTempoEstimator::getDescription() const
cannam@243 644 {
cannam@243 645 return "Study a short section of audio and estimate its tempo, assuming the tempo is constant";
cannam@243 646 }
cannam@243 647
cannam@243 648 string
cannam@243 649 FixedTempoEstimator::getMaker() const
cannam@243 650 {
cannam@243 651 return "Vamp SDK Example Plugins";
cannam@243 652 }
cannam@243 653
cannam@243 654 int
cannam@243 655 FixedTempoEstimator::getPluginVersion() const
cannam@243 656 {
cannam@243 657 return 1;
cannam@243 658 }
cannam@243 659
cannam@243 660 string
cannam@243 661 FixedTempoEstimator::getCopyright() const
cannam@243 662 {
cannam@243 663 return "Code copyright 2008 Queen Mary, University of London. Freely redistributable (BSD license)";
cannam@243 664 }
cannam@243 665
cannam@243 666 size_t
cannam@243 667 FixedTempoEstimator::getPreferredStepSize() const
cannam@243 668 {
cannam@243 669 return m_d->getPreferredStepSize();
cannam@243 670 }
cannam@243 671
cannam@243 672 size_t
cannam@243 673 FixedTempoEstimator::getPreferredBlockSize() const
cannam@243 674 {
cannam@243 675 return m_d->getPreferredBlockSize();
cannam@243 676 }
cannam@243 677
cannam@243 678 bool
cannam@243 679 FixedTempoEstimator::initialise(size_t channels, size_t stepSize, size_t blockSize)
cannam@243 680 {
cannam@243 681 if (channels < getMinChannelCount() ||
cannam@243 682 channels > getMaxChannelCount()) return false;
cannam@243 683
cannam@243 684 return m_d->initialise(channels, stepSize, blockSize);
cannam@243 685 }
cannam@243 686
cannam@243 687 void
cannam@243 688 FixedTempoEstimator::reset()
cannam@243 689 {
cannam@243 690 return m_d->reset();
cannam@243 691 }
cannam@243 692
cannam@243 693 FixedTempoEstimator::ParameterList
cannam@243 694 FixedTempoEstimator::getParameterDescriptors() const
cannam@243 695 {
cannam@243 696 return m_d->getParameterDescriptors();
cannam@243 697 }
cannam@243 698
cannam@243 699 float
cannam@243 700 FixedTempoEstimator::getParameter(std::string id) const
cannam@243 701 {
cannam@243 702 return m_d->getParameter(id);
cannam@243 703 }
cannam@243 704
cannam@243 705 void
cannam@243 706 FixedTempoEstimator::setParameter(std::string id, float value)
cannam@243 707 {
cannam@243 708 m_d->setParameter(id, value);
cannam@243 709 }
cannam@243 710
cannam@243 711 FixedTempoEstimator::OutputList
cannam@243 712 FixedTempoEstimator::getOutputDescriptors() const
cannam@243 713 {
cannam@243 714 return m_d->getOutputDescriptors();
cannam@243 715 }
cannam@243 716
cannam@243 717 FixedTempoEstimator::FeatureSet
cannam@243 718 FixedTempoEstimator::process(const float *const *inputBuffers, RealTime ts)
cannam@243 719 {
cannam@243 720 return m_d->process(inputBuffers, ts);
cannam@243 721 }
cannam@243 722
cannam@243 723 FixedTempoEstimator::FeatureSet
cannam@243 724 FixedTempoEstimator::getRemainingFeatures()
cannam@243 725 {
cannam@243 726 return m_d->getRemainingFeatures();
cannam@243 727 }