c@0
|
1
|
c@0
|
2 // This is a skeleton file for use in creating your own plugin
|
c@0
|
3 // libraries. Replace MyPlugin and myPlugin throughout with the name
|
c@0
|
4 // of your first plugin class, and fill in the gaps as appropriate.
|
c@0
|
5
|
c@0
|
6
|
c@14
|
7 #include "TempogramPlugin.h"
|
c@9
|
8 #include <sstream>
|
c@9
|
9 #include <stdexcept>
|
c@4
|
10
|
c@0
|
11 using Vamp::FFT;
|
c@7
|
12 using Vamp::RealTime;
|
c@0
|
13 using namespace std;
|
c@0
|
14
|
c@14
|
15 TempogramPlugin::TempogramPlugin(float inputSampleRate) :
|
c@0
|
16 Plugin(inputSampleRate),
|
c@0
|
17 m_blockSize(0),
|
c@1
|
18 m_stepSize(0),
|
c@13
|
19 m_compressionConstant(1000), //parameter
|
c@13
|
20 m_minDB(0),
|
c@14
|
21 m_log2WindowLength(10), //parameter
|
c@14
|
22 m_windowLength(pow((float)2,m_log2WindowLength)),
|
c@14
|
23 m_log2FftLength(m_log2WindowLength),
|
c@14
|
24 m_fftLength(m_windowLength),
|
c@14
|
25 m_log2HopSize(6), //parameter
|
c@14
|
26 m_hopSize(pow((float)2,m_log2HopSize)),
|
c@13
|
27 m_minBPM(30),
|
c@13
|
28 m_maxBPM(480),
|
c@13
|
29 m_minBin(0), //set in initialise()
|
c@14
|
30 m_maxBin(0) //set in initialise()
|
c@0
|
31
|
c@0
|
32 // Also be sure to set your plugin parameters (presumably stored
|
c@0
|
33 // in member variables) to their default values here -- the host
|
c@0
|
34 // will not do that for you
|
c@0
|
35 {
|
c@0
|
36 }
|
c@0
|
37
|
c@14
|
38 TempogramPlugin::~TempogramPlugin()
|
c@0
|
39 {
|
c@0
|
40 //delete stuff
|
c@7
|
41 cleanup();
|
c@0
|
42 }
|
c@0
|
43
|
c@0
|
44 string
|
c@14
|
45 TempogramPlugin::getIdentifier() const
|
c@0
|
46 {
|
c@0
|
47 return "tempogram";
|
c@0
|
48 }
|
c@0
|
49
|
c@0
|
50 string
|
c@14
|
51 TempogramPlugin::getName() const
|
c@0
|
52 {
|
c@0
|
53 return "Tempogram";
|
c@0
|
54 }
|
c@0
|
55
|
c@0
|
56 string
|
c@14
|
57 TempogramPlugin::getDescription() const
|
c@0
|
58 {
|
c@0
|
59 // Return something helpful here!
|
c@0
|
60 return "Cyclic Tempogram as described by Peter Grosche and Meinard Muller";
|
c@0
|
61 }
|
c@0
|
62
|
c@0
|
63 string
|
c@14
|
64 TempogramPlugin::getMaker() const
|
c@0
|
65 {
|
c@0
|
66 //Your name here
|
c@0
|
67 return "Carl Bussey";
|
c@0
|
68 }
|
c@0
|
69
|
c@0
|
70 int
|
c@14
|
71 TempogramPlugin::getPluginVersion() const
|
c@0
|
72 {
|
c@0
|
73 // Increment this each time you release a version that behaves
|
c@0
|
74 // differently from the previous one
|
c@0
|
75 return 1;
|
c@0
|
76 }
|
c@0
|
77
|
c@0
|
78 string
|
c@14
|
79 TempogramPlugin::getCopyright() const
|
c@0
|
80 {
|
c@0
|
81 // This function is not ideally named. It does not necessarily
|
c@0
|
82 // need to say who made the plugin -- getMaker does that -- but it
|
c@0
|
83 // should indicate the terms under which it is distributed. For
|
c@0
|
84 // example, "Copyright (year). All Rights Reserved", or "GPL"
|
c@0
|
85 return "";
|
c@0
|
86 }
|
c@0
|
87
|
c@14
|
88 TempogramPlugin::InputDomain
|
c@14
|
89 TempogramPlugin::getInputDomain() const
|
c@0
|
90 {
|
c@0
|
91 return FrequencyDomain;
|
c@0
|
92 }
|
c@0
|
93
|
c@0
|
94 size_t
|
c@14
|
95 TempogramPlugin::getPreferredBlockSize() const
|
c@0
|
96 {
|
c@9
|
97 return 2048; // 0 means "I can handle any block size"
|
c@0
|
98 }
|
c@0
|
99
|
c@0
|
100 size_t
|
c@14
|
101 TempogramPlugin::getPreferredStepSize() const
|
c@0
|
102 {
|
c@9
|
103 return 1024; // 0 means "anything sensible"; in practice this
|
c@0
|
104 // means the same as the block size for TimeDomain
|
c@0
|
105 // plugins, or half of it for FrequencyDomain plugins
|
c@0
|
106 }
|
c@0
|
107
|
c@0
|
108 size_t
|
c@14
|
109 TempogramPlugin::getMinChannelCount() const
|
c@0
|
110 {
|
c@0
|
111 return 1;
|
c@0
|
112 }
|
c@0
|
113
|
c@0
|
114 size_t
|
c@14
|
115 TempogramPlugin::getMaxChannelCount() const
|
c@0
|
116 {
|
c@0
|
117 return 1;
|
c@0
|
118 }
|
c@0
|
119
|
c@14
|
120 TempogramPlugin::ParameterList
|
c@14
|
121 TempogramPlugin::getParameterDescriptors() const
|
c@0
|
122 {
|
c@0
|
123 ParameterList list;
|
c@0
|
124
|
c@0
|
125 // If the plugin has no adjustable parameters, return an empty
|
c@0
|
126 // list here (and there's no need to provide implementations of
|
c@0
|
127 // getParameter and setParameter in that case either).
|
c@0
|
128
|
c@0
|
129 // Note that it is your responsibility to make sure the parameters
|
c@0
|
130 // start off having their default values (e.g. in the constructor
|
c@0
|
131 // above). The host needs to know the default value so it can do
|
c@0
|
132 // things like provide a "reset to default" function, but it will
|
c@0
|
133 // not explicitly set your parameters to their defaults for you if
|
c@0
|
134 // they have not changed in the mean time.
|
c@0
|
135
|
c@14
|
136 ParameterDescriptor d1;
|
c@14
|
137 d1.identifier = "C";
|
c@14
|
138 d1.name = "C";
|
c@14
|
139 d1.description = "Spectrogram compression constant, C, used when retrieving the novelty curve from the audio.";
|
c@14
|
140 d1.unit = "";
|
c@14
|
141 d1.minValue = 2;
|
c@14
|
142 d1.maxValue = 10000;
|
c@14
|
143 d1.defaultValue = 1000;
|
c@14
|
144 d1.isQuantized = false;
|
c@14
|
145 list.push_back(d1);
|
c@9
|
146
|
c@14
|
147 ParameterDescriptor d2;
|
c@14
|
148 d2.identifier = "log2TN";
|
c@14
|
149 d2.name = "Tempogram Window Length";
|
c@14
|
150 d2.description = "FFT window length when analysing the novelty curve and extracting the tempogram time-frequency function.";
|
c@14
|
151 d2.unit = "";
|
c@14
|
152 d2.minValue = 7;
|
c@14
|
153 d2.maxValue = 12;
|
c@14
|
154 d2.defaultValue = 10;
|
c@14
|
155 d2.isQuantized = true;
|
c@14
|
156 d2.quantizeStep = 1;
|
c@14
|
157 for (int i = d2.minValue; i <= d2.maxValue; i++){
|
c@14
|
158 d2.valueNames.push_back(floatToString(pow((float)2,(float)i)));
|
c@13
|
159 }
|
c@14
|
160 list.push_back(d2);
|
c@0
|
161
|
c@14
|
162 ParameterDescriptor d3;
|
c@14
|
163 d3.identifier = "log2HopSize";
|
c@14
|
164 d3.name = "Tempogram Hopsize";
|
c@14
|
165 d3.description = "FFT hopsize when analysing the novelty curve and extracting the tempogram time-frequency function.";
|
c@14
|
166 d3.unit = "";
|
c@14
|
167 d3.minValue = 6;
|
c@14
|
168 d3.maxValue = 12;
|
c@14
|
169 d3.defaultValue = 6;
|
c@14
|
170 d3.isQuantized = true;
|
c@14
|
171 d3.quantizeStep = 1;
|
c@14
|
172 for (int i = d3.minValue; i <= d3.maxValue; i++){
|
c@14
|
173 d3.valueNames.push_back(floatToString(pow((float)2,(float)i)));
|
c@14
|
174 }
|
c@14
|
175 list.push_back(d3);
|
c@9
|
176
|
c@14
|
177 ParameterDescriptor d4;
|
c@14
|
178 d4.identifier = "log2FftLength";
|
c@14
|
179 d4.name = "Tempogram FFT Length";
|
c@14
|
180 d4.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
|
181 d4.unit = "";
|
c@14
|
182 d4.minValue = 6;
|
c@14
|
183 d4.maxValue = 12;
|
c@14
|
184 d4.defaultValue = d2.defaultValue;
|
c@14
|
185 d4.isQuantized = true;
|
c@14
|
186 d4.quantizeStep = 1;
|
c@14
|
187 for (int i = d4.minValue; i <= d4.maxValue; i++){
|
c@14
|
188 d4.valueNames.push_back(floatToString(pow((float)2,(float)i)));
|
c@14
|
189 }
|
c@14
|
190 list.push_back(d4);
|
c@14
|
191
|
c@14
|
192 ParameterDescriptor d5;
|
c@14
|
193 d5.identifier = "minBPM";
|
c@14
|
194 d5.name = "Minimum BPM";
|
c@14
|
195 d5.description = "The minimum BPM of the tempogram output bins.";
|
c@14
|
196 d5.unit = "";
|
c@14
|
197 d5.minValue = 0;
|
c@14
|
198 d5.maxValue = 2000;
|
c@14
|
199 d5.defaultValue = 30;
|
c@14
|
200 d5.isQuantized = true;
|
c@14
|
201 d5.quantizeStep = 5;
|
c@14
|
202 list.push_back(d5);
|
c@14
|
203
|
c@14
|
204 ParameterDescriptor d6;
|
c@14
|
205 d6.identifier = "maxBPM";
|
c@14
|
206 d6.name = "Maximum BPM";
|
c@14
|
207 d6.description = "The minimum BPM of the tempogram output bins.";
|
c@14
|
208 d6.unit = "";
|
c@14
|
209 d6.minValue = 30;
|
c@14
|
210 d6.maxValue = 2000;
|
c@14
|
211 d6.defaultValue = 480;
|
c@14
|
212 d6.isQuantized = true;
|
c@14
|
213 d6.quantizeStep = 5;
|
c@14
|
214 list.push_back(d6);
|
c@0
|
215
|
c@0
|
216 return list;
|
c@0
|
217 }
|
c@0
|
218
|
c@0
|
219 float
|
c@14
|
220 TempogramPlugin::getParameter(string identifier) const
|
c@0
|
221 {
|
c@0
|
222 if (identifier == "C") {
|
c@13
|
223 return m_compressionConstant; // return the ACTUAL current value of your parameter here!
|
c@0
|
224 }
|
c@14
|
225 else if (identifier == "log2TN"){
|
c@13
|
226 return m_log2WindowLength;
|
c@9
|
227 }
|
c@14
|
228 else if (identifier == "log2HopSize"){
|
c@14
|
229 return m_log2HopSize;
|
c@14
|
230 }
|
c@14
|
231 else if (identifier == "log2FftLength"){
|
c@14
|
232 return m_log2FftLength;
|
c@14
|
233 }
|
c@14
|
234 else if (identifier == "minBPM") {
|
c@13
|
235 return m_minBPM;
|
c@9
|
236 }
|
c@14
|
237 else if (identifier == "maxBPM"){
|
c@13
|
238 return m_maxBPM;
|
c@0
|
239 }
|
c@0
|
240
|
c@0
|
241 return 0;
|
c@0
|
242 }
|
c@0
|
243
|
c@0
|
244 void
|
c@14
|
245 TempogramPlugin::setParameter(string identifier, float value)
|
c@0
|
246 {
|
c@9
|
247
|
c@0
|
248 if (identifier == "C") {
|
c@13
|
249 m_compressionConstant = value; // set the actual value of your parameter
|
c@0
|
250 }
|
c@14
|
251 else if (identifier == "log2TN") {
|
c@13
|
252 m_windowLength = pow(2,value);
|
c@13
|
253 m_log2WindowLength = value;
|
c@0
|
254 }
|
c@14
|
255 else if (identifier == "log2HopSize"){
|
c@14
|
256 m_hopSize = pow(2,value);
|
c@14
|
257 m_log2HopSize = value;
|
c@14
|
258 }
|
c@14
|
259 else if (identifier == "log2HopFftLength"){
|
c@14
|
260 m_fftLength = pow(2,value);
|
c@14
|
261 m_log2FftLength = value;
|
c@14
|
262 }
|
c@14
|
263 else if (identifier == "minBPM") {
|
c@13
|
264 m_minBPM = value;
|
c@9
|
265 }
|
c@14
|
266 else if (identifier == "maxBPM"){
|
c@13
|
267 m_maxBPM = value;
|
c@9
|
268 }
|
c@9
|
269
|
c@9
|
270 }
|
c@9
|
271
|
c@14
|
272 void TempogramPlugin::updateBPMParameters(){
|
c@9
|
273
|
c@0
|
274 }
|
c@0
|
275
|
c@14
|
276 TempogramPlugin::ProgramList
|
c@14
|
277 TempogramPlugin::getPrograms() const
|
c@0
|
278 {
|
c@0
|
279 ProgramList list;
|
c@0
|
280
|
c@0
|
281 // If you have no programs, return an empty list (or simply don't
|
c@0
|
282 // implement this function or getCurrentProgram/selectProgram)
|
c@0
|
283
|
c@0
|
284 return list;
|
c@0
|
285 }
|
c@0
|
286
|
c@0
|
287 string
|
c@14
|
288 TempogramPlugin::getCurrentProgram() const
|
c@0
|
289 {
|
c@0
|
290 return ""; // no programs
|
c@0
|
291 }
|
c@0
|
292
|
c@0
|
293 void
|
c@14
|
294 TempogramPlugin::selectProgram(string name)
|
c@0
|
295 {
|
c@0
|
296 }
|
c@0
|
297
|
c@14
|
298 string TempogramPlugin::floatToString(float value) const
|
c@9
|
299 {
|
c@9
|
300 ostringstream ss;
|
c@9
|
301
|
c@14
|
302 if(!(ss << value)) throw runtime_error("TempogramPlugin::floatToString(): invalid conversion from float to string");
|
c@9
|
303 return ss.str();
|
c@9
|
304 }
|
c@9
|
305
|
c@14
|
306 TempogramPlugin::OutputList
|
c@14
|
307 TempogramPlugin::getOutputDescriptors() const
|
c@0
|
308 {
|
c@0
|
309 OutputList list;
|
c@0
|
310
|
c@0
|
311 // See OutputDescriptor documentation for the possibilities here.
|
c@0
|
312 // Every plugin must have at least one output.
|
c@1
|
313
|
c@0
|
314 OutputDescriptor d;
|
c@7
|
315 float d_sampleRate;
|
c@9
|
316 float tempogramInputSampleRate = (float)m_inputSampleRate/m_stepSize;
|
c@7
|
317
|
c@1
|
318 d.identifier = "tempogram";
|
c@7
|
319 d.name = "Tempogram";
|
c@7
|
320 d.description = "Tempogram";
|
c@9
|
321 d.unit = "BPM";
|
c@1
|
322 d.hasFixedBinCount = true;
|
c@13
|
323 d.binCount = m_maxBin - m_minBin + 1;
|
c@0
|
324 d.hasKnownExtents = false;
|
c@0
|
325 d.isQuantized = false;
|
c@1
|
326 d.sampleType = OutputDescriptor::FixedSampleRate;
|
c@13
|
327 d_sampleRate = tempogramInputSampleRate/m_hopSize;
|
c@1
|
328 d.sampleRate = d_sampleRate > 0.0 && !isnan(d_sampleRate) ? d_sampleRate : 0.0;
|
c@13
|
329 for(int i = m_minBin; i <= (int)m_maxBin; i++){
|
c@13
|
330 float w = ((float)i/m_fftLength)*(tempogramInputSampleRate);
|
c@9
|
331 d.binNames.push_back(floatToString(w*60));
|
c@9
|
332 }
|
c@0
|
333 d.hasDuration = false;
|
c@0
|
334 list.push_back(d);
|
c@7
|
335
|
c@1
|
336 d.identifier = "nc";
|
c@1
|
337 d.name = "Novelty Curve";
|
c@1
|
338 d.description = "Novelty Curve";
|
c@1
|
339 d.unit = "";
|
c@1
|
340 d.hasFixedBinCount = true;
|
c@1
|
341 d.binCount = 1;
|
c@1
|
342 d.hasKnownExtents = false;
|
c@1
|
343 d.isQuantized = false;
|
c@1
|
344 d.sampleType = OutputDescriptor::FixedSampleRate;
|
c@9
|
345 d_sampleRate = tempogramInputSampleRate;
|
c@1
|
346 d.sampleRate = d_sampleRate > 0 && !isnan(d_sampleRate) ? d_sampleRate : 0.0;
|
c@1
|
347 d.hasDuration = false;
|
c@1
|
348 list.push_back(d);
|
c@1
|
349
|
c@0
|
350 return list;
|
c@0
|
351 }
|
c@0
|
352
|
c@0
|
353 bool
|
c@14
|
354 TempogramPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize)
|
c@0
|
355 {
|
c@0
|
356 if (channels < getMinChannelCount() ||
|
c@0
|
357 channels > getMaxChannelCount()) return false;
|
c@9
|
358
|
c@0
|
359 // Real initialisation work goes here!
|
c@0
|
360 m_blockSize = blockSize;
|
c@1
|
361 m_stepSize = stepSize;
|
c@13
|
362 m_minDB = pow(10,(float)-74/20);
|
c@0
|
363
|
c@14
|
364 if (m_fftLength < m_windowLength){
|
c@14
|
365 m_fftLength = m_windowLength;
|
c@14
|
366 }
|
c@13
|
367 if (m_minBPM > m_maxBPM){
|
c@13
|
368 m_minBPM = 30;
|
c@13
|
369 m_maxBPM = 480;
|
c@9
|
370 }
|
c@9
|
371 float tempogramInputSampleRate = (float)m_inputSampleRate/m_stepSize;
|
c@13
|
372 m_minBin = (unsigned int)(max(floor(((m_minBPM/60)/tempogramInputSampleRate)*m_fftLength), (float)0.0));
|
c@13
|
373 m_maxBin = (unsigned int)(min(ceil(((m_maxBPM/60)/tempogramInputSampleRate)*m_fftLength), (float)m_fftLength/2));
|
c@9
|
374
|
c@14
|
375 m_spectrogram = SpectrogramTransposed(m_blockSize/2 + 1);
|
c@4
|
376
|
c@0
|
377 return true;
|
c@0
|
378 }
|
c@0
|
379
|
c@14
|
380 void TempogramPlugin::cleanup(){
|
c@7
|
381
|
c@7
|
382 }
|
c@7
|
383
|
c@0
|
384 void
|
c@14
|
385 TempogramPlugin::reset()
|
c@0
|
386 {
|
c@0
|
387 // Clear buffers, reset stored values, etc
|
c@7
|
388 ncTimestamps.clear();
|
c@14
|
389 m_spectrogram = SpectrogramTransposed(m_blockSize/2 + 1);
|
c@0
|
390 }
|
c@0
|
391
|
c@14
|
392 TempogramPlugin::FeatureSet
|
c@14
|
393 TempogramPlugin::process(const float *const *inputBuffers, Vamp::RealTime timestamp)
|
c@0
|
394 {
|
c@0
|
395 size_t n = m_blockSize/2 + 1;
|
c@0
|
396
|
c@0
|
397 FeatureSet featureSet;
|
c@0
|
398 Feature feature;
|
c@0
|
399
|
c@0
|
400 const float *in = inputBuffers[0];
|
c@3
|
401
|
c@9
|
402 //calculate magnitude of FrequencyDomain input
|
c@13
|
403 for (unsigned int i = 0; i < n; i++){
|
c@0
|
404 float magnitude = sqrt(in[2*i] * in[2*i] + in[2*i + 1] * in[2*i + 1]);
|
c@13
|
405 magnitude = magnitude > m_minDB ? magnitude : m_minDB;
|
c@13
|
406 m_spectrogram[i].push_back(magnitude);
|
c@0
|
407 }
|
c@0
|
408
|
c@9
|
409 ncTimestamps.push_back(timestamp); //save timestamp
|
c@7
|
410
|
c@2
|
411 return featureSet;
|
c@0
|
412 }
|
c@0
|
413
|
c@14
|
414 TempogramPlugin::FeatureSet
|
c@14
|
415 TempogramPlugin::getRemainingFeatures()
|
c@11
|
416 {
|
c@0
|
417
|
c@14
|
418 float * hannWindow = new float[m_windowLength];
|
c@13
|
419 for (unsigned int i = 0; i < m_windowLength; i++){
|
c@14
|
420 hannWindow[i] = 0.0;
|
c@4
|
421 }
|
c@11
|
422
|
c@1
|
423 FeatureSet featureSet;
|
c@0
|
424
|
c@13
|
425 //initialise m_noveltyCurve processor
|
c@14
|
426 size_t numberOfBlocks = m_spectrogram[0].size();
|
c@14
|
427 NoveltyCurveProcessor nc(m_inputSampleRate, m_blockSize, numberOfBlocks, m_compressionConstant);
|
c@13
|
428 m_noveltyCurve = nc.spectrogramToNoveltyCurve(m_spectrogram); //calculate novelty curve from magnitude data
|
c@4
|
429
|
c@9
|
430 //push novelty curve data to featureset 1 and set timestamps
|
c@14
|
431 for (unsigned int i = 0; i < numberOfBlocks; i++){
|
c@7
|
432 Feature feature;
|
c@13
|
433 feature.values.push_back(m_noveltyCurve[i]);
|
c@14
|
434 feature.hasTimestamp = false;
|
c@14
|
435 //feature.timestamp = ncTimestamps[i];
|
c@7
|
436 featureSet[1].push_back(feature);
|
c@4
|
437 }
|
c@4
|
438
|
c@9
|
439 //window function for spectrogram
|
c@14
|
440 WindowFunction::hanning(hannWindow, m_windowLength);
|
c@9
|
441
|
c@9
|
442 //initialise spectrogram processor
|
c@14
|
443 SpectrogramProcessor spectrogramProcessor(m_windowLength, m_fftLength, m_hopSize);
|
c@9
|
444 //compute spectrogram from novelty curve data (i.e., tempogram)
|
c@14
|
445 Spectrogram tempogram = spectrogramProcessor.process(&m_noveltyCurve[0], numberOfBlocks, hannWindow);
|
c@0
|
446
|
c@13
|
447 int timePointer = m_hopSize-m_windowLength/2;
|
c@14
|
448 int tempogramLength = tempogram.size();
|
c@7
|
449
|
c@9
|
450 //push tempogram data to featureset 0 and set timestamps.
|
c@7
|
451 for (int block = 0; block < tempogramLength; block++){
|
c@0
|
452 Feature feature;
|
c@0
|
453
|
c@14
|
454 //int timeMS = floor(1000*(m_stepSize*timePointer)/m_inputSampleRate + 0.5);
|
c@7
|
455
|
c@14
|
456 assert(tempogram[block].size() == (m_fftLength/2 + 1));
|
c@13
|
457 for(int k = m_minBin; k < (int)m_maxBin; k++){
|
c@14
|
458 feature.values.push_back(tempogram[block][k]);
|
c@13
|
459 //cout << tempogram[k][block] << endl;
|
c@0
|
460 }
|
c@14
|
461 feature.hasTimestamp = false;
|
c@14
|
462 //feature.timestamp = RealTime::fromMilliseconds(timeMS);
|
c@7
|
463 featureSet[0].push_back(feature);
|
c@4
|
464
|
c@13
|
465 timePointer += m_hopSize;
|
c@0
|
466 }
|
c@0
|
467
|
c@14
|
468 delete []hannWindow;
|
c@14
|
469 hannWindow = 0;
|
c@0
|
470
|
c@0
|
471 return featureSet;
|
c@0
|
472 }
|