Chris@43
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
c@0
|
2
|
Chris@43
|
3 /*
|
Chris@43
|
4 Vamp Tempogram Plugin
|
Chris@43
|
5 Carl Bussey, Centre for Digital Music, Queen Mary University of London
|
Chris@43
|
6 Copyright 2014 Queen Mary University of London.
|
Chris@43
|
7
|
Chris@43
|
8 This program is free software; you can redistribute it and/or
|
Chris@43
|
9 modify it under the terms of the GNU General Public License as
|
Chris@43
|
10 published by the Free Software Foundation; either version 2 of the
|
Chris@43
|
11 License, or (at your option) any later version. See the file
|
Chris@43
|
12 COPYING included with this distribution for more information.
|
Chris@43
|
13 */
|
c@0
|
14
|
c@14
|
15 #include "TempogramPlugin.h"
|
c@25
|
16
|
Chris@60
|
17 #include <algorithm>
|
Chris@60
|
18
|
c@0
|
19 using Vamp::FFT;
|
c@7
|
20 using Vamp::RealTime;
|
c@0
|
21 using namespace std;
|
c@0
|
22
|
c@14
|
23 TempogramPlugin::TempogramPlugin(float inputSampleRate) :
|
c@0
|
24 Plugin(inputSampleRate),
|
c@18
|
25 m_inputBlockSize(0), //host parameter
|
c@18
|
26 m_inputStepSize(0), //host parameter
|
c@29
|
27 m_noveltyCurveMinDB(-74), //parameter
|
c@29
|
28 m_noveltyCurveMinV(0), //set in initialise()
|
c@18
|
29 m_noveltyCurveCompressionConstant(1000), //parameter
|
c@18
|
30 m_tempogramLog2WindowLength(10), //parameter
|
c@29
|
31 m_tempogramWindowLength(0), //set in initialise()
|
c@18
|
32 m_tempogramLog2FftLength(m_tempogramLog2WindowLength), //parameter
|
c@29
|
33 m_tempogramFftLength(0), //set in initialise()
|
c@18
|
34 m_tempogramLog2HopSize(6), //parameter
|
c@29
|
35 m_tempogramHopSize(0), //set in initialise()
|
c@18
|
36 m_tempogramMinBPM(30), //parameter
|
c@18
|
37 m_tempogramMaxBPM(480), //parameter
|
c@18
|
38 m_tempogramMinBin(0), //set in initialise()
|
c@18
|
39 m_tempogramMaxBin(0), //set in initialise()
|
c@29
|
40 m_tempogramMinLag(0), //set in initialise()
|
c@29
|
41 m_tempogramMaxLag(0), //set in initialise()
|
c@18
|
42 m_cyclicTempogramMinBPM(30), //reset in initialise()
|
c@18
|
43 m_cyclicTempogramNumberOfOctaves(0), //set in initialise()
|
c@49
|
44 m_cyclicTempogramOctaveDivider(30), //parameter
|
c@50
|
45 m_cyclicTempogramReferenceBPM(60) //parameter
|
c@0
|
46
|
c@0
|
47 // Also be sure to set your plugin parameters (presumably stored
|
c@0
|
48 // in member variables) to their default values here -- the host
|
c@0
|
49 // will not do that for you
|
c@0
|
50 {
|
c@0
|
51 }
|
c@0
|
52
|
c@14
|
53 TempogramPlugin::~TempogramPlugin()
|
c@0
|
54 {
|
c@0
|
55 //delete stuff
|
c@0
|
56 }
|
c@0
|
57
|
c@0
|
58 string
|
c@14
|
59 TempogramPlugin::getIdentifier() const
|
c@0
|
60 {
|
c@0
|
61 return "tempogram";
|
c@0
|
62 }
|
c@0
|
63
|
c@0
|
64 string
|
c@14
|
65 TempogramPlugin::getName() const
|
c@0
|
66 {
|
c@0
|
67 return "Tempogram";
|
c@0
|
68 }
|
c@0
|
69
|
c@0
|
70 string
|
c@14
|
71 TempogramPlugin::getDescription() const
|
c@0
|
72 {
|
Chris@56
|
73 return "Tempogram and Cyclic Tempogram as described by Peter Grosche and Meinard Müller";
|
c@0
|
74 }
|
c@0
|
75
|
c@0
|
76 string
|
c@14
|
77 TempogramPlugin::getMaker() const
|
c@0
|
78 {
|
c@0
|
79 return "Carl Bussey";
|
c@0
|
80 }
|
c@0
|
81
|
c@0
|
82 int
|
c@14
|
83 TempogramPlugin::getPluginVersion() const
|
c@0
|
84 {
|
c@0
|
85 return 1;
|
c@0
|
86 }
|
c@0
|
87
|
c@0
|
88 string
|
c@14
|
89 TempogramPlugin::getCopyright() const
|
c@0
|
90 {
|
Chris@40
|
91 return "Copyright 2014 Queen Mary University of London. GPL licence.";
|
c@0
|
92 }
|
c@0
|
93
|
c@14
|
94 TempogramPlugin::InputDomain
|
c@14
|
95 TempogramPlugin::getInputDomain() const
|
c@0
|
96 {
|
c@0
|
97 return FrequencyDomain;
|
c@0
|
98 }
|
c@0
|
99
|
c@0
|
100 size_t
|
c@14
|
101 TempogramPlugin::getPreferredBlockSize() const
|
c@0
|
102 {
|
c@9
|
103 return 2048; // 0 means "I can handle any block size"
|
c@0
|
104 }
|
c@0
|
105
|
c@0
|
106 size_t
|
c@14
|
107 TempogramPlugin::getPreferredStepSize() const
|
c@0
|
108 {
|
c@9
|
109 return 1024; // 0 means "anything sensible"; in practice this
|
c@0
|
110 // means the same as the block size for TimeDomain
|
c@0
|
111 // plugins, or half of it for FrequencyDomain plugins
|
c@0
|
112 }
|
c@0
|
113
|
c@0
|
114 size_t
|
c@14
|
115 TempogramPlugin::getMinChannelCount() const
|
c@0
|
116 {
|
c@0
|
117 return 1;
|
c@0
|
118 }
|
c@0
|
119
|
c@0
|
120 size_t
|
c@14
|
121 TempogramPlugin::getMaxChannelCount() const
|
c@0
|
122 {
|
c@0
|
123 return 1;
|
c@0
|
124 }
|
c@0
|
125
|
c@14
|
126 TempogramPlugin::ParameterList
|
c@14
|
127 TempogramPlugin::getParameterDescriptors() const
|
c@0
|
128 {
|
c@0
|
129 ParameterList list;
|
c@0
|
130
|
c@0
|
131 // If the plugin has no adjustable parameters, return an empty
|
c@0
|
132 // list here (and there's no need to provide implementations of
|
c@0
|
133 // getParameter and setParameter in that case either).
|
c@0
|
134
|
c@0
|
135 // Note that it is your responsibility to make sure the parameters
|
c@0
|
136 // start off having their default values (e.g. in the constructor
|
c@0
|
137 // above). The host needs to know the default value so it can do
|
c@0
|
138 // things like provide a "reset to default" function, but it will
|
c@0
|
139 // not explicitly set your parameters to their defaults for you if
|
c@0
|
140 // they have not changed in the mean time.
|
c@0
|
141
|
c@14
|
142 ParameterDescriptor d1;
|
c@14
|
143 d1.identifier = "C";
|
c@15
|
144 d1.name = "Novelty Curve Spectrogram Compression Constant";
|
c@14
|
145 d1.description = "Spectrogram compression constant, C, used when retrieving the novelty curve from the audio.";
|
c@14
|
146 d1.unit = "";
|
c@14
|
147 d1.minValue = 2;
|
c@14
|
148 d1.maxValue = 10000;
|
c@14
|
149 d1.defaultValue = 1000;
|
c@14
|
150 d1.isQuantized = false;
|
c@14
|
151 list.push_back(d1);
|
c@29
|
152
|
c@29
|
153 ParameterDescriptor d2;
|
c@29
|
154 d2.identifier = "minDB";
|
c@29
|
155 d2.name = "Novelty Curve Minimum DB";
|
c@29
|
156 d2.description = "Spectrogram minimum DB used when removing unwanted peaks in the Spectrogram when retrieving the novelty curve from the audio.";
|
c@29
|
157 d2.unit = "";
|
c@29
|
158 d2.minValue = -100;
|
c@29
|
159 d2.maxValue = -50;
|
c@29
|
160 d2.defaultValue = -74;
|
c@29
|
161 d2.isQuantized = false;
|
c@29
|
162 list.push_back(d2);
|
c@9
|
163
|
c@14
|
164 ParameterDescriptor d3;
|
c@29
|
165 d3.identifier = "log2TN";
|
c@29
|
166 d3.name = "Tempogram Window Length";
|
c@29
|
167 d3.description = "FFT window length when analysing the novelty curve and extracting the tempogram time-frequency function.";
|
c@14
|
168 d3.unit = "";
|
c@29
|
169 d3.minValue = 7;
|
c@14
|
170 d3.maxValue = 12;
|
c@29
|
171 d3.defaultValue = 10;
|
c@14
|
172 d3.isQuantized = true;
|
c@14
|
173 d3.quantizeStep = 1;
|
c@14
|
174 for (int i = d3.minValue; i <= d3.maxValue; i++){
|
c@14
|
175 d3.valueNames.push_back(floatToString(pow((float)2,(float)i)));
|
c@14
|
176 }
|
c@14
|
177 list.push_back(d3);
|
c@9
|
178
|
c@14
|
179 ParameterDescriptor d4;
|
c@29
|
180 d4.identifier = "log2HopSize";
|
c@29
|
181 d4.name = "Tempogram Hopsize";
|
c@29
|
182 d4.description = "FFT hopsize when analysing the novelty curve and extracting the tempogram time-frequency function.";
|
c@14
|
183 d4.unit = "";
|
c@14
|
184 d4.minValue = 6;
|
c@14
|
185 d4.maxValue = 12;
|
c@29
|
186 d4.defaultValue = 6;
|
c@14
|
187 d4.isQuantized = true;
|
c@14
|
188 d4.quantizeStep = 1;
|
c@14
|
189 for (int i = d4.minValue; i <= d4.maxValue; i++){
|
c@14
|
190 d4.valueNames.push_back(floatToString(pow((float)2,(float)i)));
|
c@14
|
191 }
|
c@14
|
192 list.push_back(d4);
|
c@14
|
193
|
c@14
|
194 ParameterDescriptor d5;
|
c@29
|
195 d5.identifier = "log2FftLength";
|
c@29
|
196 d5.name = "Tempogram FFT Length";
|
c@29
|
197 d5.description = "FFT length when analysing the novelty curve and extracting the tempogram time-frequency function. This parameter determines the amount of zero padding.";
|
c@14
|
198 d5.unit = "";
|
c@29
|
199 d5.minValue = 6;
|
c@29
|
200 d5.maxValue = 12;
|
Chris@42
|
201 d5.defaultValue = 10;
|
c@14
|
202 d5.isQuantized = true;
|
c@29
|
203 d5.quantizeStep = 1;
|
c@29
|
204 for (int i = d5.minValue; i <= d5.maxValue; i++){
|
Chris@41
|
205 d5.valueNames.push_back(floatToString(pow((float)2,(float)i)));
|
c@29
|
206 }
|
c@14
|
207 list.push_back(d5);
|
c@14
|
208
|
c@14
|
209 ParameterDescriptor d6;
|
c@29
|
210 d6.identifier = "minBPM";
|
c@29
|
211 d6.name = "(Cyclic) Tempogram Minimum BPM";
|
c@29
|
212 d6.description = "The minimum BPM of the tempogram output bins.";
|
c@14
|
213 d6.unit = "";
|
c@29
|
214 d6.minValue = 0;
|
c@14
|
215 d6.maxValue = 2000;
|
c@29
|
216 d6.defaultValue = 30;
|
c@14
|
217 d6.isQuantized = true;
|
c@14
|
218 d6.quantizeStep = 5;
|
c@14
|
219 list.push_back(d6);
|
c@18
|
220
|
c@18
|
221 ParameterDescriptor d7;
|
c@29
|
222 d7.identifier = "maxBPM";
|
c@29
|
223 d7.name = "(Cyclic) Tempogram Maximum BPM";
|
c@29
|
224 d7.description = "The maximum BPM of the tempogram output bins.";
|
c@18
|
225 d7.unit = "";
|
c@29
|
226 d7.minValue = 30;
|
c@29
|
227 d7.maxValue = 2000;
|
c@29
|
228 d7.defaultValue = 480;
|
c@18
|
229 d7.isQuantized = true;
|
c@29
|
230 d7.quantizeStep = 5;
|
c@18
|
231 list.push_back(d7);
|
c@29
|
232
|
c@29
|
233 ParameterDescriptor d8;
|
c@29
|
234 d8.identifier = "octDiv";
|
c@29
|
235 d8.name = "Cyclic Tempogram Octave Divider";
|
c@29
|
236 d8.description = "The number bins within each octave.";
|
c@29
|
237 d8.unit = "";
|
c@29
|
238 d8.minValue = 5;
|
c@29
|
239 d8.maxValue = 60;
|
c@29
|
240 d8.defaultValue = 30;
|
c@29
|
241 d8.isQuantized = true;
|
c@29
|
242 d8.quantizeStep = 1;
|
c@29
|
243 list.push_back(d8);
|
c@51
|
244
|
c@51
|
245 ParameterDescriptor d9;
|
Chris@53
|
246 d9.identifier = "refBPM";
|
Chris@53
|
247 d9.name = "Cyclic Tempogram Reference Tempo";
|
Chris@53
|
248 d9.description = "The reference tempo used when calculating the Cyclic Tempogram parameter \'s\'.";
|
Chris@53
|
249 d9.unit = "";
|
Chris@54
|
250 d9.minValue = 30;
|
Chris@54
|
251 d9.maxValue = 120;
|
Chris@53
|
252 d9.defaultValue = 60;
|
Chris@53
|
253 d9.isQuantized = true;
|
Chris@53
|
254 d9.quantizeStep = 1;
|
Chris@53
|
255 list.push_back(d9);
|
c@0
|
256
|
c@0
|
257 return list;
|
c@0
|
258 }
|
c@0
|
259
|
c@0
|
260 float
|
c@14
|
261 TempogramPlugin::getParameter(string identifier) const
|
c@0
|
262 {
|
c@0
|
263 if (identifier == "C") {
|
c@18
|
264 return m_noveltyCurveCompressionConstant; // return the ACTUAL current value of your parameter here!
|
c@0
|
265 }
|
c@29
|
266 else if (identifier == "minDB"){
|
c@29
|
267 return m_noveltyCurveMinDB;
|
c@29
|
268 }
|
c@14
|
269 else if (identifier == "log2TN"){
|
c@18
|
270 return m_tempogramLog2WindowLength;
|
c@9
|
271 }
|
c@14
|
272 else if (identifier == "log2HopSize"){
|
c@18
|
273 return m_tempogramLog2HopSize;
|
c@14
|
274 }
|
c@14
|
275 else if (identifier == "log2FftLength"){
|
c@18
|
276 return m_tempogramLog2FftLength;
|
c@14
|
277 }
|
c@14
|
278 else if (identifier == "minBPM") {
|
c@18
|
279 return m_tempogramMinBPM;
|
c@9
|
280 }
|
c@14
|
281 else if (identifier == "maxBPM"){
|
c@18
|
282 return m_tempogramMaxBPM;
|
c@18
|
283 }
|
c@18
|
284 else if (identifier == "octDiv"){
|
c@18
|
285 return m_cyclicTempogramOctaveDivider;
|
c@0
|
286 }
|
c@51
|
287 else if (identifier == "refBPM"){
|
c@51
|
288 return m_cyclicTempogramReferenceBPM;
|
c@51
|
289 }
|
c@0
|
290
|
c@0
|
291 return 0;
|
c@0
|
292 }
|
c@0
|
293
|
c@0
|
294 void
|
c@14
|
295 TempogramPlugin::setParameter(string identifier, float value)
|
c@0
|
296 {
|
c@9
|
297
|
c@0
|
298 if (identifier == "C") {
|
c@18
|
299 m_noveltyCurveCompressionConstant = value; // set the actual value of your parameter
|
c@0
|
300 }
|
c@29
|
301 else if (identifier == "minDB"){
|
c@29
|
302 m_noveltyCurveMinDB = value;
|
c@29
|
303 }
|
c@14
|
304 else if (identifier == "log2TN") {
|
c@18
|
305 m_tempogramLog2WindowLength = value;
|
c@0
|
306 }
|
c@14
|
307 else if (identifier == "log2HopSize"){
|
c@30
|
308 m_tempogramLog2HopSize = value;
|
c@14
|
309 }
|
c@18
|
310 else if (identifier == "log2FftLength"){
|
c@30
|
311 m_tempogramLog2FftLength = value;
|
c@14
|
312 }
|
c@14
|
313 else if (identifier == "minBPM") {
|
c@18
|
314 m_tempogramMinBPM = value;
|
c@9
|
315 }
|
c@14
|
316 else if (identifier == "maxBPM"){
|
c@18
|
317 m_tempogramMaxBPM = value;
|
c@18
|
318 }
|
c@18
|
319 else if (identifier == "octDiv"){
|
c@18
|
320 m_cyclicTempogramOctaveDivider = value;
|
c@9
|
321 }
|
c@51
|
322 else if (identifier == "refBPM"){
|
c@51
|
323 m_cyclicTempogramReferenceBPM = value;
|
c@51
|
324 }
|
c@9
|
325
|
c@9
|
326 }
|
c@9
|
327
|
c@14
|
328 TempogramPlugin::ProgramList
|
c@14
|
329 TempogramPlugin::getPrograms() const
|
c@0
|
330 {
|
c@0
|
331 ProgramList list;
|
c@0
|
332
|
c@0
|
333 // If you have no programs, return an empty list (or simply don't
|
c@0
|
334 // implement this function or getCurrentProgram/selectProgram)
|
c@0
|
335
|
c@0
|
336 return list;
|
c@0
|
337 }
|
c@0
|
338
|
c@0
|
339 string
|
c@14
|
340 TempogramPlugin::getCurrentProgram() const
|
c@0
|
341 {
|
c@0
|
342 return ""; // no programs
|
c@0
|
343 }
|
c@0
|
344
|
c@0
|
345 void
|
c@14
|
346 TempogramPlugin::selectProgram(string name)
|
c@0
|
347 {
|
c@0
|
348 }
|
c@0
|
349
|
c@14
|
350 TempogramPlugin::OutputList
|
c@14
|
351 TempogramPlugin::getOutputDescriptors() const
|
c@0
|
352 {
|
c@0
|
353 OutputList list;
|
c@0
|
354
|
c@0
|
355 // See OutputDescriptor documentation for the possibilities here.
|
c@0
|
356 // Every plugin must have at least one output.
|
c@1
|
357
|
c@7
|
358 float d_sampleRate;
|
c@18
|
359 float tempogramInputSampleRate = (float)m_inputSampleRate/m_inputStepSize;
|
c@25
|
360 OutputDescriptor d1;
|
c@25
|
361 d1.identifier = "cyclicTempogram";
|
c@25
|
362 d1.name = "Cyclic Tempogram";
|
Chris@43
|
363 d1.description = "Cyclic tempogram calculated by \"octave folding\" the DFT tempogram";
|
c@25
|
364 d1.unit = "";
|
c@25
|
365 d1.hasFixedBinCount = true;
|
Chris@60
|
366 d1.binCount = m_cyclicTempogramOctaveDivider > 0 ? m_cyclicTempogramOctaveDivider : 0;
|
c@25
|
367 d1.hasKnownExtents = false;
|
c@25
|
368 d1.isQuantized = false;
|
c@25
|
369 d1.sampleType = OutputDescriptor::FixedSampleRate;
|
c@25
|
370 d_sampleRate = tempogramInputSampleRate/m_tempogramHopSize;
|
c@25
|
371 d1.sampleRate = d_sampleRate > 0.0 && !isnan(d_sampleRate) ? d_sampleRate : 0;
|
c@51
|
372 vector< vector <unsigned int> > logBins = calculateTempogramNearestNeighbourLogBins();
|
c@51
|
373 if (!logBins.empty()){
|
c@52
|
374 float scale = pow(2,ceil(log2(60/binToBPM(logBins[0][0]))));
|
c@52
|
375 for(int i = 0; i < m_cyclicTempogramOctaveDivider; i++){
|
c@52
|
376 float s = scale*binToBPM(logBins[0][i])/m_cyclicTempogramReferenceBPM;
|
c@51
|
377 d1.binNames.push_back(floatToString(s));
|
c@51
|
378 //cerr << m_cyclicTempogramOctaveDivider << " " << s << endl;
|
c@51
|
379 }
|
c@51
|
380 }
|
c@25
|
381 d1.hasDuration = false;
|
c@25
|
382 list.push_back(d1);
|
c@25
|
383
|
c@25
|
384 OutputDescriptor d2;
|
c@25
|
385 d2.identifier = "tempogramDFT";
|
c@25
|
386 d2.name = "Tempogram via DFT";
|
Chris@43
|
387 d2.description = "Tempogram calculated using Discrete Fourier Transform method";
|
Chris@43
|
388 d2.unit = ""; // unit of bin contents, not of "bin label", so not bpm
|
c@25
|
389 d2.hasFixedBinCount = true;
|
c@25
|
390 d2.binCount = m_tempogramMaxBin - m_tempogramMinBin + 1;
|
c@25
|
391 d2.hasKnownExtents = false;
|
c@25
|
392 d2.isQuantized = false;
|
c@25
|
393 d2.sampleType = OutputDescriptor::FixedSampleRate;
|
c@25
|
394 d_sampleRate = tempogramInputSampleRate/m_tempogramHopSize;
|
c@25
|
395 d2.sampleRate = d_sampleRate > 0.0 && !isnan(d_sampleRate) ? d_sampleRate : 0.0;
|
c@25
|
396 for(int i = m_tempogramMinBin; i <= (int)m_tempogramMaxBin; i++){
|
c@25
|
397 float w = ((float)i/m_tempogramFftLength)*(tempogramInputSampleRate);
|
c@25
|
398 d2.binNames.push_back(floatToString(w*60));
|
c@25
|
399 }
|
c@25
|
400 d2.hasDuration = false;
|
c@25
|
401 list.push_back(d2);
|
c@25
|
402
|
c@21
|
403 OutputDescriptor d3;
|
c@25
|
404 d3.identifier = "tempogramACT";
|
c@25
|
405 d3.name = "Tempogram via ACT";
|
Chris@43
|
406 d3.description = "Tempogram calculated using autocorrelation method";
|
Chris@43
|
407 d3.unit = ""; // unit of bin contents, not of "bin label", so not bpm
|
c@21
|
408 d3.hasFixedBinCount = true;
|
c@28
|
409 d3.binCount = m_tempogramMaxLag - m_tempogramMinLag + 1;
|
c@21
|
410 d3.hasKnownExtents = false;
|
c@21
|
411 d3.isQuantized = false;
|
c@21
|
412 d3.sampleType = OutputDescriptor::FixedSampleRate;
|
c@21
|
413 d_sampleRate = tempogramInputSampleRate/m_tempogramHopSize;
|
c@25
|
414 d3.sampleRate = d_sampleRate > 0.0 && !isnan(d_sampleRate) ? d_sampleRate : 0.0;
|
c@28
|
415 for(int lag = m_tempogramMaxLag; lag >= (int)m_tempogramMinLag; lag--){
|
c@28
|
416 d3.binNames.push_back(floatToString(60/(m_inputStepSize*(lag/m_inputSampleRate))));
|
c@25
|
417 }
|
c@21
|
418 d3.hasDuration = false;
|
c@21
|
419 list.push_back(d3);
|
c@21
|
420
|
c@25
|
421 OutputDescriptor d4;
|
c@25
|
422 d4.identifier = "nc";
|
c@25
|
423 d4.name = "Novelty Curve";
|
Chris@43
|
424 d4.description = "Novelty curve underlying the tempogram calculations";
|
c@25
|
425 d4.unit = "";
|
c@25
|
426 d4.hasFixedBinCount = true;
|
c@25
|
427 d4.binCount = 1;
|
c@25
|
428 d4.hasKnownExtents = false;
|
c@25
|
429 d4.isQuantized = false;
|
c@25
|
430 d4.sampleType = OutputDescriptor::FixedSampleRate;
|
c@9
|
431 d_sampleRate = tempogramInputSampleRate;
|
c@25
|
432 d4.sampleRate = d_sampleRate > 0 && !isnan(d_sampleRate) ? d_sampleRate : 0;
|
c@25
|
433 d4.hasDuration = false;
|
c@25
|
434 list.push_back(d4);
|
c@18
|
435
|
c@0
|
436 return list;
|
c@0
|
437 }
|
c@0
|
438
|
c@20
|
439 bool
|
c@20
|
440 TempogramPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize)
|
c@20
|
441 {
|
c@20
|
442 if (channels < getMinChannelCount() ||
|
c@20
|
443 channels > getMaxChannelCount()) return false;
|
c@20
|
444
|
c@20
|
445 // Real initialisation work goes here!
|
c@20
|
446 m_inputBlockSize = blockSize;
|
c@20
|
447 m_inputStepSize = stepSize;
|
c@20
|
448
|
c@24
|
449 //m_spectrogram = Spectrogram(m_inputBlockSize/2 + 1);
|
c@21
|
450 if (!handleParameterValues()) return false;
|
c@19
|
451 //cout << m_cyclicTempogramOctaveDivider << endl;
|
c@4
|
452
|
c@0
|
453 return true;
|
c@0
|
454 }
|
c@0
|
455
|
c@0
|
456 void
|
c@14
|
457 TempogramPlugin::reset()
|
c@0
|
458 {
|
c@0
|
459 // Clear buffers, reset stored values, etc
|
c@19
|
460 m_spectrogram.clear();
|
c@21
|
461 handleParameterValues();
|
c@0
|
462 }
|
c@0
|
463
|
c@14
|
464 TempogramPlugin::FeatureSet
|
c@14
|
465 TempogramPlugin::process(const float *const *inputBuffers, Vamp::RealTime timestamp)
|
c@0
|
466 {
|
c@23
|
467 int n = m_inputBlockSize/2 + 1;
|
c@0
|
468 const float *in = inputBuffers[0];
|
c@3
|
469
|
c@9
|
470 //calculate magnitude of FrequencyDomain input
|
c@22
|
471 vector<float> fftCoefficients;
|
c@23
|
472 for (int i = 0; i < n; i++){
|
c@0
|
473 float magnitude = sqrt(in[2*i] * in[2*i] + in[2*i + 1] * in[2*i + 1]);
|
c@29
|
474 magnitude = magnitude > m_noveltyCurveMinV ? magnitude : m_noveltyCurveMinV;
|
c@22
|
475 fftCoefficients.push_back(magnitude);
|
c@0
|
476 }
|
c@22
|
477 m_spectrogram.push_back(fftCoefficients);
|
c@24
|
478 //m_spectrogram.push_back(fftCoefficients);
|
c@21
|
479
|
c@23
|
480 return FeatureSet();
|
c@0
|
481 }
|
c@0
|
482
|
c@14
|
483 TempogramPlugin::FeatureSet
|
c@14
|
484 TempogramPlugin::getRemainingFeatures()
|
c@11
|
485 {
|
c@0
|
486
|
c@18
|
487 float * hannWindow = new float[m_tempogramWindowLength];
|
c@20
|
488 for (int i = 0; i < (int)m_tempogramWindowLength; i++){
|
c@14
|
489 hannWindow[i] = 0.0;
|
c@4
|
490 }
|
c@11
|
491
|
c@1
|
492 FeatureSet featureSet;
|
c@0
|
493
|
c@19
|
494 //initialise novelty curve processor
|
c@23
|
495 int numberOfBlocks = m_spectrogram.size();
|
Chris@48
|
496
|
c@22
|
497 NoveltyCurveProcessor nc(m_inputSampleRate, m_inputBlockSize, m_noveltyCurveCompressionConstant);
|
c@21
|
498 vector<float> noveltyCurve = nc.spectrogramToNoveltyCurve(m_spectrogram); //calculate novelty curvefrom magnitude data
|
c@4
|
499
|
c@9
|
500 //push novelty curve data to featureset 1 and set timestamps
|
c@23
|
501 for (int i = 0; i < numberOfBlocks; i++){
|
c@19
|
502 Feature noveltyCurveFeature;
|
c@19
|
503 noveltyCurveFeature.values.push_back(noveltyCurve[i]);
|
c@19
|
504 noveltyCurveFeature.hasTimestamp = false;
|
c@25
|
505 featureSet[3].push_back(noveltyCurveFeature);
|
c@21
|
506 assert(!isnan(noveltyCurveFeature.values.back()));
|
c@4
|
507 }
|
c@4
|
508
|
c@9
|
509 //window function for spectrogram
|
c@18
|
510 WindowFunction::hanning(hannWindow, m_tempogramWindowLength);
|
c@9
|
511
|
c@9
|
512 //initialise spectrogram processor
|
c@18
|
513 SpectrogramProcessor spectrogramProcessor(m_tempogramWindowLength, m_tempogramFftLength, m_tempogramHopSize);
|
c@9
|
514 //compute spectrogram from novelty curve data (i.e., tempogram)
|
c@25
|
515 Tempogram tempogramDFT = spectrogramProcessor.process(&noveltyCurve[0], numberOfBlocks, hannWindow);
|
c@18
|
516 delete []hannWindow;
|
c@18
|
517 hannWindow = 0;
|
c@0
|
518
|
c@25
|
519 int tempogramLength = tempogramDFT.size();
|
c@7
|
520
|
c@9
|
521 //push tempogram data to featureset 0 and set timestamps.
|
c@7
|
522 for (int block = 0; block < tempogramLength; block++){
|
c@25
|
523 Feature tempogramDFTFeature;
|
c@28
|
524
|
c@28
|
525 assert(tempogramDFT[block].size() == (m_tempogramFftLength/2 + 1));
|
c@28
|
526 for(int k = m_tempogramMinBin; k <= (int)m_tempogramMaxBin; k++){
|
c@28
|
527 tempogramDFTFeature.values.push_back(tempogramDFT[block][k]);
|
c@28
|
528 }
|
c@28
|
529 tempogramDFTFeature.hasTimestamp = false;
|
c@28
|
530 featureSet[1].push_back(tempogramDFTFeature);
|
c@28
|
531 }
|
c@28
|
532
|
c@28
|
533 AutocorrelationProcessor autocorrelationProcessor(m_tempogramWindowLength, m_tempogramHopSize);
|
c@28
|
534 Tempogram tempogramACT = autocorrelationProcessor.process(&noveltyCurve[0], numberOfBlocks);
|
c@28
|
535
|
c@28
|
536 for (int block = 0; block < tempogramLength; block++){
|
c@25
|
537 Feature tempogramACTFeature;
|
Chris@44
|
538
|
c@28
|
539 for(int k = m_tempogramMaxLag; k >= (int)m_tempogramMinLag; k--){
|
c@25
|
540 tempogramACTFeature.values.push_back(tempogramACT[block][k]);
|
c@0
|
541 }
|
c@25
|
542 tempogramACTFeature.hasTimestamp = false;
|
c@25
|
543 featureSet[2].push_back(tempogramACTFeature);
|
c@0
|
544 }
|
c@0
|
545
|
c@18
|
546 //Calculate cyclic tempogram
|
c@22
|
547 vector< vector<unsigned int> > logBins = calculateTempogramNearestNeighbourLogBins();
|
c@18
|
548
|
c@22
|
549 //assert((int)logBins.size() == m_cyclicTempogramOctaveDivider*m_cyclicTempogramNumberOfOctaves);
|
c@18
|
550 for (int block = 0; block < tempogramLength; block++){
|
c@19
|
551 Feature cyclicTempogramFeature;
|
c@18
|
552
|
c@23
|
553 for (int i = 0; i < m_cyclicTempogramOctaveDivider; i++){
|
c@18
|
554 float sum = 0;
|
c@21
|
555
|
c@23
|
556 for (int j = 0; j < m_cyclicTempogramNumberOfOctaves; j++){
|
Chris@48
|
557 sum += tempogramDFT[block][logBins[j][i]];
|
c@18
|
558 }
|
c@19
|
559 cyclicTempogramFeature.values.push_back(sum/m_cyclicTempogramNumberOfOctaves);
|
c@21
|
560 assert(!isnan(cyclicTempogramFeature.values.back()));
|
c@18
|
561 }
|
c@18
|
562
|
c@19
|
563 cyclicTempogramFeature.hasTimestamp = false;
|
c@21
|
564 featureSet[0].push_back(cyclicTempogramFeature);
|
c@18
|
565 }
|
c@0
|
566
|
c@0
|
567 return featureSet;
|
c@0
|
568 }
|
c@22
|
569
|
c@22
|
570 vector< vector<unsigned int> > TempogramPlugin::calculateTempogramNearestNeighbourLogBins() const
|
c@22
|
571 {
|
c@22
|
572 vector< vector<unsigned int> > logBins;
|
c@22
|
573
|
c@22
|
574 for (int octave = 0; octave < (int)m_cyclicTempogramNumberOfOctaves; octave++){
|
c@22
|
575 vector<unsigned int> octaveBins;
|
Chris@47
|
576
|
c@22
|
577 for (int bin = 0; bin < (int)m_cyclicTempogramOctaveDivider; bin++){
|
c@22
|
578 float bpm = m_cyclicTempogramMinBPM*pow(2.0f, octave+(float)bin/m_cyclicTempogramOctaveDivider);
|
c@22
|
579 octaveBins.push_back(bpmToBin(bpm));
|
c@22
|
580 }
|
c@22
|
581 logBins.push_back(octaveBins);
|
c@22
|
582 }
|
c@22
|
583
|
c@22
|
584 return logBins;
|
c@22
|
585 }
|
c@22
|
586
|
c@22
|
587 unsigned int TempogramPlugin::bpmToBin(const float &bpm) const
|
c@22
|
588 {
|
c@22
|
589 float w = (float)bpm/60;
|
c@22
|
590 float sampleRate = m_inputSampleRate/m_inputStepSize;
|
c@22
|
591 int bin = floor((float)m_tempogramFftLength*w/sampleRate + 0.5);
|
c@22
|
592
|
c@22
|
593 if(bin < 0) bin = 0;
|
Chris@46
|
594 else if(bin > m_tempogramFftLength/2.0f) bin = m_tempogramFftLength/2.0f;
|
c@22
|
595
|
c@22
|
596 return bin;
|
c@22
|
597 }
|
c@22
|
598
|
c@22
|
599 float TempogramPlugin::binToBPM(const int &bin) const
|
c@22
|
600 {
|
c@22
|
601 float sampleRate = m_inputSampleRate/m_inputStepSize;
|
c@22
|
602
|
c@22
|
603 return (bin*sampleRate/m_tempogramFftLength)*60;
|
c@22
|
604 }
|
c@22
|
605
|
c@22
|
606 bool TempogramPlugin::handleParameterValues(){
|
c@22
|
607
|
Chris@42
|
608 if (m_tempogramLog2HopSize <= 0) {
|
Chris@42
|
609 cerr << "Tempogram log2 hop size " << m_tempogramLog2HopSize
|
Chris@42
|
610 << " <= 0, failing initialise" << endl;
|
Chris@42
|
611 return false;
|
Chris@42
|
612 }
|
Chris@42
|
613 if (m_tempogramLog2FftLength <= 0) {
|
Chris@42
|
614 cerr << "Tempogram log2 fft length " << m_tempogramLog2FftLength
|
Chris@42
|
615 << " <= 0, failing initialise" << endl;
|
Chris@42
|
616 return false;
|
Chris@42
|
617 }
|
c@22
|
618
|
Chris@42
|
619 if (m_tempogramMinBPM < 1) {
|
Chris@42
|
620 m_tempogramMinBPM = 1;
|
Chris@42
|
621 }
|
c@22
|
622 if (m_tempogramMinBPM >= m_tempogramMaxBPM){
|
c@22
|
623 m_tempogramMinBPM = 30;
|
c@22
|
624 m_tempogramMaxBPM = 480;
|
c@22
|
625 }
|
c@22
|
626
|
c@29
|
627 m_noveltyCurveMinV = pow(10,(float)m_noveltyCurveMinDB/20);
|
c@29
|
628
|
c@29
|
629 m_tempogramWindowLength = pow(2,m_tempogramLog2WindowLength);
|
c@29
|
630 m_tempogramHopSize = pow(2,m_tempogramLog2HopSize);
|
c@29
|
631 m_tempogramFftLength = pow(2,m_tempogramLog2FftLength);
|
c@29
|
632
|
c@30
|
633 if (m_tempogramFftLength < m_tempogramWindowLength){
|
c@30
|
634 m_tempogramFftLength = m_tempogramWindowLength;
|
c@30
|
635 }
|
c@30
|
636
|
c@22
|
637 float tempogramInputSampleRate = (float)m_inputSampleRate/m_inputStepSize;
|
c@28
|
638 m_tempogramMinBin = (max((int)floor(((m_tempogramMinBPM/60)/tempogramInputSampleRate)*m_tempogramFftLength), 0));
|
c@28
|
639 m_tempogramMaxBin = (min((int)ceil(((m_tempogramMaxBPM/60)/tempogramInputSampleRate)*m_tempogramFftLength), (int)(m_tempogramFftLength/2)));
|
Chris@32
|
640
|
Chris@32
|
641 if (m_tempogramMaxBin < m_tempogramMinBin) {
|
Chris@32
|
642 cerr << "At audio sample rate " << m_inputSampleRate
|
Chris@32
|
643 << ", tempogram sample rate " << tempogramInputSampleRate
|
Chris@32
|
644 << " with bpm range " << m_tempogramMinBPM << " -> "
|
Chris@32
|
645 << m_tempogramMaxBPM << ", min bin = " << m_tempogramMinBin
|
Chris@32
|
646 << " > max bin " << m_tempogramMaxBin
|
Chris@32
|
647 << ": can't proceed, failing initialise" << endl;
|
Chris@32
|
648 return false;
|
Chris@32
|
649 }
|
c@28
|
650
|
c@28
|
651 m_tempogramMinLag = max((int)ceil((60/(m_inputStepSize * m_tempogramMaxBPM))*m_inputSampleRate), 0);
|
Chris@45
|
652 m_tempogramMaxLag = min((int)floor((60/(m_inputStepSize * m_tempogramMinBPM))*m_inputSampleRate), (int)m_tempogramWindowLength-1);
|
Chris@32
|
653
|
Chris@32
|
654 if (m_tempogramMaxLag < m_tempogramMinLag) {
|
Chris@32
|
655 cerr << "At audio sample rate " << m_inputSampleRate
|
Chris@32
|
656 << ", tempogram sample rate " << tempogramInputSampleRate
|
Chris@42
|
657 << ", window length " << m_tempogramWindowLength
|
Chris@32
|
658 << " with bpm range " << m_tempogramMinBPM << " -> "
|
Chris@42
|
659 << m_tempogramMaxBPM << ", min lag = " << m_tempogramMinLag
|
Chris@42
|
660 << " > max lag " << m_tempogramMaxLag
|
Chris@32
|
661 << ": can't proceed, failing initialise" << endl;
|
Chris@32
|
662 return false;
|
Chris@32
|
663 }
|
c@22
|
664
|
Chris@47
|
665 m_cyclicTempogramMinBPM = max(binToBPM(m_tempogramMinBin), m_tempogramMinBPM);
|
Chris@47
|
666 float cyclicTempogramMaxBPM = min(binToBPM(m_tempogramMaxBin), m_tempogramMaxBPM);
|
Chris@47
|
667
|
c@22
|
668 m_cyclicTempogramNumberOfOctaves = floor(log2(cyclicTempogramMaxBPM/m_cyclicTempogramMinBPM));
|
Chris@42
|
669
|
Chris@42
|
670 if (m_cyclicTempogramNumberOfOctaves < 1) {
|
Chris@42
|
671 cerr << "At audio sample rate " << m_inputSampleRate
|
Chris@42
|
672 << ", tempogram sample rate " << tempogramInputSampleRate
|
Chris@42
|
673 << " with bpm range " << m_tempogramMinBPM << " -> "
|
Chris@42
|
674 << m_tempogramMaxBPM << ", cyclic tempogram min bpm = "
|
Chris@42
|
675 << m_cyclicTempogramMinBPM << " and max bpm = "
|
Chris@42
|
676 << cyclicTempogramMaxBPM << " giving number of octaves = "
|
Chris@42
|
677 << m_cyclicTempogramNumberOfOctaves
|
Chris@42
|
678 << ": can't proceed, failing initialise" << endl;
|
Chris@42
|
679 return false;
|
Chris@42
|
680 }
|
c@22
|
681
|
c@22
|
682 return true;
|
c@22
|
683 }
|
c@22
|
684
|
c@22
|
685 string TempogramPlugin::floatToString(float value) const
|
c@22
|
686 {
|
c@22
|
687 ostringstream ss;
|
c@22
|
688
|
c@22
|
689 if(!(ss << value)) throw runtime_error("TempogramPlugin::floatToString(): invalid conversion from float to string");
|
c@22
|
690 return ss.str();
|
c@22
|
691 }
|