Mercurial > hg > qm-vamp-plugins
comparison plugins/BeatTrack.cpp @ 27:3256bfa04ed8
* split out tempo/beat/onset plugin into tempo/beat and onset
author | Chris Cannam <c.cannam@qmul.ac.uk> |
---|---|
date | Mon, 21 May 2007 13:09:12 +0000 |
parents | |
children | b300de89ea30 |
comparison
equal
deleted
inserted
replaced
26:166cc6d39390 | 27:3256bfa04ed8 |
---|---|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | |
2 | |
3 /* | |
4 QM Vamp Plugin Set | |
5 | |
6 Centre for Digital Music, Queen Mary, University of London. | |
7 All rights reserved. | |
8 */ | |
9 | |
10 #include "BeatTrack.h" | |
11 | |
12 #include <dsp/onsets/DetectionFunction.h> | |
13 #include <dsp/onsets/PeakPicking.h> | |
14 #include <dsp/tempotracking/TempoTrack.h> | |
15 | |
16 using std::string; | |
17 using std::vector; | |
18 using std::cerr; | |
19 using std::endl; | |
20 | |
21 float BeatTracker::m_stepSecs = 0.01161; | |
22 | |
23 class BeatTrackerData | |
24 { | |
25 public: | |
26 BeatTrackerData(const DFConfig &config) : dfConfig(config) { | |
27 df = new DetectionFunction(config); | |
28 } | |
29 ~BeatTrackerData() { | |
30 delete df; | |
31 } | |
32 void reset() { | |
33 delete df; | |
34 df = new DetectionFunction(dfConfig); | |
35 dfOutput.clear(); | |
36 } | |
37 | |
38 DFConfig dfConfig; | |
39 DetectionFunction *df; | |
40 vector<double> dfOutput; | |
41 }; | |
42 | |
43 | |
44 BeatTracker::BeatTracker(float inputSampleRate) : | |
45 Vamp::Plugin(inputSampleRate), | |
46 m_d(0), | |
47 m_dfType(DF_COMPLEXSD) | |
48 { | |
49 } | |
50 | |
51 BeatTracker::~BeatTracker() | |
52 { | |
53 delete m_d; | |
54 } | |
55 | |
56 string | |
57 BeatTracker::getIdentifier() const | |
58 { | |
59 return "qm-tempotracker"; | |
60 } | |
61 | |
62 string | |
63 BeatTracker::getName() const | |
64 { | |
65 return "Tempo and Beat Tracker"; | |
66 } | |
67 | |
68 string | |
69 BeatTracker::getDescription() const | |
70 { | |
71 return "Estimate beat locations and tempo"; | |
72 } | |
73 | |
74 string | |
75 BeatTracker::getMaker() const | |
76 { | |
77 return "Christian Landone and Matthew Davies, Queen Mary, University of London"; | |
78 } | |
79 | |
80 int | |
81 BeatTracker::getPluginVersion() const | |
82 { | |
83 return 3; | |
84 } | |
85 | |
86 string | |
87 BeatTracker::getCopyright() const | |
88 { | |
89 return "Copyright (c) 2006-2007 - All Rights Reserved"; | |
90 } | |
91 | |
92 BeatTracker::ParameterList | |
93 BeatTracker::getParameterDescriptors() const | |
94 { | |
95 ParameterList list; | |
96 | |
97 ParameterDescriptor desc; | |
98 desc.identifier = "dftype"; | |
99 desc.name = "Onset Detection Function Type"; | |
100 desc.description = "Method used to calculate the onset detection function"; | |
101 desc.minValue = 0; | |
102 desc.maxValue = 3; | |
103 desc.defaultValue = 3; | |
104 desc.isQuantized = true; | |
105 desc.quantizeStep = 1; | |
106 desc.valueNames.push_back("High-Frequency Content"); | |
107 desc.valueNames.push_back("Spectral Difference"); | |
108 desc.valueNames.push_back("Phase Deviation"); | |
109 desc.valueNames.push_back("Complex Domain"); | |
110 desc.valueNames.push_back("Broadband Energy Rise"); | |
111 list.push_back(desc); | |
112 | |
113 return list; | |
114 } | |
115 | |
116 float | |
117 BeatTracker::getParameter(std::string name) const | |
118 { | |
119 if (name == "dftype") { | |
120 switch (m_dfType) { | |
121 case DF_HFC: return 0; | |
122 case DF_SPECDIFF: return 1; | |
123 case DF_PHASEDEV: return 2; | |
124 default: case DF_COMPLEXSD: return 3; | |
125 case DF_BROADBAND: return 4; | |
126 } | |
127 } | |
128 return 0.0; | |
129 } | |
130 | |
131 void | |
132 BeatTracker::setParameter(std::string name, float value) | |
133 { | |
134 if (name == "dftype") { | |
135 switch (lrintf(value)) { | |
136 case 0: m_dfType = DF_HFC; break; | |
137 case 1: m_dfType = DF_SPECDIFF; break; | |
138 case 2: m_dfType = DF_PHASEDEV; break; | |
139 default: case 3: m_dfType = DF_COMPLEXSD; break; | |
140 case 4: m_dfType = DF_BROADBAND; break; | |
141 } | |
142 } | |
143 } | |
144 | |
145 bool | |
146 BeatTracker::initialise(size_t channels, size_t stepSize, size_t blockSize) | |
147 { | |
148 if (m_d) { | |
149 delete m_d; | |
150 m_d = 0; | |
151 } | |
152 | |
153 if (channels < getMinChannelCount() || | |
154 channels > getMaxChannelCount()) { | |
155 std::cerr << "BeatTracker::initialise: Unsupported channel count: " | |
156 << channels << std::endl; | |
157 return false; | |
158 } | |
159 | |
160 if (blockSize != getPreferredStepSize() * 2) { | |
161 std::cerr << "BeatTracker::initialise: Unsupported block size for this sample rate: " | |
162 << blockSize << " (wanted " << (getPreferredStepSize() * 2) << ")" << std::endl; | |
163 return false; | |
164 } | |
165 | |
166 if (stepSize != getPreferredStepSize()) { | |
167 std::cerr << "BeatTracker::initialise: Unsupported step size for this sample rate: " | |
168 << stepSize << " (wanted " << (getPreferredStepSize()) << ")" << std::endl; | |
169 return false; | |
170 } | |
171 | |
172 DFConfig dfConfig; | |
173 dfConfig.DFType = m_dfType; | |
174 dfConfig.stepSecs = float(stepSize) / m_inputSampleRate; | |
175 dfConfig.stepSize = stepSize; | |
176 dfConfig.frameLength = blockSize; | |
177 dfConfig.dbRise = 3; | |
178 | |
179 m_d = new BeatTrackerData(dfConfig); | |
180 return true; | |
181 } | |
182 | |
183 void | |
184 BeatTracker::reset() | |
185 { | |
186 if (m_d) m_d->reset(); | |
187 } | |
188 | |
189 size_t | |
190 BeatTracker::getPreferredStepSize() const | |
191 { | |
192 size_t step = size_t(m_inputSampleRate * m_stepSecs + 0.0001); | |
193 // std::cerr << "BeatTracker::getPreferredStepSize: input sample rate is " << m_inputSampleRate << ", step size is " << step << std::endl; | |
194 return step; | |
195 } | |
196 | |
197 size_t | |
198 BeatTracker::getPreferredBlockSize() const | |
199 { | |
200 return getPreferredStepSize() * 2; | |
201 } | |
202 | |
203 BeatTracker::OutputList | |
204 BeatTracker::getOutputDescriptors() const | |
205 { | |
206 OutputList list; | |
207 | |
208 OutputDescriptor beat; | |
209 beat.identifier = "beats"; | |
210 beat.name = "Beats"; | |
211 beat.description = "Estimated metrical beat locations"; | |
212 beat.unit = ""; | |
213 beat.hasFixedBinCount = true; | |
214 beat.binCount = 0; | |
215 beat.sampleType = OutputDescriptor::VariableSampleRate; | |
216 beat.sampleRate = 1.0 / m_stepSecs; | |
217 | |
218 OutputDescriptor df; | |
219 df.identifier = "detection_fn"; | |
220 df.name = "Onset Detection Function"; | |
221 df.description = "Probability function of note onset likelihood"; | |
222 df.unit = ""; | |
223 df.hasFixedBinCount = true; | |
224 df.binCount = 1; | |
225 df.hasKnownExtents = false; | |
226 df.isQuantized = false; | |
227 df.sampleType = OutputDescriptor::OneSamplePerStep; | |
228 | |
229 OutputDescriptor tempo; | |
230 tempo.identifier = "tempo"; | |
231 tempo.name = "Tempo"; | |
232 tempo.description = "Locked tempo estimates"; | |
233 tempo.unit = "bpm"; | |
234 tempo.hasFixedBinCount = true; | |
235 tempo.binCount = 1; | |
236 tempo.sampleType = OutputDescriptor::VariableSampleRate; | |
237 tempo.sampleRate = 1.0 / m_stepSecs; | |
238 | |
239 list.push_back(beat); | |
240 list.push_back(df); | |
241 list.push_back(tempo); | |
242 | |
243 return list; | |
244 } | |
245 | |
246 BeatTracker::FeatureSet | |
247 BeatTracker::process(const float *const *inputBuffers, | |
248 Vamp::RealTime /* timestamp */) | |
249 { | |
250 if (!m_d) { | |
251 cerr << "ERROR: BeatTracker::process: " | |
252 << "BeatTracker has not been initialised" | |
253 << endl; | |
254 return FeatureSet(); | |
255 } | |
256 | |
257 size_t len = m_d->dfConfig.frameLength / 2; | |
258 | |
259 double *magnitudes = new double[len]; | |
260 double *phases = new double[len]; | |
261 | |
262 // We only support a single input channel | |
263 | |
264 for (size_t i = 0; i < len; ++i) { | |
265 | |
266 magnitudes[i] = sqrt(inputBuffers[0][i*2 ] * inputBuffers[0][i*2 ] + | |
267 inputBuffers[0][i*2+1] * inputBuffers[0][i*2+1]); | |
268 | |
269 phases[i] = atan2(-inputBuffers[0][i*2+1], inputBuffers[0][i*2]); | |
270 } | |
271 | |
272 double output = m_d->df->process(magnitudes, phases); | |
273 | |
274 delete[] magnitudes; | |
275 delete[] phases; | |
276 | |
277 m_d->dfOutput.push_back(output); | |
278 | |
279 FeatureSet returnFeatures; | |
280 | |
281 Feature feature; | |
282 feature.hasTimestamp = false; | |
283 feature.values.push_back(output); | |
284 | |
285 returnFeatures[1].push_back(feature); // detection function is output 1 | |
286 return returnFeatures; | |
287 } | |
288 | |
289 BeatTracker::FeatureSet | |
290 BeatTracker::getRemainingFeatures() | |
291 { | |
292 if (!m_d) { | |
293 cerr << "ERROR: BeatTracker::getRemainingFeatures: " | |
294 << "BeatTracker has not been initialised" | |
295 << endl; | |
296 return FeatureSet(); | |
297 } | |
298 | |
299 double aCoeffs[] = { 1.0000, -0.5949, 0.2348 }; | |
300 double bCoeffs[] = { 0.1600, 0.3200, 0.1600 }; | |
301 | |
302 TTParams ttParams; | |
303 ttParams.winLength = 512; | |
304 ttParams.lagLength = 128; | |
305 ttParams.LPOrd = 2; | |
306 ttParams.LPACoeffs = aCoeffs; | |
307 ttParams.LPBCoeffs = bCoeffs; | |
308 ttParams.alpha = 9; | |
309 ttParams.WinT.post = 8; | |
310 ttParams.WinT.pre = 7; | |
311 | |
312 TempoTrack tempoTracker(ttParams); | |
313 | |
314 vector<double> tempos; | |
315 vector<int> beats = tempoTracker.process(m_d->dfOutput, &tempos); | |
316 | |
317 FeatureSet returnFeatures; | |
318 | |
319 char label[100]; | |
320 | |
321 for (size_t i = 0; i < beats.size(); ++i) { | |
322 | |
323 size_t frame = beats[i] * m_d->dfConfig.stepSize; | |
324 | |
325 Feature feature; | |
326 feature.hasTimestamp = true; | |
327 feature.timestamp = Vamp::RealTime::frame2RealTime | |
328 (frame, lrintf(m_inputSampleRate)); | |
329 | |
330 float bpm = 0.0; | |
331 int frameIncrement = 0; | |
332 | |
333 if (i < beats.size() - 1) { | |
334 | |
335 frameIncrement = (beats[i+1] - beats[i]) * m_d->dfConfig.stepSize; | |
336 | |
337 // one beat is frameIncrement frames, so there are | |
338 // samplerate/frameIncrement bps, so | |
339 // 60*samplerate/frameIncrement bpm | |
340 | |
341 if (frameIncrement > 0) { | |
342 bpm = (60.0 * m_inputSampleRate) / frameIncrement; | |
343 bpm = int(bpm * 100.0 + 0.5) / 100.0; | |
344 sprintf(label, "%.2f bpm", bpm); | |
345 feature.label = label; | |
346 } | |
347 } | |
348 | |
349 returnFeatures[0].push_back(feature); // beats are output 0 | |
350 } | |
351 | |
352 double prevTempo = 0.0; | |
353 | |
354 for (size_t i = 0; i < tempos.size(); ++i) { | |
355 | |
356 size_t frame = i * m_d->dfConfig.stepSize * ttParams.lagLength; | |
357 | |
358 // std::cerr << "unit " << i << ", step size " << m_d->dfConfig.stepSize << ", hop " << ttParams.lagLength << ", frame = " << frame << std::endl; | |
359 | |
360 if (tempos[i] > 1 && int(tempos[i] * 100) != int(prevTempo * 100)) { | |
361 Feature feature; | |
362 feature.hasTimestamp = true; | |
363 feature.timestamp = Vamp::RealTime::frame2RealTime | |
364 (frame, lrintf(m_inputSampleRate)); | |
365 feature.values.push_back(tempos[i]); | |
366 sprintf(label, "%.2f bpm", tempos[i]); | |
367 feature.label = label; | |
368 returnFeatures[2].push_back(feature); // tempo is output 2 | |
369 } | |
370 } | |
371 | |
372 return returnFeatures; | |
373 } | |
374 |