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