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