annotate examples/FixedTempoEstimator.cpp @ 201:8e1b1fa94e15

...
author cannam
date Fri, 10 Oct 2008 16:04:00 +0000
parents a86e777bf9a6
children e100112ecc06
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@198 49 FixedTempoEstimator::FixedTempoEstimator(float inputSampleRate) :
cannam@198 50 Plugin(inputSampleRate),
cannam@198 51 m_stepSize(0),
cannam@198 52 m_blockSize(0),
cannam@198 53 m_priorMagnitudes(0),
cannam@200 54 m_df(0),
cannam@200 55 m_r(0),
cannam@200 56 m_fr(0),
cannam@200 57 m_n(0)
cannam@198 58 {
cannam@198 59 }
cannam@198 60
cannam@198 61 FixedTempoEstimator::~FixedTempoEstimator()
cannam@198 62 {
cannam@198 63 delete[] m_priorMagnitudes;
cannam@198 64 delete[] m_df;
cannam@200 65 delete[] m_r;
cannam@200 66 delete[] m_fr;
cannam@198 67 }
cannam@198 68
cannam@198 69 string
cannam@198 70 FixedTempoEstimator::getIdentifier() const
cannam@198 71 {
cannam@198 72 return "fixedtempo";
cannam@198 73 }
cannam@198 74
cannam@198 75 string
cannam@198 76 FixedTempoEstimator::getName() const
cannam@198 77 {
cannam@198 78 return "Simple Fixed Tempo Estimator";
cannam@198 79 }
cannam@198 80
cannam@198 81 string
cannam@198 82 FixedTempoEstimator::getDescription() const
cannam@198 83 {
cannam@198 84 return "Study a short section of audio and estimate its tempo, assuming the tempo is constant";
cannam@198 85 }
cannam@198 86
cannam@198 87 string
cannam@198 88 FixedTempoEstimator::getMaker() const
cannam@198 89 {
cannam@198 90 return "Vamp SDK Example Plugins";
cannam@198 91 }
cannam@198 92
cannam@198 93 int
cannam@198 94 FixedTempoEstimator::getPluginVersion() const
cannam@198 95 {
cannam@198 96 return 1;
cannam@198 97 }
cannam@198 98
cannam@198 99 string
cannam@198 100 FixedTempoEstimator::getCopyright() const
cannam@198 101 {
cannam@198 102 return "Code copyright 2008 Queen Mary, University of London. Freely redistributable (BSD license)";
cannam@198 103 }
cannam@198 104
cannam@198 105 size_t
cannam@198 106 FixedTempoEstimator::getPreferredStepSize() const
cannam@198 107 {
cannam@198 108 return 0;
cannam@198 109 }
cannam@198 110
cannam@198 111 size_t
cannam@198 112 FixedTempoEstimator::getPreferredBlockSize() const
cannam@198 113 {
cannam@200 114 return 64;
cannam@198 115 }
cannam@198 116
cannam@198 117 bool
cannam@198 118 FixedTempoEstimator::initialise(size_t channels, size_t stepSize, size_t blockSize)
cannam@198 119 {
cannam@198 120 if (channels < getMinChannelCount() ||
cannam@198 121 channels > getMaxChannelCount()) return false;
cannam@198 122
cannam@198 123 m_stepSize = stepSize;
cannam@198 124 m_blockSize = blockSize;
cannam@198 125
cannam@198 126 float dfLengthSecs = 8.f;
cannam@198 127 m_dfsize = (dfLengthSecs * m_inputSampleRate) / m_stepSize;
cannam@198 128
cannam@198 129 m_priorMagnitudes = new float[m_blockSize/2];
cannam@198 130 m_df = new float[m_dfsize];
cannam@198 131
cannam@198 132 for (size_t i = 0; i < m_blockSize/2; ++i) {
cannam@198 133 m_priorMagnitudes[i] = 0.f;
cannam@198 134 }
cannam@198 135 for (size_t i = 0; i < m_dfsize; ++i) {
cannam@198 136 m_df[i] = 0.f;
cannam@198 137 }
cannam@198 138
cannam@198 139 m_n = 0;
cannam@198 140
cannam@198 141 return true;
cannam@198 142 }
cannam@198 143
cannam@198 144 void
cannam@198 145 FixedTempoEstimator::reset()
cannam@198 146 {
cannam@198 147 std::cerr << "FixedTempoEstimator: reset called" << std::endl;
cannam@198 148
cannam@198 149 if (!m_priorMagnitudes) return;
cannam@198 150
cannam@198 151 std::cerr << "FixedTempoEstimator: resetting" << std::endl;
cannam@198 152
cannam@198 153 for (size_t i = 0; i < m_blockSize/2; ++i) {
cannam@198 154 m_priorMagnitudes[i] = 0.f;
cannam@198 155 }
cannam@198 156 for (size_t i = 0; i < m_dfsize; ++i) {
cannam@198 157 m_df[i] = 0.f;
cannam@198 158 }
cannam@198 159
cannam@200 160 delete[] m_r;
cannam@200 161 m_r = 0;
cannam@200 162
cannam@200 163 delete[] m_fr;
cannam@200 164 m_fr = 0;
cannam@200 165
cannam@198 166 m_n = 0;
cannam@198 167
cannam@198 168 m_start = RealTime::zeroTime;
cannam@198 169 m_lasttime = RealTime::zeroTime;
cannam@198 170 }
cannam@198 171
cannam@198 172 FixedTempoEstimator::ParameterList
cannam@198 173 FixedTempoEstimator::getParameterDescriptors() const
cannam@198 174 {
cannam@198 175 ParameterList list;
cannam@198 176 return list;
cannam@198 177 }
cannam@198 178
cannam@198 179 float
cannam@198 180 FixedTempoEstimator::getParameter(std::string id) const
cannam@198 181 {
cannam@198 182 return 0.f;
cannam@198 183 }
cannam@198 184
cannam@198 185 void
cannam@198 186 FixedTempoEstimator::setParameter(std::string id, float value)
cannam@198 187 {
cannam@198 188 }
cannam@198 189
cannam@200 190 static int TempoOutput = 0;
cannam@200 191 static int CandidatesOutput = 1;
cannam@200 192 static int DFOutput = 2;
cannam@200 193 static int ACFOutput = 3;
cannam@200 194 static int FilteredACFOutput = 4;
cannam@200 195
cannam@198 196 FixedTempoEstimator::OutputList
cannam@198 197 FixedTempoEstimator::getOutputDescriptors() const
cannam@198 198 {
cannam@198 199 OutputList list;
cannam@198 200
cannam@198 201 OutputDescriptor d;
cannam@198 202 d.identifier = "tempo";
cannam@198 203 d.name = "Tempo";
cannam@198 204 d.description = "Estimated tempo";
cannam@198 205 d.unit = "bpm";
cannam@198 206 d.hasFixedBinCount = true;
cannam@198 207 d.binCount = 1;
cannam@198 208 d.hasKnownExtents = false;
cannam@198 209 d.isQuantized = false;
cannam@198 210 d.sampleType = OutputDescriptor::VariableSampleRate;
cannam@198 211 d.sampleRate = m_inputSampleRate;
cannam@198 212 d.hasDuration = true; // our returned tempo spans a certain range
cannam@198 213 list.push_back(d);
cannam@198 214
cannam@200 215 d.identifier = "candidates";
cannam@200 216 d.name = "Tempo candidates";
cannam@200 217 d.description = "Possible tempo estimates, one per bin with the most likely in the first bin";
cannam@200 218 d.unit = "bpm";
cannam@200 219 d.hasFixedBinCount = false;
cannam@200 220 list.push_back(d);
cannam@200 221
cannam@198 222 d.identifier = "detectionfunction";
cannam@198 223 d.name = "Detection Function";
cannam@198 224 d.description = "Onset detection function";
cannam@198 225 d.unit = "";
cannam@198 226 d.hasFixedBinCount = 1;
cannam@198 227 d.binCount = 1;
cannam@198 228 d.hasKnownExtents = true;
cannam@198 229 d.minValue = 0.0;
cannam@198 230 d.maxValue = 1.0;
cannam@198 231 d.isQuantized = false;
cannam@198 232 d.quantizeStep = 0.0;
cannam@198 233 d.sampleType = OutputDescriptor::FixedSampleRate;
cannam@198 234 if (m_stepSize) {
cannam@198 235 d.sampleRate = m_inputSampleRate / m_stepSize;
cannam@198 236 } else {
cannam@198 237 d.sampleRate = m_inputSampleRate / (getPreferredBlockSize()/2);
cannam@198 238 }
cannam@198 239 d.hasDuration = false;
cannam@198 240 list.push_back(d);
cannam@198 241
cannam@198 242 d.identifier = "acf";
cannam@198 243 d.name = "Autocorrelation Function";
cannam@198 244 d.description = "Autocorrelation of onset detection function";
cannam@198 245 d.hasKnownExtents = false;
cannam@201 246 d.unit = "r";
cannam@198 247 list.push_back(d);
cannam@198 248
cannam@198 249 d.identifier = "filtered_acf";
cannam@198 250 d.name = "Filtered Autocorrelation";
cannam@198 251 d.description = "Filtered autocorrelation of onset detection function";
cannam@201 252 d.unit = "r";
cannam@198 253 list.push_back(d);
cannam@198 254
cannam@198 255 return list;
cannam@198 256 }
cannam@198 257
cannam@198 258 FixedTempoEstimator::FeatureSet
cannam@198 259 FixedTempoEstimator::process(const float *const *inputBuffers, RealTime ts)
cannam@198 260 {
cannam@198 261 FeatureSet fs;
cannam@198 262
cannam@198 263 if (m_stepSize == 0) {
cannam@198 264 cerr << "ERROR: FixedTempoEstimator::process: "
cannam@198 265 << "FixedTempoEstimator has not been initialised"
cannam@198 266 << endl;
cannam@198 267 return fs;
cannam@198 268 }
cannam@198 269
cannam@200 270 // if (m_n < m_dfsize) std::cerr << "m_n = " << m_n << std::endl;
cannam@198 271
cannam@198 272 if (m_n == 0) m_start = ts;
cannam@198 273 m_lasttime = ts;
cannam@198 274
cannam@198 275 if (m_n == m_dfsize) {
cannam@200 276 calculate();
cannam@200 277 fs = assembleFeatures();
cannam@198 278 ++m_n;
cannam@198 279 return fs;
cannam@198 280 }
cannam@198 281
cannam@198 282 if (m_n > m_dfsize) return FeatureSet();
cannam@198 283
cannam@198 284 int count = 0;
cannam@198 285
cannam@198 286 for (size_t i = 1; i < m_blockSize/2; ++i) {
cannam@198 287
cannam@198 288 float real = inputBuffers[0][i*2];
cannam@198 289 float imag = inputBuffers[0][i*2 + 1];
cannam@198 290
cannam@198 291 float sqrmag = real * real + imag * imag;
cannam@198 292
cannam@198 293 if (m_priorMagnitudes[i] > 0.f) {
cannam@198 294 float diff = 10.f * log10f(sqrmag / m_priorMagnitudes[i]);
cannam@198 295 if (diff >= 3.f) ++count;
cannam@198 296 }
cannam@198 297
cannam@198 298 m_priorMagnitudes[i] = sqrmag;
cannam@198 299 }
cannam@198 300
cannam@198 301 m_df[m_n] = float(count) / float(m_blockSize/2);
cannam@198 302 ++m_n;
cannam@198 303 return fs;
cannam@198 304 }
cannam@198 305
cannam@198 306 FixedTempoEstimator::FeatureSet
cannam@198 307 FixedTempoEstimator::getRemainingFeatures()
cannam@198 308 {
cannam@198 309 FeatureSet fs;
cannam@198 310 if (m_n > m_dfsize) return fs;
cannam@200 311 calculate();
cannam@200 312 fs = assembleFeatures();
cannam@198 313 ++m_n;
cannam@198 314 return fs;
cannam@198 315 }
cannam@198 316
cannam@198 317 float
cannam@199 318 FixedTempoEstimator::lag2tempo(int lag)
cannam@199 319 {
cannam@198 320 return 60.f / ((lag * m_stepSize) / m_inputSampleRate);
cannam@198 321 }
cannam@198 322
cannam@200 323 void
cannam@200 324 FixedTempoEstimator::calculate()
cannam@200 325 {
cannam@200 326 std::cerr << "FixedTempoEstimator::calculate: m_n = " << m_n << std::endl;
cannam@200 327
cannam@200 328 if (m_r) {
cannam@200 329 std::cerr << "FixedTempoEstimator::calculate: calculation already happened?" << std::endl;
cannam@200 330 return;
cannam@200 331 }
cannam@200 332
cannam@200 333 if (m_n < m_dfsize / 6) {
cannam@200 334 std::cerr << "FixedTempoEstimator::calculate: Not enough data to go on (have " << m_n << ", want at least " << m_dfsize/4 << ")" << std::endl;
cannam@200 335 return; // not enough data (perhaps we should return the duration of the input as the "estimated" beat length?)
cannam@200 336 }
cannam@200 337
cannam@200 338 int n = m_n;
cannam@200 339
cannam@200 340 m_r = new float[n/2];
cannam@200 341 m_fr = new float[n/2];
cannam@200 342
cannam@200 343 for (int i = 0; i < n/2; ++i) {
cannam@200 344 m_r[i] = 0.f;
cannam@200 345 m_fr[i] = 0.f;
cannam@200 346 }
cannam@200 347
cannam@200 348 for (int i = 0; i < n/2; ++i) {
cannam@200 349
cannam@200 350 for (int j = i; j < n-1; ++j) {
cannam@200 351 m_r[i] += m_df[j] * m_df[j - i];
cannam@200 352 }
cannam@200 353
cannam@200 354 m_r[i] /= n - i - 1;
cannam@200 355 }
cannam@200 356
cannam@200 357 for (int i = 1; i < n/2; ++i) {
cannam@200 358
cannam@200 359 m_fr[i] = m_r[i];
cannam@200 360
cannam@200 361 int div = 1;
cannam@200 362
cannam@200 363 int j = i;
cannam@200 364
cannam@200 365 while (j < n/2) {
cannam@200 366 m_fr[i] += m_r[j];
cannam@200 367 j *= 2;
cannam@200 368 ++div;
cannam@200 369 }
cannam@200 370 /*
cannam@200 371 for (int j = 1; j <= (n/2 - 1)/i; ++j) {
cannam@200 372 m_fr[i] += m_r[i * j];
cannam@200 373 ++div;
cannam@200 374 }
cannam@200 375 */
cannam@200 376 std::cerr << "i = " << i << ", (n/2 - 1)/i = " << (n/2 - 1)/i << ", sum = " << m_fr[i] << ", div = " << div << ", val = " << m_fr[i] / div << ", t = " << lag2tempo(i) << std::endl;
cannam@200 377
cannam@200 378
cannam@200 379 // m_fr[i] /= 1 + (n/2 - 1)/i;
cannam@200 380 m_fr[i] /= div;
cannam@200 381 }
cannam@200 382
cannam@200 383 std::cerr << "FixedTempoEstimator::calculate done" << std::endl;
cannam@200 384 }
cannam@200 385
cannam@200 386
cannam@198 387 FixedTempoEstimator::FeatureSet
cannam@200 388 FixedTempoEstimator::assembleFeatures()
cannam@198 389 {
cannam@198 390 FeatureSet fs;
cannam@200 391 if (!m_r) return fs; // No results
cannam@200 392
cannam@198 393 Feature feature;
cannam@198 394 feature.hasTimestamp = true;
cannam@198 395 feature.hasDuration = false;
cannam@198 396 feature.label = "";
cannam@198 397 feature.values.clear();
cannam@198 398 feature.values.push_back(0.f);
cannam@198 399
cannam@200 400 char buffer[40];
cannam@198 401
cannam@198 402 int n = m_n;
cannam@198 403
cannam@198 404 for (int i = 0; i < n; ++i) {
cannam@198 405 feature.timestamp = RealTime::frame2RealTime(i * m_stepSize,
cannam@198 406 m_inputSampleRate);
cannam@200 407 feature.values[0] = m_df[i];
cannam@198 408 feature.label = "";
cannam@200 409 fs[DFOutput].push_back(feature);
cannam@198 410 }
cannam@198 411
cannam@199 412 for (int i = 1; i < n/2; ++i) {
cannam@198 413 feature.timestamp = RealTime::frame2RealTime(i * m_stepSize,
cannam@198 414 m_inputSampleRate);
cannam@200 415 feature.values[0] = m_r[i];
cannam@199 416 sprintf(buffer, "%.1f bpm", lag2tempo(i));
cannam@200 417 if (i == n/2-1) feature.label = "";
cannam@200 418 else feature.label = buffer;
cannam@200 419 fs[ACFOutput].push_back(feature);
cannam@198 420 }
cannam@198 421
cannam@198 422 float t0 = 60.f;
cannam@198 423 float t1 = 180.f;
cannam@198 424
cannam@198 425 int p0 = ((60.f / t1) * m_inputSampleRate) / m_stepSize;
cannam@198 426 int p1 = ((60.f / t0) * m_inputSampleRate) / m_stepSize;
cannam@198 427
cannam@200 428 // std::cerr << "p0 = " << p0 << ", p1 = " << p1 << std::endl;
cannam@198 429
cannam@198 430 int pc = p1 - p0 + 1;
cannam@200 431 // std::cerr << "pc = " << pc << std::endl;
cannam@198 432
cannam@200 433 // int maxpi = 0;
cannam@200 434 // float maxp = 0.f;
cannam@198 435
cannam@200 436 std::map<float, int> candidates;
cannam@198 437
cannam@200 438 for (int i = p0; i <= p1 && i < n/2-1; ++i) {
cannam@198 439
cannam@200 440 // Only candidates here are those that were peaks in the
cannam@200 441 // original acf
cannam@200 442 // if (r[i] > r[i-1] && r[i] > r[i+1]) {
cannam@200 443 // candidates[filtered] = i;
cannam@200 444 // }
cannam@198 445
cannam@200 446 candidates[m_fr[i]] = i;
cannam@198 447
cannam@198 448 feature.timestamp = RealTime::frame2RealTime(i * m_stepSize,
cannam@198 449 m_inputSampleRate);
cannam@200 450 feature.values[0] = m_fr[i];
cannam@199 451 sprintf(buffer, "%.1f bpm", lag2tempo(i));
cannam@200 452 if (i == p1 || i == n/2-2) feature.label = "";
cannam@200 453 else feature.label = buffer;
cannam@200 454 fs[FilteredACFOutput].push_back(feature);
cannam@198 455 }
cannam@198 456
cannam@200 457 // std::cerr << "maxpi = " << maxpi << " for tempo " << lag2tempo(maxpi) << " (value = " << maxp << ")" << std::endl;
cannam@198 458
cannam@200 459 if (candidates.empty()) {
cannam@200 460 std::cerr << "No tempo candidates!" << std::endl;
cannam@200 461 return fs;
cannam@200 462 }
cannam@198 463
cannam@198 464 feature.hasTimestamp = true;
cannam@198 465 feature.timestamp = m_start;
cannam@198 466
cannam@198 467 feature.hasDuration = true;
cannam@198 468 feature.duration = m_lasttime - m_start;
cannam@198 469
cannam@200 470 std::map<float, int>::const_iterator ci = candidates.end();
cannam@200 471 --ci;
cannam@200 472 int maxpi = ci->second;
cannam@200 473
cannam@200 474 feature.values[0] = lag2tempo(maxpi);
cannam@198 475
cannam@200 476 sprintf(buffer, "%.1f bpm", lag2tempo(maxpi));
cannam@199 477 feature.label = buffer;
cannam@199 478
cannam@200 479 fs[TempoOutput].push_back(feature);
cannam@198 480
cannam@200 481 feature.values.clear();
cannam@200 482 feature.label = "";
cannam@200 483
cannam@200 484 while (feature.values.size() < 8) {
cannam@200 485 feature.values.push_back(lag2tempo(ci->second));
cannam@200 486 if (ci == candidates.begin()) break;
cannam@200 487 --ci;
cannam@200 488 }
cannam@200 489
cannam@200 490 fs[CandidatesOutput].push_back(feature);
cannam@200 491
cannam@198 492 return fs;
cannam@198 493 }