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