comparison Tempogram.cpp @ 9:be59b4a73f49

* Added Spectrogram zero padding functionality * Made output bins correspond to BPM * User can now specify a range of output bins to view * Comments added
author Carl Bussey <c.bussey@se10.qmul.ac.uk>
date Tue, 12 Aug 2014 14:40:37 +0100
parents 4e429b9f2b4d
children 09fb76606b2b
comparison
equal deleted inserted replaced
8:4e429b9f2b4d 9:be59b4a73f49
3 // libraries. Replace MyPlugin and myPlugin throughout with the name 3 // libraries. Replace MyPlugin and myPlugin throughout with the name
4 // of your first plugin class, and fill in the gaps as appropriate. 4 // of your first plugin class, and fill in the gaps as appropriate.
5 5
6 6
7 #include "Tempogram.h" 7 #include "Tempogram.h"
8 #include <sstream>
9 #include <stdexcept>
8 10
9 using Vamp::FFT; 11 using Vamp::FFT;
10 using Vamp::RealTime; 12 using Vamp::RealTime;
11 using namespace std; 13 using namespace std;
12 14
13 Tempogram::Tempogram(float inputSampleRate) : 15 Tempogram::Tempogram(float inputSampleRate) :
14 Plugin(inputSampleRate), 16 Plugin(inputSampleRate),
15 m_blockSize(0), 17 m_blockSize(0),
16 m_stepSize(0), 18 m_stepSize(0),
17 compressionConstant(1000), //make param 19 compressionConstant(1000), //parameter
18 specMax(0),
19 minDB(0), 20 minDB(0),
20 tN(128), //make param 21 windowLength(128), //parameter
21 thopSize(64), //make param 22 fftLength(4096), //parameter
22 fftInput(NULL), 23 thopSize(64), //parameter
23 fftOutputReal(NULL), 24 minBPM(30),
24 fftOutputImag(NULL), 25 maxBPM(480),
25 numberOfBlocks(0) 26 minBin(0), //set in initialise()
27 maxBin(0), //set in initialise()
28 numberOfBlocks(0) //incremented in process()
26 29
27 // Also be sure to set your plugin parameters (presumably stored 30 // Also be sure to set your plugin parameters (presumably stored
28 // in member variables) to their default values here -- the host 31 // in member variables) to their default values here -- the host
29 // will not do that for you 32 // will not do that for you
30 { 33 {
87 } 90 }
88 91
89 size_t 92 size_t
90 Tempogram::getPreferredBlockSize() const 93 Tempogram::getPreferredBlockSize() const
91 { 94 {
92 return 0; // 0 means "I can handle any block size" 95 return 2048; // 0 means "I can handle any block size"
93 } 96 }
94 97
95 size_t 98 size_t
96 Tempogram::getPreferredStepSize() const 99 Tempogram::getPreferredStepSize() const
97 { 100 {
98 return 0; // 0 means "anything sensible"; in practice this 101 return 1024; // 0 means "anything sensible"; in practice this
99 // means the same as the block size for TimeDomain 102 // means the same as the block size for TimeDomain
100 // plugins, or half of it for FrequencyDomain plugins 103 // plugins, or half of it for FrequencyDomain plugins
101 } 104 }
102 105
103 size_t 106 size_t
126 // above). The host needs to know the default value so it can do 129 // above). The host needs to know the default value so it can do
127 // things like provide a "reset to default" function, but it will 130 // things like provide a "reset to default" function, but it will
128 // not explicitly set your parameters to their defaults for you if 131 // not explicitly set your parameters to their defaults for you if
129 // they have not changed in the mean time. 132 // they have not changed in the mean time.
130 133
131 ParameterDescriptor C; 134 ParameterDescriptor d;
132 C.identifier = "C"; 135 d.identifier = "C";
133 C.name = "C"; 136 d.name = "C";
134 C.description = "Spectrogram compression constant, C"; 137 d.description = "Spectrogram compression constant, C, used when retrieving the novelty curve from the audio.";
135 C.unit = ""; 138 d.unit = "";
136 C.minValue = 2; 139 d.minValue = 2;
137 C.maxValue = 10000; 140 d.maxValue = 10000;
138 C.defaultValue = 1000; 141 d.defaultValue = 1000;
139 C.isQuantized = false; 142 d.isQuantized = false;
140 list.push_back(C); 143 list.push_back(d);
141 144
142 ParameterDescriptor tN; 145 d.identifier = "TN";
143 tN.identifier = "tN"; 146 d.name = "Tempogram Window Length";
144 tN.name = "Tempogram FFT length"; 147 d.description = "FFT window length when analysing the novelty curve and extracting the tempogram time-frequency function.";
145 tN.description = "Tempogram FFT length."; 148 d.unit = "";
146 tN.unit = ""; 149 d.minValue = 1024;
147 tN.minValue = 128; 150 d.maxValue = 4096;
148 tN.maxValue = 4096; 151 d.defaultValue = 128;
149 tN.defaultValue = 128; 152 d.isQuantized = true;
150 tN.isQuantized = true; 153 d.quantizeStep = 128;
151 tN.quantizeStep = 128; 154 list.push_back(d);
152 list.push_back(tN); 155
156 d.identifier = "minBPM";
157 d.name = "Minimum BPM";
158 d.description = "The minimum BPM of the tempogram output bins.";
159 d.unit = "";
160 d.minValue = 0;
161 d.maxValue = 2000;
162 d.defaultValue = 30;
163 d.isQuantized = true;
164 d.quantizeStep = 5;
165 list.push_back(d);
166
167 d.identifier = "maxBPM";
168 d.name = "Maximum BPM";
169 d.description = "The minimum BPM of the tempogram output bins.";
170 d.unit = "";
171 d.minValue = 30;
172 d.maxValue = 2000;
173 d.defaultValue = 480;
174 d.isQuantized = true;
175 d.quantizeStep = 5;
176 list.push_back(d);
153 177
154 return list; 178 return list;
155 } 179 }
156 180
157 float 181 float
158 Tempogram::getParameter(string identifier) const 182 Tempogram::getParameter(string identifier) const
159 { 183 {
160 if (identifier == "C") { 184 if (identifier == "C") {
161 return compressionConstant; // return the ACTUAL current value of your parameter here! 185 return compressionConstant; // return the ACTUAL current value of your parameter here!
162 } 186 }
163 if (identifier == "tN"){ 187 if (identifier == "TN"){
164 return tN; 188 return windowLength;
189 }
190 if (identifier == "minBPM") {
191 return minBPM;
192 }
193 if (identifier == "maxBPM"){
194 return maxBPM;
165 } 195 }
166 196
167 return 0; 197 return 0;
168 } 198 }
169 199
170 void 200 void
171 Tempogram::setParameter(string identifier, float value) 201 Tempogram::setParameter(string identifier, float value)
172 { 202 {
203
173 if (identifier == "C") { 204 if (identifier == "C") {
174 compressionConstant = value; // set the actual value of your parameter 205 compressionConstant = value; // set the actual value of your parameter
175 } 206 }
176 if (identifier == "tN") { 207 if (identifier == "TN") {
177 tN = value; 208 windowLength = value;
178 } 209 }
210 if (identifier == "minBPM") {
211 minBPM = value;
212 }
213 if (identifier == "maxBPM"){
214 maxBPM = value;
215 }
216
217 }
218
219 void Tempogram::updateBPMParameters(){
220
179 } 221 }
180 222
181 Tempogram::ProgramList 223 Tempogram::ProgramList
182 Tempogram::getPrograms() const 224 Tempogram::getPrograms() const
183 { 225 {
198 void 240 void
199 Tempogram::selectProgram(string name) 241 Tempogram::selectProgram(string name)
200 { 242 {
201 } 243 }
202 244
245 string Tempogram::floatToString(float value) const
246 {
247 ostringstream ss;
248
249 if(!(ss << value)) throw runtime_error("Tempogram::floatToString(): invalid conversion from float to string");
250 return ss.str();
251 }
252
203 Tempogram::OutputList 253 Tempogram::OutputList
204 Tempogram::getOutputDescriptors() const 254 Tempogram::getOutputDescriptors() const
205 { 255 {
206 OutputList list; 256 OutputList list;
207 257
208 // See OutputDescriptor documentation for the possibilities here. 258 // See OutputDescriptor documentation for the possibilities here.
209 // Every plugin must have at least one output. 259 // Every plugin must have at least one output.
210 260
211 OutputDescriptor d; 261 OutputDescriptor d;
212 float d_sampleRate; 262 float d_sampleRate;
263 float tempogramInputSampleRate = (float)m_inputSampleRate/m_stepSize;
213 264
214 d.identifier = "tempogram"; 265 d.identifier = "tempogram";
215 d.name = "Tempogram"; 266 d.name = "Tempogram";
216 d.description = "Tempogram"; 267 d.description = "Tempogram";
217 d.unit = ""; 268 d.unit = "BPM";
218 d.hasFixedBinCount = true; 269 d.hasFixedBinCount = true;
219 d.binCount = tN/2 + 1; 270 d.binCount = maxBin - minBin + 1;
220 d.hasKnownExtents = false; 271 d.hasKnownExtents = false;
221 d.isQuantized = false; 272 d.isQuantized = false;
222 d.sampleType = OutputDescriptor::FixedSampleRate; 273 d.sampleType = OutputDescriptor::FixedSampleRate;
223 d_sampleRate = m_inputSampleRate/(m_stepSize * thopSize); 274 d_sampleRate = tempogramInputSampleRate/thopSize;
224 d.sampleRate = d_sampleRate > 0.0 && !isnan(d_sampleRate) ? d_sampleRate : 0.0; 275 d.sampleRate = d_sampleRate > 0.0 && !isnan(d_sampleRate) ? d_sampleRate : 0.0;
276 for(int i = minBin; i <= maxBin; i++){
277 float w = ((float)i/fftLength)*(tempogramInputSampleRate);
278 d.binNames.push_back(floatToString(w*60));
279 }
225 d.hasDuration = false; 280 d.hasDuration = false;
226 list.push_back(d); 281 list.push_back(d);
227 282
228 d.identifier = "nc"; 283 d.identifier = "nc";
229 d.name = "Novelty Curve"; 284 d.name = "Novelty Curve";
232 d.hasFixedBinCount = true; 287 d.hasFixedBinCount = true;
233 d.binCount = 1; 288 d.binCount = 1;
234 d.hasKnownExtents = false; 289 d.hasKnownExtents = false;
235 d.isQuantized = false; 290 d.isQuantized = false;
236 d.sampleType = OutputDescriptor::FixedSampleRate; 291 d.sampleType = OutputDescriptor::FixedSampleRate;
237 d_sampleRate = m_inputSampleRate/m_stepSize; 292 d_sampleRate = tempogramInputSampleRate;
238 d.sampleRate = d_sampleRate > 0 && !isnan(d_sampleRate) ? d_sampleRate : 0.0; 293 d.sampleRate = d_sampleRate > 0 && !isnan(d_sampleRate) ? d_sampleRate : 0.0;
239 d.hasDuration = false; 294 d.hasDuration = false;
240 list.push_back(d); 295 list.push_back(d);
241 296
242 return list; 297 return list;
245 bool 300 bool
246 Tempogram::initialise(size_t channels, size_t stepSize, size_t blockSize) 301 Tempogram::initialise(size_t channels, size_t stepSize, size_t blockSize)
247 { 302 {
248 if (channels < getMinChannelCount() || 303 if (channels < getMinChannelCount() ||
249 channels > getMaxChannelCount()) return false; 304 channels > getMaxChannelCount()) return false;
250 305
251 // Real initialisation work goes here! 306 // Real initialisation work goes here!
252 m_blockSize = blockSize; 307 m_blockSize = blockSize;
253 m_stepSize = stepSize; 308 m_stepSize = stepSize;
254 minDB = pow(10,(float)-74/20); 309 minDB = pow(10,(float)-74/20);
310
311 if (minBPM > maxBPM){
312 minBPM = 30;
313 maxBPM = 480;
314 }
315 float tempogramInputSampleRate = (float)m_inputSampleRate/m_stepSize;
316 minBin = (unsigned int)(max(floor(((minBPM/60)/tempogramInputSampleRate)*fftLength), (float)0.0));
317 maxBin = (unsigned int)(min(ceil(((maxBPM/60)/tempogramInputSampleRate)*fftLength), (float)fftLength/2));
255 318
256 specData = vector< vector<float> >(m_blockSize/2 + 1); 319 specData = vector< vector<float> >(m_blockSize/2 + 1);
257 320
258 return true; 321 return true;
259 } 322 }
280 FeatureSet featureSet; 343 FeatureSet featureSet;
281 Feature feature; 344 Feature feature;
282 345
283 const float *in = inputBuffers[0]; 346 const float *in = inputBuffers[0];
284 347
348 //calculate magnitude of FrequencyDomain input
285 for (int i = 0; i < n; i++){ 349 for (int i = 0; i < n; i++){
286 float magnitude = sqrt(in[2*i] * in[2*i] + in[2*i + 1] * in[2*i + 1]); 350 float magnitude = sqrt(in[2*i] * in[2*i] + in[2*i + 1] * in[2*i + 1]);
287 magnitude = magnitude > minDB ? magnitude : minDB; 351 magnitude = magnitude > minDB ? magnitude : minDB;
288 specData[i].push_back(magnitude); 352 specData[i].push_back(magnitude);
289 } 353 }
290 354
291 numberOfBlocks++; 355 numberOfBlocks++;
292 ncTimestamps.push_back(timestamp); 356 ncTimestamps.push_back(timestamp); //save timestamp
293 357
294 return featureSet; 358 return featureSet;
295 } 359 }
296 360
297 void 361 void
298 Tempogram::initialiseForGRF(){ 362 Tempogram::initialiseForGRF(){
299 hannWindowtN = new float[tN]; 363 hannWindowtN = new float[windowLength];
300 fftInput = new double[tN]; 364
301 fftOutputReal = new double[tN]; 365 for (int i = 0; i < windowLength; i++){
302 fftOutputImag = new double[tN];
303
304 for (int i = 0; i < tN; i ++){
305 hannWindowtN[i] = 0.0; 366 hannWindowtN[i] = 0.0;
306 fftInput[i] = 0.0;
307 fftOutputReal[i] = 0.0;
308 fftOutputImag[i] = 0.0;
309 } 367 }
310 } 368 }
311 369
312 void 370 void
313 Tempogram::cleanupForGRF(){ 371 Tempogram::cleanupForGRF(){
314 delete []hannWindowtN; 372 delete []hannWindowtN;
315 hannWindowtN = NULL; 373 hannWindowtN = NULL;
316 fftInput = fftOutputReal = fftOutputImag = NULL; 374 }
317 } 375
376
318 377
319 Tempogram::FeatureSet 378 Tempogram::FeatureSet
320 Tempogram::getRemainingFeatures() 379 Tempogram::getRemainingFeatures()
321 { 380 {
322 //Make sure this is called at the beginning of the function 381 //Make sure this is called at the beginning of the function
323 initialiseForGRF(); 382 initialiseForGRF();
324 FeatureSet featureSet; 383 FeatureSet featureSet;
325 384
385 //initialise noveltycurve processor
326 NoveltyCurve nc(m_inputSampleRate, m_blockSize, numberOfBlocks, compressionConstant); 386 NoveltyCurve nc(m_inputSampleRate, m_blockSize, numberOfBlocks, compressionConstant);
327 noveltyCurve = nc.spectrogramToNoveltyCurve(specData); 387 noveltyCurve = nc.spectrogramToNoveltyCurve(specData); //calculate novelty curve from magnitude data
328 388
389 //push novelty curve data to featureset 1 and set timestamps
329 for (int i = 0; i < numberOfBlocks; i++){ 390 for (int i = 0; i < numberOfBlocks; i++){
330 Feature feature; 391 Feature feature;
331 feature.values.push_back(noveltyCurve[i]); 392 feature.values.push_back(noveltyCurve[i]);
332 feature.hasTimestamp = true; 393 feature.hasTimestamp = true;
333 feature.timestamp = ncTimestamps[i]; 394 feature.timestamp = ncTimestamps[i];
334 featureSet[1].push_back(feature); 395 featureSet[1].push_back(feature);
335 } 396 }
336 397
337 WindowFunction::hanning(hannWindowtN, tN); 398 //window function for spectrogram
338 Spectrogram * spectrogramProcessor = new Spectrogram(numberOfBlocks, tN, thopSize); 399 WindowFunction::hanning(hannWindowtN,windowLength);
400
401 //initialise spectrogram processor
402 Spectrogram * spectrogramProcessor = new Spectrogram(numberOfBlocks, windowLength, fftLength, thopSize);
403 //compute spectrogram from novelty curve data (i.e., tempogram)
339 vector< vector<float> > tempogram = spectrogramProcessor->audioToMagnitudeSpectrogram(&noveltyCurve[0], hannWindowtN); 404 vector< vector<float> > tempogram = spectrogramProcessor->audioToMagnitudeSpectrogram(&noveltyCurve[0], hannWindowtN);
340 delete spectrogramProcessor; 405 delete spectrogramProcessor;
341 spectrogramProcessor = NULL; 406 spectrogramProcessor = NULL;
342 407
343 int timePointer = thopSize-tN/2; 408 int timePointer = thopSize-windowLength/2;
344 int tempogramLength = tempogram[0].size(); 409 int tempogramLength = tempogram[0].size();
345 410
411 //push tempogram data to featureset 0 and set timestamps.
346 for (int block = 0; block < tempogramLength; block++){ 412 for (int block = 0; block < tempogramLength; block++){
347 Feature feature; 413 Feature feature;
348 414
349 int timeMS = floor(1000*(m_stepSize*timePointer)/m_inputSampleRate + 0.5); 415 int timeMS = floor(1000*(m_stepSize*timePointer)/m_inputSampleRate + 0.5);
350 416
351 for(int k = 0; k < tN/2 + 1; k++){ 417 assert(tempogram.size() == (fftLength/2 + 1));
418 for(int k = minBin; k < maxBin; k++){
352 feature.values.push_back(tempogram[k][block]); 419 feature.values.push_back(tempogram[k][block]);
353 } 420 }
354 feature.hasTimestamp = true; 421 feature.hasTimestamp = true;
355 feature.timestamp = RealTime::fromMilliseconds(timeMS); 422 feature.timestamp = RealTime::fromMilliseconds(timeMS);
356 featureSet[0].push_back(feature); 423 featureSet[0].push_back(feature);