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