comparison plugins/BeatTrack.cpp @ 27:3256bfa04ed8

* split out tempo/beat/onset plugin into tempo/beat and onset
author Chris Cannam <c.cannam@qmul.ac.uk>
date Mon, 21 May 2007 13:09:12 +0000
parents
children b300de89ea30
comparison
equal deleted inserted replaced
26:166cc6d39390 27:3256bfa04ed8
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 All rights reserved.
8 */
9
10 #include "BeatTrack.h"
11
12 #include <dsp/onsets/DetectionFunction.h>
13 #include <dsp/onsets/PeakPicking.h>
14 #include <dsp/tempotracking/TempoTrack.h>
15
16 using std::string;
17 using std::vector;
18 using std::cerr;
19 using std::endl;
20
21 float BeatTracker::m_stepSecs = 0.01161;
22
23 class BeatTrackerData
24 {
25 public:
26 BeatTrackerData(const DFConfig &config) : dfConfig(config) {
27 df = new DetectionFunction(config);
28 }
29 ~BeatTrackerData() {
30 delete df;
31 }
32 void reset() {
33 delete df;
34 df = new DetectionFunction(dfConfig);
35 dfOutput.clear();
36 }
37
38 DFConfig dfConfig;
39 DetectionFunction *df;
40 vector<double> dfOutput;
41 };
42
43
44 BeatTracker::BeatTracker(float inputSampleRate) :
45 Vamp::Plugin(inputSampleRate),
46 m_d(0),
47 m_dfType(DF_COMPLEXSD)
48 {
49 }
50
51 BeatTracker::~BeatTracker()
52 {
53 delete m_d;
54 }
55
56 string
57 BeatTracker::getIdentifier() const
58 {
59 return "qm-tempotracker";
60 }
61
62 string
63 BeatTracker::getName() const
64 {
65 return "Tempo and Beat Tracker";
66 }
67
68 string
69 BeatTracker::getDescription() const
70 {
71 return "Estimate beat locations and tempo";
72 }
73
74 string
75 BeatTracker::getMaker() const
76 {
77 return "Christian Landone and Matthew Davies, Queen Mary, University of London";
78 }
79
80 int
81 BeatTracker::getPluginVersion() const
82 {
83 return 3;
84 }
85
86 string
87 BeatTracker::getCopyright() const
88 {
89 return "Copyright (c) 2006-2007 - All Rights Reserved";
90 }
91
92 BeatTracker::ParameterList
93 BeatTracker::getParameterDescriptors() const
94 {
95 ParameterList list;
96
97 ParameterDescriptor desc;
98 desc.identifier = "dftype";
99 desc.name = "Onset Detection Function Type";
100 desc.description = "Method used to calculate the onset detection function";
101 desc.minValue = 0;
102 desc.maxValue = 3;
103 desc.defaultValue = 3;
104 desc.isQuantized = true;
105 desc.quantizeStep = 1;
106 desc.valueNames.push_back("High-Frequency Content");
107 desc.valueNames.push_back("Spectral Difference");
108 desc.valueNames.push_back("Phase Deviation");
109 desc.valueNames.push_back("Complex Domain");
110 desc.valueNames.push_back("Broadband Energy Rise");
111 list.push_back(desc);
112
113 return list;
114 }
115
116 float
117 BeatTracker::getParameter(std::string name) const
118 {
119 if (name == "dftype") {
120 switch (m_dfType) {
121 case DF_HFC: return 0;
122 case DF_SPECDIFF: return 1;
123 case DF_PHASEDEV: return 2;
124 default: case DF_COMPLEXSD: return 3;
125 case DF_BROADBAND: return 4;
126 }
127 }
128 return 0.0;
129 }
130
131 void
132 BeatTracker::setParameter(std::string name, float value)
133 {
134 if (name == "dftype") {
135 switch (lrintf(value)) {
136 case 0: m_dfType = DF_HFC; break;
137 case 1: m_dfType = DF_SPECDIFF; break;
138 case 2: m_dfType = DF_PHASEDEV; break;
139 default: case 3: m_dfType = DF_COMPLEXSD; break;
140 case 4: m_dfType = DF_BROADBAND; break;
141 }
142 }
143 }
144
145 bool
146 BeatTracker::initialise(size_t channels, size_t stepSize, size_t blockSize)
147 {
148 if (m_d) {
149 delete m_d;
150 m_d = 0;
151 }
152
153 if (channels < getMinChannelCount() ||
154 channels > getMaxChannelCount()) {
155 std::cerr << "BeatTracker::initialise: Unsupported channel count: "
156 << channels << std::endl;
157 return false;
158 }
159
160 if (blockSize != getPreferredStepSize() * 2) {
161 std::cerr << "BeatTracker::initialise: Unsupported block size for this sample rate: "
162 << blockSize << " (wanted " << (getPreferredStepSize() * 2) << ")" << std::endl;
163 return false;
164 }
165
166 if (stepSize != getPreferredStepSize()) {
167 std::cerr << "BeatTracker::initialise: Unsupported step size for this sample rate: "
168 << stepSize << " (wanted " << (getPreferredStepSize()) << ")" << std::endl;
169 return false;
170 }
171
172 DFConfig dfConfig;
173 dfConfig.DFType = m_dfType;
174 dfConfig.stepSecs = float(stepSize) / m_inputSampleRate;
175 dfConfig.stepSize = stepSize;
176 dfConfig.frameLength = blockSize;
177 dfConfig.dbRise = 3;
178
179 m_d = new BeatTrackerData(dfConfig);
180 return true;
181 }
182
183 void
184 BeatTracker::reset()
185 {
186 if (m_d) m_d->reset();
187 }
188
189 size_t
190 BeatTracker::getPreferredStepSize() const
191 {
192 size_t step = size_t(m_inputSampleRate * m_stepSecs + 0.0001);
193 // std::cerr << "BeatTracker::getPreferredStepSize: input sample rate is " << m_inputSampleRate << ", step size is " << step << std::endl;
194 return step;
195 }
196
197 size_t
198 BeatTracker::getPreferredBlockSize() const
199 {
200 return getPreferredStepSize() * 2;
201 }
202
203 BeatTracker::OutputList
204 BeatTracker::getOutputDescriptors() const
205 {
206 OutputList list;
207
208 OutputDescriptor beat;
209 beat.identifier = "beats";
210 beat.name = "Beats";
211 beat.description = "Estimated metrical beat locations";
212 beat.unit = "";
213 beat.hasFixedBinCount = true;
214 beat.binCount = 0;
215 beat.sampleType = OutputDescriptor::VariableSampleRate;
216 beat.sampleRate = 1.0 / m_stepSecs;
217
218 OutputDescriptor df;
219 df.identifier = "detection_fn";
220 df.name = "Onset Detection Function";
221 df.description = "Probability function of note onset likelihood";
222 df.unit = "";
223 df.hasFixedBinCount = true;
224 df.binCount = 1;
225 df.hasKnownExtents = false;
226 df.isQuantized = false;
227 df.sampleType = OutputDescriptor::OneSamplePerStep;
228
229 OutputDescriptor tempo;
230 tempo.identifier = "tempo";
231 tempo.name = "Tempo";
232 tempo.description = "Locked tempo estimates";
233 tempo.unit = "bpm";
234 tempo.hasFixedBinCount = true;
235 tempo.binCount = 1;
236 tempo.sampleType = OutputDescriptor::VariableSampleRate;
237 tempo.sampleRate = 1.0 / m_stepSecs;
238
239 list.push_back(beat);
240 list.push_back(df);
241 list.push_back(tempo);
242
243 return list;
244 }
245
246 BeatTracker::FeatureSet
247 BeatTracker::process(const float *const *inputBuffers,
248 Vamp::RealTime /* timestamp */)
249 {
250 if (!m_d) {
251 cerr << "ERROR: BeatTracker::process: "
252 << "BeatTracker has not been initialised"
253 << endl;
254 return FeatureSet();
255 }
256
257 size_t len = m_d->dfConfig.frameLength / 2;
258
259 double *magnitudes = new double[len];
260 double *phases = new double[len];
261
262 // We only support a single input channel
263
264 for (size_t i = 0; i < len; ++i) {
265
266 magnitudes[i] = sqrt(inputBuffers[0][i*2 ] * inputBuffers[0][i*2 ] +
267 inputBuffers[0][i*2+1] * inputBuffers[0][i*2+1]);
268
269 phases[i] = atan2(-inputBuffers[0][i*2+1], inputBuffers[0][i*2]);
270 }
271
272 double output = m_d->df->process(magnitudes, phases);
273
274 delete[] magnitudes;
275 delete[] phases;
276
277 m_d->dfOutput.push_back(output);
278
279 FeatureSet returnFeatures;
280
281 Feature feature;
282 feature.hasTimestamp = false;
283 feature.values.push_back(output);
284
285 returnFeatures[1].push_back(feature); // detection function is output 1
286 return returnFeatures;
287 }
288
289 BeatTracker::FeatureSet
290 BeatTracker::getRemainingFeatures()
291 {
292 if (!m_d) {
293 cerr << "ERROR: BeatTracker::getRemainingFeatures: "
294 << "BeatTracker has not been initialised"
295 << endl;
296 return FeatureSet();
297 }
298
299 double aCoeffs[] = { 1.0000, -0.5949, 0.2348 };
300 double bCoeffs[] = { 0.1600, 0.3200, 0.1600 };
301
302 TTParams ttParams;
303 ttParams.winLength = 512;
304 ttParams.lagLength = 128;
305 ttParams.LPOrd = 2;
306 ttParams.LPACoeffs = aCoeffs;
307 ttParams.LPBCoeffs = bCoeffs;
308 ttParams.alpha = 9;
309 ttParams.WinT.post = 8;
310 ttParams.WinT.pre = 7;
311
312 TempoTrack tempoTracker(ttParams);
313
314 vector<double> tempos;
315 vector<int> beats = tempoTracker.process(m_d->dfOutput, &tempos);
316
317 FeatureSet returnFeatures;
318
319 char label[100];
320
321 for (size_t i = 0; i < beats.size(); ++i) {
322
323 size_t frame = beats[i] * m_d->dfConfig.stepSize;
324
325 Feature feature;
326 feature.hasTimestamp = true;
327 feature.timestamp = Vamp::RealTime::frame2RealTime
328 (frame, lrintf(m_inputSampleRate));
329
330 float bpm = 0.0;
331 int frameIncrement = 0;
332
333 if (i < beats.size() - 1) {
334
335 frameIncrement = (beats[i+1] - beats[i]) * m_d->dfConfig.stepSize;
336
337 // one beat is frameIncrement frames, so there are
338 // samplerate/frameIncrement bps, so
339 // 60*samplerate/frameIncrement bpm
340
341 if (frameIncrement > 0) {
342 bpm = (60.0 * m_inputSampleRate) / frameIncrement;
343 bpm = int(bpm * 100.0 + 0.5) / 100.0;
344 sprintf(label, "%.2f bpm", bpm);
345 feature.label = label;
346 }
347 }
348
349 returnFeatures[0].push_back(feature); // beats are output 0
350 }
351
352 double prevTempo = 0.0;
353
354 for (size_t i = 0; i < tempos.size(); ++i) {
355
356 size_t frame = i * m_d->dfConfig.stepSize * ttParams.lagLength;
357
358 // std::cerr << "unit " << i << ", step size " << m_d->dfConfig.stepSize << ", hop " << ttParams.lagLength << ", frame = " << frame << std::endl;
359
360 if (tempos[i] > 1 && int(tempos[i] * 100) != int(prevTempo * 100)) {
361 Feature feature;
362 feature.hasTimestamp = true;
363 feature.timestamp = Vamp::RealTime::frame2RealTime
364 (frame, lrintf(m_inputSampleRate));
365 feature.values.push_back(tempos[i]);
366 sprintf(label, "%.2f bpm", tempos[i]);
367 feature.label = label;
368 returnFeatures[2].push_back(feature); // tempo is output 2
369 }
370 }
371
372 return returnFeatures;
373 }
374