annotate Tempogram.cpp @ 3:5125d34fda67

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