1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
6 Centre for Digital Music, Queen Mary, University of London.
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version. See the file
12 COPYING included with this distribution for more information.
15 #include "BarBeatTrack.h"
17 #include <dsp/onsets/DetectionFunction.h>
18 #include <dsp/onsets/PeakPicking.h>
19 #include <dsp/tempotracking/TempoTrackV2.h>
20 #include <dsp/tempotracking/DownBeat.h>
21 #include <maths/MathUtilities.h>
28 #if !defined(__GNUC__) && !defined(_MSC_VER)
32 float BarBeatTracker::m_stepSecs = 0.01161; // 512 samples at 44100
34 class BarBeatTrackerData
37 BarBeatTrackerData(float rate, const DFConfig &config) : dfConfig(config) {
38 df = new DetectionFunction(config);
39 // decimation factor aims at resampling to c. 3KHz; must be power of 2
40 int factor = MathUtilities::nextPowerOfTwo(rate / 3000);
41 // std::cerr << "BarBeatTrackerData: factor = " << factor << std::endl;
42 downBeat = new DownBeat(rate, factor, config.stepSize);
44 ~BarBeatTrackerData() {
50 df = new DetectionFunction(dfConfig);
52 downBeat->resetAudioBuffer();
53 origin = Vamp::RealTime::zeroTime;
57 DetectionFunction *df;
59 vector<double> dfOutput;
60 Vamp::RealTime origin;
64 BarBeatTracker::BarBeatTracker(float inputSampleRate) :
65 Vamp::Plugin(inputSampleRate),
68 m_alpha(0.9), // changes are as per the BeatTrack.cpp
69 m_tightness(4.), // changes are as per the BeatTrack.cpp
70 m_inputtempo(120.), // changes are as per the BeatTrack.cpp
71 m_constraintempo(false) // changes are as per the BeatTrack.cpp
75 BarBeatTracker::~BarBeatTracker()
81 BarBeatTracker::getIdentifier() const
83 return "qm-barbeattracker";
87 BarBeatTracker::getName() const
89 return "Bar and Beat Tracker";
93 BarBeatTracker::getDescription() const
95 return "Estimate bar and beat locations";
99 BarBeatTracker::getMaker() const
101 return "Queen Mary, University of London";
105 BarBeatTracker::getPluginVersion() const
111 BarBeatTracker::getCopyright() const
113 return "Plugin by Matthew Davies, Christian Landone and Chris Cannam. Copyright (c) 2006-2013 QMUL - All Rights Reserved";
116 BarBeatTracker::ParameterList
117 BarBeatTracker::getParameterDescriptors() const
121 ParameterDescriptor desc;
123 desc.identifier = "bpb";
124 desc.name = "Beats per Bar";
125 desc.description = "The number of beats in each bar";
128 desc.defaultValue = 4;
129 desc.isQuantized = true;
130 desc.quantizeStep = 1;
131 list.push_back(desc);
133 // changes are as per the BeatTrack.cpp
134 //Alpha Parameter of Beat Tracker
135 desc.identifier = "alpha";
137 desc.description = "Inertia - Flexibility Trade Off";
139 desc.maxValue = 0.99;
140 desc.defaultValue = 0.90;
142 desc.isQuantized = false;
143 list.push_back(desc);
145 // We aren't exposing tightness as a parameter, it's fixed at 4
147 // changes are as per the BeatTrack.cpp
149 desc.identifier = "inputtempo";
150 desc.name = "Tempo Hint";
151 desc.description = "User-defined tempo on which to centre the tempo preference function";
154 desc.defaultValue = 120;
156 desc.isQuantized = true;
157 list.push_back(desc);
159 // changes are as per the BeatTrack.cpp
160 desc.identifier = "constraintempo";
161 desc.name = "Constrain Tempo";
162 desc.description = "Constrain more tightly around the tempo hint, using a Gaussian weighting instead of Rayleigh";
165 desc.defaultValue = 0;
166 desc.isQuantized = true;
167 desc.quantizeStep = 1;
169 desc.valueNames.clear();
170 list.push_back(desc);
177 BarBeatTracker::getParameter(std::string name) const
181 } else if (name == "alpha") {
183 } else if (name == "inputtempo") {
185 } else if (name == "constraintempo") {
186 return m_constraintempo ? 1.0 : 0.0;
192 BarBeatTracker::setParameter(std::string name, float value)
195 m_bpb = lrintf(value);
196 } else if (name == "alpha") {
198 } else if (name == "inputtempo") {
199 m_inputtempo = value;
200 } else if (name == "constraintempo") {
201 m_constraintempo = (value > 0.5);
206 BarBeatTracker::initialise(size_t channels, size_t stepSize, size_t blockSize)
213 if (channels < getMinChannelCount() ||
214 channels > getMaxChannelCount()) {
215 std::cerr << "BarBeatTracker::initialise: Unsupported channel count: "
216 << channels << std::endl;
220 if (stepSize != getPreferredStepSize()) {
221 std::cerr << "ERROR: BarBeatTracker::initialise: Unsupported step size for this sample rate: "
222 << stepSize << " (wanted " << (getPreferredStepSize()) << ")" << std::endl;
226 if (blockSize != getPreferredBlockSize()) {
227 std::cerr << "WARNING: BarBeatTracker::initialise: Sub-optimal block size for this sample rate: "
228 << blockSize << " (wanted " << getPreferredBlockSize() << ")" << std::endl;
233 dfConfig.DFType = DF_COMPLEXSD;
234 dfConfig.stepSize = stepSize;
235 dfConfig.frameLength = blockSize;
237 dfConfig.adaptiveWhitening = false;
238 dfConfig.whiteningRelaxCoeff = -1;
239 dfConfig.whiteningFloor = -1;
241 m_d = new BarBeatTrackerData(m_inputSampleRate, dfConfig);
242 m_d->downBeat->setBeatsPerBar(m_bpb);
247 BarBeatTracker::reset()
249 if (m_d) m_d->reset();
253 BarBeatTracker::getPreferredStepSize() const
255 size_t step = size_t(m_inputSampleRate * m_stepSecs + 0.0001);
256 if (step < 1) step = 1;
257 // std::cerr << "BarBeatTracker::getPreferredStepSize: input sample rate is " << m_inputSampleRate << ", step size is " << step << std::endl;
262 BarBeatTracker::getPreferredBlockSize() const
264 size_t theoretical = getPreferredStepSize() * 2;
266 // I think this is not necessarily going to be a power of two, and
267 // the host might have a problem with that, but I'm not sure we
268 // can do much about it here
272 BarBeatTracker::OutputList
273 BarBeatTracker::getOutputDescriptors() const
277 OutputDescriptor beat;
278 beat.identifier = "beats";
280 beat.description = "Beat locations labelled with metrical position";
282 beat.hasFixedBinCount = true;
284 beat.sampleType = OutputDescriptor::VariableSampleRate;
285 beat.sampleRate = 1.0 / m_stepSecs;
287 OutputDescriptor bars;
288 bars.identifier = "bars";
290 bars.description = "Bar locations";
292 bars.hasFixedBinCount = true;
294 bars.sampleType = OutputDescriptor::VariableSampleRate;
295 bars.sampleRate = 1.0 / m_stepSecs;
297 OutputDescriptor beatcounts;
298 beatcounts.identifier = "beatcounts";
299 beatcounts.name = "Beat Count";
300 beatcounts.description = "Beat counter function";
301 beatcounts.unit = "";
302 beatcounts.hasFixedBinCount = true;
303 beatcounts.binCount = 1;
304 beatcounts.sampleType = OutputDescriptor::VariableSampleRate;
305 beatcounts.sampleRate = 1.0 / m_stepSecs;
307 OutputDescriptor beatsd;
308 beatsd.identifier = "beatsd";
309 beatsd.name = "Beat Spectral Difference";
310 beatsd.description = "Beat spectral difference function used for bar-line detection";
312 beatsd.hasFixedBinCount = true;
314 beatsd.sampleType = OutputDescriptor::VariableSampleRate;
315 beatsd.sampleRate = 1.0 / m_stepSecs;
317 list.push_back(beat);
318 list.push_back(bars);
319 list.push_back(beatcounts);
320 list.push_back(beatsd);
325 BarBeatTracker::FeatureSet
326 BarBeatTracker::process(const float *const *inputBuffers,
327 Vamp::RealTime timestamp)
330 cerr << "ERROR: BarBeatTracker::process: "
331 << "BarBeatTracker has not been initialised"
336 // We use time domain input, because DownBeat requires it -- so we
337 // use the time-domain version of DetectionFunction::process which
338 // does its own FFT. It requires doubles as input, so we need to
339 // make a temporary copy
341 // We only support a single input channel
343 const int fl = m_d->dfConfig.frameLength;
345 double *dfinput = (double *)alloca(fl * sizeof(double));
349 for (int i = 0; i < fl; ++i) dfinput[i] = inputBuffers[0][i];
351 double output = m_d->df->processTimeDomain(dfinput);
353 if (m_d->dfOutput.empty()) m_d->origin = timestamp;
355 // std::cerr << "df[" << m_d->dfOutput.size() << "] is " << output << std::endl;
356 m_d->dfOutput.push_back(output);
358 // Downsample and store the incoming audio block.
359 // We have an overlap on the incoming audio stream (step size is
360 // half block size) -- this function is configured to take only a
361 // step size's worth, so effectively ignoring the overlap. Note
362 // however that this means we omit the last blocksize - stepsize
363 // samples completely for the purposes of barline detection
364 // (hopefully not a problem)
365 m_d->downBeat->pushAudioBlock(inputBuffers[0]);
370 BarBeatTracker::FeatureSet
371 BarBeatTracker::getRemainingFeatures()
374 cerr << "ERROR: BarBeatTracker::getRemainingFeatures: "
375 << "BarBeatTracker has not been initialised"
380 return barBeatTrack();
383 BarBeatTracker::FeatureSet
384 BarBeatTracker::barBeatTrack()
387 vector<double> beatPeriod;
388 vector<double> tempi;
390 for (size_t i = 2; i < m_d->dfOutput.size(); ++i) { // discard first two elts
391 df.push_back(m_d->dfOutput[i]);
392 beatPeriod.push_back(0.0);
394 if (df.empty()) return FeatureSet();
396 TempoTrackV2 tt(m_inputSampleRate, m_d->dfConfig.stepSize);
398 // changes are as per the BeatTrack.cpp - allow m_inputtempo and m_constraintempo to be set be the user
399 tt.calculateBeatPeriod(df, beatPeriod, tempi, m_inputtempo, m_constraintempo);
401 vector<double> beats;
402 // changes are as per the BeatTrack.cpp - allow m_alpha and m_tightness to be set be the user
403 tt.calculateBeats(df, beatPeriod, beats, m_alpha, m_tightness);
405 // tt.calculateBeatPeriod(df, beatPeriod, tempi, 0., 0); // use default parameters
407 // vector<double> beats;
408 // tt.calculateBeats(df, beatPeriod, beats, 0.9, 4.); // use default parameters until i fix this plugin too
410 vector<int> downbeats;
411 size_t downLength = 0;
412 const float *downsampled = m_d->downBeat->getBufferedAudio(downLength);
413 m_d->downBeat->findDownBeats(downsampled, downLength, beats, downbeats);
415 vector<double> beatsd;
416 m_d->downBeat->getBeatSD(beatsd);
418 // std::cerr << "BarBeatTracker: found downbeats at: ";
419 // for (int i = 0; i < downbeats.size(); ++i) std::cerr << downbeats[i] << " " << std::endl;
421 FeatureSet returnFeatures;
429 if (!downbeats.empty()) {
430 // get the right number for the first beat; this will be
431 // incremented before use (at top of the following loop)
432 int firstDown = downbeats[0];
433 beat = m_bpb - firstDown - 1;
434 if (beat == m_bpb) beat = 0;
437 for (int i = 0; i < int(beats.size()); ++i) {
439 size_t frame = size_t(beats[i]) * m_d->dfConfig.stepSize;
441 if (dbi < int(downbeats.size()) && i == downbeats[dbi]) {
453 // 2 -> beat counter function
456 feature.hasTimestamp = true;
457 feature.timestamp = m_d->origin + Vamp::RealTime::frame2RealTime
458 (frame, lrintf(m_inputSampleRate));
460 sprintf(label, "%d", beat + 1);
461 feature.label = label;
462 returnFeatures[0].push_back(feature); // labelled beats
464 feature.values.push_back(beat + 1);
465 returnFeatures[2].push_back(feature); // beat function
467 if (i > 0 && i <= int(beatsd.size())) {
468 feature.values.clear();
469 feature.values.push_back(beatsd[i-1]);
471 returnFeatures[3].push_back(feature); // beat spectral difference
475 feature.values.clear();
476 sprintf(label, "%d", bar);
477 feature.label = label;
478 returnFeatures[1].push_back(feature); // bars
482 return returnFeatures;