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