fix crash during first-run configuration of the application, caused by using an incom...
[ardour.git] / libs / vamp-plugins / BarBeatTrack.cpp
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2
3 /*
4     QM Vamp Plugin Set
5
6     Centre for Digital Music, Queen Mary, University of London.
7
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.
13 */
14
15 #include "BarBeatTrack.h"
16
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>
22
23 using std::string;
24 using std::vector;
25 using std::cerr;
26 using std::endl;
27
28 #if !defined(__GNUC__) && !defined(_MSC_VER)
29 #include <alloca.h>
30 #endif
31
32 float BarBeatTracker::m_stepSecs = 0.01161; // 512 samples at 44100
33
34 class BarBeatTrackerData
35 {
36 public:
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);
43     }
44     ~BarBeatTrackerData() {
45     delete df;
46         delete downBeat;
47     }
48     void reset() {
49     delete df;
50     df = new DetectionFunction(dfConfig);
51     dfOutput.clear();
52         downBeat->resetAudioBuffer();
53         origin = Vamp::RealTime::zeroTime;
54     }
55
56     DFConfig dfConfig;
57     DetectionFunction *df;
58     DownBeat *downBeat;
59     vector<double> dfOutput;
60     Vamp::RealTime origin;
61 };
62
63
64 BarBeatTracker::BarBeatTracker(float inputSampleRate) :
65     Vamp::Plugin(inputSampleRate),
66     m_d(0),
67     m_bpb(4),
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
72 {
73 }
74
75 BarBeatTracker::~BarBeatTracker()
76 {
77     delete m_d;
78 }
79
80 string
81 BarBeatTracker::getIdentifier() const
82 {
83     return "qm-barbeattracker";
84 }
85
86 string
87 BarBeatTracker::getName() const
88 {
89     return "Bar and Beat Tracker";
90 }
91
92 string
93 BarBeatTracker::getDescription() const
94 {
95     return "Estimate bar and beat locations";
96 }
97
98 string
99 BarBeatTracker::getMaker() const
100 {
101     return "Queen Mary, University of London";
102 }
103
104 int
105 BarBeatTracker::getPluginVersion() const
106 {
107     return 3;
108 }
109
110 string
111 BarBeatTracker::getCopyright() const
112 {
113     return "Plugin by Matthew Davies, Christian Landone and Chris Cannam.  Copyright (c) 2006-2013 QMUL - All Rights Reserved";
114 }
115
116 BarBeatTracker::ParameterList
117 BarBeatTracker::getParameterDescriptors() const
118 {
119     ParameterList list;
120
121     ParameterDescriptor desc;
122
123     desc.identifier = "bpb";
124     desc.name = "Beats per Bar";
125     desc.description = "The number of beats in each bar";
126     desc.minValue = 2;
127     desc.maxValue = 16;
128     desc.defaultValue = 4;
129     desc.isQuantized = true;
130     desc.quantizeStep = 1;
131     list.push_back(desc);
132
133     // changes are as per the BeatTrack.cpp
134     //Alpha Parameter of Beat Tracker
135     desc.identifier = "alpha";
136     desc.name = "Alpha";
137     desc.description = "Inertia - Flexibility Trade Off";
138     desc.minValue =  0.1;
139     desc.maxValue = 0.99;
140     desc.defaultValue = 0.90;
141     desc.unit = "";
142     desc.isQuantized = false;
143     list.push_back(desc);
144
145     // We aren't exposing tightness as a parameter, it's fixed at 4
146
147     // changes are as per the BeatTrack.cpp
148     //User input tempo
149     desc.identifier = "inputtempo";
150     desc.name = "Tempo Hint";
151     desc.description = "User-defined tempo on which to centre the tempo preference function";
152     desc.minValue =  50;
153     desc.maxValue = 250;
154     desc.defaultValue = 120;
155     desc.unit = "BPM";
156     desc.isQuantized = true;
157     list.push_back(desc);
158
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";
163     desc.minValue = 0;
164     desc.maxValue = 1;
165     desc.defaultValue = 0;
166     desc.isQuantized = true;
167     desc.quantizeStep = 1;
168     desc.unit = "";
169     desc.valueNames.clear();
170     list.push_back(desc);
171
172
173     return list;
174 }
175
176 float
177 BarBeatTracker::getParameter(std::string name) const
178 {
179     if (name == "bpb") {
180         return m_bpb;
181     } else if (name == "alpha") {
182         return m_alpha;
183     }  else if (name == "inputtempo") {
184         return m_inputtempo;
185     }  else if (name == "constraintempo") {
186         return m_constraintempo ? 1.0 : 0.0;
187     }
188     return 0.0;
189 }
190
191 void
192 BarBeatTracker::setParameter(std::string name, float value)
193 {
194     if (name == "bpb") {
195         m_bpb = lrintf(value);
196     } else if (name == "alpha") {
197         m_alpha = value;
198     } else if (name == "inputtempo") {
199         m_inputtempo = value;
200     } else if (name == "constraintempo") {
201         m_constraintempo = (value > 0.5);
202     }
203 }
204
205 bool
206 BarBeatTracker::initialise(size_t channels, size_t stepSize, size_t blockSize)
207 {
208     if (m_d) {
209     delete m_d;
210     m_d = 0;
211     }
212
213     if (channels < getMinChannelCount() ||
214     channels > getMaxChannelCount()) {
215         std::cerr << "BarBeatTracker::initialise: Unsupported channel count: "
216                   << channels << std::endl;
217         return false;
218     }
219
220     if (stepSize != getPreferredStepSize()) {
221         std::cerr << "ERROR: BarBeatTracker::initialise: Unsupported step size for this sample rate: "
222                   << stepSize << " (wanted " << (getPreferredStepSize()) << ")" << std::endl;
223         return false;
224     }
225
226     if (blockSize != getPreferredBlockSize()) {
227         std::cerr << "WARNING: BarBeatTracker::initialise: Sub-optimal block size for this sample rate: "
228                   << blockSize << " (wanted " << getPreferredBlockSize() << ")" << std::endl;
229 //        return false;
230     }
231
232     DFConfig dfConfig;
233     dfConfig.DFType = DF_COMPLEXSD;
234     dfConfig.stepSize = stepSize;
235     dfConfig.frameLength = blockSize;
236     dfConfig.dbRise = 3;
237     dfConfig.adaptiveWhitening = false;
238     dfConfig.whiteningRelaxCoeff = -1;
239     dfConfig.whiteningFloor = -1;
240
241     m_d = new BarBeatTrackerData(m_inputSampleRate, dfConfig);
242     m_d->downBeat->setBeatsPerBar(m_bpb);
243     return true;
244 }
245
246 void
247 BarBeatTracker::reset()
248 {
249     if (m_d) m_d->reset();
250 }
251
252 size_t
253 BarBeatTracker::getPreferredStepSize() const
254 {
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;
258     return step;
259 }
260
261 size_t
262 BarBeatTracker::getPreferredBlockSize() const
263 {
264     size_t theoretical = getPreferredStepSize() * 2;
265
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
269     return theoretical;
270 }
271
272 BarBeatTracker::OutputList
273 BarBeatTracker::getOutputDescriptors() const
274 {
275     OutputList list;
276
277     OutputDescriptor beat;
278     beat.identifier = "beats";
279     beat.name = "Beats";
280     beat.description = "Beat locations labelled with metrical position";
281     beat.unit = "";
282     beat.hasFixedBinCount = true;
283     beat.binCount = 0;
284     beat.sampleType = OutputDescriptor::VariableSampleRate;
285     beat.sampleRate = 1.0 / m_stepSecs;
286
287     OutputDescriptor bars;
288     bars.identifier = "bars";
289     bars.name = "Bars";
290     bars.description = "Bar locations";
291     bars.unit = "";
292     bars.hasFixedBinCount = true;
293     bars.binCount = 0;
294     bars.sampleType = OutputDescriptor::VariableSampleRate;
295     bars.sampleRate = 1.0 / m_stepSecs;
296
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;
306
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";
311     beatsd.unit = "";
312     beatsd.hasFixedBinCount = true;
313     beatsd.binCount = 1;
314     beatsd.sampleType = OutputDescriptor::VariableSampleRate;
315     beatsd.sampleRate = 1.0 / m_stepSecs;
316
317     list.push_back(beat);
318     list.push_back(bars);
319     list.push_back(beatcounts);
320     list.push_back(beatsd);
321
322     return list;
323 }
324
325 BarBeatTracker::FeatureSet
326 BarBeatTracker::process(const float *const *inputBuffers,
327                         Vamp::RealTime timestamp)
328 {
329     if (!m_d) {
330     cerr << "ERROR: BarBeatTracker::process: "
331          << "BarBeatTracker has not been initialised"
332          << endl;
333     return FeatureSet();
334     }
335
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
340
341     // We only support a single input channel
342
343     const int fl = m_d->dfConfig.frameLength;
344 #ifndef __GNUC__
345     double *dfinput = (double *)alloca(fl * sizeof(double));
346 #else
347     double dfinput[fl];
348 #endif
349     for (int i = 0; i < fl; ++i) dfinput[i] = inputBuffers[0][i];
350
351     double output = m_d->df->processTimeDomain(dfinput);
352
353     if (m_d->dfOutput.empty()) m_d->origin = timestamp;
354
355 //    std::cerr << "df[" << m_d->dfOutput.size() << "] is " << output << std::endl;
356     m_d->dfOutput.push_back(output);
357
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]);
366
367     return FeatureSet();
368 }
369
370 BarBeatTracker::FeatureSet
371 BarBeatTracker::getRemainingFeatures()
372 {
373     if (!m_d) {
374     cerr << "ERROR: BarBeatTracker::getRemainingFeatures: "
375          << "BarBeatTracker has not been initialised"
376          << endl;
377     return FeatureSet();
378     }
379
380     return barBeatTrack();
381 }
382
383 BarBeatTracker::FeatureSet
384 BarBeatTracker::barBeatTrack()
385 {
386     vector<double> df;
387     vector<double> beatPeriod;
388     vector<double> tempi;
389
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);
393     }
394     if (df.empty()) return FeatureSet();
395
396     TempoTrackV2 tt(m_inputSampleRate, m_d->dfConfig.stepSize);
397
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);
400
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);
404
405  //   tt.calculateBeatPeriod(df, beatPeriod, tempi, 0., 0); // use default parameters
406
407   //  vector<double> beats;
408    // tt.calculateBeats(df, beatPeriod, beats, 0.9, 4.); // use default parameters until i fix this plugin too
409
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);
414
415     vector<double> beatsd;
416     m_d->downBeat->getBeatSD(beatsd);
417
418 //    std::cerr << "BarBeatTracker: found downbeats at: ";
419 //    for (int i = 0; i < downbeats.size(); ++i) std::cerr << downbeats[i] << " " << std::endl;
420
421     FeatureSet returnFeatures;
422
423     char label[20];
424
425     int dbi = 0;
426     int beat = 0;
427     int bar = 0;
428
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;
435     }
436
437     for (int i = 0; i < int(beats.size()); ++i) {
438
439         size_t frame = size_t(beats[i]) * m_d->dfConfig.stepSize;
440
441         if (dbi < int(downbeats.size()) && i == downbeats[dbi]) {
442             beat = 0;
443             ++bar;
444             ++dbi;
445         } else {
446             ++beat;
447         }
448
449         // outputs are:
450         //
451         // 0 -> beats
452         // 1 -> bars
453         // 2 -> beat counter function
454
455         Feature feature;
456         feature.hasTimestamp = true;
457         feature.timestamp = m_d->origin + Vamp::RealTime::frame2RealTime
458             (frame, lrintf(m_inputSampleRate));
459
460         sprintf(label, "%d", beat + 1);
461         feature.label = label;
462         returnFeatures[0].push_back(feature); // labelled beats
463
464         feature.values.push_back(beat + 1);
465         returnFeatures[2].push_back(feature); // beat function
466
467         if (i > 0 && i <= int(beatsd.size())) {
468             feature.values.clear();
469             feature.values.push_back(beatsd[i-1]);
470             feature.label = "";
471             returnFeatures[3].push_back(feature); // beat spectral difference
472         }
473
474         if (beat == 0) {
475             feature.values.clear();
476             sprintf(label, "%d", bar);
477             feature.label = label;
478             returnFeatures[1].push_back(feature); // bars
479         }
480     }
481
482     return returnFeatures;
483 }
484