annotate 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
rev   line source
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@9 8 #include <sstream>
c@9 9 #include <stdexcept>
c@4 10
c@0 11 using Vamp::FFT;
c@7 12 using Vamp::RealTime;
c@0 13 using namespace std;
c@0 14
c@0 15 Tempogram::Tempogram(float inputSampleRate) :
c@0 16 Plugin(inputSampleRate),
c@0 17 m_blockSize(0),
c@1 18 m_stepSize(0),
c@9 19 compressionConstant(1000), //parameter
c@3 20 minDB(0),
c@9 21 windowLength(128), //parameter
c@9 22 fftLength(4096), //parameter
c@9 23 thopSize(64), //parameter
c@9 24 minBPM(30),
c@9 25 maxBPM(480),
c@9 26 minBin(0), //set in initialise()
c@9 27 maxBin(0), //set in initialise()
c@9 28 numberOfBlocks(0) //incremented in process()
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@7 39 cleanup();
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@9 95 return 2048; // 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@9 101 return 1024; // 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@9 134 ParameterDescriptor d;
c@9 135 d.identifier = "C";
c@9 136 d.name = "C";
c@9 137 d.description = "Spectrogram compression constant, C, used when retrieving the novelty curve from the audio.";
c@9 138 d.unit = "";
c@9 139 d.minValue = 2;
c@9 140 d.maxValue = 10000;
c@9 141 d.defaultValue = 1000;
c@9 142 d.isQuantized = false;
c@9 143 list.push_back(d);
c@9 144
c@9 145 d.identifier = "TN";
c@9 146 d.name = "Tempogram Window Length";
c@9 147 d.description = "FFT window length when analysing the novelty curve and extracting the tempogram time-frequency function.";
c@9 148 d.unit = "";
c@9 149 d.minValue = 1024;
c@9 150 d.maxValue = 4096;
c@9 151 d.defaultValue = 128;
c@9 152 d.isQuantized = true;
c@9 153 d.quantizeStep = 128;
c@9 154 list.push_back(d);
c@0 155
c@9 156 d.identifier = "minBPM";
c@9 157 d.name = "Minimum BPM";
c@9 158 d.description = "The minimum BPM of the tempogram output bins.";
c@9 159 d.unit = "";
c@9 160 d.minValue = 0;
c@9 161 d.maxValue = 2000;
c@9 162 d.defaultValue = 30;
c@9 163 d.isQuantized = true;
c@9 164 d.quantizeStep = 5;
c@9 165 list.push_back(d);
c@9 166
c@9 167 d.identifier = "maxBPM";
c@9 168 d.name = "Maximum BPM";
c@9 169 d.description = "The minimum BPM of the tempogram output bins.";
c@9 170 d.unit = "";
c@9 171 d.minValue = 30;
c@9 172 d.maxValue = 2000;
c@9 173 d.defaultValue = 480;
c@9 174 d.isQuantized = true;
c@9 175 d.quantizeStep = 5;
c@9 176 list.push_back(d);
c@0 177
c@0 178 return list;
c@0 179 }
c@0 180
c@0 181 float
c@0 182 Tempogram::getParameter(string identifier) const
c@0 183 {
c@0 184 if (identifier == "C") {
c@0 185 return compressionConstant; // return the ACTUAL current value of your parameter here!
c@0 186 }
c@9 187 if (identifier == "TN"){
c@9 188 return windowLength;
c@9 189 }
c@9 190 if (identifier == "minBPM") {
c@9 191 return minBPM;
c@9 192 }
c@9 193 if (identifier == "maxBPM"){
c@9 194 return maxBPM;
c@0 195 }
c@0 196
c@0 197 return 0;
c@0 198 }
c@0 199
c@0 200 void
c@0 201 Tempogram::setParameter(string identifier, float value)
c@0 202 {
c@9 203
c@0 204 if (identifier == "C") {
c@1 205 compressionConstant = value; // set the actual value of your parameter
c@0 206 }
c@9 207 if (identifier == "TN") {
c@9 208 windowLength = value;
c@0 209 }
c@9 210 if (identifier == "minBPM") {
c@9 211 minBPM = value;
c@9 212 }
c@9 213 if (identifier == "maxBPM"){
c@9 214 maxBPM = value;
c@9 215 }
c@9 216
c@9 217 }
c@9 218
c@9 219 void Tempogram::updateBPMParameters(){
c@9 220
c@0 221 }
c@0 222
c@0 223 Tempogram::ProgramList
c@0 224 Tempogram::getPrograms() const
c@0 225 {
c@0 226 ProgramList list;
c@0 227
c@0 228 // If you have no programs, return an empty list (or simply don't
c@0 229 // implement this function or getCurrentProgram/selectProgram)
c@0 230
c@0 231 return list;
c@0 232 }
c@0 233
c@0 234 string
c@0 235 Tempogram::getCurrentProgram() const
c@0 236 {
c@0 237 return ""; // no programs
c@0 238 }
c@0 239
c@0 240 void
c@0 241 Tempogram::selectProgram(string name)
c@0 242 {
c@0 243 }
c@0 244
c@9 245 string Tempogram::floatToString(float value) const
c@9 246 {
c@9 247 ostringstream ss;
c@9 248
c@9 249 if(!(ss << value)) throw runtime_error("Tempogram::floatToString(): invalid conversion from float to string");
c@9 250 return ss.str();
c@9 251 }
c@9 252
c@0 253 Tempogram::OutputList
c@0 254 Tempogram::getOutputDescriptors() const
c@0 255 {
c@0 256 OutputList list;
c@0 257
c@0 258 // See OutputDescriptor documentation for the possibilities here.
c@0 259 // Every plugin must have at least one output.
c@1 260
c@0 261 OutputDescriptor d;
c@7 262 float d_sampleRate;
c@9 263 float tempogramInputSampleRate = (float)m_inputSampleRate/m_stepSize;
c@7 264
c@1 265 d.identifier = "tempogram";
c@7 266 d.name = "Tempogram";
c@7 267 d.description = "Tempogram";
c@9 268 d.unit = "BPM";
c@1 269 d.hasFixedBinCount = true;
c@9 270 d.binCount = maxBin - minBin + 1;
c@0 271 d.hasKnownExtents = false;
c@0 272 d.isQuantized = false;
c@1 273 d.sampleType = OutputDescriptor::FixedSampleRate;
c@9 274 d_sampleRate = tempogramInputSampleRate/thopSize;
c@1 275 d.sampleRate = d_sampleRate > 0.0 && !isnan(d_sampleRate) ? d_sampleRate : 0.0;
c@9 276 for(int i = minBin; i <= maxBin; i++){
c@9 277 float w = ((float)i/fftLength)*(tempogramInputSampleRate);
c@9 278 d.binNames.push_back(floatToString(w*60));
c@9 279 }
c@0 280 d.hasDuration = false;
c@0 281 list.push_back(d);
c@7 282
c@1 283 d.identifier = "nc";
c@1 284 d.name = "Novelty Curve";
c@1 285 d.description = "Novelty Curve";
c@1 286 d.unit = "";
c@1 287 d.hasFixedBinCount = true;
c@1 288 d.binCount = 1;
c@1 289 d.hasKnownExtents = false;
c@1 290 d.isQuantized = false;
c@1 291 d.sampleType = OutputDescriptor::FixedSampleRate;
c@9 292 d_sampleRate = tempogramInputSampleRate;
c@1 293 d.sampleRate = d_sampleRate > 0 && !isnan(d_sampleRate) ? d_sampleRate : 0.0;
c@1 294 d.hasDuration = false;
c@1 295 list.push_back(d);
c@1 296
c@0 297 return list;
c@0 298 }
c@0 299
c@0 300 bool
c@0 301 Tempogram::initialise(size_t channels, size_t stepSize, size_t blockSize)
c@0 302 {
c@0 303 if (channels < getMinChannelCount() ||
c@0 304 channels > getMaxChannelCount()) return false;
c@9 305
c@0 306 // Real initialisation work goes here!
c@0 307 m_blockSize = blockSize;
c@1 308 m_stepSize = stepSize;
c@7 309 minDB = pow(10,(float)-74/20);
c@0 310
c@9 311 if (minBPM > maxBPM){
c@9 312 minBPM = 30;
c@9 313 maxBPM = 480;
c@9 314 }
c@9 315 float tempogramInputSampleRate = (float)m_inputSampleRate/m_stepSize;
c@9 316 minBin = (unsigned int)(max(floor(((minBPM/60)/tempogramInputSampleRate)*fftLength), (float)0.0));
c@9 317 maxBin = (unsigned int)(min(ceil(((maxBPM/60)/tempogramInputSampleRate)*fftLength), (float)fftLength/2));
c@9 318
c@4 319 specData = vector< vector<float> >(m_blockSize/2 + 1);
c@4 320
c@0 321 return true;
c@0 322 }
c@0 323
c@7 324 void Tempogram::cleanup(){
c@7 325
c@7 326 }
c@7 327
c@0 328 void
c@0 329 Tempogram::reset()
c@0 330 {
c@0 331 // Clear buffers, reset stored values, etc
c@7 332 cleanupForGRF();
c@7 333 ncTimestamps.clear();
c@7 334 specData.clear();
c@8 335 specData = vector< vector<float> >(m_blockSize/2 + 1);
c@0 336 }
c@0 337
c@0 338 Tempogram::FeatureSet
c@0 339 Tempogram::process(const float *const *inputBuffers, Vamp::RealTime timestamp)
c@0 340 {
c@0 341 size_t n = m_blockSize/2 + 1;
c@0 342
c@0 343 FeatureSet featureSet;
c@0 344 Feature feature;
c@0 345
c@0 346 const float *in = inputBuffers[0];
c@3 347
c@9 348 //calculate magnitude of FrequencyDomain input
c@0 349 for (int i = 0; i < n; i++){
c@0 350 float magnitude = sqrt(in[2*i] * in[2*i] + in[2*i + 1] * in[2*i + 1]);
c@3 351 magnitude = magnitude > minDB ? magnitude : minDB;
c@4 352 specData[i].push_back(magnitude);
c@0 353 }
c@0 354
c@3 355 numberOfBlocks++;
c@9 356 ncTimestamps.push_back(timestamp); //save timestamp
c@7 357
c@2 358 return featureSet;
c@0 359 }
c@0 360
c@0 361 void
c@0 362 Tempogram::initialiseForGRF(){
c@9 363 hannWindowtN = new float[windowLength];
c@0 364
c@9 365 for (int i = 0; i < windowLength; i++){
c@7 366 hannWindowtN[i] = 0.0;
c@4 367 }
c@0 368 }
c@0 369
c@0 370 void
c@0 371 Tempogram::cleanupForGRF(){
c@0 372 delete []hannWindowtN;
c@0 373 hannWindowtN = NULL;
c@0 374 }
c@0 375
c@9 376
c@9 377
c@0 378 Tempogram::FeatureSet
c@0 379 Tempogram::getRemainingFeatures()
c@0 380 {
c@0 381 //Make sure this is called at the beginning of the function
c@0 382 initialiseForGRF();
c@1 383 FeatureSet featureSet;
c@0 384
c@9 385 //initialise noveltycurve processor
c@4 386 NoveltyCurve nc(m_inputSampleRate, m_blockSize, numberOfBlocks, compressionConstant);
c@9 387 noveltyCurve = nc.spectrogramToNoveltyCurve(specData); //calculate novelty curve from magnitude data
c@4 388
c@9 389 //push novelty curve data to featureset 1 and set timestamps
c@4 390 for (int i = 0; i < numberOfBlocks; i++){
c@7 391 Feature feature;
c@7 392 feature.values.push_back(noveltyCurve[i]);
c@7 393 feature.hasTimestamp = true;
c@7 394 feature.timestamp = ncTimestamps[i];
c@7 395 featureSet[1].push_back(feature);
c@4 396 }
c@4 397
c@9 398 //window function for spectrogram
c@9 399 WindowFunction::hanning(hannWindowtN,windowLength);
c@9 400
c@9 401 //initialise spectrogram processor
c@9 402 Spectrogram * spectrogramProcessor = new Spectrogram(numberOfBlocks, windowLength, fftLength, thopSize);
c@9 403 //compute spectrogram from novelty curve data (i.e., tempogram)
c@7 404 vector< vector<float> > tempogram = spectrogramProcessor->audioToMagnitudeSpectrogram(&noveltyCurve[0], hannWindowtN);
c@7 405 delete spectrogramProcessor;
c@7 406 spectrogramProcessor = NULL;
c@0 407
c@9 408 int timePointer = thopSize-windowLength/2;
c@7 409 int tempogramLength = tempogram[0].size();
c@7 410
c@9 411 //push tempogram data to featureset 0 and set timestamps.
c@7 412 for (int block = 0; block < tempogramLength; block++){
c@0 413 Feature feature;
c@0 414
c@7 415 int timeMS = floor(1000*(m_stepSize*timePointer)/m_inputSampleRate + 0.5);
c@7 416
c@9 417 assert(tempogram.size() == (fftLength/2 + 1));
c@9 418 for(int k = minBin; k < maxBin; k++){
c@7 419 feature.values.push_back(tempogram[k][block]);
c@0 420 }
c@7 421 feature.hasTimestamp = true;
c@7 422 feature.timestamp = RealTime::fromMilliseconds(timeMS);
c@7 423 featureSet[0].push_back(feature);
c@4 424
c@7 425 timePointer += thopSize;
c@0 426 }
c@0 427
c@0 428 //Make sure this is called at the end of the function
c@0 429 cleanupForGRF();
c@0 430
c@0 431 return featureSet;
c@0 432 }