comparison examples/FixedTempoEstimator.cpp @ 198:e3e61b7e9661

* Beginnings of a simple tempo estimator example plugin
author cannam
date Wed, 08 Oct 2008 15:26:50 +0000
parents
children 84c4bb209227
comparison
equal deleted inserted replaced
197:fe30a25ee4f8 198:e3e61b7e9661
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2
3 /*
4 Vamp
5
6 An API for audio analysis and feature extraction plugins.
7
8 Centre for Digital Music, Queen Mary, University of London.
9 Copyright 2006-2008 Chris Cannam and QMUL.
10
11 Permission is hereby granted, free of charge, to any person
12 obtaining a copy of this software and associated documentation
13 files (the "Software"), to deal in the Software without
14 restriction, including without limitation the rights to use, copy,
15 modify, merge, publish, distribute, sublicense, and/or sell copies
16 of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be
20 included in all copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
26 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
27 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29
30 Except as contained in this notice, the names of the Centre for
31 Digital Music; Queen Mary, University of London; and Chris Cannam
32 shall not be used in advertising or otherwise to promote the sale,
33 use or other dealings in this Software without prior written
34 authorization.
35 */
36
37 #include "FixedTempoEstimator.h"
38
39 using std::string;
40 using std::vector;
41 using std::cerr;
42 using std::endl;
43
44 using Vamp::RealTime;
45
46 #include <cmath>
47
48
49 FixedTempoEstimator::FixedTempoEstimator(float inputSampleRate) :
50 Plugin(inputSampleRate),
51 m_stepSize(0),
52 m_blockSize(0),
53 m_priorMagnitudes(0),
54 m_df(0)
55 {
56 }
57
58 FixedTempoEstimator::~FixedTempoEstimator()
59 {
60 delete[] m_priorMagnitudes;
61 delete[] m_df;
62 }
63
64 string
65 FixedTempoEstimator::getIdentifier() const
66 {
67 return "fixedtempo";
68 }
69
70 string
71 FixedTempoEstimator::getName() const
72 {
73 return "Simple Fixed Tempo Estimator";
74 }
75
76 string
77 FixedTempoEstimator::getDescription() const
78 {
79 return "Study a short section of audio and estimate its tempo, assuming the tempo is constant";
80 }
81
82 string
83 FixedTempoEstimator::getMaker() const
84 {
85 return "Vamp SDK Example Plugins";
86 }
87
88 int
89 FixedTempoEstimator::getPluginVersion() const
90 {
91 return 1;
92 }
93
94 string
95 FixedTempoEstimator::getCopyright() const
96 {
97 return "Code copyright 2008 Queen Mary, University of London. Freely redistributable (BSD license)";
98 }
99
100 size_t
101 FixedTempoEstimator::getPreferredStepSize() const
102 {
103 return 0;
104 }
105
106 size_t
107 FixedTempoEstimator::getPreferredBlockSize() const
108 {
109 return 128;
110 }
111
112 bool
113 FixedTempoEstimator::initialise(size_t channels, size_t stepSize, size_t blockSize)
114 {
115 if (channels < getMinChannelCount() ||
116 channels > getMaxChannelCount()) return false;
117
118 m_stepSize = stepSize;
119 m_blockSize = blockSize;
120
121 float dfLengthSecs = 8.f;
122 m_dfsize = (dfLengthSecs * m_inputSampleRate) / m_stepSize;
123
124 m_priorMagnitudes = new float[m_blockSize/2];
125 m_df = new float[m_dfsize];
126
127 for (size_t i = 0; i < m_blockSize/2; ++i) {
128 m_priorMagnitudes[i] = 0.f;
129 }
130 for (size_t i = 0; i < m_dfsize; ++i) {
131 m_df[i] = 0.f;
132 }
133
134 m_n = 0;
135
136 return true;
137 }
138
139 void
140 FixedTempoEstimator::reset()
141 {
142 std::cerr << "FixedTempoEstimator: reset called" << std::endl;
143
144 if (!m_priorMagnitudes) return;
145
146 std::cerr << "FixedTempoEstimator: resetting" << std::endl;
147
148 for (size_t i = 0; i < m_blockSize/2; ++i) {
149 m_priorMagnitudes[i] = 0.f;
150 }
151 for (size_t i = 0; i < m_dfsize; ++i) {
152 m_df[i] = 0.f;
153 }
154
155 m_n = 0;
156
157 m_start = RealTime::zeroTime;
158 m_lasttime = RealTime::zeroTime;
159 }
160
161 FixedTempoEstimator::ParameterList
162 FixedTempoEstimator::getParameterDescriptors() const
163 {
164 ParameterList list;
165 return list;
166 }
167
168 float
169 FixedTempoEstimator::getParameter(std::string id) const
170 {
171 return 0.f;
172 }
173
174 void
175 FixedTempoEstimator::setParameter(std::string id, float value)
176 {
177 }
178
179 FixedTempoEstimator::OutputList
180 FixedTempoEstimator::getOutputDescriptors() const
181 {
182 OutputList list;
183
184 OutputDescriptor d;
185 d.identifier = "tempo";
186 d.name = "Tempo";
187 d.description = "Estimated tempo";
188 d.unit = "bpm";
189 d.hasFixedBinCount = true;
190 d.binCount = 1;
191 d.hasKnownExtents = false;
192 d.isQuantized = false;
193 d.sampleType = OutputDescriptor::VariableSampleRate;
194 d.sampleRate = m_inputSampleRate;
195 d.hasDuration = true; // our returned tempo spans a certain range
196 list.push_back(d);
197
198 d.identifier = "detectionfunction";
199 d.name = "Detection Function";
200 d.description = "Onset detection function";
201 d.unit = "";
202 d.hasFixedBinCount = 1;
203 d.binCount = 1;
204 d.hasKnownExtents = true;
205 d.minValue = 0.0;
206 d.maxValue = 1.0;
207 d.isQuantized = false;
208 d.quantizeStep = 0.0;
209 d.sampleType = OutputDescriptor::FixedSampleRate;
210 if (m_stepSize) {
211 d.sampleRate = m_inputSampleRate / m_stepSize;
212 } else {
213 d.sampleRate = m_inputSampleRate / (getPreferredBlockSize()/2);
214 }
215 d.hasDuration = false;
216 list.push_back(d);
217
218 d.identifier = "acf";
219 d.name = "Autocorrelation Function";
220 d.description = "Autocorrelation of onset detection function";
221 d.hasKnownExtents = false;
222 list.push_back(d);
223
224 d.identifier = "filtered_acf";
225 d.name = "Filtered Autocorrelation";
226 d.description = "Filtered autocorrelation of onset detection function";
227 list.push_back(d);
228
229 return list;
230 }
231
232 FixedTempoEstimator::FeatureSet
233 FixedTempoEstimator::process(const float *const *inputBuffers, RealTime ts)
234 {
235 FeatureSet fs;
236
237 if (m_stepSize == 0) {
238 cerr << "ERROR: FixedTempoEstimator::process: "
239 << "FixedTempoEstimator has not been initialised"
240 << endl;
241 return fs;
242 }
243
244 if (m_n < m_dfsize) std::cerr << "m_n = " << m_n << std::endl;
245
246 if (m_n == 0) m_start = ts;
247 m_lasttime = ts;
248
249 if (m_n == m_dfsize) {
250 fs = calculateFeatures();
251 ++m_n;
252 return fs;
253 }
254
255 if (m_n > m_dfsize) return FeatureSet();
256
257 int count = 0;
258
259 for (size_t i = 1; i < m_blockSize/2; ++i) {
260
261 float real = inputBuffers[0][i*2];
262 float imag = inputBuffers[0][i*2 + 1];
263
264 float sqrmag = real * real + imag * imag;
265
266 if (m_priorMagnitudes[i] > 0.f) {
267 float diff = 10.f * log10f(sqrmag / m_priorMagnitudes[i]);
268 if (diff >= 3.f) ++count;
269 }
270
271 m_priorMagnitudes[i] = sqrmag;
272 }
273
274 m_df[m_n] = float(count) / float(m_blockSize/2);
275 ++m_n;
276 return fs;
277 }
278
279 FixedTempoEstimator::FeatureSet
280 FixedTempoEstimator::getRemainingFeatures()
281 {
282 FeatureSet fs;
283 if (m_n > m_dfsize) return fs;
284 fs = calculateFeatures();
285 ++m_n;
286 return fs;
287 }
288
289 float
290 FixedTempoEstimator::lag2tempo(int lag) {
291 return 60.f / ((lag * m_stepSize) / m_inputSampleRate);
292 }
293
294 FixedTempoEstimator::FeatureSet
295 FixedTempoEstimator::calculateFeatures()
296 {
297 FeatureSet fs;
298 Feature feature;
299 feature.hasTimestamp = true;
300 feature.hasDuration = false;
301 feature.label = "";
302 feature.values.clear();
303 feature.values.push_back(0.f);
304
305 char buffer[20];
306
307 if (m_n < m_dfsize / 4) return fs; // not enough data (perhaps we should return the duration of the input as the "estimated" beat length?)
308
309 std::cerr << "FixedTempoEstimator::calculateTempo: m_n = " << m_n << std::endl;
310
311 int n = m_n;
312 float *f = m_df;
313
314 for (int i = 0; i < n; ++i) {
315 feature.timestamp = RealTime::frame2RealTime(i * m_stepSize,
316 m_inputSampleRate);
317 std::cerr << "step = " << m_stepSize << ", timestamp = " << feature.timestamp << std::endl;
318 feature.values[0] = f[i];
319 feature.label = "";
320 fs[1].push_back(feature);
321 }
322
323 float *r = new float[n/2];
324 for (int i = 0; i < n/2; ++i) r[i] = 0.f;
325
326 int minlag = 10;
327
328 for (int i = 0; i < n/2; ++i) {
329 for (int j = i; j < n-1; ++j) {
330 r[i] += f[j] * f[j - i];
331 }
332 r[i] /= n - i - 1;
333 }
334
335 for (int i = 0; i < n/2; ++i) {
336 feature.timestamp = RealTime::frame2RealTime(i * m_stepSize,
337 m_inputSampleRate);
338 feature.values[0] = r[i];
339 sprintf(buffer, "%f bpm", lag2tempo(i));
340 feature.label = buffer;
341 fs[2].push_back(feature);
342 }
343
344 float max = 0.f;
345 int maxindex = 0;
346
347 std::cerr << "n/2 = " << n/2 << std::endl;
348
349 for (int i = minlag; i < n/2; ++i) {
350
351 if (i == minlag || r[i] > max) {
352 max = r[i];
353 maxindex = i;
354 }
355
356 if (i == 0 || i == n/2-1) continue;
357
358 if (r[i] > r[i-1] && r[i] > r[i+1]) {
359 std::cerr << "peak at " << i << " (value=" << r[i] << ", tempo would be " << lag2tempo(i) << ")" << std::endl;
360 }
361 }
362
363 std::cerr << "overall max at " << maxindex << " (value=" << max << ")" << std::endl;
364
365 float tempo = lag2tempo(maxindex);
366
367 std::cerr << "provisional tempo = " << tempo << std::endl;
368
369 float t0 = 60.f;
370 float t1 = 180.f;
371
372 int p0 = ((60.f / t1) * m_inputSampleRate) / m_stepSize;
373 int p1 = ((60.f / t0) * m_inputSampleRate) / m_stepSize;
374
375 std::cerr << "p0 = " << p0 << ", p1 = " << p1 << std::endl;
376
377 int pc = p1 - p0 + 1;
378 std::cerr << "pc = " << pc << std::endl;
379 // float *filtered = new float[pc];
380 // for (int i = 0; i < pc; ++i) filtered[i] = 0.f;
381
382 int maxpi = 0;
383 float maxp = 0.f;
384
385 for (int i = p0; i <= p1; ++i) {
386
387 // int fi = i - p0;
388
389 float filtered = 0.f;
390
391 for (int j = 1; j <= (n/2)/p1; ++j) {
392 std::cerr << "j = " << j << ", i = " << i << std::endl;
393 filtered += r[i * j];
394 }
395
396 if (i == p0 || filtered > maxp) {
397 maxp = filtered;
398 maxpi = i;
399 }
400
401 feature.timestamp = RealTime::frame2RealTime(i * m_stepSize,
402 m_inputSampleRate);
403 feature.values[0] = filtered;
404 sprintf(buffer, "%f bpm", lag2tempo(i));
405 feature.label = buffer;
406 fs[3].push_back(feature);
407 }
408
409 std::cerr << "maxpi = " << maxpi << " for tempo " << lag2tempo(maxpi) << " (value = " << maxp << ")" << std::endl;
410
411 tempo = lag2tempo(maxpi);
412
413 delete[] r;
414
415 feature.hasTimestamp = true;
416 feature.timestamp = m_start;
417
418 feature.hasDuration = true;
419 feature.duration = m_lasttime - m_start;
420
421 feature.values[0] = tempo;
422
423 fs[0].push_back(feature);
424
425 return fs;
426 }