annotate plugins/BeatTrack.cpp @ 266:d04675d44928 tip master

Refer to SDK from Github
author Chris Cannam <cannam@all-day-breakfast.com>
date Wed, 02 Jun 2021 14:41:26 +0100
parents f96ea0e4b475
children
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_alpha(0.9), // MEPD new exposed parameter for beat tracker, default value = 0.9 (as old version)
c@148 61 m_tightness(4.),
luis@144 62 m_inputtempo(120.), // MEPD new exposed parameter for beat tracker, default value = 120. (as old version)
c@178 63 m_constraintempo(false), // MEPD new exposed parameter for beat tracker, default value = false (as old version)
luis@144 64 // calling the beat tracker with these default parameters will give the same output as the previous existing version
c@178 65 m_whiten(false)
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 {
c@149 108 return "Plugin by Christian Landone and Matthew Davies. Copyright (c) 2006-2013 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@151 174 desc.description = "User-defined tempo on which to centre the tempo preference function";
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@153 370 size_t len = m_d->dfConfig.frameLength / 2 + 1;
c@27 371
c@153 372 double *reals = new double[len];
c@153 373 double *imags = 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@153 378 reals[i] = inputBuffers[0][i*2];
c@153 379 imags[i] = inputBuffers[0][i*2+1];
c@27 380 }
c@27 381
c@153 382 double output = m_d->df->processFrequencyDomain(reals, imags);
c@27 383
c@153 384 delete[] reals;
c@153 385 delete[] imags;
c@27 386
c@85 387 if (m_d->dfOutput.empty()) m_d->origin = timestamp;
c@85 388
c@27 389 m_d->dfOutput.push_back(output);
c@27 390
c@27 391 FeatureSet returnFeatures;
c@27 392
c@27 393 Feature feature;
c@27 394 feature.hasTimestamp = false;
c@27 395 feature.values.push_back(output);
c@27 396
c@27 397 returnFeatures[1].push_back(feature); // detection function is output 1
c@27 398 return returnFeatures;
c@27 399 }
c@27 400
c@27 401 BeatTracker::FeatureSet
c@27 402 BeatTracker::getRemainingFeatures()
c@27 403 {
c@27 404 if (!m_d) {
luis@144 405 cerr << "ERROR: BeatTracker::getRemainingFeatures: "
luis@144 406 << "BeatTracker has not been initialised"
luis@144 407 << endl;
luis@144 408 return FeatureSet();
c@27 409 }
c@27 410
c@86 411 if (m_method == METHOD_OLD) return beatTrackOld();
c@86 412 else return beatTrackNew();
c@86 413 }
c@86 414
c@86 415 BeatTracker::FeatureSet
c@86 416 BeatTracker::beatTrackOld()
c@86 417 {
c@27 418 double aCoeffs[] = { 1.0000, -0.5949, 0.2348 };
c@27 419 double bCoeffs[] = { 0.1600, 0.3200, 0.1600 };
c@27 420
c@27 421 TTParams ttParams;
c@27 422 ttParams.winLength = 512;
c@27 423 ttParams.lagLength = 128;
c@27 424 ttParams.LPOrd = 2;
c@27 425 ttParams.LPACoeffs = aCoeffs;
c@27 426 ttParams.LPBCoeffs = bCoeffs;
c@27 427 ttParams.alpha = 9;
c@27 428 ttParams.WinT.post = 8;
c@27 429 ttParams.WinT.pre = 7;
c@27 430
c@27 431 TempoTrack tempoTracker(ttParams);
c@27 432
c@87 433 vector<double> tempi;
c@87 434 vector<int> beats = tempoTracker.process(m_d->dfOutput, &tempi);
c@27 435
c@27 436 FeatureSet returnFeatures;
c@27 437
c@27 438 char label[100];
c@27 439
c@27 440 for (size_t i = 0; i < beats.size(); ++i) {
c@27 441
luis@144 442 size_t frame = beats[i] * m_d->dfConfig.stepSize;
c@27 443
luis@144 444 Feature feature;
luis@144 445 feature.hasTimestamp = true;
luis@144 446 feature.timestamp = m_d->origin + Vamp::RealTime::frame2RealTime
luis@144 447 (frame, lrintf(m_inputSampleRate));
c@27 448
luis@144 449 float bpm = 0.0;
luis@144 450 int frameIncrement = 0;
c@27 451
luis@144 452 if (i < beats.size() - 1) {
c@27 453
luis@144 454 frameIncrement = (beats[i+1] - beats[i]) * m_d->dfConfig.stepSize;
c@27 455
luis@144 456 // one beat is frameIncrement frames, so there are
luis@144 457 // samplerate/frameIncrement bps, so
luis@144 458 // 60*samplerate/frameIncrement bpm
c@27 459
luis@144 460 if (frameIncrement > 0) {
luis@144 461 bpm = (60.0 * m_inputSampleRate) / frameIncrement;
luis@144 462 bpm = int(bpm * 100.0 + 0.5) / 100.0;
c@27 463 sprintf(label, "%.2f bpm", bpm);
c@27 464 feature.label = label;
luis@144 465 }
luis@144 466 }
c@27 467
luis@144 468 returnFeatures[0].push_back(feature); // beats are output 0
c@27 469 }
c@27 470
c@27 471 double prevTempo = 0.0;
c@27 472
c@87 473 for (size_t i = 0; i < tempi.size(); ++i) {
c@27 474
c@27 475 size_t frame = i * m_d->dfConfig.stepSize * ttParams.lagLength;
c@27 476
c@27 477 // std::cerr << "unit " << i << ", step size " << m_d->dfConfig.stepSize << ", hop " << ttParams.lagLength << ", frame = " << frame << std::endl;
luis@144 478
c@87 479 if (tempi[i] > 1 && int(tempi[i] * 100) != int(prevTempo * 100)) {
c@27 480 Feature feature;
c@27 481 feature.hasTimestamp = true;
c@85 482 feature.timestamp = m_d->origin + Vamp::RealTime::frame2RealTime
c@27 483 (frame, lrintf(m_inputSampleRate));
c@87 484 feature.values.push_back(tempi[i]);
c@87 485 sprintf(label, "%.2f bpm", tempi[i]);
c@27 486 feature.label = label;
c@27 487 returnFeatures[2].push_back(feature); // tempo is output 2
c@87 488 prevTempo = tempi[i];
c@27 489 }
c@27 490 }
c@27 491
c@27 492 return returnFeatures;
c@27 493 }
c@27 494
c@86 495 BeatTracker::FeatureSet
c@86 496 BeatTracker::beatTrackNew()
c@86 497 {
c@86 498 vector<double> df;
c@86 499 vector<double> beatPeriod;
c@87 500 vector<double> tempi;
c@86 501
c@120 502 size_t nonZeroCount = m_d->dfOutput.size();
c@120 503 while (nonZeroCount > 0) {
c@120 504 if (m_d->dfOutput[nonZeroCount-1] > 0.0) {
c@120 505 break;
c@120 506 }
c@120 507 --nonZeroCount;
c@120 508 }
c@120 509
c@147 510 // std::cerr << "Note: nonZeroCount was " << m_d->dfOutput.size() << ", is now " << nonZeroCount << std::endl;
c@120 511
c@120 512 for (size_t i = 2; i < nonZeroCount; ++i) { // discard first two elts
c@86 513 df.push_back(m_d->dfOutput[i]);
c@86 514 beatPeriod.push_back(0.0);
c@86 515 }
c@86 516 if (df.empty()) return FeatureSet();
c@86 517
c@88 518 TempoTrackV2 tt(m_inputSampleRate, m_d->dfConfig.stepSize);
c@86 519
luis@144 520
luis@144 521 // MEPD - note this function is now passed 2 new parameters, m_inputtempo and m_constraintempo
luis@144 522 tt.calculateBeatPeriod(df, beatPeriod, tempi, m_inputtempo, m_constraintempo);
c@86 523
c@86 524 vector<double> beats;
luis@144 525
luis@144 526 // MEPD - note this function is now passed 2 new parameters, m_alpha and m_tightness
luis@144 527 tt.calculateBeats(df, beatPeriod, beats, m_alpha, m_tightness);
luis@144 528
c@86 529 FeatureSet returnFeatures;
c@86 530
c@86 531 char label[100];
c@86 532
c@86 533 for (size_t i = 0; i < beats.size(); ++i) {
c@86 534
luis@144 535 size_t frame = beats[i] * m_d->dfConfig.stepSize;
c@86 536
luis@144 537 Feature feature;
luis@144 538 feature.hasTimestamp = true;
luis@144 539 feature.timestamp = m_d->origin + Vamp::RealTime::frame2RealTime
luis@144 540 (frame, lrintf(m_inputSampleRate));
c@86 541
luis@144 542 float bpm = 0.0;
luis@144 543 int frameIncrement = 0;
c@86 544
luis@144 545 if (i+1 < beats.size()) {
c@86 546
luis@144 547 frameIncrement = (beats[i+1] - beats[i]) * m_d->dfConfig.stepSize;
c@86 548
luis@144 549 // one beat is frameIncrement frames, so there are
luis@144 550 // samplerate/frameIncrement bps, so
luis@144 551 // 60*samplerate/frameIncrement bpm
luis@144 552
luis@144 553 if (frameIncrement > 0) {
luis@144 554 bpm = (60.0 * m_inputSampleRate) / frameIncrement;
luis@144 555 bpm = int(bpm * 100.0 + 0.5) / 100.0;
c@86 556 sprintf(label, "%.2f bpm", bpm);
c@86 557 feature.label = label;
luis@144 558 }
luis@144 559 }
c@86 560
luis@144 561 returnFeatures[0].push_back(feature); // beats are output 0
c@86 562 }
c@86 563
c@87 564 double prevTempo = 0.0;
c@87 565
c@87 566 for (size_t i = 0; i < tempi.size(); ++i) {
c@87 567
luis@144 568 size_t frame = i * m_d->dfConfig.stepSize;
luis@144 569
c@87 570 if (tempi[i] > 1 && int(tempi[i] * 100) != int(prevTempo * 100)) {
c@87 571 Feature feature;
c@87 572 feature.hasTimestamp = true;
c@87 573 feature.timestamp = m_d->origin + Vamp::RealTime::frame2RealTime
c@87 574 (frame, lrintf(m_inputSampleRate));
c@87 575 feature.values.push_back(tempi[i]);
c@87 576 sprintf(label, "%.2f bpm", tempi[i]);
c@87 577 feature.label = label;
c@87 578 returnFeatures[2].push_back(feature); // tempo is output 2
c@87 579 prevTempo = tempi[i];
c@87 580 }
c@87 581 }
c@87 582
c@86 583 return returnFeatures;
c@86 584 }