cannam@50: cannam@50: cannam@50: cannam@50: cannam@50: VampPluginSDK: FixedTempoEstimator.cpp Source File cannam@50: cannam@50: cannam@50: cannam@50: cannam@50: cannam@50: cannam@50: cannam@50: cannam@50: cannam@50: cannam@50: cannam@50: cannam@50:
cannam@50: cannam@50: cannam@50:
cannam@50: cannam@50: cannam@50: cannam@50: cannam@50: cannam@50: cannam@50: 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: cannam@50: cannam@50:
cannam@50:
cannam@50: cannam@50:
cannam@50:
cannam@50:
cannam@50: cannam@50:
cannam@50:
cannam@50:
cannam@50:
FixedTempoEstimator.cpp
cannam@50:
cannam@50:
cannam@50: Go to the documentation of this file.
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: 
cannam@50:
cannam@50: cannam@50: cannam@50: cannam@50: cannam@50: