annotate examples/FixedTempoEstimator.cpp @ 198:e3e61b7e9661

* Beginnings of a simple tempo estimator example plugin
author cannam
date Wed, 08 Oct 2008 15:26:50 +0000
parents
children 84c4bb209227
rev   line source
cannam@198 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
cannam@198 2
cannam@198 3 /*
cannam@198 4 Vamp
cannam@198 5
cannam@198 6 An API for audio analysis and feature extraction plugins.
cannam@198 7
cannam@198 8 Centre for Digital Music, Queen Mary, University of London.
cannam@198 9 Copyright 2006-2008 Chris Cannam and QMUL.
cannam@198 10
cannam@198 11 Permission is hereby granted, free of charge, to any person
cannam@198 12 obtaining a copy of this software and associated documentation
cannam@198 13 files (the "Software"), to deal in the Software without
cannam@198 14 restriction, including without limitation the rights to use, copy,
cannam@198 15 modify, merge, publish, distribute, sublicense, and/or sell copies
cannam@198 16 of the Software, and to permit persons to whom the Software is
cannam@198 17 furnished to do so, subject to the following conditions:
cannam@198 18
cannam@198 19 The above copyright notice and this permission notice shall be
cannam@198 20 included in all copies or substantial portions of the Software.
cannam@198 21
cannam@198 22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
cannam@198 23 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
cannam@198 24 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
cannam@198 25 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
cannam@198 26 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
cannam@198 27 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
cannam@198 28 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
cannam@198 29
cannam@198 30 Except as contained in this notice, the names of the Centre for
cannam@198 31 Digital Music; Queen Mary, University of London; and Chris Cannam
cannam@198 32 shall not be used in advertising or otherwise to promote the sale,
cannam@198 33 use or other dealings in this Software without prior written
cannam@198 34 authorization.
cannam@198 35 */
cannam@198 36
cannam@198 37 #include "FixedTempoEstimator.h"
cannam@198 38
cannam@198 39 using std::string;
cannam@198 40 using std::vector;
cannam@198 41 using std::cerr;
cannam@198 42 using std::endl;
cannam@198 43
cannam@198 44 using Vamp::RealTime;
cannam@198 45
cannam@198 46 #include <cmath>
cannam@198 47
cannam@198 48
cannam@198 49 FixedTempoEstimator::FixedTempoEstimator(float inputSampleRate) :
cannam@198 50 Plugin(inputSampleRate),
cannam@198 51 m_stepSize(0),
cannam@198 52 m_blockSize(0),
cannam@198 53 m_priorMagnitudes(0),
cannam@198 54 m_df(0)
cannam@198 55 {
cannam@198 56 }
cannam@198 57
cannam@198 58 FixedTempoEstimator::~FixedTempoEstimator()
cannam@198 59 {
cannam@198 60 delete[] m_priorMagnitudes;
cannam@198 61 delete[] m_df;
cannam@198 62 }
cannam@198 63
cannam@198 64 string
cannam@198 65 FixedTempoEstimator::getIdentifier() const
cannam@198 66 {
cannam@198 67 return "fixedtempo";
cannam@198 68 }
cannam@198 69
cannam@198 70 string
cannam@198 71 FixedTempoEstimator::getName() const
cannam@198 72 {
cannam@198 73 return "Simple Fixed Tempo Estimator";
cannam@198 74 }
cannam@198 75
cannam@198 76 string
cannam@198 77 FixedTempoEstimator::getDescription() const
cannam@198 78 {
cannam@198 79 return "Study a short section of audio and estimate its tempo, assuming the tempo is constant";
cannam@198 80 }
cannam@198 81
cannam@198 82 string
cannam@198 83 FixedTempoEstimator::getMaker() const
cannam@198 84 {
cannam@198 85 return "Vamp SDK Example Plugins";
cannam@198 86 }
cannam@198 87
cannam@198 88 int
cannam@198 89 FixedTempoEstimator::getPluginVersion() const
cannam@198 90 {
cannam@198 91 return 1;
cannam@198 92 }
cannam@198 93
cannam@198 94 string
cannam@198 95 FixedTempoEstimator::getCopyright() const
cannam@198 96 {
cannam@198 97 return "Code copyright 2008 Queen Mary, University of London. Freely redistributable (BSD license)";
cannam@198 98 }
cannam@198 99
cannam@198 100 size_t
cannam@198 101 FixedTempoEstimator::getPreferredStepSize() const
cannam@198 102 {
cannam@198 103 return 0;
cannam@198 104 }
cannam@198 105
cannam@198 106 size_t
cannam@198 107 FixedTempoEstimator::getPreferredBlockSize() const
cannam@198 108 {
cannam@198 109 return 128;
cannam@198 110 }
cannam@198 111
cannam@198 112 bool
cannam@198 113 FixedTempoEstimator::initialise(size_t channels, size_t stepSize, size_t blockSize)
cannam@198 114 {
cannam@198 115 if (channels < getMinChannelCount() ||
cannam@198 116 channels > getMaxChannelCount()) return false;
cannam@198 117
cannam@198 118 m_stepSize = stepSize;
cannam@198 119 m_blockSize = blockSize;
cannam@198 120
cannam@198 121 float dfLengthSecs = 8.f;
cannam@198 122 m_dfsize = (dfLengthSecs * m_inputSampleRate) / m_stepSize;
cannam@198 123
cannam@198 124 m_priorMagnitudes = new float[m_blockSize/2];
cannam@198 125 m_df = new float[m_dfsize];
cannam@198 126
cannam@198 127 for (size_t i = 0; i < m_blockSize/2; ++i) {
cannam@198 128 m_priorMagnitudes[i] = 0.f;
cannam@198 129 }
cannam@198 130 for (size_t i = 0; i < m_dfsize; ++i) {
cannam@198 131 m_df[i] = 0.f;
cannam@198 132 }
cannam@198 133
cannam@198 134 m_n = 0;
cannam@198 135
cannam@198 136 return true;
cannam@198 137 }
cannam@198 138
cannam@198 139 void
cannam@198 140 FixedTempoEstimator::reset()
cannam@198 141 {
cannam@198 142 std::cerr << "FixedTempoEstimator: reset called" << std::endl;
cannam@198 143
cannam@198 144 if (!m_priorMagnitudes) return;
cannam@198 145
cannam@198 146 std::cerr << "FixedTempoEstimator: resetting" << std::endl;
cannam@198 147
cannam@198 148 for (size_t i = 0; i < m_blockSize/2; ++i) {
cannam@198 149 m_priorMagnitudes[i] = 0.f;
cannam@198 150 }
cannam@198 151 for (size_t i = 0; i < m_dfsize; ++i) {
cannam@198 152 m_df[i] = 0.f;
cannam@198 153 }
cannam@198 154
cannam@198 155 m_n = 0;
cannam@198 156
cannam@198 157 m_start = RealTime::zeroTime;
cannam@198 158 m_lasttime = RealTime::zeroTime;
cannam@198 159 }
cannam@198 160
cannam@198 161 FixedTempoEstimator::ParameterList
cannam@198 162 FixedTempoEstimator::getParameterDescriptors() const
cannam@198 163 {
cannam@198 164 ParameterList list;
cannam@198 165 return list;
cannam@198 166 }
cannam@198 167
cannam@198 168 float
cannam@198 169 FixedTempoEstimator::getParameter(std::string id) const
cannam@198 170 {
cannam@198 171 return 0.f;
cannam@198 172 }
cannam@198 173
cannam@198 174 void
cannam@198 175 FixedTempoEstimator::setParameter(std::string id, float value)
cannam@198 176 {
cannam@198 177 }
cannam@198 178
cannam@198 179 FixedTempoEstimator::OutputList
cannam@198 180 FixedTempoEstimator::getOutputDescriptors() const
cannam@198 181 {
cannam@198 182 OutputList list;
cannam@198 183
cannam@198 184 OutputDescriptor d;
cannam@198 185 d.identifier = "tempo";
cannam@198 186 d.name = "Tempo";
cannam@198 187 d.description = "Estimated tempo";
cannam@198 188 d.unit = "bpm";
cannam@198 189 d.hasFixedBinCount = true;
cannam@198 190 d.binCount = 1;
cannam@198 191 d.hasKnownExtents = false;
cannam@198 192 d.isQuantized = false;
cannam@198 193 d.sampleType = OutputDescriptor::VariableSampleRate;
cannam@198 194 d.sampleRate = m_inputSampleRate;
cannam@198 195 d.hasDuration = true; // our returned tempo spans a certain range
cannam@198 196 list.push_back(d);
cannam@198 197
cannam@198 198 d.identifier = "detectionfunction";
cannam@198 199 d.name = "Detection Function";
cannam@198 200 d.description = "Onset detection function";
cannam@198 201 d.unit = "";
cannam@198 202 d.hasFixedBinCount = 1;
cannam@198 203 d.binCount = 1;
cannam@198 204 d.hasKnownExtents = true;
cannam@198 205 d.minValue = 0.0;
cannam@198 206 d.maxValue = 1.0;
cannam@198 207 d.isQuantized = false;
cannam@198 208 d.quantizeStep = 0.0;
cannam@198 209 d.sampleType = OutputDescriptor::FixedSampleRate;
cannam@198 210 if (m_stepSize) {
cannam@198 211 d.sampleRate = m_inputSampleRate / m_stepSize;
cannam@198 212 } else {
cannam@198 213 d.sampleRate = m_inputSampleRate / (getPreferredBlockSize()/2);
cannam@198 214 }
cannam@198 215 d.hasDuration = false;
cannam@198 216 list.push_back(d);
cannam@198 217
cannam@198 218 d.identifier = "acf";
cannam@198 219 d.name = "Autocorrelation Function";
cannam@198 220 d.description = "Autocorrelation of onset detection function";
cannam@198 221 d.hasKnownExtents = false;
cannam@198 222 list.push_back(d);
cannam@198 223
cannam@198 224 d.identifier = "filtered_acf";
cannam@198 225 d.name = "Filtered Autocorrelation";
cannam@198 226 d.description = "Filtered autocorrelation of onset detection function";
cannam@198 227 list.push_back(d);
cannam@198 228
cannam@198 229 return list;
cannam@198 230 }
cannam@198 231
cannam@198 232 FixedTempoEstimator::FeatureSet
cannam@198 233 FixedTempoEstimator::process(const float *const *inputBuffers, RealTime ts)
cannam@198 234 {
cannam@198 235 FeatureSet fs;
cannam@198 236
cannam@198 237 if (m_stepSize == 0) {
cannam@198 238 cerr << "ERROR: FixedTempoEstimator::process: "
cannam@198 239 << "FixedTempoEstimator has not been initialised"
cannam@198 240 << endl;
cannam@198 241 return fs;
cannam@198 242 }
cannam@198 243
cannam@198 244 if (m_n < m_dfsize) std::cerr << "m_n = " << m_n << std::endl;
cannam@198 245
cannam@198 246 if (m_n == 0) m_start = ts;
cannam@198 247 m_lasttime = ts;
cannam@198 248
cannam@198 249 if (m_n == m_dfsize) {
cannam@198 250 fs = calculateFeatures();
cannam@198 251 ++m_n;
cannam@198 252 return fs;
cannam@198 253 }
cannam@198 254
cannam@198 255 if (m_n > m_dfsize) return FeatureSet();
cannam@198 256
cannam@198 257 int count = 0;
cannam@198 258
cannam@198 259 for (size_t i = 1; i < m_blockSize/2; ++i) {
cannam@198 260
cannam@198 261 float real = inputBuffers[0][i*2];
cannam@198 262 float imag = inputBuffers[0][i*2 + 1];
cannam@198 263
cannam@198 264 float sqrmag = real * real + imag * imag;
cannam@198 265
cannam@198 266 if (m_priorMagnitudes[i] > 0.f) {
cannam@198 267 float diff = 10.f * log10f(sqrmag / m_priorMagnitudes[i]);
cannam@198 268 if (diff >= 3.f) ++count;
cannam@198 269 }
cannam@198 270
cannam@198 271 m_priorMagnitudes[i] = sqrmag;
cannam@198 272 }
cannam@198 273
cannam@198 274 m_df[m_n] = float(count) / float(m_blockSize/2);
cannam@198 275 ++m_n;
cannam@198 276 return fs;
cannam@198 277 }
cannam@198 278
cannam@198 279 FixedTempoEstimator::FeatureSet
cannam@198 280 FixedTempoEstimator::getRemainingFeatures()
cannam@198 281 {
cannam@198 282 FeatureSet fs;
cannam@198 283 if (m_n > m_dfsize) return fs;
cannam@198 284 fs = calculateFeatures();
cannam@198 285 ++m_n;
cannam@198 286 return fs;
cannam@198 287 }
cannam@198 288
cannam@198 289 float
cannam@198 290 FixedTempoEstimator::lag2tempo(int lag) {
cannam@198 291 return 60.f / ((lag * m_stepSize) / m_inputSampleRate);
cannam@198 292 }
cannam@198 293
cannam@198 294 FixedTempoEstimator::FeatureSet
cannam@198 295 FixedTempoEstimator::calculateFeatures()
cannam@198 296 {
cannam@198 297 FeatureSet fs;
cannam@198 298 Feature feature;
cannam@198 299 feature.hasTimestamp = true;
cannam@198 300 feature.hasDuration = false;
cannam@198 301 feature.label = "";
cannam@198 302 feature.values.clear();
cannam@198 303 feature.values.push_back(0.f);
cannam@198 304
cannam@198 305 char buffer[20];
cannam@198 306
cannam@198 307 if (m_n < m_dfsize / 4) return fs; // not enough data (perhaps we should return the duration of the input as the "estimated" beat length?)
cannam@198 308
cannam@198 309 std::cerr << "FixedTempoEstimator::calculateTempo: m_n = " << m_n << std::endl;
cannam@198 310
cannam@198 311 int n = m_n;
cannam@198 312 float *f = m_df;
cannam@198 313
cannam@198 314 for (int i = 0; i < n; ++i) {
cannam@198 315 feature.timestamp = RealTime::frame2RealTime(i * m_stepSize,
cannam@198 316 m_inputSampleRate);
cannam@198 317 std::cerr << "step = " << m_stepSize << ", timestamp = " << feature.timestamp << std::endl;
cannam@198 318 feature.values[0] = f[i];
cannam@198 319 feature.label = "";
cannam@198 320 fs[1].push_back(feature);
cannam@198 321 }
cannam@198 322
cannam@198 323 float *r = new float[n/2];
cannam@198 324 for (int i = 0; i < n/2; ++i) r[i] = 0.f;
cannam@198 325
cannam@198 326 int minlag = 10;
cannam@198 327
cannam@198 328 for (int i = 0; i < n/2; ++i) {
cannam@198 329 for (int j = i; j < n-1; ++j) {
cannam@198 330 r[i] += f[j] * f[j - i];
cannam@198 331 }
cannam@198 332 r[i] /= n - i - 1;
cannam@198 333 }
cannam@198 334
cannam@198 335 for (int i = 0; i < n/2; ++i) {
cannam@198 336 feature.timestamp = RealTime::frame2RealTime(i * m_stepSize,
cannam@198 337 m_inputSampleRate);
cannam@198 338 feature.values[0] = r[i];
cannam@198 339 sprintf(buffer, "%f bpm", lag2tempo(i));
cannam@198 340 feature.label = buffer;
cannam@198 341 fs[2].push_back(feature);
cannam@198 342 }
cannam@198 343
cannam@198 344 float max = 0.f;
cannam@198 345 int maxindex = 0;
cannam@198 346
cannam@198 347 std::cerr << "n/2 = " << n/2 << std::endl;
cannam@198 348
cannam@198 349 for (int i = minlag; i < n/2; ++i) {
cannam@198 350
cannam@198 351 if (i == minlag || r[i] > max) {
cannam@198 352 max = r[i];
cannam@198 353 maxindex = i;
cannam@198 354 }
cannam@198 355
cannam@198 356 if (i == 0 || i == n/2-1) continue;
cannam@198 357
cannam@198 358 if (r[i] > r[i-1] && r[i] > r[i+1]) {
cannam@198 359 std::cerr << "peak at " << i << " (value=" << r[i] << ", tempo would be " << lag2tempo(i) << ")" << std::endl;
cannam@198 360 }
cannam@198 361 }
cannam@198 362
cannam@198 363 std::cerr << "overall max at " << maxindex << " (value=" << max << ")" << std::endl;
cannam@198 364
cannam@198 365 float tempo = lag2tempo(maxindex);
cannam@198 366
cannam@198 367 std::cerr << "provisional tempo = " << tempo << std::endl;
cannam@198 368
cannam@198 369 float t0 = 60.f;
cannam@198 370 float t1 = 180.f;
cannam@198 371
cannam@198 372 int p0 = ((60.f / t1) * m_inputSampleRate) / m_stepSize;
cannam@198 373 int p1 = ((60.f / t0) * m_inputSampleRate) / m_stepSize;
cannam@198 374
cannam@198 375 std::cerr << "p0 = " << p0 << ", p1 = " << p1 << std::endl;
cannam@198 376
cannam@198 377 int pc = p1 - p0 + 1;
cannam@198 378 std::cerr << "pc = " << pc << std::endl;
cannam@198 379 // float *filtered = new float[pc];
cannam@198 380 // for (int i = 0; i < pc; ++i) filtered[i] = 0.f;
cannam@198 381
cannam@198 382 int maxpi = 0;
cannam@198 383 float maxp = 0.f;
cannam@198 384
cannam@198 385 for (int i = p0; i <= p1; ++i) {
cannam@198 386
cannam@198 387 // int fi = i - p0;
cannam@198 388
cannam@198 389 float filtered = 0.f;
cannam@198 390
cannam@198 391 for (int j = 1; j <= (n/2)/p1; ++j) {
cannam@198 392 std::cerr << "j = " << j << ", i = " << i << std::endl;
cannam@198 393 filtered += r[i * j];
cannam@198 394 }
cannam@198 395
cannam@198 396 if (i == p0 || filtered > maxp) {
cannam@198 397 maxp = filtered;
cannam@198 398 maxpi = i;
cannam@198 399 }
cannam@198 400
cannam@198 401 feature.timestamp = RealTime::frame2RealTime(i * m_stepSize,
cannam@198 402 m_inputSampleRate);
cannam@198 403 feature.values[0] = filtered;
cannam@198 404 sprintf(buffer, "%f bpm", lag2tempo(i));
cannam@198 405 feature.label = buffer;
cannam@198 406 fs[3].push_back(feature);
cannam@198 407 }
cannam@198 408
cannam@198 409 std::cerr << "maxpi = " << maxpi << " for tempo " << lag2tempo(maxpi) << " (value = " << maxp << ")" << std::endl;
cannam@198 410
cannam@198 411 tempo = lag2tempo(maxpi);
cannam@198 412
cannam@198 413 delete[] r;
cannam@198 414
cannam@198 415 feature.hasTimestamp = true;
cannam@198 416 feature.timestamp = m_start;
cannam@198 417
cannam@198 418 feature.hasDuration = true;
cannam@198 419 feature.duration = m_lasttime - m_start;
cannam@198 420
cannam@198 421 feature.values[0] = tempo;
cannam@198 422
cannam@198 423 fs[0].push_back(feature);
cannam@198 424
cannam@198 425 return fs;
cannam@198 426 }