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