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