00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037 #include "FixedTempoEstimator.h"
00038
00039 using std::string;
00040 using std::vector;
00041 using std::cerr;
00042 using std::endl;
00043
00044 using Vamp::RealTime;
00045
00046 #include <cmath>
00047 #include <cstdio>
00048
00049
00050 class FixedTempoEstimator::D
00051
00052 {
00053 public:
00054 D(float inputSampleRate);
00055 ~D();
00056
00057 size_t getPreferredStepSize() const { return 64; }
00058 size_t getPreferredBlockSize() const { return 256; }
00059
00060 ParameterList getParameterDescriptors() const;
00061 float getParameter(string id) const;
00062 void setParameter(string id, float value);
00063
00064 OutputList getOutputDescriptors() const;
00065
00066 bool initialise(size_t channels, size_t stepSize, size_t blockSize);
00067 void reset();
00068 FeatureSet process(const float *const *, RealTime);
00069 FeatureSet getRemainingFeatures();
00070
00071 private:
00072 void calculate();
00073 FeatureSet assembleFeatures();
00074
00075 float lag2tempo(int);
00076 int tempo2lag(float);
00077
00078 float m_inputSampleRate;
00079 size_t m_stepSize;
00080 size_t m_blockSize;
00081
00082 float m_minbpm;
00083 float m_maxbpm;
00084 float m_maxdflen;
00085
00086 float *m_priorMagnitudes;
00087
00088 size_t m_dfsize;
00089 float *m_df;
00090 float *m_r;
00091 float *m_fr;
00092 float *m_t;
00093 size_t m_n;
00094
00095 Vamp::RealTime m_start;
00096 Vamp::RealTime m_lasttime;
00097 };
00098
00099 FixedTempoEstimator::D::D(float inputSampleRate) :
00100 m_inputSampleRate(inputSampleRate),
00101 m_stepSize(0),
00102 m_blockSize(0),
00103 m_minbpm(50),
00104 m_maxbpm(190),
00105 m_maxdflen(10),
00106 m_priorMagnitudes(0),
00107 m_df(0),
00108 m_r(0),
00109 m_fr(0),
00110 m_t(0),
00111 m_n(0)
00112 {
00113 }
00114
00115 FixedTempoEstimator::D::~D()
00116 {
00117 delete[] m_priorMagnitudes;
00118 delete[] m_df;
00119 delete[] m_r;
00120 delete[] m_fr;
00121 delete[] m_t;
00122 }
00123
00124 FixedTempoEstimator::ParameterList
00125 FixedTempoEstimator::D::getParameterDescriptors() const
00126 {
00127 ParameterList list;
00128
00129 ParameterDescriptor d;
00130 d.identifier = "minbpm";
00131 d.name = "Minimum estimated tempo";
00132 d.description = "Minimum beat-per-minute value which the tempo estimator is able to return";
00133 d.unit = "bpm";
00134 d.minValue = 10;
00135 d.maxValue = 360;
00136 d.defaultValue = 50;
00137 d.isQuantized = false;
00138 list.push_back(d);
00139
00140 d.identifier = "maxbpm";
00141 d.name = "Maximum estimated tempo";
00142 d.description = "Maximum beat-per-minute value which the tempo estimator is able to return";
00143 d.defaultValue = 190;
00144 list.push_back(d);
00145
00146 d.identifier = "maxdflen";
00147 d.name = "Input duration to study";
00148 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.";
00149 d.unit = "s";
00150 d.minValue = 2;
00151 d.maxValue = 40;
00152 d.defaultValue = 10;
00153 list.push_back(d);
00154
00155 return list;
00156 }
00157
00158 float
00159 FixedTempoEstimator::D::getParameter(string id) const
00160 {
00161 if (id == "minbpm") {
00162 return m_minbpm;
00163 } else if (id == "maxbpm") {
00164 return m_maxbpm;
00165 } else if (id == "maxdflen") {
00166 return m_maxdflen;
00167 }
00168 return 0.f;
00169 }
00170
00171 void
00172 FixedTempoEstimator::D::setParameter(string id, float value)
00173 {
00174 if (id == "minbpm") {
00175 m_minbpm = value;
00176 } else if (id == "maxbpm") {
00177 m_maxbpm = value;
00178 } else if (id == "maxdflen") {
00179 m_maxdflen = value;
00180 }
00181 }
00182
00183 static int TempoOutput = 0;
00184 static int CandidatesOutput = 1;
00185 static int DFOutput = 2;
00186 static int ACFOutput = 3;
00187 static int FilteredACFOutput = 4;
00188
00189 FixedTempoEstimator::OutputList
00190 FixedTempoEstimator::D::getOutputDescriptors() const
00191 {
00192 OutputList list;
00193
00194 OutputDescriptor d;
00195 d.identifier = "tempo";
00196 d.name = "Tempo";
00197 d.description = "Estimated tempo";
00198 d.unit = "bpm";
00199 d.hasFixedBinCount = true;
00200 d.binCount = 1;
00201 d.hasKnownExtents = false;
00202 d.isQuantized = false;
00203 d.sampleType = OutputDescriptor::VariableSampleRate;
00204 d.sampleRate = m_inputSampleRate;
00205 d.hasDuration = true;
00206 list.push_back(d);
00207
00208 d.identifier = "candidates";
00209 d.name = "Tempo candidates";
00210 d.description = "Possible tempo estimates, one per bin with the most likely in the first bin";
00211 d.unit = "bpm";
00212 d.hasFixedBinCount = false;
00213 list.push_back(d);
00214
00215 d.identifier = "detectionfunction";
00216 d.name = "Detection Function";
00217 d.description = "Onset detection function";
00218 d.unit = "";
00219 d.hasFixedBinCount = 1;
00220 d.binCount = 1;
00221 d.hasKnownExtents = true;
00222 d.minValue = 0.0;
00223 d.maxValue = 1.0;
00224 d.isQuantized = false;
00225 d.quantizeStep = 0.0;
00226 d.sampleType = OutputDescriptor::FixedSampleRate;
00227 if (m_stepSize) {
00228 d.sampleRate = m_inputSampleRate / m_stepSize;
00229 } else {
00230 d.sampleRate = m_inputSampleRate / (getPreferredBlockSize()/2);
00231 }
00232 d.hasDuration = false;
00233 list.push_back(d);
00234
00235 d.identifier = "acf";
00236 d.name = "Autocorrelation Function";
00237 d.description = "Autocorrelation of onset detection function";
00238 d.hasKnownExtents = false;
00239 d.unit = "r";
00240 list.push_back(d);
00241
00242 d.identifier = "filtered_acf";
00243 d.name = "Filtered Autocorrelation";
00244 d.description = "Filtered autocorrelation of onset detection function";
00245 d.unit = "r";
00246 list.push_back(d);
00247
00248 return list;
00249 }
00250
00251 bool
00252 FixedTempoEstimator::D::initialise(size_t, size_t stepSize, size_t blockSize)
00253 {
00254 m_stepSize = stepSize;
00255 m_blockSize = blockSize;
00256
00257 float dfLengthSecs = m_maxdflen;
00258 m_dfsize = (dfLengthSecs * m_inputSampleRate) / m_stepSize;
00259
00260 m_priorMagnitudes = new float[m_blockSize/2];
00261 m_df = new float[m_dfsize];
00262
00263 for (size_t i = 0; i < m_blockSize/2; ++i) {
00264 m_priorMagnitudes[i] = 0.f;
00265 }
00266 for (size_t i = 0; i < m_dfsize; ++i) {
00267 m_df[i] = 0.f;
00268 }
00269
00270 m_n = 0;
00271
00272 return true;
00273 }
00274
00275 void
00276 FixedTempoEstimator::D::reset()
00277 {
00278 if (!m_priorMagnitudes) return;
00279
00280 for (size_t i = 0; i < m_blockSize/2; ++i) {
00281 m_priorMagnitudes[i] = 0.f;
00282 }
00283 for (size_t i = 0; i < m_dfsize; ++i) {
00284 m_df[i] = 0.f;
00285 }
00286
00287 delete[] m_r;
00288 m_r = 0;
00289
00290 delete[] m_fr;
00291 m_fr = 0;
00292
00293 delete[] m_t;
00294 m_t = 0;
00295
00296 m_n = 0;
00297
00298 m_start = RealTime::zeroTime;
00299 m_lasttime = RealTime::zeroTime;
00300 }
00301
00302 FixedTempoEstimator::FeatureSet
00303 FixedTempoEstimator::D::process(const float *const *inputBuffers, RealTime ts)
00304 {
00305 FeatureSet fs;
00306
00307 if (m_stepSize == 0) {
00308 cerr << "ERROR: FixedTempoEstimator::process: "
00309 << "FixedTempoEstimator has not been initialised"
00310 << endl;
00311 return fs;
00312 }
00313
00314 if (m_n == 0) m_start = ts;
00315 m_lasttime = ts;
00316
00317 if (m_n == m_dfsize) {
00318
00319 calculate();
00320 fs = assembleFeatures();
00321 ++m_n;
00322 return fs;
00323 }
00324
00325
00326 if (m_n > m_dfsize) return FeatureSet();
00327
00328 float value = 0.f;
00329
00330
00331
00332
00333
00334 for (size_t i = 1; i < m_blockSize/2; ++i) {
00335
00336 float real = inputBuffers[0][i*2];
00337 float imag = inputBuffers[0][i*2 + 1];
00338
00339 float sqrmag = real * real + imag * imag;
00340 value += fabsf(sqrmag - m_priorMagnitudes[i]);
00341
00342 m_priorMagnitudes[i] = sqrmag;
00343 }
00344
00345 m_df[m_n] = value;
00346
00347 ++m_n;
00348 return fs;
00349 }
00350
00351 FixedTempoEstimator::FeatureSet
00352 FixedTempoEstimator::D::getRemainingFeatures()
00353 {
00354 FeatureSet fs;
00355 if (m_n > m_dfsize) return fs;
00356 calculate();
00357 fs = assembleFeatures();
00358 ++m_n;
00359 return fs;
00360 }
00361
00362 float
00363 FixedTempoEstimator::D::lag2tempo(int lag)
00364 {
00365 return 60.f / ((lag * m_stepSize) / m_inputSampleRate);
00366 }
00367
00368 int
00369 FixedTempoEstimator::D::tempo2lag(float tempo)
00370 {
00371 return ((60.f / tempo) * m_inputSampleRate) / m_stepSize;
00372 }
00373
00374 void
00375 FixedTempoEstimator::D::calculate()
00376 {
00377 if (m_r) {
00378 cerr << "FixedTempoEstimator::calculate: calculation already happened?" << endl;
00379 return;
00380 }
00381
00382 if (m_n < m_dfsize / 9 &&
00383 m_n < (1.0 * m_inputSampleRate) / m_stepSize) {
00384 cerr << "FixedTempoEstimator::calculate: Input is too short" << endl;
00385 return;
00386 }
00387
00388
00389
00390
00391
00392
00393 int n = m_n;
00394
00395 m_r = new float[n/2];
00396 m_fr = new float[n/2];
00397 m_t = new float[n/2];
00398
00399 for (int i = 0; i < n/2; ++i) {
00400 m_r[i] = 0.f;
00401 m_fr[i] = 0.f;
00402 m_t[i] = lag2tempo(i);
00403 }
00404
00405
00406
00407 for (int i = 0; i < n/2; ++i) {
00408
00409 for (int j = i; j < n; ++j) {
00410 m_r[i] += m_df[j] * m_df[j - i];
00411 }
00412
00413 m_r[i] /= n - i - 1;
00414 }
00415
00416
00417
00418 float related[] = { 0.5, 2, 4, 8 };
00419
00420 for (int i = 1; i < n/2-1; ++i) {
00421
00422 m_fr[i] = m_r[i];
00423
00424 int div = 1;
00425
00426 for (int j = 0; j < int(sizeof(related)/sizeof(related[0])); ++j) {
00427
00428
00429
00430 int k0 = int(i * related[j] + 0.5);
00431
00432 if (k0 >= 0 && k0 < int(n/2)) {
00433
00434 int kmax = 0, kmin = 0;
00435 float kvmax = 0, kvmin = 0;
00436 bool have = false;
00437
00438 for (int k = k0 - 1; k <= k0 + 1; ++k) {
00439
00440 if (k < 0 || k >= n/2) continue;
00441
00442 if (!have || (m_r[k] > kvmax)) { kmax = k; kvmax = m_r[k]; }
00443 if (!have || (m_r[k] < kvmin)) { kmin = k; kvmin = m_r[k]; }
00444
00445 have = true;
00446 }
00447
00448
00449
00450
00451 m_fr[i] += m_r[kmax] / 5;
00452
00453 if ((kmax == 0 || m_r[kmax] > m_r[kmax-1]) &&
00454 (kmax == n/2-1 || m_r[kmax] > m_r[kmax+1]) &&
00455 kvmax > kvmin * 1.05) {
00456
00457
00458
00459
00460
00461 m_t[i] = m_t[i] + lag2tempo(kmax) * related[j];
00462 ++div;
00463 }
00464 }
00465 }
00466
00467 m_t[i] /= div;
00468
00469
00470
00471
00472 float weight = 1.f - fabsf(128.f - lag2tempo(i)) * 0.005;
00473 if (weight < 0.f) weight = 0.f;
00474 weight = weight * weight * weight;
00475
00476 m_fr[i] += m_fr[i] * (weight / 3);
00477 }
00478 }
00479
00480 FixedTempoEstimator::FeatureSet
00481 FixedTempoEstimator::D::assembleFeatures()
00482 {
00483 FeatureSet fs;
00484 if (!m_r) return fs;
00485
00486 Feature feature;
00487 feature.hasTimestamp = true;
00488 feature.hasDuration = false;
00489 feature.label = "";
00490 feature.values.clear();
00491 feature.values.push_back(0.f);
00492
00493 char buffer[40];
00494
00495 int n = m_n;
00496
00497 for (int i = 0; i < n; ++i) {
00498
00499
00500
00501 feature.timestamp = m_start +
00502 RealTime::frame2RealTime(i * m_stepSize, m_inputSampleRate);
00503 feature.values[0] = m_df[i];
00504 feature.label = "";
00505 fs[DFOutput].push_back(feature);
00506 }
00507
00508 for (int i = 1; i < n/2; ++i) {
00509
00510
00511
00512
00513 feature.timestamp = m_start +
00514 RealTime::frame2RealTime(i * m_stepSize, m_inputSampleRate);
00515 feature.values[0] = m_r[i];
00516 sprintf(buffer, "%.1f bpm", lag2tempo(i));
00517 if (i == n/2-1) feature.label = "";
00518 else feature.label = buffer;
00519 fs[ACFOutput].push_back(feature);
00520 }
00521
00522 float t0 = m_minbpm;
00523 float t1 = m_maxbpm;
00524
00525 int p0 = tempo2lag(t1);
00526 int p1 = tempo2lag(t0);
00527
00528 std::map<float, int> candidates;
00529
00530 for (int i = p0; i <= p1 && i+1 < n/2; ++i) {
00531
00532 if (m_fr[i] > m_fr[i-1] &&
00533 m_fr[i] > m_fr[i+1]) {
00534
00535
00536
00537
00538
00539 candidates[m_fr[i]] = i;
00540 }
00541
00542
00543
00544 feature.timestamp = m_start +
00545 RealTime::frame2RealTime(i * m_stepSize, m_inputSampleRate);
00546 feature.values[0] = m_fr[i];
00547 sprintf(buffer, "%.1f bpm", lag2tempo(i));
00548 if (i == p1 || i == n/2-2) feature.label = "";
00549 else feature.label = buffer;
00550 fs[FilteredACFOutput].push_back(feature);
00551 }
00552
00553 if (candidates.empty()) {
00554 cerr << "No tempo candidates!" << endl;
00555 return fs;
00556 }
00557
00558 feature.hasTimestamp = true;
00559 feature.timestamp = m_start;
00560
00561 feature.hasDuration = true;
00562 feature.duration = m_lasttime - m_start;
00563
00564
00565
00566
00567 std::map<float, int>::const_iterator ci = candidates.end();
00568 --ci;
00569 int maxpi = ci->second;
00570
00571 if (m_t[maxpi] > 0) {
00572
00573
00574
00575
00576 feature.values[0] = m_t[maxpi];
00577
00578 } else {
00579
00580
00581
00582
00583 feature.values[0] = lag2tempo(maxpi);
00584 cerr << "WARNING: No stored tempo for index " << maxpi << endl;
00585 }
00586
00587 sprintf(buffer, "%.1f bpm", feature.values[0]);
00588 feature.label = buffer;
00589
00590
00591
00592 fs[TempoOutput].push_back(feature);
00593
00594
00595
00596
00597 feature.values.clear();
00598 feature.label = "";
00599
00600 while (feature.values.size() < 10) {
00601 if (m_t[ci->second] > 0) {
00602 feature.values.push_back(m_t[ci->second]);
00603 } else {
00604 feature.values.push_back(lag2tempo(ci->second));
00605 }
00606 if (ci == candidates.begin()) break;
00607 --ci;
00608 }
00609
00610 fs[CandidatesOutput].push_back(feature);
00611
00612 return fs;
00613 }
00614
00615
00616
00617 FixedTempoEstimator::FixedTempoEstimator(float inputSampleRate) :
00618 Plugin(inputSampleRate),
00619 m_d(new D(inputSampleRate))
00620 {
00621 }
00622
00623 FixedTempoEstimator::~FixedTempoEstimator()
00624 {
00625 delete m_d;
00626 }
00627
00628 string
00629 FixedTempoEstimator::getIdentifier() const
00630 {
00631 return "fixedtempo";
00632 }
00633
00634 string
00635 FixedTempoEstimator::getName() const
00636 {
00637 return "Simple Fixed Tempo Estimator";
00638 }
00639
00640 string
00641 FixedTempoEstimator::getDescription() const
00642 {
00643 return "Study a short section of audio and estimate its tempo, assuming the tempo is constant";
00644 }
00645
00646 string
00647 FixedTempoEstimator::getMaker() const
00648 {
00649 return "Vamp SDK Example Plugins";
00650 }
00651
00652 int
00653 FixedTempoEstimator::getPluginVersion() const
00654 {
00655 return 1;
00656 }
00657
00658 string
00659 FixedTempoEstimator::getCopyright() const
00660 {
00661 return "Code copyright 2008 Queen Mary, University of London. Freely redistributable (BSD license)";
00662 }
00663
00664 size_t
00665 FixedTempoEstimator::getPreferredStepSize() const
00666 {
00667 return m_d->getPreferredStepSize();
00668 }
00669
00670 size_t
00671 FixedTempoEstimator::getPreferredBlockSize() const
00672 {
00673 return m_d->getPreferredBlockSize();
00674 }
00675
00676 bool
00677 FixedTempoEstimator::initialise(size_t channels, size_t stepSize, size_t blockSize)
00678 {
00679 if (channels < getMinChannelCount() ||
00680 channels > getMaxChannelCount()) return false;
00681
00682 return m_d->initialise(channels, stepSize, blockSize);
00683 }
00684
00685 void
00686 FixedTempoEstimator::reset()
00687 {
00688 return m_d->reset();
00689 }
00690
00691 FixedTempoEstimator::ParameterList
00692 FixedTempoEstimator::getParameterDescriptors() const
00693 {
00694 return m_d->getParameterDescriptors();
00695 }
00696
00697 float
00698 FixedTempoEstimator::getParameter(std::string id) const
00699 {
00700 return m_d->getParameter(id);
00701 }
00702
00703 void
00704 FixedTempoEstimator::setParameter(std::string id, float value)
00705 {
00706 m_d->setParameter(id, value);
00707 }
00708
00709 FixedTempoEstimator::OutputList
00710 FixedTempoEstimator::getOutputDescriptors() const
00711 {
00712 return m_d->getOutputDescriptors();
00713 }
00714
00715 FixedTempoEstimator::FeatureSet
00716 FixedTempoEstimator::process(const float *const *inputBuffers, RealTime ts)
00717 {
00718 return m_d->process(inputBuffers, ts);
00719 }
00720
00721 FixedTempoEstimator::FeatureSet
00722 FixedTempoEstimator::getRemainingFeatures()
00723 {
00724 return m_d->getRemainingFeatures();
00725 }