annotate plugins/BarBeatTrack.cpp @ 130:c655fa61884f

* Solaris build fixes
author Chris Cannam <c.cannam@qmul.ac.uk>
date Mon, 14 Sep 2009 13:01:57 +0000
parents 7e5fc22c9ab1
children 0258a32639e6
rev   line source
c@89 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
c@89 2
c@89 3 /*
c@89 4 QM Vamp Plugin Set
c@89 5
c@89 6 Centre for Digital Music, Queen Mary, University of London.
c@89 7 All rights reserved.
c@89 8 */
c@89 9
c@89 10 #include "BarBeatTrack.h"
c@89 11
c@89 12 #include <dsp/onsets/DetectionFunction.h>
c@89 13 #include <dsp/onsets/PeakPicking.h>
c@89 14 #include <dsp/tempotracking/TempoTrackV2.h>
c@89 15 #include <dsp/tempotracking/DownBeat.h>
c@89 16 #include <maths/MathUtilities.h>
c@89 17
c@89 18 using std::string;
c@89 19 using std::vector;
c@89 20 using std::cerr;
c@89 21 using std::endl;
c@89 22
c@130 23 #ifndef __GNUC__
c@130 24 #include <alloca.h>
c@130 25 #endif
c@130 26
c@89 27 float BarBeatTracker::m_stepSecs = 0.01161; // 512 samples at 44100
c@89 28
c@89 29 class BarBeatTrackerData
c@89 30 {
c@89 31 public:
c@89 32 BarBeatTrackerData(float rate, const DFConfig &config) : dfConfig(config) {
c@89 33 df = new DetectionFunction(config);
c@89 34 // decimation factor aims at resampling to c. 3KHz; must be power of 2
c@89 35 int factor = MathUtilities::nextPowerOfTwo(rate / 3000);
c@95 36 // std::cerr << "BarBeatTrackerData: factor = " << factor << std::endl;
c@89 37 downBeat = new DownBeat(rate, factor, config.stepSize);
c@89 38 }
c@89 39 ~BarBeatTrackerData() {
c@89 40 delete df;
c@89 41 delete downBeat;
c@89 42 }
c@89 43 void reset() {
c@89 44 delete df;
c@89 45 df = new DetectionFunction(dfConfig);
c@89 46 dfOutput.clear();
c@89 47 downBeat->resetAudioBuffer();
c@89 48 origin = Vamp::RealTime::zeroTime;
c@89 49 }
c@89 50
c@89 51 DFConfig dfConfig;
c@89 52 DetectionFunction *df;
c@89 53 DownBeat *downBeat;
c@89 54 vector<double> dfOutput;
c@89 55 Vamp::RealTime origin;
c@89 56 };
c@89 57
c@89 58
c@89 59 BarBeatTracker::BarBeatTracker(float inputSampleRate) :
c@89 60 Vamp::Plugin(inputSampleRate),
c@89 61 m_d(0),
c@89 62 m_bpb(4)
c@89 63 {
c@89 64 }
c@89 65
c@89 66 BarBeatTracker::~BarBeatTracker()
c@89 67 {
c@89 68 delete m_d;
c@89 69 }
c@89 70
c@89 71 string
c@89 72 BarBeatTracker::getIdentifier() const
c@89 73 {
c@89 74 return "qm-barbeattracker";
c@89 75 }
c@89 76
c@89 77 string
c@89 78 BarBeatTracker::getName() const
c@89 79 {
c@89 80 return "Bar and Beat Tracker";
c@89 81 }
c@89 82
c@89 83 string
c@89 84 BarBeatTracker::getDescription() const
c@89 85 {
c@89 86 return "Estimate bar and beat locations";
c@89 87 }
c@89 88
c@89 89 string
c@89 90 BarBeatTracker::getMaker() const
c@89 91 {
c@89 92 return "Queen Mary, University of London";
c@89 93 }
c@89 94
c@89 95 int
c@89 96 BarBeatTracker::getPluginVersion() const
c@89 97 {
c@89 98 return 1;
c@89 99 }
c@89 100
c@89 101 string
c@89 102 BarBeatTracker::getCopyright() const
c@89 103 {
c@89 104 return "Plugin by Matthew Davies, Christian Landone and Chris Cannam. Copyright (c) 2006-2009 QMUL - All Rights Reserved";
c@89 105 }
c@89 106
c@89 107 BarBeatTracker::ParameterList
c@89 108 BarBeatTracker::getParameterDescriptors() const
c@89 109 {
c@89 110 ParameterList list;
c@89 111
c@89 112 ParameterDescriptor desc;
c@89 113
c@89 114 desc.identifier = "bpb";
c@89 115 desc.name = "Beats per Bar";
c@89 116 desc.description = "The number of beats in each bar";
c@89 117 desc.minValue = 2;
c@89 118 desc.maxValue = 16;
c@89 119 desc.defaultValue = 4;
c@89 120 desc.isQuantized = true;
c@89 121 desc.quantizeStep = 1;
c@89 122 list.push_back(desc);
c@89 123
c@89 124 return list;
c@89 125 }
c@89 126
c@89 127 float
c@89 128 BarBeatTracker::getParameter(std::string name) const
c@89 129 {
c@89 130 if (name == "bpb") return m_bpb;
c@89 131 return 0.0;
c@89 132 }
c@89 133
c@89 134 void
c@89 135 BarBeatTracker::setParameter(std::string name, float value)
c@89 136 {
c@89 137 if (name == "bpb") m_bpb = lrintf(value);
c@89 138 }
c@89 139
c@89 140 bool
c@89 141 BarBeatTracker::initialise(size_t channels, size_t stepSize, size_t blockSize)
c@89 142 {
c@89 143 if (m_d) {
c@89 144 delete m_d;
c@89 145 m_d = 0;
c@89 146 }
c@89 147
c@89 148 if (channels < getMinChannelCount() ||
c@89 149 channels > getMaxChannelCount()) {
c@89 150 std::cerr << "BarBeatTracker::initialise: Unsupported channel count: "
c@89 151 << channels << std::endl;
c@89 152 return false;
c@89 153 }
c@89 154
c@89 155 if (stepSize != getPreferredStepSize()) {
c@89 156 std::cerr << "ERROR: BarBeatTracker::initialise: Unsupported step size for this sample rate: "
c@89 157 << stepSize << " (wanted " << (getPreferredStepSize()) << ")" << std::endl;
c@89 158 return false;
c@89 159 }
c@89 160
c@89 161 if (blockSize != getPreferredBlockSize()) {
c@89 162 std::cerr << "WARNING: BarBeatTracker::initialise: Sub-optimal block size for this sample rate: "
c@89 163 << blockSize << " (wanted " << getPreferredBlockSize() << ")" << std::endl;
c@89 164 // return false;
c@89 165 }
c@89 166
c@89 167 DFConfig dfConfig;
c@89 168 dfConfig.DFType = DF_COMPLEXSD;
c@89 169 dfConfig.stepSize = stepSize;
c@89 170 dfConfig.frameLength = blockSize;
c@89 171 dfConfig.dbRise = 3;
c@89 172 dfConfig.adaptiveWhitening = false;
c@89 173 dfConfig.whiteningRelaxCoeff = -1;
c@89 174 dfConfig.whiteningFloor = -1;
c@89 175
c@89 176 m_d = new BarBeatTrackerData(m_inputSampleRate, dfConfig);
c@89 177 m_d->downBeat->setBeatsPerBar(m_bpb);
c@89 178 return true;
c@89 179 }
c@89 180
c@89 181 void
c@89 182 BarBeatTracker::reset()
c@89 183 {
c@89 184 if (m_d) m_d->reset();
c@89 185 }
c@89 186
c@89 187 size_t
c@89 188 BarBeatTracker::getPreferredStepSize() const
c@89 189 {
c@89 190 size_t step = size_t(m_inputSampleRate * m_stepSecs + 0.0001);
c@95 191 if (step < 1) step = 1;
c@89 192 // std::cerr << "BarBeatTracker::getPreferredStepSize: input sample rate is " << m_inputSampleRate << ", step size is " << step << std::endl;
c@89 193 return step;
c@89 194 }
c@89 195
c@89 196 size_t
c@89 197 BarBeatTracker::getPreferredBlockSize() const
c@89 198 {
c@89 199 size_t theoretical = getPreferredStepSize() * 2;
c@89 200
c@89 201 // I think this is not necessarily going to be a power of two, and
c@89 202 // the host might have a problem with that, but I'm not sure we
c@89 203 // can do much about it here
c@89 204 return theoretical;
c@89 205 }
c@89 206
c@89 207 BarBeatTracker::OutputList
c@89 208 BarBeatTracker::getOutputDescriptors() const
c@89 209 {
c@89 210 OutputList list;
c@89 211
c@89 212 OutputDescriptor beat;
c@89 213 beat.identifier = "beats";
c@89 214 beat.name = "Beats";
c@89 215 beat.description = "Beat locations labelled with metrical position";
c@89 216 beat.unit = "";
c@89 217 beat.hasFixedBinCount = true;
c@89 218 beat.binCount = 0;
c@89 219 beat.sampleType = OutputDescriptor::VariableSampleRate;
c@89 220 beat.sampleRate = 1.0 / m_stepSecs;
c@89 221
c@89 222 OutputDescriptor bars;
c@89 223 bars.identifier = "bars";
c@89 224 bars.name = "Bars";
c@89 225 bars.description = "Bar locations";
c@89 226 bars.unit = "";
c@89 227 bars.hasFixedBinCount = true;
c@89 228 bars.binCount = 0;
c@89 229 bars.sampleType = OutputDescriptor::VariableSampleRate;
c@89 230 bars.sampleRate = 1.0 / m_stepSecs;
c@89 231
c@89 232 OutputDescriptor beatcounts;
c@89 233 beatcounts.identifier = "beatcounts";
c@89 234 beatcounts.name = "Beat Count";
c@89 235 beatcounts.description = "Beat counter function";
c@89 236 beatcounts.unit = "";
c@89 237 beatcounts.hasFixedBinCount = true;
c@89 238 beatcounts.binCount = 1;
c@89 239 beatcounts.sampleType = OutputDescriptor::VariableSampleRate;
c@89 240 beatcounts.sampleRate = 1.0 / m_stepSecs;
c@89 241
c@90 242 OutputDescriptor beatsd;
c@90 243 beatsd.identifier = "beatsd";
c@90 244 beatsd.name = "Beat Spectral Difference";
c@90 245 beatsd.description = "Beat spectral difference function used for bar-line detection";
c@90 246 beatsd.unit = "";
c@90 247 beatsd.hasFixedBinCount = true;
c@90 248 beatsd.binCount = 1;
c@90 249 beatsd.sampleType = OutputDescriptor::VariableSampleRate;
c@90 250 beatsd.sampleRate = 1.0 / m_stepSecs;
c@90 251
c@89 252 list.push_back(beat);
c@89 253 list.push_back(bars);
c@89 254 list.push_back(beatcounts);
c@90 255 list.push_back(beatsd);
c@89 256
c@89 257 return list;
c@89 258 }
c@89 259
c@89 260 BarBeatTracker::FeatureSet
c@89 261 BarBeatTracker::process(const float *const *inputBuffers,
c@89 262 Vamp::RealTime timestamp)
c@89 263 {
c@89 264 if (!m_d) {
c@89 265 cerr << "ERROR: BarBeatTracker::process: "
c@89 266 << "BarBeatTracker has not been initialised"
c@89 267 << endl;
c@89 268 return FeatureSet();
c@89 269 }
c@89 270
c@89 271 // We use time domain input, because DownBeat requires it -- so we
c@89 272 // use the time-domain version of DetectionFunction::process which
c@89 273 // does its own FFT. It requires doubles as input, so we need to
c@89 274 // make a temporary copy
c@89 275
c@89 276 // We only support a single input channel
c@89 277
c@89 278 const int fl = m_d->dfConfig.frameLength;
c@130 279 #ifndef __GNUC__
c@130 280 double *dfinput = (double *)alloca(fl * sizeof(double));
c@130 281 #else
c@89 282 double dfinput[fl];
c@130 283 #endif
c@89 284 for (int i = 0; i < fl; ++i) dfinput[i] = inputBuffers[0][i];
c@89 285
c@89 286 double output = m_d->df->process(dfinput);
c@89 287
c@89 288 if (m_d->dfOutput.empty()) m_d->origin = timestamp;
c@89 289
c@93 290 // std::cerr << "df[" << m_d->dfOutput.size() << "] is " << output << std::endl;
c@89 291 m_d->dfOutput.push_back(output);
c@89 292
c@89 293 // Downsample and store the incoming audio block.
c@89 294 // We have an overlap on the incoming audio stream (step size is
c@89 295 // half block size) -- this function is configured to take only a
c@89 296 // step size's worth, so effectively ignoring the overlap. Note
c@89 297 // however that this means we omit the last blocksize - stepsize
c@89 298 // samples completely for the purposes of barline detection
c@89 299 // (hopefully not a problem)
c@89 300 m_d->downBeat->pushAudioBlock(inputBuffers[0]);
c@89 301
c@89 302 return FeatureSet();
c@89 303 }
c@89 304
c@89 305 BarBeatTracker::FeatureSet
c@89 306 BarBeatTracker::getRemainingFeatures()
c@89 307 {
c@89 308 if (!m_d) {
c@89 309 cerr << "ERROR: BarBeatTracker::getRemainingFeatures: "
c@89 310 << "BarBeatTracker has not been initialised"
c@89 311 << endl;
c@89 312 return FeatureSet();
c@89 313 }
c@89 314
c@89 315 return barBeatTrack();
c@89 316 }
c@89 317
c@89 318 BarBeatTracker::FeatureSet
c@89 319 BarBeatTracker::barBeatTrack()
c@89 320 {
c@89 321 vector<double> df;
c@89 322 vector<double> beatPeriod;
c@89 323 vector<double> tempi;
c@89 324
c@89 325 for (size_t i = 2; i < m_d->dfOutput.size(); ++i) { // discard first two elts
c@89 326 df.push_back(m_d->dfOutput[i]);
c@89 327 beatPeriod.push_back(0.0);
c@89 328 }
c@89 329 if (df.empty()) return FeatureSet();
c@89 330
c@89 331 TempoTrackV2 tt(m_inputSampleRate, m_d->dfConfig.stepSize);
c@89 332 tt.calculateBeatPeriod(df, beatPeriod, tempi);
c@89 333
c@89 334 vector<double> beats;
c@89 335 tt.calculateBeats(df, beatPeriod, beats);
c@89 336
c@89 337 vector<int> downbeats;
c@89 338 size_t downLength = 0;
c@89 339 const float *downsampled = m_d->downBeat->getBufferedAudio(downLength);
c@89 340 m_d->downBeat->findDownBeats(downsampled, downLength, beats, downbeats);
c@89 341
c@90 342 vector<double> beatsd;
c@90 343 m_d->downBeat->getBeatSD(beatsd);
c@90 344
c@89 345 // std::cerr << "BarBeatTracker: found downbeats at: ";
c@89 346 // for (int i = 0; i < downbeats.size(); ++i) std::cerr << downbeats[i] << " " << std::endl;
c@89 347
c@89 348 FeatureSet returnFeatures;
c@89 349
c@89 350 char label[20];
c@89 351
c@89 352 int dbi = 0;
c@89 353 int beat = 0;
c@89 354 int bar = 0;
c@89 355
c@124 356 if (!downbeats.empty()) {
c@124 357 // get the right number for the first beat; this will be
c@124 358 // incremented before use (at top of the following loop)
c@124 359 int firstDown = downbeats[0];
c@124 360 beat = m_bpb - firstDown - 1;
c@124 361 if (beat == m_bpb) beat = 0;
c@124 362 }
c@124 363
c@89 364 for (size_t i = 0; i < beats.size(); ++i) {
c@89 365
c@89 366 size_t frame = beats[i] * m_d->dfConfig.stepSize;
c@89 367
c@89 368 if (dbi < downbeats.size() && i == downbeats[dbi]) {
c@89 369 beat = 0;
c@89 370 ++bar;
c@89 371 ++dbi;
c@89 372 } else {
c@89 373 ++beat;
c@89 374 }
c@89 375
c@89 376 // outputs are:
c@89 377 //
c@89 378 // 0 -> beats
c@89 379 // 1 -> bars
c@89 380 // 2 -> beat counter function
c@89 381
c@89 382 Feature feature;
c@89 383 feature.hasTimestamp = true;
c@89 384 feature.timestamp = m_d->origin + Vamp::RealTime::frame2RealTime
c@89 385 (frame, lrintf(m_inputSampleRate));
c@89 386
c@89 387 sprintf(label, "%d", beat + 1);
c@89 388 feature.label = label;
c@89 389 returnFeatures[0].push_back(feature); // labelled beats
c@89 390
c@89 391 feature.values.push_back(beat + 1);
c@89 392 returnFeatures[2].push_back(feature); // beat function
c@89 393
c@90 394 if (i > 0 && i <= beatsd.size()) {
c@90 395 feature.values.clear();
c@90 396 feature.values.push_back(beatsd[i-1]);
c@90 397 feature.label = "";
c@90 398 returnFeatures[3].push_back(feature); // beat spectral difference
c@90 399 }
c@90 400
c@89 401 if (beat == 0) {
c@89 402 feature.values.clear();
c@89 403 sprintf(label, "%d", bar);
c@89 404 feature.label = label;
c@89 405 returnFeatures[1].push_back(feature); // bars
c@89 406 }
c@89 407 }
c@89 408
c@89 409 return returnFeatures;
c@89 410 }
c@89 411