cannam@21: cannam@21:
cannam@21:00001 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ cannam@21: 00002 cannam@21: 00003 /* cannam@21: 00004 Vamp cannam@21: 00005 cannam@21: 00006 An API for audio analysis and feature extraction plugins. cannam@21: 00007 cannam@21: 00008 Centre for Digital Music, Queen Mary, University of London. cannam@21: 00009 Copyright 2006-2008 Chris Cannam and QMUL. cannam@21: 00010 cannam@21: 00011 Permission is hereby granted, free of charge, to any person cannam@21: 00012 obtaining a copy of this software and associated documentation cannam@21: 00013 files (the "Software"), to deal in the Software without cannam@21: 00014 restriction, including without limitation the rights to use, copy, cannam@21: 00015 modify, merge, publish, distribute, sublicense, and/or sell copies cannam@21: 00016 of the Software, and to permit persons to whom the Software is cannam@21: 00017 furnished to do so, subject to the following conditions: cannam@21: 00018 cannam@21: 00019 The above copyright notice and this permission notice shall be cannam@21: 00020 included in all copies or substantial portions of the Software. cannam@21: 00021 cannam@21: 00022 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, cannam@21: 00023 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF cannam@21: 00024 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND cannam@21: 00025 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR cannam@21: 00026 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF cannam@21: 00027 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION cannam@21: 00028 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cannam@21: 00029 cannam@21: 00030 Except as contained in this notice, the names of the Centre for cannam@21: 00031 Digital Music; Queen Mary, University of London; and Chris Cannam cannam@21: 00032 shall not be used in advertising or otherwise to promote the sale, cannam@21: 00033 use or other dealings in this Software without prior written cannam@21: 00034 authorization. cannam@21: 00035 */ cannam@21: 00036 cannam@21: 00037 #include "FixedTempoEstimator.h" cannam@21: 00038 cannam@21: 00039 using std::string; cannam@21: 00040 using std::vector; cannam@21: 00041 using std::cerr; cannam@21: 00042 using std::endl; cannam@21: 00043 cannam@21: 00044 using Vamp::RealTime; cannam@21: 00045 cannam@21: 00046 #include <cmath> cannam@21: 00047 cannam@21: 00048 cannam@21: 00049 class FixedTempoEstimator::D cannam@21: 00050 // this class just avoids us having to declare any data members in the header cannam@21: 00051 { cannam@21: 00052 public: cannam@21: 00053 D(float inputSampleRate); cannam@21: 00054 ~D(); cannam@21: 00055 cannam@21: 00056 size_t getPreferredStepSize() const { return 64; } cannam@21: 00057 size_t getPreferredBlockSize() const { return 256; } cannam@21: 00058 cannam@21: 00059 ParameterList getParameterDescriptors() const; cannam@21: 00060 float getParameter(string id) const; cannam@21: 00061 void setParameter(string id, float value); cannam@21: 00062 cannam@21: 00063 OutputList getOutputDescriptors() const; cannam@21: 00064 cannam@21: 00065 bool initialise(size_t channels, size_t stepSize, size_t blockSize); cannam@21: 00066 void reset(); cannam@21: 00067 FeatureSet process(const float *const *, RealTime); cannam@21: 00068 FeatureSet getRemainingFeatures(); cannam@21: 00069 cannam@21: 00070 private: cannam@21: 00071 void calculate(); cannam@21: 00072 FeatureSet assembleFeatures(); cannam@21: 00073 cannam@21: 00074 float lag2tempo(int); cannam@21: 00075 int tempo2lag(float); cannam@21: 00076 cannam@21: 00077 float m_inputSampleRate; cannam@21: 00078 size_t m_stepSize; cannam@21: 00079 size_t m_blockSize; cannam@21: 00080 cannam@21: 00081 float m_minbpm; cannam@21: 00082 float m_maxbpm; cannam@21: 00083 float m_maxdflen; cannam@21: 00084 cannam@21: 00085 float *m_priorMagnitudes; cannam@21: 00086 cannam@21: 00087 size_t m_dfsize; cannam@21: 00088 float *m_df; cannam@21: 00089 float *m_r; cannam@21: 00090 float *m_fr; cannam@21: 00091 float *m_t; cannam@21: 00092 size_t m_n; cannam@21: 00093 cannam@21: 00094 Vamp::RealTime m_start; cannam@21: 00095 Vamp::RealTime m_lasttime; cannam@21: 00096 }; cannam@21: 00097 cannam@21: 00098 FixedTempoEstimator::D::D(float inputSampleRate) : cannam@21: 00099 m_inputSampleRate(inputSampleRate), cannam@21: 00100 m_stepSize(0), cannam@21: 00101 m_blockSize(0), cannam@21: 00102 m_minbpm(50), cannam@21: 00103 m_maxbpm(190), cannam@21: 00104 m_maxdflen(10), cannam@21: 00105 m_priorMagnitudes(0), cannam@21: 00106 m_df(0), cannam@21: 00107 m_r(0), cannam@21: 00108 m_fr(0), cannam@21: 00109 m_t(0), cannam@21: 00110 m_n(0) cannam@21: 00111 { cannam@21: 00112 } cannam@21: 00113 cannam@21: 00114 FixedTempoEstimator::D::~D() cannam@21: 00115 { cannam@21: 00116 delete[] m_priorMagnitudes; cannam@21: 00117 delete[] m_df; cannam@21: 00118 delete[] m_r; cannam@21: 00119 delete[] m_fr; cannam@21: 00120 delete[] m_t; cannam@21: 00121 } cannam@21: 00122 cannam@21: 00123 FixedTempoEstimator::ParameterList cannam@21: 00124 FixedTempoEstimator::D::getParameterDescriptors() const cannam@21: 00125 { cannam@21: 00126 ParameterList list; cannam@21: 00127 cannam@21: 00128 ParameterDescriptor d; cannam@21: 00129 d.identifier = "minbpm"; cannam@21: 00130 d.name = "Minimum estimated tempo"; cannam@21: 00131 d.description = "Minimum beat-per-minute value which the tempo estimator is able to return"; cannam@21: 00132 d.unit = "bpm"; cannam@21: 00133 d.minValue = 10; cannam@21: 00134 d.maxValue = 360; cannam@21: 00135 d.defaultValue = 50; cannam@21: 00136 d.isQuantized = false; cannam@21: 00137 list.push_back(d); cannam@21: 00138 cannam@21: 00139 d.identifier = "maxbpm"; cannam@21: 00140 d.name = "Maximum estimated tempo"; cannam@21: 00141 d.description = "Maximum beat-per-minute value which the tempo estimator is able to return"; cannam@21: 00142 d.defaultValue = 190; cannam@21: 00143 list.push_back(d); cannam@21: 00144 cannam@21: 00145 d.identifier = "maxdflen"; cannam@21: 00146 d.name = "Input duration to study"; cannam@21: 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."; cannam@21: 00148 d.unit = "s"; cannam@21: 00149 d.minValue = 2; cannam@21: 00150 d.maxValue = 40; cannam@21: 00151 d.defaultValue = 10; cannam@21: 00152 list.push_back(d); cannam@21: 00153 cannam@21: 00154 return list; cannam@21: 00155 } cannam@21: 00156 cannam@21: 00157 float cannam@21: 00158 FixedTempoEstimator::D::getParameter(string id) const cannam@21: 00159 { cannam@21: 00160 if (id == "minbpm") { cannam@21: 00161 return m_minbpm; cannam@21: 00162 } else if (id == "maxbpm") { cannam@21: 00163 return m_maxbpm; cannam@21: 00164 } else if (id == "maxdflen") { cannam@21: 00165 return m_maxdflen; cannam@21: 00166 } cannam@21: 00167 return 0.f; cannam@21: 00168 } cannam@21: 00169 cannam@21: 00170 void cannam@21: 00171 FixedTempoEstimator::D::setParameter(string id, float value) cannam@21: 00172 { cannam@21: 00173 if (id == "minbpm") { cannam@21: 00174 m_minbpm = value; cannam@21: 00175 } else if (id == "maxbpm") { cannam@21: 00176 m_maxbpm = value; cannam@21: 00177 } else if (id == "maxdflen") { cannam@21: 00178 m_maxdflen = value; cannam@21: 00179 } cannam@21: 00180 } cannam@21: 00181 cannam@21: 00182 static int TempoOutput = 0; cannam@21: 00183 static int CandidatesOutput = 1; cannam@21: 00184 static int DFOutput = 2; cannam@21: 00185 static int ACFOutput = 3; cannam@21: 00186 static int FilteredACFOutput = 4; cannam@21: 00187 cannam@21: 00188 FixedTempoEstimator::OutputList cannam@21: 00189 FixedTempoEstimator::D::getOutputDescriptors() const cannam@21: 00190 { cannam@21: 00191 OutputList list; cannam@21: 00192 cannam@21: 00193 OutputDescriptor d; cannam@21: 00194 d.identifier = "tempo"; cannam@21: 00195 d.name = "Tempo"; cannam@21: 00196 d.description = "Estimated tempo"; cannam@21: 00197 d.unit = "bpm"; cannam@21: 00198 d.hasFixedBinCount = true; cannam@21: 00199 d.binCount = 1; cannam@21: 00200 d.hasKnownExtents = false; cannam@21: 00201 d.isQuantized = false; cannam@21: 00202 d.sampleType = OutputDescriptor::VariableSampleRate; cannam@21: 00203 d.sampleRate = m_inputSampleRate; cannam@21: 00204 d.hasDuration = true; // our returned tempo spans a certain range cannam@21: 00205 list.push_back(d); cannam@21: 00206 cannam@21: 00207 d.identifier = "candidates"; cannam@21: 00208 d.name = "Tempo candidates"; cannam@21: 00209 d.description = "Possible tempo estimates, one per bin with the most likely in the first bin"; cannam@21: 00210 d.unit = "bpm"; cannam@21: 00211 d.hasFixedBinCount = false; cannam@21: 00212 list.push_back(d); cannam@21: 00213 cannam@21: 00214 d.identifier = "detectionfunction"; cannam@21: 00215 d.name = "Detection Function"; cannam@21: 00216 d.description = "Onset detection function"; cannam@21: 00217 d.unit = ""; cannam@21: 00218 d.hasFixedBinCount = 1; cannam@21: 00219 d.binCount = 1; cannam@21: 00220 d.hasKnownExtents = true; cannam@21: 00221 d.minValue = 0.0; cannam@21: 00222 d.maxValue = 1.0; cannam@21: 00223 d.isQuantized = false; cannam@21: 00224 d.quantizeStep = 0.0; cannam@21: 00225 d.sampleType = OutputDescriptor::FixedSampleRate; cannam@21: 00226 if (m_stepSize) { cannam@21: 00227 d.sampleRate = m_inputSampleRate / m_stepSize; cannam@21: 00228 } else { cannam@21: 00229 d.sampleRate = m_inputSampleRate / (getPreferredBlockSize()/2); cannam@21: 00230 } cannam@21: 00231 d.hasDuration = false; cannam@21: 00232 list.push_back(d); cannam@21: 00233 cannam@21: 00234 d.identifier = "acf"; cannam@21: 00235 d.name = "Autocorrelation Function"; cannam@21: 00236 d.description = "Autocorrelation of onset detection function"; cannam@21: 00237 d.hasKnownExtents = false; cannam@21: 00238 d.unit = "r"; cannam@21: 00239 list.push_back(d); cannam@21: 00240 cannam@21: 00241 d.identifier = "filtered_acf"; cannam@21: 00242 d.name = "Filtered Autocorrelation"; cannam@21: 00243 d.description = "Filtered autocorrelation of onset detection function"; cannam@21: 00244 d.unit = "r"; cannam@21: 00245 list.push_back(d); cannam@21: 00246 cannam@21: 00247 return list; cannam@21: 00248 } cannam@21: 00249 cannam@21: 00250 bool cannam@21: 00251 FixedTempoEstimator::D::initialise(size_t, size_t stepSize, size_t blockSize) cannam@21: 00252 { cannam@21: 00253 m_stepSize = stepSize; cannam@21: 00254 m_blockSize = blockSize; cannam@21: 00255 cannam@21: 00256 float dfLengthSecs = m_maxdflen; cannam@21: 00257 m_dfsize = (dfLengthSecs * m_inputSampleRate) / m_stepSize; cannam@21: 00258 cannam@21: 00259 m_priorMagnitudes = new float[m_blockSize/2]; cannam@21: 00260 m_df = new float[m_dfsize]; cannam@21: 00261 cannam@21: 00262 for (size_t i = 0; i < m_blockSize/2; ++i) { cannam@21: 00263 m_priorMagnitudes[i] = 0.f; cannam@21: 00264 } cannam@21: 00265 for (size_t i = 0; i < m_dfsize; ++i) { cannam@21: 00266 m_df[i] = 0.f; cannam@21: 00267 } cannam@21: 00268 cannam@21: 00269 m_n = 0; cannam@21: 00270 cannam@21: 00271 return true; cannam@21: 00272 } cannam@21: 00273 cannam@21: 00274 void cannam@21: 00275 FixedTempoEstimator::D::reset() cannam@21: 00276 { cannam@21: 00277 if (!m_priorMagnitudes) return; cannam@21: 00278 cannam@21: 00279 for (size_t i = 0; i < m_blockSize/2; ++i) { cannam@21: 00280 m_priorMagnitudes[i] = 0.f; cannam@21: 00281 } cannam@21: 00282 for (size_t i = 0; i < m_dfsize; ++i) { cannam@21: 00283 m_df[i] = 0.f; cannam@21: 00284 } cannam@21: 00285 cannam@21: 00286 delete[] m_r; cannam@21: 00287 m_r = 0; cannam@21: 00288 cannam@21: 00289 delete[] m_fr; cannam@21: 00290 m_fr = 0; cannam@21: 00291 cannam@21: 00292 delete[] m_t; cannam@21: 00293 m_t = 0; cannam@21: 00294 cannam@21: 00295 m_n = 0; cannam@21: 00296 cannam@21: 00297 m_start = RealTime::zeroTime; cannam@21: 00298 m_lasttime = RealTime::zeroTime; cannam@21: 00299 } cannam@21: 00300 cannam@21: 00301 FixedTempoEstimator::FeatureSet cannam@21: 00302 FixedTempoEstimator::D::process(const float *const *inputBuffers, RealTime ts) cannam@21: 00303 { cannam@21: 00304 FeatureSet fs; cannam@21: 00305 cannam@21: 00306 if (m_stepSize == 0) { cannam@21: 00307 cerr << "ERROR: FixedTempoEstimator::process: " cannam@21: 00308 << "FixedTempoEstimator has not been initialised" cannam@21: 00309 << endl; cannam@21: 00310 return fs; cannam@21: 00311 } cannam@21: 00312 cannam@21: 00313 if (m_n == 0) m_start = ts; cannam@21: 00314 m_lasttime = ts; cannam@21: 00315 cannam@21: 00316 if (m_n == m_dfsize) { cannam@21: 00317 // If we have seen enough input, do the estimation and return cannam@21: 00318 calculate(); cannam@21: 00319 fs = assembleFeatures(); cannam@21: 00320 ++m_n; cannam@21: 00321 return fs; cannam@21: 00322 } cannam@21: 00323 cannam@21: 00324 // If we have seen more than enough, just discard and return! cannam@21: 00325 if (m_n > m_dfsize) return FeatureSet(); cannam@21: 00326 cannam@21: 00327 float value = 0.f; cannam@21: 00328 cannam@21: 00329 // m_df will contain an onset detection function based on the rise cannam@21: 00330 // in overall power from one spectral frame to the next -- cannam@21: 00331 // simplistic but reasonably effective for our purposes. cannam@21: 00332 cannam@21: 00333 for (size_t i = 1; i < m_blockSize/2; ++i) { cannam@21: 00334 cannam@21: 00335 float real = inputBuffers[0][i*2]; cannam@21: 00336 float imag = inputBuffers[0][i*2 + 1]; cannam@21: 00337 cannam@21: 00338 float sqrmag = real * real + imag * imag; cannam@21: 00339 value += fabsf(sqrmag - m_priorMagnitudes[i]); cannam@21: 00340 cannam@21: 00341 m_priorMagnitudes[i] = sqrmag; cannam@21: 00342 } cannam@21: 00343 cannam@21: 00344 m_df[m_n] = value; cannam@21: 00345 cannam@21: 00346 ++m_n; cannam@21: 00347 return fs; cannam@21: 00348 } cannam@21: 00349 cannam@21: 00350 FixedTempoEstimator::FeatureSet cannam@21: 00351 FixedTempoEstimator::D::getRemainingFeatures() cannam@21: 00352 { cannam@21: 00353 FeatureSet fs; cannam@21: 00354 if (m_n > m_dfsize) return fs; cannam@21: 00355 calculate(); cannam@21: 00356 fs = assembleFeatures(); cannam@21: 00357 ++m_n; cannam@21: 00358 return fs; cannam@21: 00359 } cannam@21: 00360 cannam@21: 00361 float cannam@21: 00362 FixedTempoEstimator::D::lag2tempo(int lag) cannam@21: 00363 { cannam@21: 00364 return 60.f / ((lag * m_stepSize) / m_inputSampleRate); cannam@21: 00365 } cannam@21: 00366 cannam@21: 00367 int cannam@21: 00368 FixedTempoEstimator::D::tempo2lag(float tempo) cannam@21: 00369 { cannam@21: 00370 return ((60.f / tempo) * m_inputSampleRate) / m_stepSize; cannam@21: 00371 } cannam@21: 00372 cannam@21: 00373 void cannam@21: 00374 FixedTempoEstimator::D::calculate() cannam@21: 00375 { cannam@21: 00376 if (m_r) { cannam@21: 00377 cerr << "FixedTempoEstimator::calculate: calculation already happened?" << endl; cannam@21: 00378 return; cannam@21: 00379 } cannam@21: 00380 cannam@21: 00381 if (m_n < m_dfsize / 9 && cannam@21: 00382 m_n < (1.0 * m_inputSampleRate) / m_stepSize) { // 1 second cannam@21: 00383 cerr << "FixedTempoEstimator::calculate: Input is too short" << endl; cannam@21: 00384 return; cannam@21: 00385 } cannam@21: 00386 cannam@21: 00387 // This function takes m_df (the detection function array filled cannam@21: 00388 // out in process()) and calculates m_r (the raw autocorrelation) cannam@21: 00389 // and m_fr (the filtered autocorrelation from whose peaks tempo cannam@21: 00390 // estimates will be taken). cannam@21: 00391 cannam@21: 00392 int n = m_n; // length of actual df array (m_dfsize is the theoretical max) cannam@21: 00393 cannam@21: 00394 m_r = new float[n/2]; // raw autocorrelation cannam@21: 00395 m_fr = new float[n/2]; // filtered autocorrelation cannam@21: 00396 m_t = new float[n/2]; // averaged tempo estimate for each lag value cannam@21: 00397 cannam@21: 00398 for (int i = 0; i < n/2; ++i) { cannam@21: 00399 m_r[i] = 0.f; cannam@21: 00400 m_fr[i] = 0.f; cannam@21: 00401 m_t[i] = lag2tempo(i); cannam@21: 00402 } cannam@21: 00403 cannam@21: 00404 // Calculate the raw autocorrelation of the detection function cannam@21: 00405 cannam@21: 00406 for (int i = 0; i < n/2; ++i) { cannam@21: 00407 cannam@21: 00408 for (int j = i; j < n; ++j) { cannam@21: 00409 m_r[i] += m_df[j] * m_df[j - i]; cannam@21: 00410 } cannam@21: 00411 cannam@21: 00412 m_r[i] /= n - i - 1; cannam@21: 00413 } cannam@21: 00414 cannam@21: 00415 // Filter the autocorrelation and average out the tempo estimates cannam@21: 00416 cannam@21: 00417 float related[] = { 0.5, 2, 4, 8 }; cannam@21: 00418 cannam@21: 00419 for (int i = 1; i < n/2-1; ++i) { cannam@21: 00420 cannam@21: 00421 m_fr[i] = m_r[i]; cannam@21: 00422 cannam@21: 00423 int div = 1; cannam@21: 00424 cannam@21: 00425 for (int j = 0; j < int(sizeof(related)/sizeof(related[0])); ++j) { cannam@21: 00426 cannam@21: 00427 // Check for an obvious peak at each metrically related lag cannam@21: 00428 cannam@21: 00429 int k0 = int(i * related[j] + 0.5); cannam@21: 00430 cannam@21: 00431 if (k0 >= 0 && k0 < int(n/2)) { cannam@21: 00432 cannam@21: 00433 int kmax = 0, kmin = 0; cannam@21: 00434 float kvmax = 0, kvmin = 0; cannam@21: 00435 bool have = false; cannam@21: 00436 cannam@21: 00437 for (int k = k0 - 1; k <= k0 + 1; ++k) { cannam@21: 00438 cannam@21: 00439 if (k < 0 || k >= n/2) continue; cannam@21: 00440 cannam@21: 00441 if (!have || (m_r[k] > kvmax)) { kmax = k; kvmax = m_r[k]; } cannam@21: 00442 if (!have || (m_r[k] < kvmin)) { kmin = k; kvmin = m_r[k]; } cannam@21: 00443 cannam@21: 00444 have = true; cannam@21: 00445 } cannam@21: 00446 cannam@21: 00447 // Boost the original lag according to the strongest cannam@21: 00448 // value found close to this related lag cannam@21: 00449 cannam@21: 00450 m_fr[i] += m_r[kmax] / 5; cannam@21: 00451 cannam@21: 00452 if ((kmax == 0 || m_r[kmax] > m_r[kmax-1]) && cannam@21: 00453 (kmax == n/2-1 || m_r[kmax] > m_r[kmax+1]) && cannam@21: 00454 kvmax > kvmin * 1.05) { cannam@21: 00455 cannam@21: 00456 // The strongest value close to the related lag is cannam@21: 00457 // also a pretty good looking peak, so use it to cannam@21: 00458 // improve our tempo estimate for the original lag cannam@21: 00459 cannam@21: 00460 m_t[i] = m_t[i] + lag2tempo(kmax) * related[j]; cannam@21: 00461 ++div; cannam@21: 00462 } cannam@21: 00463 } cannam@21: 00464 } cannam@21: 00465 cannam@21: 00466 m_t[i] /= div; cannam@21: 00467 cannam@21: 00468 // Finally apply a primitive perceptual weighting (to prefer cannam@21: 00469 // tempi of around 120-130) cannam@21: 00470 cannam@21: 00471 float weight = 1.f - fabsf(128.f - lag2tempo(i)) * 0.005; cannam@21: 00472 if (weight < 0.f) weight = 0.f; cannam@21: 00473 weight = weight * weight * weight; cannam@21: 00474 cannam@21: 00475 m_fr[i] += m_fr[i] * (weight / 3); cannam@21: 00476 } cannam@21: 00477 } cannam@21: 00478 cannam@21: 00479 FixedTempoEstimator::FeatureSet cannam@21: 00480 FixedTempoEstimator::D::assembleFeatures() cannam@21: 00481 { cannam@21: 00482 FeatureSet fs; cannam@21: 00483 if (!m_r) return fs; // No autocorrelation: no results cannam@21: 00484 cannam@21: 00485 Feature feature; cannam@21: 00486 feature.hasTimestamp = true; cannam@21: 00487 feature.hasDuration = false; cannam@21: 00488 feature.label = ""; cannam@21: 00489 feature.values.clear(); cannam@21: 00490 feature.values.push_back(0.f); cannam@21: 00491 cannam@21: 00492 char buffer[40]; cannam@21: 00493 cannam@21: 00494 int n = m_n; cannam@21: 00495 cannam@21: 00496 for (int i = 0; i < n; ++i) { cannam@21: 00497 cannam@21: 00498 // Return the detection function in the DF output cannam@21: 00499 cannam@21: 00500 feature.timestamp = m_start + cannam@21: 00501 RealTime::frame2RealTime(i * m_stepSize, m_inputSampleRate); cannam@21: 00502 feature.values[0] = m_df[i]; cannam@21: 00503 feature.label = ""; cannam@21: 00504 fs[DFOutput].push_back(feature); cannam@21: 00505 } cannam@21: 00506 cannam@21: 00507 for (int i = 1; i < n/2; ++i) { cannam@21: 00508 cannam@21: 00509 // Return the raw autocorrelation in the ACF output, each cannam@21: 00510 // value labelled according to its corresponding tempo cannam@21: 00511 cannam@21: 00512 feature.timestamp = m_start + cannam@21: 00513 RealTime::frame2RealTime(i * m_stepSize, m_inputSampleRate); cannam@21: 00514 feature.values[0] = m_r[i]; cannam@21: 00515 sprintf(buffer, "%.1f bpm", lag2tempo(i)); cannam@21: 00516 if (i == n/2-1) feature.label = ""; cannam@21: 00517 else feature.label = buffer; cannam@21: 00518 fs[ACFOutput].push_back(feature); cannam@21: 00519 } cannam@21: 00520 cannam@21: 00521 float t0 = m_minbpm; // our minimum detected tempo cannam@21: 00522 float t1 = m_maxbpm; // our maximum detected tempo cannam@21: 00523 cannam@21: 00524 int p0 = tempo2lag(t1); cannam@21: 00525 int p1 = tempo2lag(t0); cannam@21: 00526 cannam@21: 00527 std::map<float, int> candidates; cannam@21: 00528 cannam@21: 00529 for (int i = p0; i <= p1 && i+1 < n/2; ++i) { cannam@21: 00530 cannam@21: 00531 if (m_fr[i] > m_fr[i-1] && cannam@21: 00532 m_fr[i] > m_fr[i+1]) { cannam@21: 00533 cannam@21: 00534 // This is a peak in the filtered autocorrelation: stick cannam@21: 00535 // it into the map from filtered autocorrelation to lag cannam@21: 00536 // index -- this sorts our peaks by filtered acf value cannam@21: 00537 cannam@21: 00538 candidates[m_fr[i]] = i; cannam@21: 00539 } cannam@21: 00540 cannam@21: 00541 // Also return the filtered autocorrelation in its own output cannam@21: 00542 cannam@21: 00543 feature.timestamp = m_start + cannam@21: 00544 RealTime::frame2RealTime(i * m_stepSize, m_inputSampleRate); cannam@21: 00545 feature.values[0] = m_fr[i]; cannam@21: 00546 sprintf(buffer, "%.1f bpm", lag2tempo(i)); cannam@21: 00547 if (i == p1 || i == n/2-2) feature.label = ""; cannam@21: 00548 else feature.label = buffer; cannam@21: 00549 fs[FilteredACFOutput].push_back(feature); cannam@21: 00550 } cannam@21: 00551 cannam@21: 00552 if (candidates.empty()) { cannam@21: 00553 cerr << "No tempo candidates!" << endl; cannam@21: 00554 return fs; cannam@21: 00555 } cannam@21: 00556 cannam@21: 00557 feature.hasTimestamp = true; cannam@21: 00558 feature.timestamp = m_start; cannam@21: 00559 cannam@21: 00560 feature.hasDuration = true; cannam@21: 00561 feature.duration = m_lasttime - m_start; cannam@21: 00562 cannam@21: 00563 // The map contains only peaks and is sorted by filtered acf cannam@21: 00564 // value, so the final element in it is our "best" tempo guess cannam@21: 00565 cannam@21: 00566 std::map<float, int>::const_iterator ci = candidates.end(); cannam@21: 00567 --ci; cannam@21: 00568 int maxpi = ci->second; cannam@21: 00569 cannam@21: 00570 if (m_t[maxpi] > 0) { cannam@21: 00571 cannam@21: 00572 // This lag has an adjusted tempo from the averaging process: cannam@21: 00573 // use it cannam@21: 00574 cannam@21: 00575 feature.values[0] = m_t[maxpi]; cannam@21: 00576 cannam@21: 00577 } else { cannam@21: 00578 cannam@21: 00579 // shouldn't happen -- it would imply that this high value was cannam@21: 00580 // not a peak! cannam@21: 00581 cannam@21: 00582 feature.values[0] = lag2tempo(maxpi); cannam@21: 00583 cerr << "WARNING: No stored tempo for index " << maxpi << endl; cannam@21: 00584 } cannam@21: 00585 cannam@21: 00586 sprintf(buffer, "%.1f bpm", feature.values[0]); cannam@21: 00587 feature.label = buffer; cannam@21: 00588 cannam@21: 00589 // Return the best tempo in the main output cannam@21: 00590 cannam@21: 00591 fs[TempoOutput].push_back(feature); cannam@21: 00592 cannam@21: 00593 // And return the other estimates (up to the arbitrarily chosen cannam@21: 00594 // number of 10 of them) in the candidates output cannam@21: 00595 cannam@21: 00596 feature.values.clear(); cannam@21: 00597 feature.label = ""; cannam@21: 00598 cannam@21: 00599 while (feature.values.size() < 10) { cannam@21: 00600 if (m_t[ci->second] > 0) { cannam@21: 00601 feature.values.push_back(m_t[ci->second]); cannam@21: 00602 } else { cannam@21: 00603 feature.values.push_back(lag2tempo(ci->second)); cannam@21: 00604 } cannam@21: 00605 if (ci == candidates.begin()) break; cannam@21: 00606 --ci; cannam@21: 00607 } cannam@21: 00608 cannam@21: 00609 fs[CandidatesOutput].push_back(feature); cannam@21: 00610 cannam@21: 00611 return fs; cannam@21: 00612 } cannam@21: 00613 cannam@21: 00614 cannam@21: 00615 cannam@21: 00616 FixedTempoEstimator::FixedTempoEstimator(float inputSampleRate) : cannam@21: 00617 Plugin(inputSampleRate), cannam@21: 00618 m_d(new D(inputSampleRate)) cannam@21: 00619 { cannam@21: 00620 } cannam@21: 00621 cannam@21: 00622 FixedTempoEstimator::~FixedTempoEstimator() cannam@21: 00623 { cannam@21: 00624 delete m_d; cannam@21: 00625 } cannam@21: 00626 cannam@21: 00627 string cannam@21: 00628 FixedTempoEstimator::getIdentifier() const cannam@21: 00629 { cannam@21: 00630 return "fixedtempo"; cannam@21: 00631 } cannam@21: 00632 cannam@21: 00633 string cannam@21: 00634 FixedTempoEstimator::getName() const cannam@21: 00635 { cannam@21: 00636 return "Simple Fixed Tempo Estimator"; cannam@21: 00637 } cannam@21: 00638 cannam@21: 00639 string cannam@21: 00640 FixedTempoEstimator::getDescription() const cannam@21: 00641 { cannam@21: 00642 return "Study a short section of audio and estimate its tempo, assuming the tempo is constant"; cannam@21: 00643 } cannam@21: 00644 cannam@21: 00645 string cannam@21: 00646 FixedTempoEstimator::getMaker() const cannam@21: 00647 { cannam@21: 00648 return "Vamp SDK Example Plugins"; cannam@21: 00649 } cannam@21: 00650 cannam@21: 00651 int cannam@21: 00652 FixedTempoEstimator::getPluginVersion() const cannam@21: 00653 { cannam@21: 00654 return 1; cannam@21: 00655 } cannam@21: 00656 cannam@21: 00657 string cannam@21: 00658 FixedTempoEstimator::getCopyright() const cannam@21: 00659 { cannam@21: 00660 return "Code copyright 2008 Queen Mary, University of London. Freely redistributable (BSD license)"; cannam@21: 00661 } cannam@21: 00662 cannam@21: 00663 size_t cannam@21: 00664 FixedTempoEstimator::getPreferredStepSize() const cannam@21: 00665 { cannam@21: 00666 return m_d->getPreferredStepSize(); cannam@21: 00667 } cannam@21: 00668 cannam@21: 00669 size_t cannam@21: 00670 FixedTempoEstimator::getPreferredBlockSize() const cannam@21: 00671 { cannam@21: 00672 return m_d->getPreferredBlockSize(); cannam@21: 00673 } cannam@21: 00674 cannam@21: 00675 bool cannam@21: 00676 FixedTempoEstimator::initialise(size_t channels, size_t stepSize, size_t blockSize) cannam@21: 00677 { cannam@21: 00678 if (channels < getMinChannelCount() || cannam@21: 00679 channels > getMaxChannelCount()) return false; cannam@21: 00680 cannam@21: 00681 return m_d->initialise(channels, stepSize, blockSize); cannam@21: 00682 } cannam@21: 00683 cannam@21: 00684 void cannam@21: 00685 FixedTempoEstimator::reset() cannam@21: 00686 { cannam@21: 00687 return m_d->reset(); cannam@21: 00688 } cannam@21: 00689 cannam@21: 00690 FixedTempoEstimator::ParameterList cannam@21: 00691 FixedTempoEstimator::getParameterDescriptors() const cannam@21: 00692 { cannam@21: 00693 return m_d->getParameterDescriptors(); cannam@21: 00694 } cannam@21: 00695 cannam@21: 00696 float cannam@21: 00697 FixedTempoEstimator::getParameter(std::string id) const cannam@21: 00698 { cannam@21: 00699 return m_d->getParameter(id); cannam@21: 00700 } cannam@21: 00701 cannam@21: 00702 void cannam@21: 00703 FixedTempoEstimator::setParameter(std::string id, float value) cannam@21: 00704 { cannam@21: 00705 m_d->setParameter(id, value); cannam@21: 00706 } cannam@21: 00707 cannam@21: 00708 FixedTempoEstimator::OutputList cannam@21: 00709 FixedTempoEstimator::getOutputDescriptors() const cannam@21: 00710 { cannam@21: 00711 return m_d->getOutputDescriptors(); cannam@21: 00712 } cannam@21: 00713 cannam@21: 00714 FixedTempoEstimator::FeatureSet cannam@21: 00715 FixedTempoEstimator::process(const float *const *inputBuffers, RealTime ts) cannam@21: 00716 { cannam@21: 00717 return m_d->process(inputBuffers, ts); cannam@21: 00718 } cannam@21: 00719 cannam@21: 00720 FixedTempoEstimator::FeatureSet cannam@21: 00721 FixedTempoEstimator::getRemainingFeatures() cannam@21: 00722 { cannam@21: 00723 return m_d->getRemainingFeatures(); cannam@21: 00724 } cannam@21: