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