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