annotate examples/FixedTempoEstimator.cpp @ 255:88ef5ffdbe8d

* docs
author cannam
date Wed, 12 Nov 2008 14:11:01 +0000
parents 5bfed156b45d
children 23352e424631
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@198 9 Copyright 2006-2008 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@198 47
cannam@198 48
cannam@243 49 class FixedTempoEstimator::D
cannam@255 50 // this class just avoids us having to declare any data members in the header
cannam@243 51 {
cannam@243 52 public:
cannam@243 53 D(float inputSampleRate);
cannam@243 54 ~D();
cannam@243 55
cannam@243 56 size_t getPreferredStepSize() const { return 64; }
cannam@243 57 size_t getPreferredBlockSize() const { return 256; }
cannam@243 58
cannam@243 59 ParameterList getParameterDescriptors() const;
cannam@243 60 float getParameter(string id) const;
cannam@243 61 void setParameter(string id, float value);
cannam@243 62
cannam@243 63 OutputList getOutputDescriptors() const;
cannam@243 64
cannam@243 65 bool initialise(size_t channels, size_t stepSize, size_t blockSize);
cannam@243 66 void reset();
cannam@243 67 FeatureSet process(const float *const *, RealTime);
cannam@243 68 FeatureSet getRemainingFeatures();
cannam@243 69
cannam@243 70 private:
cannam@243 71 void calculate();
cannam@243 72 FeatureSet assembleFeatures();
cannam@243 73
cannam@243 74 float lag2tempo(int);
cannam@243 75 int tempo2lag(float);
cannam@243 76
cannam@243 77 float m_inputSampleRate;
cannam@243 78 size_t m_stepSize;
cannam@243 79 size_t m_blockSize;
cannam@243 80
cannam@243 81 float m_minbpm;
cannam@243 82 float m_maxbpm;
cannam@243 83 float m_maxdflen;
cannam@243 84
cannam@243 85 float *m_priorMagnitudes;
cannam@243 86
cannam@243 87 size_t m_dfsize;
cannam@243 88 float *m_df;
cannam@243 89 float *m_r;
cannam@243 90 float *m_fr;
cannam@243 91 float *m_t;
cannam@243 92 size_t m_n;
cannam@243 93
cannam@243 94 Vamp::RealTime m_start;
cannam@243 95 Vamp::RealTime m_lasttime;
cannam@243 96 };
cannam@243 97
cannam@243 98 FixedTempoEstimator::D::D(float inputSampleRate) :
cannam@243 99 m_inputSampleRate(inputSampleRate),
cannam@198 100 m_stepSize(0),
cannam@198 101 m_blockSize(0),
cannam@243 102 m_minbpm(50),
cannam@243 103 m_maxbpm(190),
cannam@243 104 m_maxdflen(10),
cannam@198 105 m_priorMagnitudes(0),
cannam@200 106 m_df(0),
cannam@200 107 m_r(0),
cannam@200 108 m_fr(0),
cannam@204 109 m_t(0),
cannam@200 110 m_n(0)
cannam@198 111 {
cannam@198 112 }
cannam@198 113
cannam@243 114 FixedTempoEstimator::D::~D()
cannam@198 115 {
cannam@198 116 delete[] m_priorMagnitudes;
cannam@198 117 delete[] m_df;
cannam@200 118 delete[] m_r;
cannam@200 119 delete[] m_fr;
cannam@204 120 delete[] m_t;
cannam@198 121 }
cannam@198 122
cannam@198 123 FixedTempoEstimator::ParameterList
cannam@243 124 FixedTempoEstimator::D::getParameterDescriptors() const
cannam@198 125 {
cannam@198 126 ParameterList list;
cannam@243 127
cannam@243 128 ParameterDescriptor d;
cannam@243 129 d.identifier = "minbpm";
cannam@243 130 d.name = "Minimum estimated tempo";
cannam@243 131 d.description = "Minimum beat-per-minute value which the tempo estimator is able to return";
cannam@243 132 d.unit = "bpm";
cannam@243 133 d.minValue = 10;
cannam@243 134 d.maxValue = 360;
cannam@243 135 d.defaultValue = 50;
cannam@243 136 d.isQuantized = false;
cannam@243 137 list.push_back(d);
cannam@243 138
cannam@243 139 d.identifier = "maxbpm";
cannam@243 140 d.name = "Maximum estimated tempo";
cannam@243 141 d.description = "Maximum beat-per-minute value which the tempo estimator is able to return";
cannam@243 142 d.defaultValue = 190;
cannam@243 143 list.push_back(d);
cannam@243 144
cannam@243 145 d.identifier = "maxdflen";
cannam@243 146 d.name = "Input duration to study";
cannam@243 147 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 148 d.unit = "s";
cannam@243 149 d.minValue = 2;
cannam@243 150 d.maxValue = 40;
cannam@243 151 d.defaultValue = 10;
cannam@243 152 list.push_back(d);
cannam@243 153
cannam@198 154 return list;
cannam@198 155 }
cannam@198 156
cannam@198 157 float
cannam@243 158 FixedTempoEstimator::D::getParameter(string id) const
cannam@198 159 {
cannam@243 160 if (id == "minbpm") {
cannam@243 161 return m_minbpm;
cannam@243 162 } else if (id == "maxbpm") {
cannam@243 163 return m_maxbpm;
cannam@243 164 } else if (id == "maxdflen") {
cannam@243 165 return m_maxdflen;
cannam@243 166 }
cannam@198 167 return 0.f;
cannam@198 168 }
cannam@198 169
cannam@198 170 void
cannam@243 171 FixedTempoEstimator::D::setParameter(string id, float value)
cannam@198 172 {
cannam@243 173 if (id == "minbpm") {
cannam@243 174 m_minbpm = value;
cannam@243 175 } else if (id == "maxbpm") {
cannam@243 176 m_maxbpm = value;
cannam@243 177 } else if (id == "maxdflen") {
cannam@243 178 m_maxdflen = value;
cannam@243 179 }
cannam@198 180 }
cannam@198 181
cannam@200 182 static int TempoOutput = 0;
cannam@200 183 static int CandidatesOutput = 1;
cannam@200 184 static int DFOutput = 2;
cannam@200 185 static int ACFOutput = 3;
cannam@200 186 static int FilteredACFOutput = 4;
cannam@200 187
cannam@198 188 FixedTempoEstimator::OutputList
cannam@243 189 FixedTempoEstimator::D::getOutputDescriptors() const
cannam@198 190 {
cannam@198 191 OutputList list;
cannam@198 192
cannam@198 193 OutputDescriptor d;
cannam@198 194 d.identifier = "tempo";
cannam@198 195 d.name = "Tempo";
cannam@198 196 d.description = "Estimated tempo";
cannam@198 197 d.unit = "bpm";
cannam@198 198 d.hasFixedBinCount = true;
cannam@198 199 d.binCount = 1;
cannam@198 200 d.hasKnownExtents = false;
cannam@198 201 d.isQuantized = false;
cannam@198 202 d.sampleType = OutputDescriptor::VariableSampleRate;
cannam@198 203 d.sampleRate = m_inputSampleRate;
cannam@198 204 d.hasDuration = true; // our returned tempo spans a certain range
cannam@198 205 list.push_back(d);
cannam@198 206
cannam@200 207 d.identifier = "candidates";
cannam@200 208 d.name = "Tempo candidates";
cannam@200 209 d.description = "Possible tempo estimates, one per bin with the most likely in the first bin";
cannam@200 210 d.unit = "bpm";
cannam@200 211 d.hasFixedBinCount = false;
cannam@200 212 list.push_back(d);
cannam@200 213
cannam@198 214 d.identifier = "detectionfunction";
cannam@198 215 d.name = "Detection Function";
cannam@198 216 d.description = "Onset detection function";
cannam@198 217 d.unit = "";
cannam@198 218 d.hasFixedBinCount = 1;
cannam@198 219 d.binCount = 1;
cannam@198 220 d.hasKnownExtents = true;
cannam@198 221 d.minValue = 0.0;
cannam@198 222 d.maxValue = 1.0;
cannam@198 223 d.isQuantized = false;
cannam@198 224 d.quantizeStep = 0.0;
cannam@198 225 d.sampleType = OutputDescriptor::FixedSampleRate;
cannam@198 226 if (m_stepSize) {
cannam@198 227 d.sampleRate = m_inputSampleRate / m_stepSize;
cannam@198 228 } else {
cannam@198 229 d.sampleRate = m_inputSampleRate / (getPreferredBlockSize()/2);
cannam@198 230 }
cannam@198 231 d.hasDuration = false;
cannam@198 232 list.push_back(d);
cannam@198 233
cannam@198 234 d.identifier = "acf";
cannam@198 235 d.name = "Autocorrelation Function";
cannam@198 236 d.description = "Autocorrelation of onset detection function";
cannam@198 237 d.hasKnownExtents = false;
cannam@201 238 d.unit = "r";
cannam@198 239 list.push_back(d);
cannam@198 240
cannam@198 241 d.identifier = "filtered_acf";
cannam@198 242 d.name = "Filtered Autocorrelation";
cannam@198 243 d.description = "Filtered autocorrelation of onset detection function";
cannam@201 244 d.unit = "r";
cannam@198 245 list.push_back(d);
cannam@198 246
cannam@198 247 return list;
cannam@198 248 }
cannam@198 249
cannam@243 250 bool
cannam@243 251 FixedTempoEstimator::D::initialise(size_t channels,
cannam@243 252 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@200 409 for (int j = i; j < n-1; ++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
cannam@207 434 int kmax = 0, kmin = 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
cannam@215 442 if (!have || (m_r[k] > kvmax)) { kmax = k; kvmax = m_r[k]; }
cannam@215 443 if (!have || (m_r[k] < kvmin)) { kmin = k; 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@200 530 for (int i = p0; i <= p1 && i < n/2-1; ++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@243 625 }
cannam@243 626
cannam@243 627 string
cannam@243 628 FixedTempoEstimator::getIdentifier() const
cannam@243 629 {
cannam@243 630 return "fixedtempo";
cannam@243 631 }
cannam@243 632
cannam@243 633 string
cannam@243 634 FixedTempoEstimator::getName() const
cannam@243 635 {
cannam@243 636 return "Simple Fixed Tempo Estimator";
cannam@243 637 }
cannam@243 638
cannam@243 639 string
cannam@243 640 FixedTempoEstimator::getDescription() const
cannam@243 641 {
cannam@243 642 return "Study a short section of audio and estimate its tempo, assuming the tempo is constant";
cannam@243 643 }
cannam@243 644
cannam@243 645 string
cannam@243 646 FixedTempoEstimator::getMaker() const
cannam@243 647 {
cannam@243 648 return "Vamp SDK Example Plugins";
cannam@243 649 }
cannam@243 650
cannam@243 651 int
cannam@243 652 FixedTempoEstimator::getPluginVersion() const
cannam@243 653 {
cannam@243 654 return 1;
cannam@243 655 }
cannam@243 656
cannam@243 657 string
cannam@243 658 FixedTempoEstimator::getCopyright() const
cannam@243 659 {
cannam@243 660 return "Code copyright 2008 Queen Mary, University of London. Freely redistributable (BSD license)";
cannam@243 661 }
cannam@243 662
cannam@243 663 size_t
cannam@243 664 FixedTempoEstimator::getPreferredStepSize() const
cannam@243 665 {
cannam@243 666 return m_d->getPreferredStepSize();
cannam@243 667 }
cannam@243 668
cannam@243 669 size_t
cannam@243 670 FixedTempoEstimator::getPreferredBlockSize() const
cannam@243 671 {
cannam@243 672 return m_d->getPreferredBlockSize();
cannam@243 673 }
cannam@243 674
cannam@243 675 bool
cannam@243 676 FixedTempoEstimator::initialise(size_t channels, size_t stepSize, size_t blockSize)
cannam@243 677 {
cannam@243 678 if (channels < getMinChannelCount() ||
cannam@243 679 channels > getMaxChannelCount()) return false;
cannam@243 680
cannam@243 681 return m_d->initialise(channels, stepSize, blockSize);
cannam@243 682 }
cannam@243 683
cannam@243 684 void
cannam@243 685 FixedTempoEstimator::reset()
cannam@243 686 {
cannam@243 687 return m_d->reset();
cannam@243 688 }
cannam@243 689
cannam@243 690 FixedTempoEstimator::ParameterList
cannam@243 691 FixedTempoEstimator::getParameterDescriptors() const
cannam@243 692 {
cannam@243 693 return m_d->getParameterDescriptors();
cannam@243 694 }
cannam@243 695
cannam@243 696 float
cannam@243 697 FixedTempoEstimator::getParameter(std::string id) const
cannam@243 698 {
cannam@243 699 return m_d->getParameter(id);
cannam@243 700 }
cannam@243 701
cannam@243 702 void
cannam@243 703 FixedTempoEstimator::setParameter(std::string id, float value)
cannam@243 704 {
cannam@243 705 m_d->setParameter(id, value);
cannam@243 706 }
cannam@243 707
cannam@243 708 FixedTempoEstimator::OutputList
cannam@243 709 FixedTempoEstimator::getOutputDescriptors() const
cannam@243 710 {
cannam@243 711 return m_d->getOutputDescriptors();
cannam@243 712 }
cannam@243 713
cannam@243 714 FixedTempoEstimator::FeatureSet
cannam@243 715 FixedTempoEstimator::process(const float *const *inputBuffers, RealTime ts)
cannam@243 716 {
cannam@243 717 return m_d->process(inputBuffers, ts);
cannam@243 718 }
cannam@243 719
cannam@243 720 FixedTempoEstimator::FeatureSet
cannam@243 721 FixedTempoEstimator::getRemainingFeatures()
cannam@243 722 {
cannam@243 723 return m_d->getRemainingFeatures();
cannam@243 724 }