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@0
|
20 compressionConstant(1000), //make param
|
c@0
|
21 previousY(NULL),
|
c@0
|
22 currentY(NULL),
|
c@0
|
23 tN(1024), //make param
|
c@0
|
24 thopSize(512), //make param
|
c@0
|
25 fftInput(NULL),
|
c@0
|
26 fftOutputReal(NULL),
|
c@0
|
27 fftOutputImag(NULL),
|
c@0
|
28 ncLength(0)
|
c@0
|
29
|
c@0
|
30 // Also be sure to set your plugin parameters (presumably stored
|
c@0
|
31 // in member variables) to their default values here -- the host
|
c@0
|
32 // will not do that for you
|
c@0
|
33 {
|
c@0
|
34 }
|
c@0
|
35
|
c@0
|
36 Tempogram::~Tempogram()
|
c@0
|
37 {
|
c@0
|
38 //delete stuff
|
c@0
|
39 }
|
c@0
|
40
|
c@0
|
41 string
|
c@0
|
42 Tempogram::getIdentifier() const
|
c@0
|
43 {
|
c@0
|
44 return "tempogram";
|
c@0
|
45 }
|
c@0
|
46
|
c@0
|
47 string
|
c@0
|
48 Tempogram::getName() const
|
c@0
|
49 {
|
c@0
|
50 return "Tempogram";
|
c@0
|
51 }
|
c@0
|
52
|
c@0
|
53 string
|
c@0
|
54 Tempogram::getDescription() const
|
c@0
|
55 {
|
c@0
|
56 // Return something helpful here!
|
c@0
|
57 return "Cyclic Tempogram as described by Peter Grosche and Meinard Muller";
|
c@0
|
58 }
|
c@0
|
59
|
c@0
|
60 string
|
c@0
|
61 Tempogram::getMaker() const
|
c@0
|
62 {
|
c@0
|
63 //Your name here
|
c@0
|
64 return "Carl Bussey";
|
c@0
|
65 }
|
c@0
|
66
|
c@0
|
67 int
|
c@0
|
68 Tempogram::getPluginVersion() const
|
c@0
|
69 {
|
c@0
|
70 // Increment this each time you release a version that behaves
|
c@0
|
71 // differently from the previous one
|
c@0
|
72 return 1;
|
c@0
|
73 }
|
c@0
|
74
|
c@0
|
75 string
|
c@0
|
76 Tempogram::getCopyright() const
|
c@0
|
77 {
|
c@0
|
78 // This function is not ideally named. It does not necessarily
|
c@0
|
79 // need to say who made the plugin -- getMaker does that -- but it
|
c@0
|
80 // should indicate the terms under which it is distributed. For
|
c@0
|
81 // example, "Copyright (year). All Rights Reserved", or "GPL"
|
c@0
|
82 return "";
|
c@0
|
83 }
|
c@0
|
84
|
c@0
|
85 Tempogram::InputDomain
|
c@0
|
86 Tempogram::getInputDomain() const
|
c@0
|
87 {
|
c@0
|
88 return FrequencyDomain;
|
c@0
|
89 }
|
c@0
|
90
|
c@0
|
91 size_t
|
c@0
|
92 Tempogram::getPreferredBlockSize() const
|
c@0
|
93 {
|
c@0
|
94 return 0; // 0 means "I can handle any block size"
|
c@0
|
95 }
|
c@0
|
96
|
c@0
|
97 size_t
|
c@0
|
98 Tempogram::getPreferredStepSize() const
|
c@0
|
99 {
|
c@0
|
100 //23 ms?
|
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@0
|
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@0
|
213
|
c@0
|
214 OutputDescriptor d;
|
c@0
|
215 d.identifier = "output";
|
c@0
|
216 d.name = "Cyclic Tempogram";
|
c@0
|
217 d.description = "Cyclic Tempogram";
|
c@0
|
218 d.unit = "";
|
c@0
|
219 d.hasFixedBinCount = false;
|
c@0
|
220 //d.binCount = 1;
|
c@0
|
221 d.hasKnownExtents = false;
|
c@0
|
222 d.isQuantized = false;
|
c@0
|
223 d.sampleType = OutputDescriptor::VariableSampleRate;
|
c@0
|
224 d.sampleRate = 0.0;
|
c@0
|
225 d.hasDuration = false;
|
c@0
|
226 list.push_back(d);
|
c@0
|
227
|
c@0
|
228 return list;
|
c@0
|
229 }
|
c@0
|
230
|
c@0
|
231 bool
|
c@0
|
232 Tempogram::initialise(size_t channels, size_t stepSize, size_t blockSize)
|
c@0
|
233 {
|
c@0
|
234 if (channels < getMinChannelCount() ||
|
c@0
|
235 channels > getMaxChannelCount()) return false;
|
c@0
|
236
|
c@0
|
237 // Real initialisation work goes here!
|
c@0
|
238 m_blockSize = blockSize;
|
c@0
|
239 currentY = new float[m_blockSize];
|
c@0
|
240 previousY = new float[m_blockSize];
|
c@0
|
241
|
c@0
|
242 return true;
|
c@0
|
243 }
|
c@0
|
244
|
c@0
|
245 void
|
c@0
|
246 Tempogram::reset()
|
c@0
|
247 {
|
c@0
|
248 // Clear buffers, reset stored values, etc
|
c@0
|
249 }
|
c@0
|
250
|
c@0
|
251 Tempogram::FeatureSet
|
c@0
|
252 Tempogram::process(const float *const *inputBuffers, Vamp::RealTime timestamp)
|
c@0
|
253 {
|
c@0
|
254 size_t n = m_blockSize/2 + 1;
|
c@0
|
255
|
c@0
|
256 FeatureSet featureSet;
|
c@0
|
257 Feature feature;
|
c@0
|
258 feature.hasTimestamp = false;
|
c@0
|
259
|
c@0
|
260 const float *in = inputBuffers[0];
|
c@0
|
261
|
c@0
|
262 //Calculate log magnitude
|
c@0
|
263 float sum = 0;
|
c@0
|
264 for (int i = 0; i < n; i++){
|
c@0
|
265 float magnitude = sqrt(in[2*i] * in[2*i] + in[2*i + 1] * in[2*i + 1]);
|
c@0
|
266 currentY[i] = log(1+compressionConstant*magnitude); //should be 1+C*magnitude
|
c@0
|
267 if(currentY[i] >= previousY[i]){
|
c@0
|
268 sum += (currentY[i] - previousY[i]);
|
c@0
|
269 }
|
c@0
|
270 }
|
c@0
|
271
|
c@0
|
272 noveltyCurve.push_back(sum);
|
c@0
|
273
|
c@0
|
274 float *tmpY = currentY;
|
c@0
|
275 currentY = previousY;
|
c@0
|
276 previousY = tmpY;
|
c@0
|
277
|
c@0
|
278 ncTimestamps.push_back(timestamp);
|
c@0
|
279
|
c@0
|
280 return FeatureSet();
|
c@0
|
281 }
|
c@0
|
282
|
c@0
|
283 void
|
c@0
|
284 Tempogram::initialiseForGRF(){
|
c@0
|
285 hannN = 129;
|
c@0
|
286 hannWindow = new float[hannN];
|
c@0
|
287 hannWindowtN = new float[tN];
|
c@0
|
288 fftInput = new double[tN];
|
c@0
|
289 fftOutputReal = new double[tN];
|
c@0
|
290 fftOutputImag = new double[tN];
|
c@0
|
291 ncLength = noveltyCurve.size();
|
c@0
|
292
|
c@0
|
293 WindowFunction::hanning(hannWindow, hannN, true);
|
c@0
|
294 }
|
c@0
|
295
|
c@0
|
296 void
|
c@0
|
297 Tempogram::cleanupForGRF(){
|
c@0
|
298 delete []hannWindow;
|
c@0
|
299 hannWindow = NULL;
|
c@0
|
300 delete []hannWindowtN;
|
c@0
|
301 hannWindowtN = NULL;
|
c@0
|
302 delete []fftInput;
|
c@0
|
303 fftInput = NULL;
|
c@0
|
304 delete []fftOutputReal;
|
c@0
|
305 fftOutputReal = NULL;
|
c@0
|
306 delete []fftOutputImag;
|
c@0
|
307 fftOutputImag = NULL;
|
c@0
|
308 }
|
c@0
|
309
|
c@0
|
310 Tempogram::FeatureSet
|
c@0
|
311 Tempogram::getRemainingFeatures()
|
c@0
|
312 {
|
c@0
|
313 //Make sure this is called at the beginning of the function
|
c@0
|
314 initialiseForGRF();
|
c@0
|
315
|
c@0
|
316 vector<float> noveltyCurveLocalAverage(ncLength);
|
c@0
|
317
|
c@0
|
318 FIRFilter *filter = new FIRFilter(ncLength, hannN);
|
c@0
|
319 filter->process(&noveltyCurve[0], hannWindow, &noveltyCurveLocalAverage[0]);
|
c@0
|
320 delete filter;
|
c@0
|
321
|
c@0
|
322 for(int i = 0; i < ncLength; i++){
|
c@0
|
323 noveltyCurve[i] -= noveltyCurveLocalAverage[i];
|
c@0
|
324 noveltyCurve[i] = noveltyCurve[i] >= 0 ? noveltyCurve[i] : 0;
|
c@0
|
325 }
|
c@0
|
326
|
c@0
|
327 int i=0;
|
c@0
|
328 WindowFunction::hanning(hannWindowtN, tN);
|
c@0
|
329
|
c@0
|
330 int index;
|
c@0
|
331 int start = floor(tN/2 + 0.5);
|
c@0
|
332 int timestampInc = floor((((float)ncTimestamps[1].nsec - ncTimestamps[0].nsec)/1e9)*(thopSize) + 0.5);
|
c@0
|
333 //cout << timestampInc << endl;
|
c@0
|
334
|
c@0
|
335 FeatureSet featureSet;
|
c@0
|
336
|
c@0
|
337 while(i < ncLength){
|
c@0
|
338 Feature feature;
|
c@0
|
339 Vamp::RealTime timestamp;
|
c@0
|
340
|
c@0
|
341 for (int n = start; n < tN; n++){
|
c@0
|
342 index = i + n - tN/2;
|
c@0
|
343 assert (index >= 0);
|
c@0
|
344
|
c@0
|
345 if(index < ncLength){
|
c@0
|
346 fftInput[n] = noveltyCurve[i + n] * hannWindowtN[n];
|
c@0
|
347 }
|
c@0
|
348 else if(index >= ncLength){
|
c@0
|
349 fftInput[n] = 0.0; //pad the end with zeros
|
c@0
|
350 }
|
c@0
|
351 //cout << fftInput[n] << endl;
|
c@0
|
352 }
|
c@0
|
353 if (i+tN/2 > ncLength){
|
c@0
|
354 timestamp = Vamp::RealTime::fromSeconds(ncTimestamps[i].sec + timestampInc);
|
c@0
|
355 }
|
c@0
|
356 else{
|
c@0
|
357 timestamp = ncTimestamps[i + tN/2];
|
c@0
|
358 }
|
c@0
|
359
|
c@0
|
360 FFT::forward(tN, fftInput, NULL, fftOutputReal, fftOutputImag);
|
c@0
|
361
|
c@0
|
362 //TODO: sample at logarithmic spacing
|
c@0
|
363 for(int k = 0; k < tN; k++){
|
c@0
|
364 double fftOutputPower = (fftOutputReal[k]*fftOutputReal[k] + fftOutputImag[k]*fftOutputImag[k]); //Magnitude or power?
|
c@0
|
365 assert (!isinf(fftOutputPower));
|
c@0
|
366
|
c@0
|
367 feature.values.push_back(fftOutputPower);
|
c@0
|
368 }
|
c@0
|
369
|
c@0
|
370 i += thopSize;
|
c@0
|
371 start = 0;
|
c@0
|
372
|
c@0
|
373 feature.timestamp = timestamp;
|
c@0
|
374 feature.hasTimestamp = true;
|
c@0
|
375 featureSet[0].push_back(feature);
|
c@0
|
376 }
|
c@0
|
377
|
c@0
|
378 //Make sure this is called at the end of the function
|
c@0
|
379 cleanupForGRF();
|
c@0
|
380
|
c@0
|
381 return featureSet;
|
c@0
|
382 }
|