annotate plugins/BarBeatTrack.cpp @ 146:c4837ed2eeb1

Merge mepd_new_params branch
author Chris Cannam <c.cannam@qmul.ac.uk>
date Mon, 02 Sep 2013 09:29:34 +0100
parents edad8a88a074
children 88c05c0ac438
rev   line source
c@89 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
c@89 2
c@89 3 /*
c@89 4 QM Vamp Plugin Set
c@89 5
c@89 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@89 13 */
c@89 14
c@89 15 #include "BarBeatTrack.h"
c@89 16
c@89 17 #include <dsp/onsets/DetectionFunction.h>
c@89 18 #include <dsp/onsets/PeakPicking.h>
c@89 19 #include <dsp/tempotracking/TempoTrackV2.h>
c@89 20 #include <dsp/tempotracking/DownBeat.h>
c@89 21 #include <maths/MathUtilities.h>
c@89 22
c@89 23 using std::string;
c@89 24 using std::vector;
c@89 25 using std::cerr;
c@89 26 using std::endl;
c@89 27
c@130 28 #ifndef __GNUC__
c@130 29 #include <alloca.h>
c@130 30 #endif
c@130 31
c@89 32 float BarBeatTracker::m_stepSecs = 0.01161; // 512 samples at 44100
c@89 33
c@89 34 class BarBeatTrackerData
c@89 35 {
c@89 36 public:
c@89 37 BarBeatTrackerData(float rate, const DFConfig &config) : dfConfig(config) {
luis@144 38 df = new DetectionFunction(config);
c@89 39 // decimation factor aims at resampling to c. 3KHz; must be power of 2
c@89 40 int factor = MathUtilities::nextPowerOfTwo(rate / 3000);
c@95 41 // std::cerr << "BarBeatTrackerData: factor = " << factor << std::endl;
c@89 42 downBeat = new DownBeat(rate, factor, config.stepSize);
c@89 43 }
c@89 44 ~BarBeatTrackerData() {
luis@144 45 delete df;
c@89 46 delete downBeat;
c@89 47 }
c@89 48 void reset() {
luis@144 49 delete df;
luis@144 50 df = new DetectionFunction(dfConfig);
luis@144 51 dfOutput.clear();
c@89 52 downBeat->resetAudioBuffer();
c@89 53 origin = Vamp::RealTime::zeroTime;
c@89 54 }
c@89 55
c@89 56 DFConfig dfConfig;
c@89 57 DetectionFunction *df;
c@89 58 DownBeat *downBeat;
c@89 59 vector<double> dfOutput;
c@89 60 Vamp::RealTime origin;
c@89 61 };
luis@144 62
c@89 63
c@89 64 BarBeatTracker::BarBeatTracker(float inputSampleRate) :
c@89 65 Vamp::Plugin(inputSampleRate),
c@89 66 m_d(0),
luis@144 67 m_bpb(4),
luis@144 68 m_alpha(0.9), // changes are as per the BeatTrack.cpp
luis@144 69 m_tightness(4.), // changes are as per the BeatTrack.cpp
luis@144 70 m_inputtempo(120.), // changes are as per the BeatTrack.cpp
luis@144 71 m_constraintempo(false) // changes are as per the BeatTrack.cpp
c@89 72 {
c@89 73 }
c@89 74
c@89 75 BarBeatTracker::~BarBeatTracker()
c@89 76 {
c@89 77 delete m_d;
c@89 78 }
c@89 79
c@89 80 string
c@89 81 BarBeatTracker::getIdentifier() const
c@89 82 {
c@89 83 return "qm-barbeattracker";
c@89 84 }
c@89 85
c@89 86 string
c@89 87 BarBeatTracker::getName() const
c@89 88 {
c@89 89 return "Bar and Beat Tracker";
c@89 90 }
c@89 91
c@89 92 string
c@89 93 BarBeatTracker::getDescription() const
c@89 94 {
c@89 95 return "Estimate bar and beat locations";
c@89 96 }
c@89 97
c@89 98 string
c@89 99 BarBeatTracker::getMaker() const
c@89 100 {
c@89 101 return "Queen Mary, University of London";
c@89 102 }
c@89 103
c@89 104 int
c@89 105 BarBeatTracker::getPluginVersion() const
c@89 106 {
c@145 107 return 3;
c@89 108 }
c@89 109
c@89 110 string
c@89 111 BarBeatTracker::getCopyright() const
c@89 112 {
c@89 113 return "Plugin by Matthew Davies, Christian Landone and Chris Cannam. Copyright (c) 2006-2009 QMUL - All Rights Reserved";
c@89 114 }
c@89 115
c@89 116 BarBeatTracker::ParameterList
c@89 117 BarBeatTracker::getParameterDescriptors() const
c@89 118 {
c@89 119 ParameterList list;
c@89 120
c@89 121 ParameterDescriptor desc;
c@89 122
c@89 123 desc.identifier = "bpb";
c@89 124 desc.name = "Beats per Bar";
c@89 125 desc.description = "The number of beats in each bar";
c@89 126 desc.minValue = 2;
c@89 127 desc.maxValue = 16;
c@89 128 desc.defaultValue = 4;
c@89 129 desc.isQuantized = true;
c@89 130 desc.quantizeStep = 1;
c@89 131 list.push_back(desc);
c@89 132
luis@144 133 // changes are as per the BeatTrack.cpp
luis@144 134 //Alpha Parameter of Beat Tracker
luis@144 135 desc.identifier = "alpha";
luis@144 136 desc.name = "Alpha";
luis@144 137 desc.description = "Inertia - Flexibility Trade Off";
luis@144 138 desc.minValue = 0.1;
luis@144 139 desc.maxValue = 0.99;
luis@144 140 desc.defaultValue = 0.90;
luis@144 141 desc.unit = "";
luis@144 142 desc.isQuantized = false;
luis@144 143 list.push_back(desc);
luis@144 144
luis@144 145
luis@144 146 // changes are as per the BeatTrack.cpp
luis@144 147 //Tightness Parameter of Beat Tracker
luis@144 148 desc.identifier = "tightness";
luis@144 149 desc.name = "Tightness";
luis@144 150 desc.description = "Inertia - Flexibility Trade Off 2";
luis@144 151 desc.minValue = 3;
luis@144 152 desc.maxValue = 7;
luis@144 153 desc.defaultValue = 4;
luis@144 154 desc.unit = "";
luis@144 155 desc.isQuantized = true;
luis@144 156 list.push_back(desc);
luis@144 157
luis@144 158 // changes are as per the BeatTrack.cpp
luis@144 159 //User input tempo
luis@144 160 desc.identifier = "inputtempo";
luis@144 161 desc.name = "InputTempo";
luis@144 162 desc.description = "User defined Tempo";
luis@144 163 desc.minValue = 50;
luis@144 164 desc.maxValue = 250;
luis@144 165 desc.defaultValue = 120;
luis@144 166 desc.unit = "BPM";
luis@144 167 desc.isQuantized = true;
luis@144 168 list.push_back(desc);
luis@144 169
luis@144 170 // changes are as per the BeatTrack.cpp
luis@144 171 desc.identifier = "constraintempo";
luis@144 172 desc.name = "Constrain Tempo";
luis@144 173 desc.description = "Constrain tempo to use Gaussian weighting";
luis@144 174 desc.minValue = 0;
luis@144 175 desc.maxValue = 1;
luis@144 176 desc.defaultValue = 0;
luis@144 177 desc.isQuantized = true;
luis@144 178 desc.quantizeStep = 1;
luis@144 179 desc.unit = "";
luis@144 180 desc.valueNames.clear();
luis@144 181 list.push_back(desc);
luis@144 182
luis@144 183
c@89 184 return list;
c@89 185 }
c@89 186
c@89 187 float
c@89 188 BarBeatTracker::getParameter(std::string name) const
c@89 189 {
luis@144 190 if (name == "bpb") {
luis@144 191 return m_bpb;
luis@144 192 } else if (name == "alpha") {
luis@144 193 return m_alpha;
luis@144 194 } else if (name == "tightness") {
luis@144 195 return m_tightness;
luis@144 196 } else if (name == "inputtempo") {
luis@144 197 return m_inputtempo;
luis@144 198 } else if (name == "constraintempo") {
luis@144 199 return m_constraintempo ? 1.0 : 0.0;
luis@144 200 }
c@89 201 return 0.0;
c@89 202 }
c@89 203
c@89 204 void
c@89 205 BarBeatTracker::setParameter(std::string name, float value)
c@89 206 {
luis@144 207 if (name == "bpb") {
luis@144 208 m_bpb = lrintf(value);
luis@144 209 } else if (name == "alpha") {
luis@144 210 m_alpha = value;
luis@144 211 } else if (name == "tightness") {
luis@144 212 m_tightness = value;
luis@144 213 } else if (name == "inputtempo") {
luis@144 214 m_inputtempo = value;
luis@144 215 } else if (name == "constraintempo") {
luis@144 216 m_constraintempo = (value > 0.5);
luis@144 217 }
c@89 218 }
c@89 219
c@89 220 bool
c@89 221 BarBeatTracker::initialise(size_t channels, size_t stepSize, size_t blockSize)
c@89 222 {
c@89 223 if (m_d) {
luis@144 224 delete m_d;
luis@144 225 m_d = 0;
c@89 226 }
c@89 227
c@89 228 if (channels < getMinChannelCount() ||
luis@144 229 channels > getMaxChannelCount()) {
c@89 230 std::cerr << "BarBeatTracker::initialise: Unsupported channel count: "
c@89 231 << channels << std::endl;
c@89 232 return false;
c@89 233 }
c@89 234
c@89 235 if (stepSize != getPreferredStepSize()) {
c@89 236 std::cerr << "ERROR: BarBeatTracker::initialise: Unsupported step size for this sample rate: "
c@89 237 << stepSize << " (wanted " << (getPreferredStepSize()) << ")" << std::endl;
c@89 238 return false;
c@89 239 }
c@89 240
c@89 241 if (blockSize != getPreferredBlockSize()) {
c@89 242 std::cerr << "WARNING: BarBeatTracker::initialise: Sub-optimal block size for this sample rate: "
c@89 243 << blockSize << " (wanted " << getPreferredBlockSize() << ")" << std::endl;
c@89 244 // return false;
c@89 245 }
c@89 246
c@89 247 DFConfig dfConfig;
c@89 248 dfConfig.DFType = DF_COMPLEXSD;
c@89 249 dfConfig.stepSize = stepSize;
c@89 250 dfConfig.frameLength = blockSize;
c@89 251 dfConfig.dbRise = 3;
c@89 252 dfConfig.adaptiveWhitening = false;
c@89 253 dfConfig.whiteningRelaxCoeff = -1;
c@89 254 dfConfig.whiteningFloor = -1;
luis@144 255
c@89 256 m_d = new BarBeatTrackerData(m_inputSampleRate, dfConfig);
c@89 257 m_d->downBeat->setBeatsPerBar(m_bpb);
c@89 258 return true;
c@89 259 }
c@89 260
c@89 261 void
c@89 262 BarBeatTracker::reset()
c@89 263 {
c@89 264 if (m_d) m_d->reset();
c@89 265 }
c@89 266
c@89 267 size_t
c@89 268 BarBeatTracker::getPreferredStepSize() const
c@89 269 {
c@89 270 size_t step = size_t(m_inputSampleRate * m_stepSecs + 0.0001);
c@95 271 if (step < 1) step = 1;
c@89 272 // std::cerr << "BarBeatTracker::getPreferredStepSize: input sample rate is " << m_inputSampleRate << ", step size is " << step << std::endl;
c@89 273 return step;
c@89 274 }
c@89 275
c@89 276 size_t
c@89 277 BarBeatTracker::getPreferredBlockSize() const
c@89 278 {
c@89 279 size_t theoretical = getPreferredStepSize() * 2;
c@89 280
c@89 281 // I think this is not necessarily going to be a power of two, and
c@89 282 // the host might have a problem with that, but I'm not sure we
c@89 283 // can do much about it here
c@89 284 return theoretical;
c@89 285 }
c@89 286
c@89 287 BarBeatTracker::OutputList
c@89 288 BarBeatTracker::getOutputDescriptors() const
c@89 289 {
c@89 290 OutputList list;
c@89 291
c@89 292 OutputDescriptor beat;
c@89 293 beat.identifier = "beats";
c@89 294 beat.name = "Beats";
c@89 295 beat.description = "Beat locations labelled with metrical position";
c@89 296 beat.unit = "";
c@89 297 beat.hasFixedBinCount = true;
c@89 298 beat.binCount = 0;
c@89 299 beat.sampleType = OutputDescriptor::VariableSampleRate;
c@89 300 beat.sampleRate = 1.0 / m_stepSecs;
c@89 301
c@89 302 OutputDescriptor bars;
c@89 303 bars.identifier = "bars";
c@89 304 bars.name = "Bars";
c@89 305 bars.description = "Bar locations";
c@89 306 bars.unit = "";
c@89 307 bars.hasFixedBinCount = true;
c@89 308 bars.binCount = 0;
c@89 309 bars.sampleType = OutputDescriptor::VariableSampleRate;
c@89 310 bars.sampleRate = 1.0 / m_stepSecs;
c@89 311
c@89 312 OutputDescriptor beatcounts;
c@89 313 beatcounts.identifier = "beatcounts";
c@89 314 beatcounts.name = "Beat Count";
c@89 315 beatcounts.description = "Beat counter function";
c@89 316 beatcounts.unit = "";
c@89 317 beatcounts.hasFixedBinCount = true;
c@89 318 beatcounts.binCount = 1;
c@89 319 beatcounts.sampleType = OutputDescriptor::VariableSampleRate;
c@89 320 beatcounts.sampleRate = 1.0 / m_stepSecs;
c@89 321
c@90 322 OutputDescriptor beatsd;
c@90 323 beatsd.identifier = "beatsd";
c@90 324 beatsd.name = "Beat Spectral Difference";
c@90 325 beatsd.description = "Beat spectral difference function used for bar-line detection";
c@90 326 beatsd.unit = "";
c@90 327 beatsd.hasFixedBinCount = true;
c@90 328 beatsd.binCount = 1;
c@90 329 beatsd.sampleType = OutputDescriptor::VariableSampleRate;
c@90 330 beatsd.sampleRate = 1.0 / m_stepSecs;
c@90 331
c@89 332 list.push_back(beat);
c@89 333 list.push_back(bars);
c@89 334 list.push_back(beatcounts);
c@90 335 list.push_back(beatsd);
c@89 336
c@89 337 return list;
c@89 338 }
c@89 339
c@89 340 BarBeatTracker::FeatureSet
c@89 341 BarBeatTracker::process(const float *const *inputBuffers,
c@89 342 Vamp::RealTime timestamp)
c@89 343 {
c@89 344 if (!m_d) {
luis@144 345 cerr << "ERROR: BarBeatTracker::process: "
luis@144 346 << "BarBeatTracker has not been initialised"
luis@144 347 << endl;
luis@144 348 return FeatureSet();
c@89 349 }
c@89 350
c@89 351 // We use time domain input, because DownBeat requires it -- so we
c@89 352 // use the time-domain version of DetectionFunction::process which
c@89 353 // does its own FFT. It requires doubles as input, so we need to
c@89 354 // make a temporary copy
c@89 355
c@89 356 // We only support a single input channel
c@89 357
c@89 358 const int fl = m_d->dfConfig.frameLength;
c@130 359 #ifndef __GNUC__
c@130 360 double *dfinput = (double *)alloca(fl * sizeof(double));
c@130 361 #else
c@89 362 double dfinput[fl];
c@130 363 #endif
c@89 364 for (int i = 0; i < fl; ++i) dfinput[i] = inputBuffers[0][i];
c@89 365
c@89 366 double output = m_d->df->process(dfinput);
c@89 367
c@89 368 if (m_d->dfOutput.empty()) m_d->origin = timestamp;
c@89 369
c@93 370 // std::cerr << "df[" << m_d->dfOutput.size() << "] is " << output << std::endl;
c@89 371 m_d->dfOutput.push_back(output);
c@89 372
c@89 373 // Downsample and store the incoming audio block.
c@89 374 // We have an overlap on the incoming audio stream (step size is
c@89 375 // half block size) -- this function is configured to take only a
c@89 376 // step size's worth, so effectively ignoring the overlap. Note
c@89 377 // however that this means we omit the last blocksize - stepsize
c@89 378 // samples completely for the purposes of barline detection
c@89 379 // (hopefully not a problem)
c@89 380 m_d->downBeat->pushAudioBlock(inputBuffers[0]);
c@89 381
c@89 382 return FeatureSet();
c@89 383 }
c@89 384
c@89 385 BarBeatTracker::FeatureSet
c@89 386 BarBeatTracker::getRemainingFeatures()
c@89 387 {
c@89 388 if (!m_d) {
luis@144 389 cerr << "ERROR: BarBeatTracker::getRemainingFeatures: "
luis@144 390 << "BarBeatTracker has not been initialised"
luis@144 391 << endl;
luis@144 392 return FeatureSet();
c@89 393 }
c@89 394
c@89 395 return barBeatTrack();
c@89 396 }
c@89 397
c@89 398 BarBeatTracker::FeatureSet
c@89 399 BarBeatTracker::barBeatTrack()
c@89 400 {
c@89 401 vector<double> df;
c@89 402 vector<double> beatPeriod;
c@89 403 vector<double> tempi;
c@89 404
c@89 405 for (size_t i = 2; i < m_d->dfOutput.size(); ++i) { // discard first two elts
c@89 406 df.push_back(m_d->dfOutput[i]);
c@89 407 beatPeriod.push_back(0.0);
c@89 408 }
c@89 409 if (df.empty()) return FeatureSet();
c@89 410
c@89 411 TempoTrackV2 tt(m_inputSampleRate, m_d->dfConfig.stepSize);
luis@144 412
luis@144 413 // changes are as per the BeatTrack.cpp - allow m_inputtempo and m_constraintempo to be set be the user
luis@144 414 tt.calculateBeatPeriod(df, beatPeriod, tempi, m_inputtempo, m_constraintempo);
c@89 415
c@89 416 vector<double> beats;
luis@144 417 // changes are as per the BeatTrack.cpp - allow m_alpha and m_tightness to be set be the user
luis@144 418 tt.calculateBeats(df, beatPeriod, beats, m_alpha, m_tightness);
luis@144 419
luis@144 420 // tt.calculateBeatPeriod(df, beatPeriod, tempi, 0., 0); // use default parameters
luis@144 421
luis@144 422 // vector<double> beats;
luis@144 423 // tt.calculateBeats(df, beatPeriod, beats, 0.9, 4.); // use default parameters until i fix this plugin too
c@89 424
c@89 425 vector<int> downbeats;
c@89 426 size_t downLength = 0;
c@89 427 const float *downsampled = m_d->downBeat->getBufferedAudio(downLength);
c@89 428 m_d->downBeat->findDownBeats(downsampled, downLength, beats, downbeats);
c@89 429
c@90 430 vector<double> beatsd;
c@90 431 m_d->downBeat->getBeatSD(beatsd);
c@90 432
c@89 433 // std::cerr << "BarBeatTracker: found downbeats at: ";
c@89 434 // for (int i = 0; i < downbeats.size(); ++i) std::cerr << downbeats[i] << " " << std::endl;
luis@144 435
c@89 436 FeatureSet returnFeatures;
c@89 437
c@89 438 char label[20];
c@89 439
c@89 440 int dbi = 0;
c@89 441 int beat = 0;
c@89 442 int bar = 0;
c@89 443
c@124 444 if (!downbeats.empty()) {
c@124 445 // get the right number for the first beat; this will be
c@124 446 // incremented before use (at top of the following loop)
c@124 447 int firstDown = downbeats[0];
c@124 448 beat = m_bpb - firstDown - 1;
c@124 449 if (beat == m_bpb) beat = 0;
c@124 450 }
c@124 451
c@89 452 for (size_t i = 0; i < beats.size(); ++i) {
c@89 453
luis@144 454 size_t frame = beats[i] * m_d->dfConfig.stepSize;
c@89 455
c@89 456 if (dbi < downbeats.size() && i == downbeats[dbi]) {
c@89 457 beat = 0;
c@89 458 ++bar;
c@89 459 ++dbi;
c@89 460 } else {
c@89 461 ++beat;
c@89 462 }
c@89 463
c@89 464 // outputs are:
c@89 465 //
c@89 466 // 0 -> beats
c@89 467 // 1 -> bars
c@89 468 // 2 -> beat counter function
luis@144 469
luis@144 470 Feature feature;
luis@144 471 feature.hasTimestamp = true;
luis@144 472 feature.timestamp = m_d->origin + Vamp::RealTime::frame2RealTime
luis@144 473 (frame, lrintf(m_inputSampleRate));
c@89 474
c@89 475 sprintf(label, "%d", beat + 1);
c@89 476 feature.label = label;
luis@144 477 returnFeatures[0].push_back(feature); // labelled beats
c@89 478
c@89 479 feature.values.push_back(beat + 1);
c@89 480 returnFeatures[2].push_back(feature); // beat function
c@89 481
c@90 482 if (i > 0 && i <= beatsd.size()) {
c@90 483 feature.values.clear();
c@90 484 feature.values.push_back(beatsd[i-1]);
c@90 485 feature.label = "";
c@90 486 returnFeatures[3].push_back(feature); // beat spectral difference
c@90 487 }
c@90 488
c@89 489 if (beat == 0) {
c@89 490 feature.values.clear();
c@89 491 sprintf(label, "%d", bar);
c@89 492 feature.label = label;
c@89 493 returnFeatures[1].push_back(feature); // bars
c@89 494 }
c@89 495 }
c@89 496
c@89 497 return returnFeatures;
c@89 498 }
c@89 499