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@200
|
370 if (m_r) {
|
cannam@207
|
371 cerr << "FixedTempoEstimator::calculate: calculation already happened?" << endl;
|
cannam@200
|
372 return;
|
cannam@200
|
373 }
|
cannam@200
|
374
|
cannam@243
|
375 if (m_n < m_dfsize / 9 &&
|
cannam@243
|
376 m_n < (1.0 * m_inputSampleRate) / m_stepSize) { // 1 second
|
cannam@243
|
377 cerr << "FixedTempoEstimator::calculate: Input is too short" << endl;
|
cannam@243
|
378 return;
|
cannam@200
|
379 }
|
cannam@200
|
380
|
cannam@200
|
381 int n = m_n;
|
cannam@200
|
382
|
cannam@200
|
383 m_r = new float[n/2];
|
cannam@200
|
384 m_fr = new float[n/2];
|
cannam@204
|
385 m_t = new float[n/2];
|
cannam@200
|
386
|
cannam@200
|
387 for (int i = 0; i < n/2; ++i) {
|
cannam@200
|
388 m_r[i] = 0.f;
|
cannam@200
|
389 m_fr[i] = 0.f;
|
cannam@207
|
390 m_t[i] = lag2tempo(i);
|
cannam@200
|
391 }
|
cannam@200
|
392
|
cannam@200
|
393 for (int i = 0; i < n/2; ++i) {
|
cannam@200
|
394
|
cannam@200
|
395 for (int j = i; j < n-1; ++j) {
|
cannam@200
|
396 m_r[i] += m_df[j] * m_df[j - i];
|
cannam@200
|
397 }
|
cannam@200
|
398
|
cannam@200
|
399 m_r[i] /= n - i - 1;
|
cannam@200
|
400 }
|
cannam@200
|
401
|
cannam@215
|
402 float related[] = { 0.5, 2, 3, 4 };
|
cannam@208
|
403
|
cannam@209
|
404 for (int i = 1; i < n/2-1; ++i) {
|
cannam@204
|
405
|
cannam@209
|
406 float weight = 1.f - fabsf(128.f - lag2tempo(i)) * 0.005;
|
cannam@209
|
407 if (weight < 0.f) weight = 0.f;
|
cannam@215
|
408 weight = weight * weight * weight;
|
cannam@209
|
409
|
cannam@209
|
410 m_fr[i] = m_r[i];
|
cannam@204
|
411
|
cannam@200
|
412 int div = 1;
|
cannam@200
|
413
|
cannam@215
|
414 for (int j = 0; j < int(sizeof(related)/sizeof(related[0])); ++j) {
|
cannam@204
|
415
|
cannam@215
|
416 int k0 = int(i * related[j] + 0.5);
|
cannam@209
|
417
|
cannam@215
|
418 if (k0 >= 0 && k0 < int(n/2)) {
|
cannam@204
|
419
|
cannam@207
|
420 int kmax = 0, kmin = 0;
|
cannam@207
|
421 float kvmax = 0, kvmin = 0;
|
cannam@209
|
422 bool have = false;
|
cannam@204
|
423
|
cannam@209
|
424 for (int k = k0 - 1; k <= k0 + 1; ++k) {
|
cannam@204
|
425
|
cannam@209
|
426 if (k < 0 || k >= n/2) continue;
|
cannam@209
|
427
|
cannam@215
|
428 if (!have || (m_r[k] > kvmax)) { kmax = k; kvmax = m_r[k]; }
|
cannam@215
|
429 if (!have || (m_r[k] < kvmin)) { kmin = k; kvmin = m_r[k]; }
|
cannam@209
|
430
|
cannam@209
|
431 have = true;
|
cannam@204
|
432 }
|
cannam@209
|
433
|
cannam@215
|
434 m_fr[i] += m_r[kmax] / 5;
|
cannam@209
|
435
|
cannam@209
|
436 if ((kmax == 0 || m_r[kmax] > m_r[kmax-1]) &&
|
cannam@209
|
437 (kmax == n/2-1 || m_r[kmax] > m_r[kmax+1]) &&
|
cannam@207
|
438 kvmax > kvmin * 1.05) {
|
cannam@209
|
439
|
cannam@207
|
440 m_t[i] = m_t[i] + lag2tempo(kmax) * related[j];
|
cannam@207
|
441 ++div;
|
cannam@207
|
442 }
|
cannam@204
|
443 }
|
cannam@204
|
444 }
|
cannam@209
|
445
|
cannam@204
|
446 m_t[i] /= div;
|
cannam@204
|
447
|
cannam@215
|
448 m_fr[i] += m_fr[i] * (weight / 3);
|
cannam@207
|
449 }
|
cannam@200
|
450 }
|
cannam@200
|
451
|
cannam@198
|
452 FixedTempoEstimator::FeatureSet
|
cannam@243
|
453 FixedTempoEstimator::D::assembleFeatures()
|
cannam@198
|
454 {
|
cannam@198
|
455 FeatureSet fs;
|
cannam@200
|
456 if (!m_r) return fs; // No results
|
cannam@200
|
457
|
cannam@198
|
458 Feature feature;
|
cannam@198
|
459 feature.hasTimestamp = true;
|
cannam@198
|
460 feature.hasDuration = false;
|
cannam@198
|
461 feature.label = "";
|
cannam@198
|
462 feature.values.clear();
|
cannam@198
|
463 feature.values.push_back(0.f);
|
cannam@198
|
464
|
cannam@200
|
465 char buffer[40];
|
cannam@198
|
466
|
cannam@198
|
467 int n = m_n;
|
cannam@198
|
468
|
cannam@198
|
469 for (int i = 0; i < n; ++i) {
|
cannam@208
|
470 feature.timestamp = m_start +
|
cannam@208
|
471 RealTime::frame2RealTime(i * m_stepSize, m_inputSampleRate);
|
cannam@200
|
472 feature.values[0] = m_df[i];
|
cannam@198
|
473 feature.label = "";
|
cannam@200
|
474 fs[DFOutput].push_back(feature);
|
cannam@198
|
475 }
|
cannam@198
|
476
|
cannam@199
|
477 for (int i = 1; i < n/2; ++i) {
|
cannam@208
|
478 feature.timestamp = m_start +
|
cannam@208
|
479 RealTime::frame2RealTime(i * m_stepSize, m_inputSampleRate);
|
cannam@200
|
480 feature.values[0] = m_r[i];
|
cannam@199
|
481 sprintf(buffer, "%.1f bpm", lag2tempo(i));
|
cannam@200
|
482 if (i == n/2-1) feature.label = "";
|
cannam@200
|
483 else feature.label = buffer;
|
cannam@200
|
484 fs[ACFOutput].push_back(feature);
|
cannam@198
|
485 }
|
cannam@198
|
486
|
cannam@243
|
487 float t0 = m_minbpm; // our minimum detected tempo
|
cannam@243
|
488 float t1 = m_maxbpm; // our maximum detected tempo
|
cannam@216
|
489
|
cannam@207
|
490 int p0 = tempo2lag(t1);
|
cannam@207
|
491 int p1 = tempo2lag(t0);
|
cannam@198
|
492
|
cannam@200
|
493 std::map<float, int> candidates;
|
cannam@198
|
494
|
cannam@200
|
495 for (int i = p0; i <= p1 && i < n/2-1; ++i) {
|
cannam@198
|
496
|
cannam@209
|
497 if (m_fr[i] > m_fr[i-1] &&
|
cannam@209
|
498 m_fr[i] > m_fr[i+1]) {
|
cannam@209
|
499 candidates[m_fr[i]] = i;
|
cannam@209
|
500 }
|
cannam@198
|
501
|
cannam@208
|
502 feature.timestamp = m_start +
|
cannam@208
|
503 RealTime::frame2RealTime(i * m_stepSize, m_inputSampleRate);
|
cannam@200
|
504 feature.values[0] = m_fr[i];
|
cannam@199
|
505 sprintf(buffer, "%.1f bpm", lag2tempo(i));
|
cannam@200
|
506 if (i == p1 || i == n/2-2) feature.label = "";
|
cannam@200
|
507 else feature.label = buffer;
|
cannam@200
|
508 fs[FilteredACFOutput].push_back(feature);
|
cannam@198
|
509 }
|
cannam@198
|
510
|
cannam@200
|
511 if (candidates.empty()) {
|
cannam@207
|
512 cerr << "No tempo candidates!" << endl;
|
cannam@200
|
513 return fs;
|
cannam@200
|
514 }
|
cannam@198
|
515
|
cannam@198
|
516 feature.hasTimestamp = true;
|
cannam@198
|
517 feature.timestamp = m_start;
|
cannam@198
|
518
|
cannam@198
|
519 feature.hasDuration = true;
|
cannam@198
|
520 feature.duration = m_lasttime - m_start;
|
cannam@198
|
521
|
cannam@200
|
522 std::map<float, int>::const_iterator ci = candidates.end();
|
cannam@200
|
523 --ci;
|
cannam@200
|
524 int maxpi = ci->second;
|
cannam@198
|
525
|
cannam@204
|
526 if (m_t[maxpi] > 0) {
|
cannam@207
|
527 cerr << "*** Using adjusted tempo " << m_t[maxpi] << " instead of lag tempo " << lag2tempo(maxpi) << endl;
|
cannam@204
|
528 feature.values[0] = m_t[maxpi];
|
cannam@204
|
529 } else {
|
cannam@204
|
530 // shouldn't happen -- it would imply that this high value was not a peak!
|
cannam@204
|
531 feature.values[0] = lag2tempo(maxpi);
|
cannam@207
|
532 cerr << "WARNING: No stored tempo for index " << maxpi << endl;
|
cannam@204
|
533 }
|
cannam@204
|
534
|
cannam@204
|
535 sprintf(buffer, "%.1f bpm", feature.values[0]);
|
cannam@199
|
536 feature.label = buffer;
|
cannam@199
|
537
|
cannam@200
|
538 fs[TempoOutput].push_back(feature);
|
cannam@198
|
539
|
cannam@200
|
540 feature.values.clear();
|
cannam@200
|
541 feature.label = "";
|
cannam@200
|
542
|
cannam@200
|
543 while (feature.values.size() < 8) {
|
cannam@207
|
544 if (m_t[ci->second] > 0) {
|
cannam@207
|
545 feature.values.push_back(m_t[ci->second]);
|
cannam@207
|
546 } else {
|
cannam@207
|
547 feature.values.push_back(lag2tempo(ci->second));
|
cannam@207
|
548 }
|
cannam@200
|
549 if (ci == candidates.begin()) break;
|
cannam@200
|
550 --ci;
|
cannam@200
|
551 }
|
cannam@200
|
552
|
cannam@200
|
553 fs[CandidatesOutput].push_back(feature);
|
cannam@200
|
554
|
cannam@198
|
555 return fs;
|
cannam@198
|
556 }
|
cannam@243
|
557
|
cannam@243
|
558
|
cannam@243
|
559
|
cannam@243
|
560 FixedTempoEstimator::FixedTempoEstimator(float inputSampleRate) :
|
cannam@243
|
561 Plugin(inputSampleRate),
|
cannam@243
|
562 m_d(new D(inputSampleRate))
|
cannam@243
|
563 {
|
cannam@243
|
564 }
|
cannam@243
|
565
|
cannam@243
|
566 FixedTempoEstimator::~FixedTempoEstimator()
|
cannam@243
|
567 {
|
cannam@243
|
568 }
|
cannam@243
|
569
|
cannam@243
|
570 string
|
cannam@243
|
571 FixedTempoEstimator::getIdentifier() const
|
cannam@243
|
572 {
|
cannam@243
|
573 return "fixedtempo";
|
cannam@243
|
574 }
|
cannam@243
|
575
|
cannam@243
|
576 string
|
cannam@243
|
577 FixedTempoEstimator::getName() const
|
cannam@243
|
578 {
|
cannam@243
|
579 return "Simple Fixed Tempo Estimator";
|
cannam@243
|
580 }
|
cannam@243
|
581
|
cannam@243
|
582 string
|
cannam@243
|
583 FixedTempoEstimator::getDescription() const
|
cannam@243
|
584 {
|
cannam@243
|
585 return "Study a short section of audio and estimate its tempo, assuming the tempo is constant";
|
cannam@243
|
586 }
|
cannam@243
|
587
|
cannam@243
|
588 string
|
cannam@243
|
589 FixedTempoEstimator::getMaker() const
|
cannam@243
|
590 {
|
cannam@243
|
591 return "Vamp SDK Example Plugins";
|
cannam@243
|
592 }
|
cannam@243
|
593
|
cannam@243
|
594 int
|
cannam@243
|
595 FixedTempoEstimator::getPluginVersion() const
|
cannam@243
|
596 {
|
cannam@243
|
597 return 1;
|
cannam@243
|
598 }
|
cannam@243
|
599
|
cannam@243
|
600 string
|
cannam@243
|
601 FixedTempoEstimator::getCopyright() const
|
cannam@243
|
602 {
|
cannam@243
|
603 return "Code copyright 2008 Queen Mary, University of London. Freely redistributable (BSD license)";
|
cannam@243
|
604 }
|
cannam@243
|
605
|
cannam@243
|
606 size_t
|
cannam@243
|
607 FixedTempoEstimator::getPreferredStepSize() const
|
cannam@243
|
608 {
|
cannam@243
|
609 return m_d->getPreferredStepSize();
|
cannam@243
|
610 }
|
cannam@243
|
611
|
cannam@243
|
612 size_t
|
cannam@243
|
613 FixedTempoEstimator::getPreferredBlockSize() const
|
cannam@243
|
614 {
|
cannam@243
|
615 return m_d->getPreferredBlockSize();
|
cannam@243
|
616 }
|
cannam@243
|
617
|
cannam@243
|
618 bool
|
cannam@243
|
619 FixedTempoEstimator::initialise(size_t channels, size_t stepSize, size_t blockSize)
|
cannam@243
|
620 {
|
cannam@243
|
621 if (channels < getMinChannelCount() ||
|
cannam@243
|
622 channels > getMaxChannelCount()) return false;
|
cannam@243
|
623
|
cannam@243
|
624 return m_d->initialise(channels, stepSize, blockSize);
|
cannam@243
|
625 }
|
cannam@243
|
626
|
cannam@243
|
627 void
|
cannam@243
|
628 FixedTempoEstimator::reset()
|
cannam@243
|
629 {
|
cannam@243
|
630 return m_d->reset();
|
cannam@243
|
631 }
|
cannam@243
|
632
|
cannam@243
|
633 FixedTempoEstimator::ParameterList
|
cannam@243
|
634 FixedTempoEstimator::getParameterDescriptors() const
|
cannam@243
|
635 {
|
cannam@243
|
636 return m_d->getParameterDescriptors();
|
cannam@243
|
637 }
|
cannam@243
|
638
|
cannam@243
|
639 float
|
cannam@243
|
640 FixedTempoEstimator::getParameter(std::string id) const
|
cannam@243
|
641 {
|
cannam@243
|
642 return m_d->getParameter(id);
|
cannam@243
|
643 }
|
cannam@243
|
644
|
cannam@243
|
645 void
|
cannam@243
|
646 FixedTempoEstimator::setParameter(std::string id, float value)
|
cannam@243
|
647 {
|
cannam@243
|
648 m_d->setParameter(id, value);
|
cannam@243
|
649 }
|
cannam@243
|
650
|
cannam@243
|
651 FixedTempoEstimator::OutputList
|
cannam@243
|
652 FixedTempoEstimator::getOutputDescriptors() const
|
cannam@243
|
653 {
|
cannam@243
|
654 return m_d->getOutputDescriptors();
|
cannam@243
|
655 }
|
cannam@243
|
656
|
cannam@243
|
657 FixedTempoEstimator::FeatureSet
|
cannam@243
|
658 FixedTempoEstimator::process(const float *const *inputBuffers, RealTime ts)
|
cannam@243
|
659 {
|
cannam@243
|
660 return m_d->process(inputBuffers, ts);
|
cannam@243
|
661 }
|
cannam@243
|
662
|
cannam@243
|
663 FixedTempoEstimator::FeatureSet
|
cannam@243
|
664 FixedTempoEstimator::getRemainingFeatures()
|
cannam@243
|
665 {
|
cannam@243
|
666 return m_d->getRemainingFeatures();
|
cannam@243
|
667 }
|