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@243
|
49 class FixedTempoEstimator::D
|
cannam@243
|
50 {
|
cannam@243
|
51 public:
|
cannam@243
|
52 D(float inputSampleRate);
|
cannam@243
|
53 ~D();
|
cannam@243
|
54
|
cannam@243
|
55 size_t getPreferredStepSize() const { return 64; }
|
cannam@243
|
56 size_t getPreferredBlockSize() const { return 256; }
|
cannam@243
|
57
|
cannam@243
|
58 ParameterList getParameterDescriptors() const;
|
cannam@243
|
59 float getParameter(string id) const;
|
cannam@243
|
60 void setParameter(string id, float value);
|
cannam@243
|
61
|
cannam@243
|
62 OutputList getOutputDescriptors() const;
|
cannam@243
|
63
|
cannam@243
|
64 bool initialise(size_t channels, size_t stepSize, size_t blockSize);
|
cannam@243
|
65 void reset();
|
cannam@243
|
66 FeatureSet process(const float *const *, RealTime);
|
cannam@243
|
67 FeatureSet getRemainingFeatures();
|
cannam@243
|
68
|
cannam@243
|
69 private:
|
cannam@243
|
70 void calculate();
|
cannam@243
|
71 FeatureSet assembleFeatures();
|
cannam@243
|
72
|
cannam@243
|
73 float lag2tempo(int);
|
cannam@243
|
74 int tempo2lag(float);
|
cannam@243
|
75
|
cannam@243
|
76 float m_inputSampleRate;
|
cannam@243
|
77 size_t m_stepSize;
|
cannam@243
|
78 size_t m_blockSize;
|
cannam@243
|
79
|
cannam@243
|
80 float m_minbpm;
|
cannam@243
|
81 float m_maxbpm;
|
cannam@243
|
82 float m_maxdflen;
|
cannam@243
|
83
|
cannam@243
|
84 float *m_priorMagnitudes;
|
cannam@243
|
85
|
cannam@243
|
86 size_t m_dfsize;
|
cannam@243
|
87 float *m_df;
|
cannam@243
|
88 float *m_r;
|
cannam@243
|
89 float *m_fr;
|
cannam@243
|
90 float *m_t;
|
cannam@243
|
91 size_t m_n;
|
cannam@243
|
92
|
cannam@243
|
93 Vamp::RealTime m_start;
|
cannam@243
|
94 Vamp::RealTime m_lasttime;
|
cannam@243
|
95 };
|
cannam@243
|
96
|
cannam@243
|
97 FixedTempoEstimator::D::D(float inputSampleRate) :
|
cannam@243
|
98 m_inputSampleRate(inputSampleRate),
|
cannam@198
|
99 m_stepSize(0),
|
cannam@198
|
100 m_blockSize(0),
|
cannam@243
|
101 m_minbpm(50),
|
cannam@243
|
102 m_maxbpm(190),
|
cannam@243
|
103 m_maxdflen(10),
|
cannam@198
|
104 m_priorMagnitudes(0),
|
cannam@200
|
105 m_df(0),
|
cannam@200
|
106 m_r(0),
|
cannam@200
|
107 m_fr(0),
|
cannam@204
|
108 m_t(0),
|
cannam@200
|
109 m_n(0)
|
cannam@198
|
110 {
|
cannam@198
|
111 }
|
cannam@198
|
112
|
cannam@243
|
113 FixedTempoEstimator::D::~D()
|
cannam@198
|
114 {
|
cannam@198
|
115 delete[] m_priorMagnitudes;
|
cannam@198
|
116 delete[] m_df;
|
cannam@200
|
117 delete[] m_r;
|
cannam@200
|
118 delete[] m_fr;
|
cannam@204
|
119 delete[] m_t;
|
cannam@198
|
120 }
|
cannam@198
|
121
|
cannam@198
|
122 FixedTempoEstimator::ParameterList
|
cannam@243
|
123 FixedTempoEstimator::D::getParameterDescriptors() const
|
cannam@198
|
124 {
|
cannam@198
|
125 ParameterList list;
|
cannam@243
|
126
|
cannam@243
|
127 ParameterDescriptor d;
|
cannam@243
|
128 d.identifier = "minbpm";
|
cannam@243
|
129 d.name = "Minimum estimated tempo";
|
cannam@243
|
130 d.description = "Minimum beat-per-minute value which the tempo estimator is able to return";
|
cannam@243
|
131 d.unit = "bpm";
|
cannam@243
|
132 d.minValue = 10;
|
cannam@243
|
133 d.maxValue = 360;
|
cannam@243
|
134 d.defaultValue = 50;
|
cannam@243
|
135 d.isQuantized = false;
|
cannam@243
|
136 list.push_back(d);
|
cannam@243
|
137
|
cannam@243
|
138 d.identifier = "maxbpm";
|
cannam@243
|
139 d.name = "Maximum estimated tempo";
|
cannam@243
|
140 d.description = "Maximum beat-per-minute value which the tempo estimator is able to return";
|
cannam@243
|
141 d.defaultValue = 190;
|
cannam@243
|
142 list.push_back(d);
|
cannam@243
|
143
|
cannam@243
|
144 d.identifier = "maxdflen";
|
cannam@243
|
145 d.name = "Input duration to study";
|
cannam@243
|
146 d.description = "Length of audio input, in seconds, which should be taken into account when estimating tempo. There is no need to supply the plugin with any further input once this time has elapsed since the start of the audio. The tempo estimator may use only the first part of this, up to eight times the slowest beat duration: increasing this value further than that is unlikely to improve results.";
|
cannam@243
|
147 d.unit = "s";
|
cannam@243
|
148 d.minValue = 2;
|
cannam@243
|
149 d.maxValue = 40;
|
cannam@243
|
150 d.defaultValue = 10;
|
cannam@243
|
151 list.push_back(d);
|
cannam@243
|
152
|
cannam@198
|
153 return list;
|
cannam@198
|
154 }
|
cannam@198
|
155
|
cannam@198
|
156 float
|
cannam@243
|
157 FixedTempoEstimator::D::getParameter(string id) const
|
cannam@198
|
158 {
|
cannam@243
|
159 if (id == "minbpm") {
|
cannam@243
|
160 return m_minbpm;
|
cannam@243
|
161 } else if (id == "maxbpm") {
|
cannam@243
|
162 return m_maxbpm;
|
cannam@243
|
163 } else if (id == "maxdflen") {
|
cannam@243
|
164 return m_maxdflen;
|
cannam@243
|
165 }
|
cannam@198
|
166 return 0.f;
|
cannam@198
|
167 }
|
cannam@198
|
168
|
cannam@198
|
169 void
|
cannam@243
|
170 FixedTempoEstimator::D::setParameter(string id, float value)
|
cannam@198
|
171 {
|
cannam@243
|
172 if (id == "minbpm") {
|
cannam@243
|
173 m_minbpm = value;
|
cannam@243
|
174 } else if (id == "maxbpm") {
|
cannam@243
|
175 m_maxbpm = value;
|
cannam@243
|
176 } else if (id == "maxdflen") {
|
cannam@243
|
177 m_maxdflen = value;
|
cannam@243
|
178 }
|
cannam@198
|
179 }
|
cannam@198
|
180
|
cannam@200
|
181 static int TempoOutput = 0;
|
cannam@200
|
182 static int CandidatesOutput = 1;
|
cannam@200
|
183 static int DFOutput = 2;
|
cannam@200
|
184 static int ACFOutput = 3;
|
cannam@200
|
185 static int FilteredACFOutput = 4;
|
cannam@200
|
186
|
cannam@198
|
187 FixedTempoEstimator::OutputList
|
cannam@243
|
188 FixedTempoEstimator::D::getOutputDescriptors() const
|
cannam@198
|
189 {
|
cannam@198
|
190 OutputList list;
|
cannam@198
|
191
|
cannam@198
|
192 OutputDescriptor d;
|
cannam@198
|
193 d.identifier = "tempo";
|
cannam@198
|
194 d.name = "Tempo";
|
cannam@198
|
195 d.description = "Estimated tempo";
|
cannam@198
|
196 d.unit = "bpm";
|
cannam@198
|
197 d.hasFixedBinCount = true;
|
cannam@198
|
198 d.binCount = 1;
|
cannam@198
|
199 d.hasKnownExtents = false;
|
cannam@198
|
200 d.isQuantized = false;
|
cannam@198
|
201 d.sampleType = OutputDescriptor::VariableSampleRate;
|
cannam@198
|
202 d.sampleRate = m_inputSampleRate;
|
cannam@198
|
203 d.hasDuration = true; // our returned tempo spans a certain range
|
cannam@198
|
204 list.push_back(d);
|
cannam@198
|
205
|
cannam@200
|
206 d.identifier = "candidates";
|
cannam@200
|
207 d.name = "Tempo candidates";
|
cannam@200
|
208 d.description = "Possible tempo estimates, one per bin with the most likely in the first bin";
|
cannam@200
|
209 d.unit = "bpm";
|
cannam@200
|
210 d.hasFixedBinCount = false;
|
cannam@200
|
211 list.push_back(d);
|
cannam@200
|
212
|
cannam@198
|
213 d.identifier = "detectionfunction";
|
cannam@198
|
214 d.name = "Detection Function";
|
cannam@198
|
215 d.description = "Onset detection function";
|
cannam@198
|
216 d.unit = "";
|
cannam@198
|
217 d.hasFixedBinCount = 1;
|
cannam@198
|
218 d.binCount = 1;
|
cannam@198
|
219 d.hasKnownExtents = true;
|
cannam@198
|
220 d.minValue = 0.0;
|
cannam@198
|
221 d.maxValue = 1.0;
|
cannam@198
|
222 d.isQuantized = false;
|
cannam@198
|
223 d.quantizeStep = 0.0;
|
cannam@198
|
224 d.sampleType = OutputDescriptor::FixedSampleRate;
|
cannam@198
|
225 if (m_stepSize) {
|
cannam@198
|
226 d.sampleRate = m_inputSampleRate / m_stepSize;
|
cannam@198
|
227 } else {
|
cannam@198
|
228 d.sampleRate = m_inputSampleRate / (getPreferredBlockSize()/2);
|
cannam@198
|
229 }
|
cannam@198
|
230 d.hasDuration = false;
|
cannam@198
|
231 list.push_back(d);
|
cannam@198
|
232
|
cannam@198
|
233 d.identifier = "acf";
|
cannam@198
|
234 d.name = "Autocorrelation Function";
|
cannam@198
|
235 d.description = "Autocorrelation of onset detection function";
|
cannam@198
|
236 d.hasKnownExtents = false;
|
cannam@201
|
237 d.unit = "r";
|
cannam@198
|
238 list.push_back(d);
|
cannam@198
|
239
|
cannam@198
|
240 d.identifier = "filtered_acf";
|
cannam@198
|
241 d.name = "Filtered Autocorrelation";
|
cannam@198
|
242 d.description = "Filtered autocorrelation of onset detection function";
|
cannam@201
|
243 d.unit = "r";
|
cannam@198
|
244 list.push_back(d);
|
cannam@198
|
245
|
cannam@198
|
246 return list;
|
cannam@198
|
247 }
|
cannam@198
|
248
|
cannam@243
|
249 bool
|
cannam@243
|
250 FixedTempoEstimator::D::initialise(size_t channels,
|
cannam@243
|
251 size_t stepSize, size_t blockSize)
|
cannam@243
|
252 {
|
cannam@243
|
253 m_stepSize = stepSize;
|
cannam@243
|
254 m_blockSize = blockSize;
|
cannam@243
|
255
|
cannam@243
|
256 float dfLengthSecs = m_maxdflen;
|
cannam@243
|
257 m_dfsize = (dfLengthSecs * m_inputSampleRate) / m_stepSize;
|
cannam@243
|
258
|
cannam@243
|
259 m_priorMagnitudes = new float[m_blockSize/2];
|
cannam@243
|
260 m_df = new float[m_dfsize];
|
cannam@243
|
261
|
cannam@243
|
262 for (size_t i = 0; i < m_blockSize/2; ++i) {
|
cannam@243
|
263 m_priorMagnitudes[i] = 0.f;
|
cannam@243
|
264 }
|
cannam@243
|
265 for (size_t i = 0; i < m_dfsize; ++i) {
|
cannam@243
|
266 m_df[i] = 0.f;
|
cannam@243
|
267 }
|
cannam@243
|
268
|
cannam@243
|
269 m_n = 0;
|
cannam@243
|
270
|
cannam@243
|
271 return true;
|
cannam@243
|
272 }
|
cannam@243
|
273
|
cannam@243
|
274 void
|
cannam@243
|
275 FixedTempoEstimator::D::reset()
|
cannam@243
|
276 {
|
cannam@243
|
277 if (!m_priorMagnitudes) return;
|
cannam@243
|
278
|
cannam@243
|
279 for (size_t i = 0; i < m_blockSize/2; ++i) {
|
cannam@243
|
280 m_priorMagnitudes[i] = 0.f;
|
cannam@243
|
281 }
|
cannam@243
|
282 for (size_t i = 0; i < m_dfsize; ++i) {
|
cannam@243
|
283 m_df[i] = 0.f;
|
cannam@243
|
284 }
|
cannam@243
|
285
|
cannam@243
|
286 delete[] m_r;
|
cannam@243
|
287 m_r = 0;
|
cannam@243
|
288
|
cannam@243
|
289 delete[] m_fr;
|
cannam@243
|
290 m_fr = 0;
|
cannam@243
|
291
|
cannam@243
|
292 delete[] m_t;
|
cannam@243
|
293 m_t = 0;
|
cannam@243
|
294
|
cannam@243
|
295 m_n = 0;
|
cannam@243
|
296
|
cannam@243
|
297 m_start = RealTime::zeroTime;
|
cannam@243
|
298 m_lasttime = RealTime::zeroTime;
|
cannam@243
|
299 }
|
cannam@243
|
300
|
cannam@198
|
301 FixedTempoEstimator::FeatureSet
|
cannam@243
|
302 FixedTempoEstimator::D::process(const float *const *inputBuffers, RealTime ts)
|
cannam@198
|
303 {
|
cannam@198
|
304 FeatureSet fs;
|
cannam@198
|
305
|
cannam@198
|
306 if (m_stepSize == 0) {
|
cannam@198
|
307 cerr << "ERROR: FixedTempoEstimator::process: "
|
cannam@198
|
308 << "FixedTempoEstimator has not been initialised"
|
cannam@198
|
309 << endl;
|
cannam@198
|
310 return fs;
|
cannam@198
|
311 }
|
cannam@198
|
312
|
cannam@198
|
313 if (m_n == 0) m_start = ts;
|
cannam@198
|
314 m_lasttime = ts;
|
cannam@198
|
315
|
cannam@198
|
316 if (m_n == m_dfsize) {
|
cannam@200
|
317 calculate();
|
cannam@200
|
318 fs = assembleFeatures();
|
cannam@198
|
319 ++m_n;
|
cannam@198
|
320 return fs;
|
cannam@198
|
321 }
|
cannam@198
|
322
|
cannam@198
|
323 if (m_n > m_dfsize) return FeatureSet();
|
cannam@198
|
324
|
cannam@207
|
325 float value = 0.f;
|
cannam@207
|
326
|
cannam@198
|
327 for (size_t i = 1; i < m_blockSize/2; ++i) {
|
cannam@198
|
328
|
cannam@198
|
329 float real = inputBuffers[0][i*2];
|
cannam@198
|
330 float imag = inputBuffers[0][i*2 + 1];
|
cannam@198
|
331
|
cannam@198
|
332 float sqrmag = real * real + imag * imag;
|
cannam@207
|
333 value += fabsf(sqrmag - m_priorMagnitudes[i]);
|
cannam@198
|
334
|
cannam@198
|
335 m_priorMagnitudes[i] = sqrmag;
|
cannam@198
|
336 }
|
cannam@198
|
337
|
cannam@207
|
338 m_df[m_n] = value;
|
cannam@207
|
339
|
cannam@198
|
340 ++m_n;
|
cannam@198
|
341 return fs;
|
cannam@243
|
342 }
|
cannam@198
|
343
|
cannam@198
|
344 FixedTempoEstimator::FeatureSet
|
cannam@243
|
345 FixedTempoEstimator::D::getRemainingFeatures()
|
cannam@198
|
346 {
|
cannam@198
|
347 FeatureSet fs;
|
cannam@198
|
348 if (m_n > m_dfsize) return fs;
|
cannam@200
|
349 calculate();
|
cannam@200
|
350 fs = assembleFeatures();
|
cannam@198
|
351 ++m_n;
|
cannam@198
|
352 return fs;
|
cannam@198
|
353 }
|
cannam@198
|
354
|
cannam@198
|
355 float
|
cannam@243
|
356 FixedTempoEstimator::D::lag2tempo(int lag)
|
cannam@199
|
357 {
|
cannam@198
|
358 return 60.f / ((lag * m_stepSize) / m_inputSampleRate);
|
cannam@198
|
359 }
|
cannam@198
|
360
|
cannam@207
|
361 int
|
cannam@243
|
362 FixedTempoEstimator::D::tempo2lag(float tempo)
|
cannam@207
|
363 {
|
cannam@207
|
364 return ((60.f / tempo) * m_inputSampleRate) / m_stepSize;
|
cannam@207
|
365 }
|
cannam@207
|
366
|
cannam@200
|
367 void
|
cannam@243
|
368 FixedTempoEstimator::D::calculate()
|
cannam@200
|
369 {
|
cannam@207
|
370 cerr << "FixedTempoEstimator::calculate: m_n = " << m_n << endl;
|
cannam@200
|
371
|
cannam@200
|
372 if (m_r) {
|
cannam@207
|
373 cerr << "FixedTempoEstimator::calculate: calculation already happened?" << endl;
|
cannam@200
|
374 return;
|
cannam@200
|
375 }
|
cannam@200
|
376
|
cannam@243
|
377 if (m_n < m_dfsize / 9 &&
|
cannam@243
|
378 m_n < (1.0 * m_inputSampleRate) / m_stepSize) { // 1 second
|
cannam@243
|
379 cerr << "FixedTempoEstimator::calculate: Input is too short" << endl;
|
cannam@243
|
380 return;
|
cannam@200
|
381 }
|
cannam@200
|
382
|
cannam@200
|
383 int n = m_n;
|
cannam@200
|
384
|
cannam@200
|
385 m_r = new float[n/2];
|
cannam@200
|
386 m_fr = new float[n/2];
|
cannam@204
|
387 m_t = new float[n/2];
|
cannam@200
|
388
|
cannam@200
|
389 for (int i = 0; i < n/2; ++i) {
|
cannam@200
|
390 m_r[i] = 0.f;
|
cannam@200
|
391 m_fr[i] = 0.f;
|
cannam@207
|
392 m_t[i] = lag2tempo(i);
|
cannam@200
|
393 }
|
cannam@200
|
394
|
cannam@200
|
395 for (int i = 0; i < n/2; ++i) {
|
cannam@200
|
396
|
cannam@200
|
397 for (int j = i; j < n-1; ++j) {
|
cannam@200
|
398 m_r[i] += m_df[j] * m_df[j - i];
|
cannam@200
|
399 }
|
cannam@200
|
400
|
cannam@200
|
401 m_r[i] /= n - i - 1;
|
cannam@200
|
402 }
|
cannam@200
|
403
|
cannam@215
|
404 float related[] = { 0.5, 2, 3, 4 };
|
cannam@208
|
405
|
cannam@209
|
406 for (int i = 1; i < n/2-1; ++i) {
|
cannam@204
|
407
|
cannam@209
|
408 float weight = 1.f - fabsf(128.f - lag2tempo(i)) * 0.005;
|
cannam@209
|
409 if (weight < 0.f) weight = 0.f;
|
cannam@215
|
410 weight = weight * weight * weight;
|
cannam@209
|
411
|
cannam@209
|
412 m_fr[i] = m_r[i];
|
cannam@204
|
413
|
cannam@200
|
414 int div = 1;
|
cannam@200
|
415
|
cannam@215
|
416 for (int j = 0; j < int(sizeof(related)/sizeof(related[0])); ++j) {
|
cannam@204
|
417
|
cannam@215
|
418 int k0 = int(i * related[j] + 0.5);
|
cannam@209
|
419
|
cannam@215
|
420 if (k0 >= 0 && k0 < int(n/2)) {
|
cannam@204
|
421
|
cannam@207
|
422 int kmax = 0, kmin = 0;
|
cannam@207
|
423 float kvmax = 0, kvmin = 0;
|
cannam@209
|
424 bool have = false;
|
cannam@204
|
425
|
cannam@209
|
426 for (int k = k0 - 1; k <= k0 + 1; ++k) {
|
cannam@204
|
427
|
cannam@209
|
428 if (k < 0 || k >= n/2) continue;
|
cannam@209
|
429
|
cannam@215
|
430 if (!have || (m_r[k] > kvmax)) { kmax = k; kvmax = m_r[k]; }
|
cannam@215
|
431 if (!have || (m_r[k] < kvmin)) { kmin = k; kvmin = m_r[k]; }
|
cannam@209
|
432
|
cannam@209
|
433 have = true;
|
cannam@204
|
434 }
|
cannam@209
|
435
|
cannam@215
|
436 m_fr[i] += m_r[kmax] / 5;
|
cannam@209
|
437
|
cannam@209
|
438 if ((kmax == 0 || m_r[kmax] > m_r[kmax-1]) &&
|
cannam@209
|
439 (kmax == n/2-1 || m_r[kmax] > m_r[kmax+1]) &&
|
cannam@207
|
440 kvmax > kvmin * 1.05) {
|
cannam@209
|
441
|
cannam@207
|
442 m_t[i] = m_t[i] + lag2tempo(kmax) * related[j];
|
cannam@207
|
443 ++div;
|
cannam@207
|
444 }
|
cannam@204
|
445 }
|
cannam@204
|
446 }
|
cannam@209
|
447
|
cannam@204
|
448 m_t[i] /= div;
|
cannam@204
|
449
|
cannam@215
|
450 // if (div > 1) {
|
cannam@215
|
451 // cerr << "adjusting tempo from " << lag2tempo(i) << " to "
|
cannam@215
|
452 // << m_t[i] << " for fr = " << m_fr[i] << " (div = " << div << ")" << endl;
|
cannam@215
|
453 // }
|
cannam@209
|
454
|
cannam@215
|
455 m_fr[i] += m_fr[i] * (weight / 3);
|
cannam@207
|
456 }
|
cannam@200
|
457 }
|
cannam@200
|
458
|
cannam@198
|
459 FixedTempoEstimator::FeatureSet
|
cannam@243
|
460 FixedTempoEstimator::D::assembleFeatures()
|
cannam@198
|
461 {
|
cannam@198
|
462 FeatureSet fs;
|
cannam@200
|
463 if (!m_r) return fs; // No results
|
cannam@200
|
464
|
cannam@198
|
465 Feature feature;
|
cannam@198
|
466 feature.hasTimestamp = true;
|
cannam@198
|
467 feature.hasDuration = false;
|
cannam@198
|
468 feature.label = "";
|
cannam@198
|
469 feature.values.clear();
|
cannam@198
|
470 feature.values.push_back(0.f);
|
cannam@198
|
471
|
cannam@200
|
472 char buffer[40];
|
cannam@198
|
473
|
cannam@198
|
474 int n = m_n;
|
cannam@198
|
475
|
cannam@198
|
476 for (int i = 0; i < n; ++i) {
|
cannam@208
|
477 feature.timestamp = m_start +
|
cannam@208
|
478 RealTime::frame2RealTime(i * m_stepSize, m_inputSampleRate);
|
cannam@200
|
479 feature.values[0] = m_df[i];
|
cannam@198
|
480 feature.label = "";
|
cannam@200
|
481 fs[DFOutput].push_back(feature);
|
cannam@198
|
482 }
|
cannam@198
|
483
|
cannam@199
|
484 for (int i = 1; i < n/2; ++i) {
|
cannam@208
|
485 feature.timestamp = m_start +
|
cannam@208
|
486 RealTime::frame2RealTime(i * m_stepSize, m_inputSampleRate);
|
cannam@200
|
487 feature.values[0] = m_r[i];
|
cannam@199
|
488 sprintf(buffer, "%.1f bpm", lag2tempo(i));
|
cannam@200
|
489 if (i == n/2-1) feature.label = "";
|
cannam@200
|
490 else feature.label = buffer;
|
cannam@200
|
491 fs[ACFOutput].push_back(feature);
|
cannam@198
|
492 }
|
cannam@198
|
493
|
cannam@243
|
494 float t0 = m_minbpm; // our minimum detected tempo
|
cannam@243
|
495 float t1 = m_maxbpm; // our maximum detected tempo
|
cannam@216
|
496
|
cannam@207
|
497 int p0 = tempo2lag(t1);
|
cannam@207
|
498 int p1 = tempo2lag(t0);
|
cannam@198
|
499
|
cannam@200
|
500 std::map<float, int> candidates;
|
cannam@198
|
501
|
cannam@243
|
502 std::cerr << "minbpm = " << m_minbpm << ", p0 = " << p0 << ", p1 = " << p1 << std::endl;
|
cannam@243
|
503
|
cannam@200
|
504 for (int i = p0; i <= p1 && i < n/2-1; ++i) {
|
cannam@198
|
505
|
cannam@209
|
506 if (m_fr[i] > m_fr[i-1] &&
|
cannam@209
|
507 m_fr[i] > m_fr[i+1]) {
|
cannam@209
|
508 candidates[m_fr[i]] = i;
|
cannam@209
|
509 }
|
cannam@198
|
510
|
cannam@208
|
511 feature.timestamp = m_start +
|
cannam@208
|
512 RealTime::frame2RealTime(i * m_stepSize, m_inputSampleRate);
|
cannam@200
|
513 feature.values[0] = m_fr[i];
|
cannam@199
|
514 sprintf(buffer, "%.1f bpm", lag2tempo(i));
|
cannam@200
|
515 if (i == p1 || i == n/2-2) feature.label = "";
|
cannam@200
|
516 else feature.label = buffer;
|
cannam@200
|
517 fs[FilteredACFOutput].push_back(feature);
|
cannam@198
|
518 }
|
cannam@198
|
519
|
cannam@207
|
520 // cerr << "maxpi = " << maxpi << " for tempo " << lag2tempo(maxpi) << " (value = " << maxp << ")" << endl;
|
cannam@198
|
521
|
cannam@200
|
522 if (candidates.empty()) {
|
cannam@207
|
523 cerr << "No tempo candidates!" << endl;
|
cannam@200
|
524 return fs;
|
cannam@200
|
525 }
|
cannam@198
|
526
|
cannam@198
|
527 feature.hasTimestamp = true;
|
cannam@198
|
528 feature.timestamp = m_start;
|
cannam@198
|
529
|
cannam@198
|
530 feature.hasDuration = true;
|
cannam@198
|
531 feature.duration = m_lasttime - m_start;
|
cannam@198
|
532
|
cannam@200
|
533 std::map<float, int>::const_iterator ci = candidates.end();
|
cannam@200
|
534 --ci;
|
cannam@200
|
535 int maxpi = ci->second;
|
cannam@198
|
536
|
cannam@204
|
537 if (m_t[maxpi] > 0) {
|
cannam@207
|
538 cerr << "*** Using adjusted tempo " << m_t[maxpi] << " instead of lag tempo " << lag2tempo(maxpi) << endl;
|
cannam@204
|
539 feature.values[0] = m_t[maxpi];
|
cannam@204
|
540 } else {
|
cannam@204
|
541 // shouldn't happen -- it would imply that this high value was not a peak!
|
cannam@204
|
542 feature.values[0] = lag2tempo(maxpi);
|
cannam@207
|
543 cerr << "WARNING: No stored tempo for index " << maxpi << endl;
|
cannam@204
|
544 }
|
cannam@204
|
545
|
cannam@204
|
546 sprintf(buffer, "%.1f bpm", feature.values[0]);
|
cannam@199
|
547 feature.label = buffer;
|
cannam@199
|
548
|
cannam@200
|
549 fs[TempoOutput].push_back(feature);
|
cannam@198
|
550
|
cannam@200
|
551 feature.values.clear();
|
cannam@200
|
552 feature.label = "";
|
cannam@200
|
553
|
cannam@200
|
554 while (feature.values.size() < 8) {
|
cannam@213
|
555 // cerr << "adding tempo value from lag " << ci->second << endl;
|
cannam@207
|
556 if (m_t[ci->second] > 0) {
|
cannam@207
|
557 feature.values.push_back(m_t[ci->second]);
|
cannam@207
|
558 } else {
|
cannam@207
|
559 feature.values.push_back(lag2tempo(ci->second));
|
cannam@207
|
560 }
|
cannam@200
|
561 if (ci == candidates.begin()) break;
|
cannam@200
|
562 --ci;
|
cannam@200
|
563 }
|
cannam@200
|
564
|
cannam@200
|
565 fs[CandidatesOutput].push_back(feature);
|
cannam@200
|
566
|
cannam@198
|
567 return fs;
|
cannam@198
|
568 }
|
cannam@243
|
569
|
cannam@243
|
570
|
cannam@243
|
571
|
cannam@243
|
572 FixedTempoEstimator::FixedTempoEstimator(float inputSampleRate) :
|
cannam@243
|
573 Plugin(inputSampleRate),
|
cannam@243
|
574 m_d(new D(inputSampleRate))
|
cannam@243
|
575 {
|
cannam@243
|
576 }
|
cannam@243
|
577
|
cannam@243
|
578 FixedTempoEstimator::~FixedTempoEstimator()
|
cannam@243
|
579 {
|
cannam@243
|
580 }
|
cannam@243
|
581
|
cannam@243
|
582 string
|
cannam@243
|
583 FixedTempoEstimator::getIdentifier() const
|
cannam@243
|
584 {
|
cannam@243
|
585 return "fixedtempo";
|
cannam@243
|
586 }
|
cannam@243
|
587
|
cannam@243
|
588 string
|
cannam@243
|
589 FixedTempoEstimator::getName() const
|
cannam@243
|
590 {
|
cannam@243
|
591 return "Simple Fixed Tempo Estimator";
|
cannam@243
|
592 }
|
cannam@243
|
593
|
cannam@243
|
594 string
|
cannam@243
|
595 FixedTempoEstimator::getDescription() const
|
cannam@243
|
596 {
|
cannam@243
|
597 return "Study a short section of audio and estimate its tempo, assuming the tempo is constant";
|
cannam@243
|
598 }
|
cannam@243
|
599
|
cannam@243
|
600 string
|
cannam@243
|
601 FixedTempoEstimator::getMaker() const
|
cannam@243
|
602 {
|
cannam@243
|
603 return "Vamp SDK Example Plugins";
|
cannam@243
|
604 }
|
cannam@243
|
605
|
cannam@243
|
606 int
|
cannam@243
|
607 FixedTempoEstimator::getPluginVersion() const
|
cannam@243
|
608 {
|
cannam@243
|
609 return 1;
|
cannam@243
|
610 }
|
cannam@243
|
611
|
cannam@243
|
612 string
|
cannam@243
|
613 FixedTempoEstimator::getCopyright() const
|
cannam@243
|
614 {
|
cannam@243
|
615 return "Code copyright 2008 Queen Mary, University of London. Freely redistributable (BSD license)";
|
cannam@243
|
616 }
|
cannam@243
|
617
|
cannam@243
|
618 size_t
|
cannam@243
|
619 FixedTempoEstimator::getPreferredStepSize() const
|
cannam@243
|
620 {
|
cannam@243
|
621 return m_d->getPreferredStepSize();
|
cannam@243
|
622 }
|
cannam@243
|
623
|
cannam@243
|
624 size_t
|
cannam@243
|
625 FixedTempoEstimator::getPreferredBlockSize() const
|
cannam@243
|
626 {
|
cannam@243
|
627 return m_d->getPreferredBlockSize();
|
cannam@243
|
628 }
|
cannam@243
|
629
|
cannam@243
|
630 bool
|
cannam@243
|
631 FixedTempoEstimator::initialise(size_t channels, size_t stepSize, size_t blockSize)
|
cannam@243
|
632 {
|
cannam@243
|
633 if (channels < getMinChannelCount() ||
|
cannam@243
|
634 channels > getMaxChannelCount()) return false;
|
cannam@243
|
635
|
cannam@243
|
636 return m_d->initialise(channels, stepSize, blockSize);
|
cannam@243
|
637 }
|
cannam@243
|
638
|
cannam@243
|
639 void
|
cannam@243
|
640 FixedTempoEstimator::reset()
|
cannam@243
|
641 {
|
cannam@243
|
642 return m_d->reset();
|
cannam@243
|
643 }
|
cannam@243
|
644
|
cannam@243
|
645 FixedTempoEstimator::ParameterList
|
cannam@243
|
646 FixedTempoEstimator::getParameterDescriptors() const
|
cannam@243
|
647 {
|
cannam@243
|
648 return m_d->getParameterDescriptors();
|
cannam@243
|
649 }
|
cannam@243
|
650
|
cannam@243
|
651 float
|
cannam@243
|
652 FixedTempoEstimator::getParameter(std::string id) const
|
cannam@243
|
653 {
|
cannam@243
|
654 return m_d->getParameter(id);
|
cannam@243
|
655 }
|
cannam@243
|
656
|
cannam@243
|
657 void
|
cannam@243
|
658 FixedTempoEstimator::setParameter(std::string id, float value)
|
cannam@243
|
659 {
|
cannam@243
|
660 m_d->setParameter(id, value);
|
cannam@243
|
661 }
|
cannam@243
|
662
|
cannam@243
|
663 FixedTempoEstimator::OutputList
|
cannam@243
|
664 FixedTempoEstimator::getOutputDescriptors() const
|
cannam@243
|
665 {
|
cannam@243
|
666 return m_d->getOutputDescriptors();
|
cannam@243
|
667 }
|
cannam@243
|
668
|
cannam@243
|
669 FixedTempoEstimator::FeatureSet
|
cannam@243
|
670 FixedTempoEstimator::process(const float *const *inputBuffers, RealTime ts)
|
cannam@243
|
671 {
|
cannam@243
|
672 return m_d->process(inputBuffers, ts);
|
cannam@243
|
673 }
|
cannam@243
|
674
|
cannam@243
|
675 FixedTempoEstimator::FeatureSet
|
cannam@243
|
676 FixedTempoEstimator::getRemainingFeatures()
|
cannam@243
|
677 {
|
cannam@243
|
678 return m_d->getRemainingFeatures();
|
cannam@243
|
679 }
|