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@0
|
7 #include "Tempogram.h"
|
c@0
|
8 #include "FIRFilter.h"
|
c@0
|
9 #include "WindowFunction.h"
|
c@0
|
10 #include <vamp-sdk/FFT.h>
|
c@0
|
11 #include <cmath>
|
c@0
|
12 #include <fstream>
|
c@0
|
13 #include <assert.h>
|
c@0
|
14 using Vamp::FFT;
|
c@0
|
15 using namespace std;
|
c@0
|
16
|
c@0
|
17 Tempogram::Tempogram(float inputSampleRate) :
|
c@0
|
18 Plugin(inputSampleRate),
|
c@0
|
19 m_blockSize(0),
|
c@1
|
20 m_stepSize(0),
|
c@0
|
21 compressionConstant(1000), //make param
|
c@3
|
22 specMax(0),
|
c@0
|
23 previousY(NULL),
|
c@0
|
24 currentY(NULL),
|
c@3
|
25 minDB(0),
|
c@0
|
26 tN(1024), //make param
|
c@0
|
27 thopSize(512), //make param
|
c@0
|
28 fftInput(NULL),
|
c@0
|
29 fftOutputReal(NULL),
|
c@0
|
30 fftOutputImag(NULL),
|
c@3
|
31 numberOfBlocks(0),
|
c@3
|
32 hannN(0)
|
c@0
|
33
|
c@0
|
34 // Also be sure to set your plugin parameters (presumably stored
|
c@0
|
35 // in member variables) to their default values here -- the host
|
c@0
|
36 // will not do that for you
|
c@0
|
37 {
|
c@0
|
38 }
|
c@0
|
39
|
c@0
|
40 Tempogram::~Tempogram()
|
c@0
|
41 {
|
c@0
|
42 //delete stuff
|
c@0
|
43 }
|
c@0
|
44
|
c@0
|
45 string
|
c@0
|
46 Tempogram::getIdentifier() const
|
c@0
|
47 {
|
c@0
|
48 return "tempogram";
|
c@0
|
49 }
|
c@0
|
50
|
c@0
|
51 string
|
c@0
|
52 Tempogram::getName() const
|
c@0
|
53 {
|
c@0
|
54 return "Tempogram";
|
c@0
|
55 }
|
c@0
|
56
|
c@0
|
57 string
|
c@0
|
58 Tempogram::getDescription() const
|
c@0
|
59 {
|
c@0
|
60 // Return something helpful here!
|
c@0
|
61 return "Cyclic Tempogram as described by Peter Grosche and Meinard Muller";
|
c@0
|
62 }
|
c@0
|
63
|
c@0
|
64 string
|
c@0
|
65 Tempogram::getMaker() const
|
c@0
|
66 {
|
c@0
|
67 //Your name here
|
c@0
|
68 return "Carl Bussey";
|
c@0
|
69 }
|
c@0
|
70
|
c@0
|
71 int
|
c@0
|
72 Tempogram::getPluginVersion() const
|
c@0
|
73 {
|
c@0
|
74 // Increment this each time you release a version that behaves
|
c@0
|
75 // differently from the previous one
|
c@0
|
76 return 1;
|
c@0
|
77 }
|
c@0
|
78
|
c@0
|
79 string
|
c@0
|
80 Tempogram::getCopyright() const
|
c@0
|
81 {
|
c@0
|
82 // This function is not ideally named. It does not necessarily
|
c@0
|
83 // need to say who made the plugin -- getMaker does that -- but it
|
c@0
|
84 // should indicate the terms under which it is distributed. For
|
c@0
|
85 // example, "Copyright (year). All Rights Reserved", or "GPL"
|
c@0
|
86 return "";
|
c@0
|
87 }
|
c@0
|
88
|
c@0
|
89 Tempogram::InputDomain
|
c@0
|
90 Tempogram::getInputDomain() const
|
c@0
|
91 {
|
c@0
|
92 return FrequencyDomain;
|
c@0
|
93 }
|
c@0
|
94
|
c@0
|
95 size_t
|
c@0
|
96 Tempogram::getPreferredBlockSize() const
|
c@0
|
97 {
|
c@0
|
98 return 0; // 0 means "I can handle any block size"
|
c@0
|
99 }
|
c@0
|
100
|
c@0
|
101 size_t
|
c@0
|
102 Tempogram::getPreferredStepSize() const
|
c@0
|
103 {
|
c@0
|
104 return 0; // 0 means "anything sensible"; in practice this
|
c@0
|
105 // means the same as the block size for TimeDomain
|
c@0
|
106 // plugins, or half of it for FrequencyDomain plugins
|
c@0
|
107 }
|
c@0
|
108
|
c@0
|
109 size_t
|
c@0
|
110 Tempogram::getMinChannelCount() const
|
c@0
|
111 {
|
c@0
|
112 return 1;
|
c@0
|
113 }
|
c@0
|
114
|
c@0
|
115 size_t
|
c@0
|
116 Tempogram::getMaxChannelCount() const
|
c@0
|
117 {
|
c@0
|
118 return 1;
|
c@0
|
119 }
|
c@0
|
120
|
c@0
|
121 Tempogram::ParameterList
|
c@0
|
122 Tempogram::getParameterDescriptors() const
|
c@0
|
123 {
|
c@0
|
124 ParameterList list;
|
c@0
|
125
|
c@0
|
126 // If the plugin has no adjustable parameters, return an empty
|
c@0
|
127 // list here (and there's no need to provide implementations of
|
c@0
|
128 // getParameter and setParameter in that case either).
|
c@0
|
129
|
c@0
|
130 // Note that it is your responsibility to make sure the parameters
|
c@0
|
131 // start off having their default values (e.g. in the constructor
|
c@0
|
132 // above). The host needs to know the default value so it can do
|
c@0
|
133 // things like provide a "reset to default" function, but it will
|
c@0
|
134 // not explicitly set your parameters to their defaults for you if
|
c@0
|
135 // they have not changed in the mean time.
|
c@0
|
136
|
c@0
|
137 ParameterDescriptor C;
|
c@0
|
138 C.identifier = "C";
|
c@0
|
139 C.name = "C";
|
c@0
|
140 C.description = "Spectrogram compression constant, C";
|
c@0
|
141 C.unit = "";
|
c@0
|
142 C.minValue = 2;
|
c@0
|
143 C.maxValue = 10000;
|
c@0
|
144 C.defaultValue = 1000;
|
c@0
|
145 C.isQuantized = false;
|
c@0
|
146 list.push_back(C);
|
c@0
|
147
|
c@0
|
148 ParameterDescriptor tN;
|
c@0
|
149 tN.identifier = "tN";
|
c@0
|
150 tN.name = "Tempogram FFT length";
|
c@0
|
151 tN.description = "Tempogram FFT length.";
|
c@0
|
152 tN.unit = "";
|
c@0
|
153 tN.minValue = 128;
|
c@0
|
154 tN.maxValue = 4096;
|
c@0
|
155 tN.defaultValue = 1024;
|
c@0
|
156 tN.isQuantized = true;
|
c@0
|
157 tN.quantizeStep = 128;
|
c@0
|
158 list.push_back(tN);
|
c@0
|
159
|
c@0
|
160 return list;
|
c@0
|
161 }
|
c@0
|
162
|
c@0
|
163 float
|
c@0
|
164 Tempogram::getParameter(string identifier) const
|
c@0
|
165 {
|
c@0
|
166 if (identifier == "C") {
|
c@0
|
167 return compressionConstant; // return the ACTUAL current value of your parameter here!
|
c@0
|
168 }
|
c@0
|
169 if (identifier == "tN"){
|
c@0
|
170 return tN;
|
c@0
|
171 }
|
c@0
|
172
|
c@0
|
173 return 0;
|
c@0
|
174 }
|
c@0
|
175
|
c@0
|
176 void
|
c@0
|
177 Tempogram::setParameter(string identifier, float value)
|
c@0
|
178 {
|
c@0
|
179 if (identifier == "C") {
|
c@1
|
180 compressionConstant = value; // set the actual value of your parameter
|
c@0
|
181 }
|
c@0
|
182 if (identifier == "tN") {
|
c@0
|
183 tN = value;
|
c@0
|
184 }
|
c@0
|
185 }
|
c@0
|
186
|
c@0
|
187 Tempogram::ProgramList
|
c@0
|
188 Tempogram::getPrograms() const
|
c@0
|
189 {
|
c@0
|
190 ProgramList list;
|
c@0
|
191
|
c@0
|
192 // If you have no programs, return an empty list (or simply don't
|
c@0
|
193 // implement this function or getCurrentProgram/selectProgram)
|
c@0
|
194
|
c@0
|
195 return list;
|
c@0
|
196 }
|
c@0
|
197
|
c@0
|
198 string
|
c@0
|
199 Tempogram::getCurrentProgram() const
|
c@0
|
200 {
|
c@0
|
201 return ""; // no programs
|
c@0
|
202 }
|
c@0
|
203
|
c@0
|
204 void
|
c@0
|
205 Tempogram::selectProgram(string name)
|
c@0
|
206 {
|
c@0
|
207 }
|
c@0
|
208
|
c@0
|
209 Tempogram::OutputList
|
c@0
|
210 Tempogram::getOutputDescriptors() const
|
c@0
|
211 {
|
c@0
|
212 OutputList list;
|
c@0
|
213
|
c@0
|
214 // See OutputDescriptor documentation for the possibilities here.
|
c@0
|
215 // Every plugin must have at least one output.
|
c@1
|
216
|
c@0
|
217 OutputDescriptor d;
|
c@1
|
218 d.identifier = "tempogram";
|
c@0
|
219 d.name = "Cyclic Tempogram";
|
c@0
|
220 d.description = "Cyclic Tempogram";
|
c@0
|
221 d.unit = "";
|
c@1
|
222 d.hasFixedBinCount = true;
|
c@1
|
223 d.binCount = tN;
|
c@0
|
224 d.hasKnownExtents = false;
|
c@0
|
225 d.isQuantized = false;
|
c@1
|
226 d.sampleType = OutputDescriptor::FixedSampleRate;
|
c@1
|
227 float d_sampleRate = m_inputSampleRate/(m_stepSize * thopSize);
|
c@1
|
228 d.sampleRate = d_sampleRate > 0.0 && !isnan(d_sampleRate) ? d_sampleRate : 0.0;
|
c@0
|
229 d.hasDuration = false;
|
c@0
|
230 list.push_back(d);
|
c@0
|
231
|
c@1
|
232 d.identifier = "nc";
|
c@1
|
233 d.name = "Novelty Curve";
|
c@1
|
234 d.description = "Novelty Curve";
|
c@1
|
235 d.unit = "";
|
c@1
|
236 d.hasFixedBinCount = true;
|
c@1
|
237 d.binCount = 1;
|
c@1
|
238 d.hasKnownExtents = false;
|
c@1
|
239 d.isQuantized = false;
|
c@1
|
240 d.sampleType = OutputDescriptor::FixedSampleRate;
|
c@1
|
241 d_sampleRate = m_inputSampleRate/m_stepSize;
|
c@1
|
242 d.sampleRate = d_sampleRate > 0 && !isnan(d_sampleRate) ? d_sampleRate : 0.0;
|
c@1
|
243 d.hasDuration = false;
|
c@1
|
244 list.push_back(d);
|
c@1
|
245
|
c@2
|
246 d.identifier = "spect";
|
c@2
|
247 d.name = "spect";
|
c@2
|
248 d.description = "spect";
|
c@2
|
249 d.unit = "";
|
c@2
|
250 d.hasFixedBinCount = true;
|
c@2
|
251 d.binCount = m_blockSize/2;
|
c@2
|
252 d.hasKnownExtents = false;
|
c@2
|
253 d.isQuantized = false;
|
c@2
|
254 d.sampleType = OutputDescriptor::OneSamplePerStep;
|
c@2
|
255 d.hasDuration = false;
|
c@2
|
256 list.push_back(d);
|
c@2
|
257
|
c@0
|
258 return list;
|
c@0
|
259 }
|
c@0
|
260
|
c@0
|
261 bool
|
c@0
|
262 Tempogram::initialise(size_t channels, size_t stepSize, size_t blockSize)
|
c@0
|
263 {
|
c@0
|
264 if (channels < getMinChannelCount() ||
|
c@0
|
265 channels > getMaxChannelCount()) return false;
|
c@0
|
266
|
c@0
|
267 // Real initialisation work goes here!
|
c@0
|
268 m_blockSize = blockSize;
|
c@1
|
269 m_stepSize = stepSize;
|
c@0
|
270 currentY = new float[m_blockSize];
|
c@0
|
271 previousY = new float[m_blockSize];
|
c@3
|
272 minDB = pow((float)10,(float)-74/20);
|
c@0
|
273
|
c@0
|
274 return true;
|
c@0
|
275 }
|
c@0
|
276
|
c@0
|
277 void
|
c@0
|
278 Tempogram::reset()
|
c@0
|
279 {
|
c@0
|
280 // Clear buffers, reset stored values, etc
|
c@0
|
281 }
|
c@0
|
282
|
c@0
|
283 Tempogram::FeatureSet
|
c@0
|
284 Tempogram::process(const float *const *inputBuffers, Vamp::RealTime timestamp)
|
c@0
|
285 {
|
c@0
|
286 size_t n = m_blockSize/2 + 1;
|
c@0
|
287
|
c@0
|
288 FeatureSet featureSet;
|
c@0
|
289 Feature feature;
|
c@0
|
290
|
c@0
|
291 const float *in = inputBuffers[0];
|
c@3
|
292
|
c@0
|
293 for (int i = 0; i < n; i++){
|
c@0
|
294 float magnitude = sqrt(in[2*i] * in[2*i] + in[2*i + 1] * in[2*i + 1]);
|
c@3
|
295 magnitude = magnitude > minDB ? magnitude : minDB;
|
c@3
|
296 specData.push_back(magnitude);
|
c@2
|
297 feature.values.push_back(magnitude);
|
c@3
|
298
|
c@3
|
299 specMax = specMax > magnitude ? specMax : magnitude;
|
c@0
|
300 }
|
c@0
|
301
|
c@3
|
302 numberOfBlocks++;
|
c@0
|
303 ncTimestamps.push_back(timestamp);
|
c@2
|
304 featureSet[2].push_back(feature);
|
c@0
|
305
|
c@2
|
306 return featureSet;
|
c@0
|
307 }
|
c@0
|
308
|
c@0
|
309 void
|
c@0
|
310 Tempogram::initialiseForGRF(){
|
c@0
|
311 hannN = 129;
|
c@0
|
312 hannWindow = new float[hannN];
|
c@0
|
313 hannWindowtN = new float[tN];
|
c@0
|
314 fftInput = new double[tN];
|
c@0
|
315 fftOutputReal = new double[tN];
|
c@0
|
316 fftOutputImag = new double[tN];
|
c@0
|
317
|
c@0
|
318 WindowFunction::hanning(hannWindow, hannN, true);
|
c@0
|
319 }
|
c@0
|
320
|
c@0
|
321 void
|
c@0
|
322 Tempogram::cleanupForGRF(){
|
c@0
|
323 delete []hannWindow;
|
c@0
|
324 hannWindow = NULL;
|
c@0
|
325 delete []hannWindowtN;
|
c@0
|
326 hannWindowtN = NULL;
|
c@0
|
327 delete []fftInput;
|
c@0
|
328 fftInput = NULL;
|
c@0
|
329 delete []fftOutputReal;
|
c@0
|
330 fftOutputReal = NULL;
|
c@0
|
331 delete []fftOutputImag;
|
c@0
|
332 fftOutputImag = NULL;
|
c@0
|
333 }
|
c@0
|
334
|
c@3
|
335 vector<float>
|
c@3
|
336 Tempogram::spectrogramToNoveltyCurve(vector<float> spectrogram, int numberOfBlocks, int blockSize, float samplingFrequency, FeatureSet * featureSet){
|
c@3
|
337 int numberOfBands = 5;
|
c@3
|
338
|
c@3
|
339 for (int block = 0; block < numberOfBlocks; block++){
|
c@3
|
340 vector<float> sum = vector<float>(numberOfBands);
|
c@3
|
341
|
c@3
|
342 int band = 0;
|
c@3
|
343 for (int k = 0; k < blockSize; k++){
|
c@3
|
344 int index = block*blockSize + k;
|
c@3
|
345
|
c@3
|
346 if(index > 500*pow(2.5, band))
|
c@3
|
347 if(band < numberOfBands)
|
c@3
|
348 band++;
|
c@3
|
349
|
c@3
|
350 specData[index] = log(1+compressionConstant*(specData[index]/specMax));
|
c@3
|
351
|
c@3
|
352 float currentY = specData[index];
|
c@3
|
353 float prevI = index - m_blockSize;
|
c@3
|
354 float previousY = prevI >= 0 ? specData[prevI] : 0;
|
c@3
|
355
|
c@3
|
356 if(currentY >= previousY){
|
c@3
|
357 sum[band] += (currentY - previousY);
|
c@3
|
358 }
|
c@3
|
359 }
|
c@3
|
360
|
c@3
|
361 float total = 0;
|
c@3
|
362 for(int band = 0; band < numberOfBands; band++){
|
c@3
|
363 total += sum[band];
|
c@3
|
364 }
|
c@3
|
365 float average = total/numberOfBands;
|
c@3
|
366
|
c@3
|
367 noveltyCurve.push_back(average);
|
c@3
|
368 }
|
c@3
|
369
|
c@3
|
370 vector<float> noveltyCurveLocalAverage(numberOfBlocks);
|
c@3
|
371
|
c@3
|
372 FIRFilter *filter = new FIRFilter(numberOfBlocks, hannN);
|
c@3
|
373 filter->process(&noveltyCurve[0], hannWindow, &noveltyCurveLocalAverage[0]);
|
c@3
|
374 delete filter;
|
c@3
|
375
|
c@3
|
376 for (int i = 0; i < numberOfBlocks; i++){
|
c@3
|
377
|
c@3
|
378 noveltyCurve[i] -= noveltyCurveLocalAverage[i];
|
c@3
|
379 noveltyCurve[i] = noveltyCurve[i] >= 0 ? noveltyCurve[i] : 0;
|
c@3
|
380
|
c@3
|
381 if(featureSet != NULL){
|
c@3
|
382 Feature ncFeature;
|
c@3
|
383 ncFeature.hasTimestamp = true;
|
c@3
|
384 ncFeature.timestamp = ncTimestamps[i];
|
c@3
|
385 ncFeature.values.push_back(noveltyCurve[i]);
|
c@3
|
386 (*featureSet)[1].push_back(ncFeature);
|
c@3
|
387 }
|
c@3
|
388 }
|
c@3
|
389
|
c@3
|
390 return noveltyCurve;
|
c@3
|
391 }
|
c@3
|
392
|
c@0
|
393 Tempogram::FeatureSet
|
c@0
|
394 Tempogram::getRemainingFeatures()
|
c@0
|
395 {
|
c@0
|
396 //Make sure this is called at the beginning of the function
|
c@0
|
397 initialiseForGRF();
|
c@1
|
398 FeatureSet featureSet;
|
c@0
|
399
|
c@3
|
400 noveltyCurve = spectrogramToNoveltyCurve(specData, numberOfBlocks, m_blockSize, m_inputSampleRate, &featureSet);
|
c@0
|
401 WindowFunction::hanning(hannWindowtN, tN);
|
c@0
|
402
|
c@2
|
403 int timestampInc = floor((((float)ncTimestamps[1].nsec - ncTimestamps[0].nsec)/1e9)*(thopSize) + 0.5);
|
c@3
|
404 int i = 0;
|
c@0
|
405 int index;
|
c@1
|
406 int frameBeginOffset = floor(tN/2 + 0.5);
|
c@0
|
407
|
c@3
|
408 while(i < numberOfBlocks){
|
c@0
|
409 Feature feature;
|
c@0
|
410
|
c@1
|
411 for (int n = frameBeginOffset; n < tN; n++){
|
c@0
|
412 index = i + n - tN/2;
|
c@0
|
413 assert (index >= 0);
|
c@0
|
414
|
c@3
|
415 if(index < numberOfBlocks){
|
c@0
|
416 fftInput[n] = noveltyCurve[i + n] * hannWindowtN[n];
|
c@0
|
417 }
|
c@3
|
418 else if(index >= numberOfBlocks){
|
c@0
|
419 fftInput[n] = 0.0; //pad the end with zeros
|
c@0
|
420 }
|
c@0
|
421 //cout << fftInput[n] << endl;
|
c@0
|
422 }
|
c@3
|
423 if (i+tN/2 > numberOfBlocks){
|
c@1
|
424 feature.timestamp = Vamp::RealTime::fromSeconds(ncTimestamps[i].sec + timestampInc);
|
c@0
|
425 }
|
c@0
|
426 else{
|
c@1
|
427 feature.timestamp = ncTimestamps[i + tN/2];
|
c@0
|
428 }
|
c@0
|
429
|
c@0
|
430 FFT::forward(tN, fftInput, NULL, fftOutputReal, fftOutputImag);
|
c@0
|
431
|
c@0
|
432 //TODO: sample at logarithmic spacing
|
c@0
|
433 for(int k = 0; k < tN; k++){
|
c@1
|
434 float fftOutputPower = (fftOutputReal[k]*fftOutputReal[k] + fftOutputImag[k]*fftOutputImag[k]); //Magnitude or power?
|
c@0
|
435
|
c@0
|
436 feature.values.push_back(fftOutputPower);
|
c@0
|
437 }
|
c@0
|
438
|
c@0
|
439 i += thopSize;
|
c@1
|
440 frameBeginOffset = 0;
|
c@0
|
441
|
c@0
|
442 feature.hasTimestamp = true;
|
c@0
|
443 featureSet[0].push_back(feature);
|
c@0
|
444 }
|
c@0
|
445
|
c@0
|
446 //Make sure this is called at the end of the function
|
c@0
|
447 cleanupForGRF();
|
c@0
|
448
|
c@0
|
449 return featureSet;
|
c@0
|
450 }
|