annotate plugins/BeatTrack.cpp @ 148:88c05c0ac438

Edit some descriptions; don't expose tightness parameter, only alpha (on advice from MEPD)
author Chris Cannam <c.cannam@qmul.ac.uk>
date Mon, 02 Sep 2013 11:50:04 +0100
parents d169df0c0cbc
children c45f74550f38
rev   line source
c@27 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
c@27 2
c@27 3 /*
c@27 4 QM Vamp Plugin Set
c@27 5
c@27 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@27 13 */
c@27 14
c@27 15 #include "BeatTrack.h"
c@27 16
c@27 17 #include <dsp/onsets/DetectionFunction.h>
c@27 18 #include <dsp/onsets/PeakPicking.h>
c@27 19 #include <dsp/tempotracking/TempoTrack.h>
c@86 20 #include <dsp/tempotracking/TempoTrackV2.h>
c@27 21
c@27 22 using std::string;
c@27 23 using std::vector;
c@27 24 using std::cerr;
c@27 25 using std::endl;
c@27 26
c@86 27 float BeatTracker::m_stepSecs = 0.01161; // 512 samples at 44100
c@86 28
c@86 29 #define METHOD_OLD 0
c@86 30 #define METHOD_NEW 1
c@27 31
c@27 32 class BeatTrackerData
c@27 33 {
c@27 34 public:
c@27 35 BeatTrackerData(const DFConfig &config) : dfConfig(config) {
luis@144 36 df = new DetectionFunction(config);
c@27 37 }
c@27 38 ~BeatTrackerData() {
luis@144 39 delete df;
c@27 40 }
c@27 41 void reset() {
luis@144 42 delete df;
luis@144 43 df = new DetectionFunction(dfConfig);
luis@144 44 dfOutput.clear();
c@85 45 origin = Vamp::RealTime::zeroTime;
c@27 46 }
c@27 47
c@27 48 DFConfig dfConfig;
c@27 49 DetectionFunction *df;
c@27 50 vector<double> dfOutput;
c@85 51 Vamp::RealTime origin;
c@27 52 };
luis@144 53
c@27 54
c@27 55 BeatTracker::BeatTracker(float inputSampleRate) :
c@27 56 Vamp::Plugin(inputSampleRate),
c@27 57 m_d(0),
c@86 58 m_method(METHOD_NEW),
c@30 59 m_dfType(DF_COMPLEXSD),
luis@144 60 m_whiten(false),
luis@144 61 m_alpha(0.9), // MEPD new exposed parameter for beat tracker, default value = 0.9 (as old version)
c@148 62 m_tightness(4.),
luis@144 63 m_inputtempo(120.), // MEPD new exposed parameter for beat tracker, default value = 120. (as old version)
luis@144 64 m_constraintempo(false) // MEPD new exposed parameter for beat tracker, default value = false (as old version)
luis@144 65 // calling the beat tracker with these default parameters will give the same output as the previous existing version
luis@144 66
c@27 67 {
c@27 68 }
c@27 69
c@27 70 BeatTracker::~BeatTracker()
c@27 71 {
c@27 72 delete m_d;
c@27 73 }
c@27 74
c@27 75 string
c@27 76 BeatTracker::getIdentifier() const
c@27 77 {
c@27 78 return "qm-tempotracker";
c@27 79 }
c@27 80
c@27 81 string
c@27 82 BeatTracker::getName() const
c@27 83 {
c@27 84 return "Tempo and Beat Tracker";
c@27 85 }
c@27 86
c@27 87 string
c@27 88 BeatTracker::getDescription() const
c@27 89 {
c@27 90 return "Estimate beat locations and tempo";
c@27 91 }
c@27 92
c@27 93 string
c@27 94 BeatTracker::getMaker() const
c@27 95 {
c@50 96 return "Queen Mary, University of London";
c@27 97 }
c@27 98
c@27 99 int
c@27 100 BeatTracker::getPluginVersion() const
c@27 101 {
c@145 102 return 6;
c@27 103 }
c@27 104
c@27 105 string
c@27 106 BeatTracker::getCopyright() const
c@27 107 {
luis@144 108 return "Plugin by Christian Landone and Matthew Davies. Copyright (c) 2006-2012 QMUL - All Rights Reserved";
c@27 109 }
c@27 110
c@27 111 BeatTracker::ParameterList
c@27 112 BeatTracker::getParameterDescriptors() const
c@27 113 {
c@27 114 ParameterList list;
c@27 115
c@27 116 ParameterDescriptor desc;
c@86 117
c@86 118 desc.identifier = "method";
c@86 119 desc.name = "Beat Tracking Method";
c@119 120 desc.description = "Basic method to use ";
c@86 121 desc.minValue = 0;
c@86 122 desc.maxValue = 1;
c@86 123 desc.defaultValue = METHOD_NEW;
c@86 124 desc.isQuantized = true;
c@86 125 desc.quantizeStep = 1;
c@86 126 desc.valueNames.push_back("Old");
c@86 127 desc.valueNames.push_back("New");
c@86 128 list.push_back(desc);
c@86 129
c@27 130 desc.identifier = "dftype";
c@27 131 desc.name = "Onset Detection Function Type";
c@27 132 desc.description = "Method used to calculate the onset detection function";
c@27 133 desc.minValue = 0;
c@31 134 desc.maxValue = 4;
c@27 135 desc.defaultValue = 3;
c@86 136 desc.valueNames.clear();
c@27 137 desc.valueNames.push_back("High-Frequency Content");
c@27 138 desc.valueNames.push_back("Spectral Difference");
c@27 139 desc.valueNames.push_back("Phase Deviation");
c@27 140 desc.valueNames.push_back("Complex Domain");
c@27 141 desc.valueNames.push_back("Broadband Energy Rise");
c@27 142 list.push_back(desc);
c@27 143
c@30 144 desc.identifier = "whiten";
c@30 145 desc.name = "Adaptive Whitening";
c@30 146 desc.description = "Normalize frequency bin magnitudes relative to recent peak levels";
c@30 147 desc.minValue = 0;
c@30 148 desc.maxValue = 1;
c@30 149 desc.defaultValue = 0;
c@30 150 desc.isQuantized = true;
c@30 151 desc.quantizeStep = 1;
c@30 152 desc.unit = "";
c@30 153 desc.valueNames.clear();
c@30 154 list.push_back(desc);
c@30 155
luis@144 156 // MEPD new exposed parameter - used in the dynamic programming part of the beat tracker
luis@144 157 //Alpha Parameter of Beat Tracker
luis@144 158 desc.identifier = "alpha";
luis@144 159 desc.name = "Alpha";
luis@144 160 desc.description = "Inertia - Flexibility Trade Off";
luis@144 161 desc.minValue = 0.1;
luis@144 162 desc.maxValue = 0.99;
luis@144 163 desc.defaultValue = 0.90;
luis@144 164 desc.unit = "";
luis@144 165 desc.isQuantized = false;
luis@144 166 list.push_back(desc);
luis@144 167
c@148 168 // We aren't exposing tightness as a parameter, it's fixed at 4
luis@144 169
luis@144 170 // MEPD new exposed parameter - used in the periodicity estimation
luis@144 171 //User input tempo
luis@144 172 desc.identifier = "inputtempo";
c@148 173 desc.name = "Tempo Hint";
c@148 174 desc.description = "User-defined tempo on which to centre the probability distribution";
luis@144 175 desc.minValue = 50;
luis@144 176 desc.maxValue = 250;
luis@144 177 desc.defaultValue = 120;
luis@144 178 desc.unit = "BPM";
luis@144 179 desc.isQuantized = true;
luis@144 180 list.push_back(desc);
luis@144 181
luis@144 182 // MEPD new exposed parameter - used in periodicity estimation
luis@144 183 desc.identifier = "constraintempo";
luis@144 184 desc.name = "Constrain Tempo";
c@148 185 desc.description = "Constrain more tightly around the tempo hint, using a Gaussian weighting instead of Rayleigh";
luis@144 186 desc.minValue = 0;
luis@144 187 desc.maxValue = 1;
luis@144 188 desc.defaultValue = 0;
luis@144 189 desc.isQuantized = true;
luis@144 190 desc.quantizeStep = 1;
luis@144 191 desc.unit = "";
luis@144 192 desc.valueNames.clear();
luis@144 193 list.push_back(desc);
luis@144 194
luis@144 195
luis@144 196
c@27 197 return list;
c@27 198 }
c@27 199
c@27 200 float
c@27 201 BeatTracker::getParameter(std::string name) const
c@27 202 {
c@27 203 if (name == "dftype") {
c@27 204 switch (m_dfType) {
c@27 205 case DF_HFC: return 0;
c@27 206 case DF_SPECDIFF: return 1;
c@27 207 case DF_PHASEDEV: return 2;
c@27 208 default: case DF_COMPLEXSD: return 3;
c@27 209 case DF_BROADBAND: return 4;
c@27 210 }
c@86 211 } else if (name == "method") {
c@86 212 return m_method;
c@30 213 } else if (name == "whiten") {
luis@144 214 return m_whiten ? 1.0 : 0.0;
luis@144 215 } else if (name == "alpha") {
luis@144 216 return m_alpha;
luis@144 217 } else if (name == "inputtempo") {
luis@144 218 return m_inputtempo;
luis@144 219 } else if (name == "constraintempo") {
luis@144 220 return m_constraintempo ? 1.0 : 0.0;
c@27 221 }
c@27 222 return 0.0;
c@27 223 }
c@27 224
c@27 225 void
c@27 226 BeatTracker::setParameter(std::string name, float value)
c@27 227 {
c@27 228 if (name == "dftype") {
c@27 229 switch (lrintf(value)) {
c@27 230 case 0: m_dfType = DF_HFC; break;
c@27 231 case 1: m_dfType = DF_SPECDIFF; break;
c@27 232 case 2: m_dfType = DF_PHASEDEV; break;
c@27 233 default: case 3: m_dfType = DF_COMPLEXSD; break;
c@27 234 case 4: m_dfType = DF_BROADBAND; break;
c@27 235 }
c@86 236 } else if (name == "method") {
c@86 237 m_method = lrintf(value);
c@30 238 } else if (name == "whiten") {
c@30 239 m_whiten = (value > 0.5);
luis@144 240 } else if (name == "alpha") {
luis@144 241 m_alpha = value;
luis@144 242 } else if (name == "inputtempo") {
luis@144 243 m_inputtempo = value;
luis@144 244 } else if (name == "constraintempo") {
luis@144 245 m_constraintempo = (value > 0.5);
c@27 246 }
c@27 247 }
c@27 248
c@27 249 bool
c@27 250 BeatTracker::initialise(size_t channels, size_t stepSize, size_t blockSize)
c@27 251 {
c@27 252 if (m_d) {
luis@144 253 delete m_d;
luis@144 254 m_d = 0;
c@27 255 }
c@27 256
c@27 257 if (channels < getMinChannelCount() ||
luis@144 258 channels > getMaxChannelCount()) {
c@27 259 std::cerr << "BeatTracker::initialise: Unsupported channel count: "
c@27 260 << channels << std::endl;
c@27 261 return false;
c@27 262 }
c@27 263
c@28 264 if (stepSize != getPreferredStepSize()) {
c@28 265 std::cerr << "ERROR: BeatTracker::initialise: Unsupported step size for this sample rate: "
c@28 266 << stepSize << " (wanted " << (getPreferredStepSize()) << ")" << std::endl;
c@27 267 return false;
c@27 268 }
c@27 269
c@28 270 if (blockSize != getPreferredBlockSize()) {
c@29 271 std::cerr << "WARNING: BeatTracker::initialise: Sub-optimal block size for this sample rate: "
c@28 272 << blockSize << " (wanted " << getPreferredBlockSize() << ")" << std::endl;
c@28 273 // return false;
c@27 274 }
c@27 275
c@27 276 DFConfig dfConfig;
c@27 277 dfConfig.DFType = m_dfType;
c@27 278 dfConfig.stepSize = stepSize;
c@27 279 dfConfig.frameLength = blockSize;
c@27 280 dfConfig.dbRise = 3;
c@30 281 dfConfig.adaptiveWhitening = m_whiten;
c@30 282 dfConfig.whiteningRelaxCoeff = -1;
c@30 283 dfConfig.whiteningFloor = -1;
luis@144 284
c@27 285 m_d = new BeatTrackerData(dfConfig);
c@27 286 return true;
c@27 287 }
c@27 288
c@27 289 void
c@27 290 BeatTracker::reset()
c@27 291 {
c@27 292 if (m_d) m_d->reset();
c@27 293 }
c@27 294
c@27 295 size_t
c@27 296 BeatTracker::getPreferredStepSize() const
c@27 297 {
c@27 298 size_t step = size_t(m_inputSampleRate * m_stepSecs + 0.0001);
c@27 299 // std::cerr << "BeatTracker::getPreferredStepSize: input sample rate is " << m_inputSampleRate << ", step size is " << step << std::endl;
c@27 300 return step;
c@27 301 }
c@27 302
c@27 303 size_t
c@27 304 BeatTracker::getPreferredBlockSize() const
c@27 305 {
c@28 306 size_t theoretical = getPreferredStepSize() * 2;
c@28 307
c@52 308 // I think this is not necessarily going to be a power of two, and
c@52 309 // the host might have a problem with that, but I'm not sure we
c@52 310 // can do much about it here
c@28 311 return theoretical;
c@27 312 }
c@27 313
c@27 314 BeatTracker::OutputList
c@27 315 BeatTracker::getOutputDescriptors() const
c@27 316 {
c@27 317 OutputList list;
c@27 318
c@27 319 OutputDescriptor beat;
c@27 320 beat.identifier = "beats";
c@27 321 beat.name = "Beats";
c@27 322 beat.description = "Estimated metrical beat locations";
c@27 323 beat.unit = "";
c@27 324 beat.hasFixedBinCount = true;
c@27 325 beat.binCount = 0;
c@27 326 beat.sampleType = OutputDescriptor::VariableSampleRate;
c@27 327 beat.sampleRate = 1.0 / m_stepSecs;
c@27 328
c@27 329 OutputDescriptor df;
c@27 330 df.identifier = "detection_fn";
c@27 331 df.name = "Onset Detection Function";
c@27 332 df.description = "Probability function of note onset likelihood";
c@27 333 df.unit = "";
c@27 334 df.hasFixedBinCount = true;
c@27 335 df.binCount = 1;
c@27 336 df.hasKnownExtents = false;
c@27 337 df.isQuantized = false;
c@27 338 df.sampleType = OutputDescriptor::OneSamplePerStep;
c@27 339
c@27 340 OutputDescriptor tempo;
c@27 341 tempo.identifier = "tempo";
c@27 342 tempo.name = "Tempo";
c@27 343 tempo.description = "Locked tempo estimates";
c@27 344 tempo.unit = "bpm";
c@27 345 tempo.hasFixedBinCount = true;
c@27 346 tempo.binCount = 1;
c@31 347 tempo.hasKnownExtents = false;
c@31 348 tempo.isQuantized = false;
c@27 349 tempo.sampleType = OutputDescriptor::VariableSampleRate;
c@27 350 tempo.sampleRate = 1.0 / m_stepSecs;
c@27 351
c@27 352 list.push_back(beat);
c@27 353 list.push_back(df);
c@27 354 list.push_back(tempo);
c@27 355
c@27 356 return list;
c@27 357 }
c@27 358
c@27 359 BeatTracker::FeatureSet
c@27 360 BeatTracker::process(const float *const *inputBuffers,
c@85 361 Vamp::RealTime timestamp)
c@27 362 {
c@27 363 if (!m_d) {
luis@144 364 cerr << "ERROR: BeatTracker::process: "
luis@144 365 << "BeatTracker has not been initialised"
luis@144 366 << endl;
luis@144 367 return FeatureSet();
c@27 368 }
c@27 369
c@27 370 size_t len = m_d->dfConfig.frameLength / 2;
c@27 371
c@27 372 double *magnitudes = new double[len];
c@27 373 double *phases = new double[len];
c@27 374
c@27 375 // We only support a single input channel
c@27 376
c@27 377 for (size_t i = 0; i < len; ++i) {
c@27 378
c@27 379 magnitudes[i] = sqrt(inputBuffers[0][i*2 ] * inputBuffers[0][i*2 ] +
c@27 380 inputBuffers[0][i*2+1] * inputBuffers[0][i*2+1]);
c@27 381
luis@144 382 phases[i] = atan2(-inputBuffers[0][i*2+1], inputBuffers[0][i*2]);
c@27 383 }
c@27 384
c@27 385 double output = m_d->df->process(magnitudes, phases);
c@27 386
c@27 387 delete[] magnitudes;
c@27 388 delete[] phases;
c@27 389
c@85 390 if (m_d->dfOutput.empty()) m_d->origin = timestamp;
c@85 391
c@27 392 m_d->dfOutput.push_back(output);
c@27 393
c@27 394 FeatureSet returnFeatures;
c@27 395
c@27 396 Feature feature;
c@27 397 feature.hasTimestamp = false;
c@27 398 feature.values.push_back(output);
c@27 399
c@27 400 returnFeatures[1].push_back(feature); // detection function is output 1
c@27 401 return returnFeatures;
c@27 402 }
c@27 403
c@27 404 BeatTracker::FeatureSet
c@27 405 BeatTracker::getRemainingFeatures()
c@27 406 {
c@27 407 if (!m_d) {
luis@144 408 cerr << "ERROR: BeatTracker::getRemainingFeatures: "
luis@144 409 << "BeatTracker has not been initialised"
luis@144 410 << endl;
luis@144 411 return FeatureSet();
c@27 412 }
c@27 413
c@86 414 if (m_method == METHOD_OLD) return beatTrackOld();
c@86 415 else return beatTrackNew();
c@86 416 }
c@86 417
c@86 418 BeatTracker::FeatureSet
c@86 419 BeatTracker::beatTrackOld()
c@86 420 {
c@27 421 double aCoeffs[] = { 1.0000, -0.5949, 0.2348 };
c@27 422 double bCoeffs[] = { 0.1600, 0.3200, 0.1600 };
c@27 423
c@27 424 TTParams ttParams;
c@27 425 ttParams.winLength = 512;
c@27 426 ttParams.lagLength = 128;
c@27 427 ttParams.LPOrd = 2;
c@27 428 ttParams.LPACoeffs = aCoeffs;
c@27 429 ttParams.LPBCoeffs = bCoeffs;
c@27 430 ttParams.alpha = 9;
c@27 431 ttParams.WinT.post = 8;
c@27 432 ttParams.WinT.pre = 7;
c@27 433
c@27 434 TempoTrack tempoTracker(ttParams);
c@27 435
c@87 436 vector<double> tempi;
c@87 437 vector<int> beats = tempoTracker.process(m_d->dfOutput, &tempi);
c@27 438
c@27 439 FeatureSet returnFeatures;
c@27 440
c@27 441 char label[100];
c@27 442
c@27 443 for (size_t i = 0; i < beats.size(); ++i) {
c@27 444
luis@144 445 size_t frame = beats[i] * m_d->dfConfig.stepSize;
c@27 446
luis@144 447 Feature feature;
luis@144 448 feature.hasTimestamp = true;
luis@144 449 feature.timestamp = m_d->origin + Vamp::RealTime::frame2RealTime
luis@144 450 (frame, lrintf(m_inputSampleRate));
c@27 451
luis@144 452 float bpm = 0.0;
luis@144 453 int frameIncrement = 0;
c@27 454
luis@144 455 if (i < beats.size() - 1) {
c@27 456
luis@144 457 frameIncrement = (beats[i+1] - beats[i]) * m_d->dfConfig.stepSize;
c@27 458
luis@144 459 // one beat is frameIncrement frames, so there are
luis@144 460 // samplerate/frameIncrement bps, so
luis@144 461 // 60*samplerate/frameIncrement bpm
c@27 462
luis@144 463 if (frameIncrement > 0) {
luis@144 464 bpm = (60.0 * m_inputSampleRate) / frameIncrement;
luis@144 465 bpm = int(bpm * 100.0 + 0.5) / 100.0;
c@27 466 sprintf(label, "%.2f bpm", bpm);
c@27 467 feature.label = label;
luis@144 468 }
luis@144 469 }
c@27 470
luis@144 471 returnFeatures[0].push_back(feature); // beats are output 0
c@27 472 }
c@27 473
c@27 474 double prevTempo = 0.0;
c@27 475
c@87 476 for (size_t i = 0; i < tempi.size(); ++i) {
c@27 477
c@27 478 size_t frame = i * m_d->dfConfig.stepSize * ttParams.lagLength;
c@27 479
c@27 480 // std::cerr << "unit " << i << ", step size " << m_d->dfConfig.stepSize << ", hop " << ttParams.lagLength << ", frame = " << frame << std::endl;
luis@144 481
c@87 482 if (tempi[i] > 1 && int(tempi[i] * 100) != int(prevTempo * 100)) {
c@27 483 Feature feature;
c@27 484 feature.hasTimestamp = true;
c@85 485 feature.timestamp = m_d->origin + Vamp::RealTime::frame2RealTime
c@27 486 (frame, lrintf(m_inputSampleRate));
c@87 487 feature.values.push_back(tempi[i]);
c@87 488 sprintf(label, "%.2f bpm", tempi[i]);
c@27 489 feature.label = label;
c@27 490 returnFeatures[2].push_back(feature); // tempo is output 2
c@87 491 prevTempo = tempi[i];
c@27 492 }
c@27 493 }
c@27 494
c@27 495 return returnFeatures;
c@27 496 }
c@27 497
c@86 498 BeatTracker::FeatureSet
c@86 499 BeatTracker::beatTrackNew()
c@86 500 {
c@86 501 vector<double> df;
c@86 502 vector<double> beatPeriod;
c@87 503 vector<double> tempi;
c@86 504
c@120 505 size_t nonZeroCount = m_d->dfOutput.size();
c@120 506 while (nonZeroCount > 0) {
c@120 507 if (m_d->dfOutput[nonZeroCount-1] > 0.0) {
c@120 508 break;
c@120 509 }
c@120 510 --nonZeroCount;
c@120 511 }
c@120 512
c@147 513 // std::cerr << "Note: nonZeroCount was " << m_d->dfOutput.size() << ", is now " << nonZeroCount << std::endl;
c@120 514
c@120 515 for (size_t i = 2; i < nonZeroCount; ++i) { // discard first two elts
c@86 516 df.push_back(m_d->dfOutput[i]);
c@86 517 beatPeriod.push_back(0.0);
c@86 518 }
c@86 519 if (df.empty()) return FeatureSet();
c@86 520
c@88 521 TempoTrackV2 tt(m_inputSampleRate, m_d->dfConfig.stepSize);
c@86 522
luis@144 523
luis@144 524 // MEPD - note this function is now passed 2 new parameters, m_inputtempo and m_constraintempo
luis@144 525 tt.calculateBeatPeriod(df, beatPeriod, tempi, m_inputtempo, m_constraintempo);
c@86 526
c@86 527 vector<double> beats;
luis@144 528
luis@144 529 // MEPD - note this function is now passed 2 new parameters, m_alpha and m_tightness
luis@144 530 tt.calculateBeats(df, beatPeriod, beats, m_alpha, m_tightness);
luis@144 531
c@86 532 FeatureSet returnFeatures;
c@86 533
c@86 534 char label[100];
c@86 535
c@86 536 for (size_t i = 0; i < beats.size(); ++i) {
c@86 537
luis@144 538 size_t frame = beats[i] * m_d->dfConfig.stepSize;
c@86 539
luis@144 540 Feature feature;
luis@144 541 feature.hasTimestamp = true;
luis@144 542 feature.timestamp = m_d->origin + Vamp::RealTime::frame2RealTime
luis@144 543 (frame, lrintf(m_inputSampleRate));
c@86 544
luis@144 545 float bpm = 0.0;
luis@144 546 int frameIncrement = 0;
c@86 547
luis@144 548 if (i+1 < beats.size()) {
c@86 549
luis@144 550 frameIncrement = (beats[i+1] - beats[i]) * m_d->dfConfig.stepSize;
c@86 551
luis@144 552 // one beat is frameIncrement frames, so there are
luis@144 553 // samplerate/frameIncrement bps, so
luis@144 554 // 60*samplerate/frameIncrement bpm
luis@144 555
luis@144 556 if (frameIncrement > 0) {
luis@144 557 bpm = (60.0 * m_inputSampleRate) / frameIncrement;
luis@144 558 bpm = int(bpm * 100.0 + 0.5) / 100.0;
c@86 559 sprintf(label, "%.2f bpm", bpm);
c@86 560 feature.label = label;
luis@144 561 }
luis@144 562 }
c@86 563
luis@144 564 returnFeatures[0].push_back(feature); // beats are output 0
c@86 565 }
c@86 566
c@87 567 double prevTempo = 0.0;
c@87 568
c@87 569 for (size_t i = 0; i < tempi.size(); ++i) {
c@87 570
luis@144 571 size_t frame = i * m_d->dfConfig.stepSize;
luis@144 572
c@87 573 if (tempi[i] > 1 && int(tempi[i] * 100) != int(prevTempo * 100)) {
c@87 574 Feature feature;
c@87 575 feature.hasTimestamp = true;
c@87 576 feature.timestamp = m_d->origin + Vamp::RealTime::frame2RealTime
c@87 577 (frame, lrintf(m_inputSampleRate));
c@87 578 feature.values.push_back(tempi[i]);
c@87 579 sprintf(label, "%.2f bpm", tempi[i]);
c@87 580 feature.label = label;
c@87 581 returnFeatures[2].push_back(feature); // tempo is output 2
c@87 582 prevTempo = tempi[i];
c@87 583 }
c@87 584 }
c@87 585
c@86 586 return returnFeatures;
c@86 587 }