| Chris@43 | 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */ | 
| c@0 | 2 | 
| Chris@43 | 3 /* | 
| Chris@43 | 4    Vamp Tempogram Plugin | 
| Chris@43 | 5    Carl Bussey, Centre for Digital Music, Queen Mary University of London | 
| Chris@43 | 6    Copyright 2014 Queen Mary University of London. | 
| Chris@43 | 7 | 
| Chris@43 | 8    This program is free software; you can redistribute it and/or | 
| Chris@43 | 9    modify it under the terms of the GNU General Public License as | 
| Chris@43 | 10    published by the Free Software Foundation; either version 2 of the | 
| Chris@43 | 11    License, or (at your option) any later version.  See the file | 
| Chris@43 | 12    COPYING included with this distribution for more information. | 
| Chris@43 | 13 */ | 
| c@0 | 14 | 
| c@14 | 15 #include "TempogramPlugin.h" | 
| c@25 | 16 | 
| c@0 | 17 using Vamp::FFT; | 
| c@7 | 18 using Vamp::RealTime; | 
| c@0 | 19 using namespace std; | 
| c@0 | 20 | 
| c@14 | 21 TempogramPlugin::TempogramPlugin(float inputSampleRate) : | 
| c@0 | 22     Plugin(inputSampleRate), | 
| c@18 | 23     m_inputBlockSize(0), //host parameter | 
| c@18 | 24     m_inputStepSize(0), //host parameter | 
| c@29 | 25     m_noveltyCurveMinDB(-74), //parameter | 
| c@29 | 26     m_noveltyCurveMinV(0), //set in initialise() | 
| c@18 | 27     m_noveltyCurveCompressionConstant(1000), //parameter | 
| c@18 | 28     m_tempogramLog2WindowLength(10), //parameter | 
| c@29 | 29     m_tempogramWindowLength(0), //set in initialise() | 
| c@18 | 30     m_tempogramLog2FftLength(m_tempogramLog2WindowLength), //parameter | 
| c@29 | 31     m_tempogramFftLength(0), //set in initialise() | 
| c@18 | 32     m_tempogramLog2HopSize(6), //parameter | 
| c@29 | 33     m_tempogramHopSize(0), //set in initialise() | 
| c@18 | 34     m_tempogramMinBPM(30), //parameter | 
| c@18 | 35     m_tempogramMaxBPM(480), //parameter | 
| c@18 | 36     m_tempogramMinBin(0), //set in initialise() | 
| c@18 | 37     m_tempogramMaxBin(0), //set in initialise() | 
| c@29 | 38     m_tempogramMinLag(0), //set in initialise() | 
| c@29 | 39     m_tempogramMaxLag(0), //set in initialise() | 
| c@18 | 40     m_cyclicTempogramMinBPM(30), //reset in initialise() | 
| c@18 | 41     m_cyclicTempogramNumberOfOctaves(0), //set in initialise() | 
| c@18 | 42     m_cyclicTempogramOctaveDivider(30) //parameter | 
| c@0 | 43 | 
| c@0 | 44     // Also be sure to set your plugin parameters (presumably stored | 
| c@0 | 45     // in member variables) to their default values here -- the host | 
| c@0 | 46     // will not do that for you | 
| c@0 | 47 { | 
| c@0 | 48 } | 
| c@0 | 49 | 
| c@14 | 50 TempogramPlugin::~TempogramPlugin() | 
| c@0 | 51 { | 
| c@0 | 52     //delete stuff | 
| c@0 | 53 } | 
| c@0 | 54 | 
| c@0 | 55 string | 
| c@14 | 56 TempogramPlugin::getIdentifier() const | 
| c@0 | 57 { | 
| c@0 | 58     return "tempogram"; | 
| c@0 | 59 } | 
| c@0 | 60 | 
| c@0 | 61 string | 
| c@14 | 62 TempogramPlugin::getName() const | 
| c@0 | 63 { | 
| c@0 | 64     return "Tempogram"; | 
| c@0 | 65 } | 
| c@0 | 66 | 
| c@0 | 67 string | 
| c@14 | 68 TempogramPlugin::getDescription() const | 
| c@0 | 69 { | 
| c@0 | 70     return "Cyclic Tempogram as described by Peter Grosche and Meinard Muller"; | 
| c@0 | 71 } | 
| c@0 | 72 | 
| c@0 | 73 string | 
| c@14 | 74 TempogramPlugin::getMaker() const | 
| c@0 | 75 { | 
| c@0 | 76     return "Carl Bussey"; | 
| c@0 | 77 } | 
| c@0 | 78 | 
| c@0 | 79 int | 
| c@14 | 80 TempogramPlugin::getPluginVersion() const | 
| c@0 | 81 { | 
| c@0 | 82     return 1; | 
| c@0 | 83 } | 
| c@0 | 84 | 
| c@0 | 85 string | 
| c@14 | 86 TempogramPlugin::getCopyright() const | 
| c@0 | 87 { | 
| Chris@40 | 88     return "Copyright 2014 Queen Mary University of London. GPL licence."; | 
| c@0 | 89 } | 
| c@0 | 90 | 
| c@14 | 91 TempogramPlugin::InputDomain | 
| c@14 | 92 TempogramPlugin::getInputDomain() const | 
| c@0 | 93 { | 
| c@0 | 94     return FrequencyDomain; | 
| c@0 | 95 } | 
| c@0 | 96 | 
| c@0 | 97 size_t | 
| c@14 | 98 TempogramPlugin::getPreferredBlockSize() const | 
| c@0 | 99 { | 
| c@9 | 100     return 2048; // 0 means "I can handle any block size" | 
| c@0 | 101 } | 
| c@0 | 102 | 
| c@0 | 103 size_t | 
| c@14 | 104 TempogramPlugin::getPreferredStepSize() const | 
| c@0 | 105 { | 
| c@9 | 106     return 1024; // 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@14 | 112 TempogramPlugin::getMinChannelCount() const | 
| c@0 | 113 { | 
| c@0 | 114     return 1; | 
| c@0 | 115 } | 
| c@0 | 116 | 
| c@0 | 117 size_t | 
| c@14 | 118 TempogramPlugin::getMaxChannelCount() const | 
| c@0 | 119 { | 
| c@0 | 120     return 1; | 
| c@0 | 121 } | 
| c@0 | 122 | 
| c@14 | 123 TempogramPlugin::ParameterList | 
| c@14 | 124 TempogramPlugin::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@14 | 139     ParameterDescriptor d1; | 
| c@14 | 140     d1.identifier = "C"; | 
| c@15 | 141     d1.name = "Novelty Curve Spectrogram Compression Constant"; | 
| c@14 | 142     d1.description = "Spectrogram compression constant, C, used when retrieving the novelty curve from the audio."; | 
| c@14 | 143     d1.unit = ""; | 
| c@14 | 144     d1.minValue = 2; | 
| c@14 | 145     d1.maxValue = 10000; | 
| c@14 | 146     d1.defaultValue = 1000; | 
| c@14 | 147     d1.isQuantized = false; | 
| c@14 | 148     list.push_back(d1); | 
| c@29 | 149 | 
| c@29 | 150     ParameterDescriptor d2; | 
| c@29 | 151     d2.identifier = "minDB"; | 
| c@29 | 152     d2.name = "Novelty Curve Minimum DB"; | 
| c@29 | 153     d2.description = "Spectrogram minimum DB used when removing unwanted peaks in the Spectrogram when retrieving the novelty curve from the audio."; | 
| c@29 | 154     d2.unit = ""; | 
| c@29 | 155     d2.minValue = -100; | 
| c@29 | 156     d2.maxValue = -50; | 
| c@29 | 157     d2.defaultValue = -74; | 
| c@29 | 158     d2.isQuantized = false; | 
| c@29 | 159     list.push_back(d2); | 
| c@9 | 160 | 
| c@14 | 161     ParameterDescriptor d3; | 
| c@29 | 162     d3.identifier = "log2TN"; | 
| c@29 | 163     d3.name = "Tempogram Window Length"; | 
| c@29 | 164     d3.description = "FFT window length when analysing the novelty curve and extracting the tempogram time-frequency function."; | 
| c@14 | 165     d3.unit = ""; | 
| c@29 | 166     d3.minValue = 7; | 
| c@14 | 167     d3.maxValue = 12; | 
| c@29 | 168     d3.defaultValue = 10; | 
| c@14 | 169     d3.isQuantized = true; | 
| c@14 | 170     d3.quantizeStep = 1; | 
| c@14 | 171     for (int i = d3.minValue; i <= d3.maxValue; i++){ | 
| c@14 | 172         d3.valueNames.push_back(floatToString(pow((float)2,(float)i))); | 
| c@14 | 173     } | 
| c@14 | 174     list.push_back(d3); | 
| c@9 | 175 | 
| c@14 | 176     ParameterDescriptor d4; | 
| c@29 | 177     d4.identifier = "log2HopSize"; | 
| c@29 | 178     d4.name = "Tempogram Hopsize"; | 
| c@29 | 179     d4.description = "FFT hopsize when analysing the novelty curve and extracting the tempogram time-frequency function."; | 
| c@14 | 180     d4.unit = ""; | 
| c@14 | 181     d4.minValue = 6; | 
| c@14 | 182     d4.maxValue = 12; | 
| c@29 | 183     d4.defaultValue = 6; | 
| c@14 | 184     d4.isQuantized = true; | 
| c@14 | 185     d4.quantizeStep = 1; | 
| c@14 | 186     for (int i = d4.minValue; i <= d4.maxValue; i++){ | 
| c@14 | 187         d4.valueNames.push_back(floatToString(pow((float)2,(float)i))); | 
| c@14 | 188     } | 
| c@14 | 189     list.push_back(d4); | 
| c@14 | 190 | 
| c@14 | 191     ParameterDescriptor d5; | 
| c@29 | 192     d5.identifier = "log2FftLength"; | 
| c@29 | 193     d5.name = "Tempogram FFT Length"; | 
| c@29 | 194     d5.description = "FFT length when analysing the novelty curve and extracting the tempogram time-frequency function. This parameter determines the amount of zero padding."; | 
| c@14 | 195     d5.unit = ""; | 
| c@29 | 196     d5.minValue = 6; | 
| c@29 | 197     d5.maxValue = 12; | 
| Chris@42 | 198     d5.defaultValue = 10; | 
| c@14 | 199     d5.isQuantized = true; | 
| c@29 | 200     d5.quantizeStep = 1; | 
| c@29 | 201     for (int i = d5.minValue; i <= d5.maxValue; i++){ | 
| Chris@41 | 202         d5.valueNames.push_back(floatToString(pow((float)2,(float)i))); | 
| c@29 | 203     } | 
| c@14 | 204     list.push_back(d5); | 
| c@14 | 205 | 
| c@14 | 206     ParameterDescriptor d6; | 
| c@29 | 207     d6.identifier = "minBPM"; | 
| c@29 | 208     d6.name = "(Cyclic) Tempogram Minimum BPM"; | 
| c@29 | 209     d6.description = "The minimum BPM of the tempogram output bins."; | 
| c@14 | 210     d6.unit = ""; | 
| c@29 | 211     d6.minValue = 0; | 
| c@14 | 212     d6.maxValue = 2000; | 
| c@29 | 213     d6.defaultValue = 30; | 
| c@14 | 214     d6.isQuantized = true; | 
| c@14 | 215     d6.quantizeStep = 5; | 
| c@14 | 216     list.push_back(d6); | 
| c@18 | 217 | 
| c@18 | 218     ParameterDescriptor d7; | 
| c@29 | 219     d7.identifier = "maxBPM"; | 
| c@29 | 220     d7.name = "(Cyclic) Tempogram Maximum BPM"; | 
| c@29 | 221     d7.description = "The maximum BPM of the tempogram output bins."; | 
| c@18 | 222     d7.unit = ""; | 
| c@29 | 223     d7.minValue = 30; | 
| c@29 | 224     d7.maxValue = 2000; | 
| c@29 | 225     d7.defaultValue = 480; | 
| c@18 | 226     d7.isQuantized = true; | 
| c@29 | 227     d7.quantizeStep = 5; | 
| c@18 | 228     list.push_back(d7); | 
| c@29 | 229 | 
| c@29 | 230     ParameterDescriptor d8; | 
| c@29 | 231     d8.identifier = "octDiv"; | 
| c@29 | 232     d8.name = "Cyclic Tempogram Octave Divider"; | 
| c@29 | 233     d8.description = "The number bins within each octave."; | 
| c@29 | 234     d8.unit = ""; | 
| c@29 | 235     d8.minValue = 5; | 
| c@29 | 236     d8.maxValue = 60; | 
| c@29 | 237     d8.defaultValue = 30; | 
| c@29 | 238     d8.isQuantized = true; | 
| c@29 | 239     d8.quantizeStep = 1; | 
| c@29 | 240     list.push_back(d8); | 
| c@0 | 241 | 
| c@0 | 242     return list; | 
| c@0 | 243 } | 
| c@0 | 244 | 
| c@0 | 245 float | 
| c@14 | 246 TempogramPlugin::getParameter(string identifier) const | 
| c@0 | 247 { | 
| c@0 | 248     if (identifier == "C") { | 
| c@18 | 249         return m_noveltyCurveCompressionConstant; // return the ACTUAL current value of your parameter here! | 
| c@0 | 250     } | 
| c@29 | 251     else if (identifier == "minDB"){ | 
| c@29 | 252         return m_noveltyCurveMinDB; | 
| c@29 | 253     } | 
| c@14 | 254     else if (identifier == "log2TN"){ | 
| c@18 | 255         return m_tempogramLog2WindowLength; | 
| c@9 | 256     } | 
| c@14 | 257     else if (identifier == "log2HopSize"){ | 
| c@18 | 258         return m_tempogramLog2HopSize; | 
| c@14 | 259     } | 
| c@14 | 260     else if (identifier == "log2FftLength"){ | 
| c@18 | 261         return m_tempogramLog2FftLength; | 
| c@14 | 262     } | 
| c@14 | 263     else if (identifier == "minBPM") { | 
| c@18 | 264         return m_tempogramMinBPM; | 
| c@9 | 265     } | 
| c@14 | 266     else if (identifier == "maxBPM"){ | 
| c@18 | 267         return m_tempogramMaxBPM; | 
| c@18 | 268     } | 
| c@18 | 269     else if (identifier == "octDiv"){ | 
| c@18 | 270         return m_cyclicTempogramOctaveDivider; | 
| c@0 | 271     } | 
| c@0 | 272 | 
| c@0 | 273     return 0; | 
| c@0 | 274 } | 
| c@0 | 275 | 
| c@0 | 276 void | 
| c@14 | 277 TempogramPlugin::setParameter(string identifier, float value) | 
| c@0 | 278 { | 
| c@9 | 279 | 
| c@0 | 280     if (identifier == "C") { | 
| c@18 | 281         m_noveltyCurveCompressionConstant = value; // set the actual value of your parameter | 
| c@0 | 282     } | 
| c@29 | 283     else if (identifier == "minDB"){ | 
| c@29 | 284         m_noveltyCurveMinDB = value; | 
| c@29 | 285     } | 
| c@14 | 286     else if (identifier == "log2TN") { | 
| c@18 | 287         m_tempogramLog2WindowLength = value; | 
| c@0 | 288     } | 
| c@14 | 289     else if (identifier == "log2HopSize"){ | 
| c@30 | 290         m_tempogramLog2HopSize = value; | 
| c@14 | 291     } | 
| c@18 | 292     else if (identifier == "log2FftLength"){ | 
| c@30 | 293         m_tempogramLog2FftLength = value; | 
| c@14 | 294     } | 
| c@14 | 295     else if (identifier == "minBPM") { | 
| c@18 | 296         m_tempogramMinBPM = value; | 
| c@9 | 297     } | 
| c@14 | 298     else if (identifier == "maxBPM"){ | 
| c@18 | 299         m_tempogramMaxBPM = value; | 
| c@18 | 300     } | 
| c@18 | 301     else if (identifier == "octDiv"){ | 
| c@18 | 302         m_cyclicTempogramOctaveDivider = value; | 
| c@9 | 303     } | 
| c@9 | 304 | 
| c@9 | 305 } | 
| c@9 | 306 | 
| c@14 | 307 TempogramPlugin::ProgramList | 
| c@14 | 308 TempogramPlugin::getPrograms() const | 
| c@0 | 309 { | 
| c@0 | 310     ProgramList list; | 
| c@0 | 311 | 
| c@0 | 312     // If you have no programs, return an empty list (or simply don't | 
| c@0 | 313     // implement this function or getCurrentProgram/selectProgram) | 
| c@0 | 314 | 
| c@0 | 315     return list; | 
| c@0 | 316 } | 
| c@0 | 317 | 
| c@0 | 318 string | 
| c@14 | 319 TempogramPlugin::getCurrentProgram() const | 
| c@0 | 320 { | 
| c@0 | 321     return ""; // no programs | 
| c@0 | 322 } | 
| c@0 | 323 | 
| c@0 | 324 void | 
| c@14 | 325 TempogramPlugin::selectProgram(string name) | 
| c@0 | 326 { | 
| c@0 | 327 } | 
| c@0 | 328 | 
| c@14 | 329 TempogramPlugin::OutputList | 
| c@14 | 330 TempogramPlugin::getOutputDescriptors() const | 
| c@0 | 331 { | 
| c@0 | 332     OutputList list; | 
| c@0 | 333 | 
| c@0 | 334     // See OutputDescriptor documentation for the possibilities here. | 
| c@0 | 335     // Every plugin must have at least one output. | 
| c@1 | 336 | 
| c@7 | 337     float d_sampleRate; | 
| c@18 | 338     float tempogramInputSampleRate = (float)m_inputSampleRate/m_inputStepSize; | 
| c@25 | 339     OutputDescriptor d1; | 
| c@25 | 340     d1.identifier = "cyclicTempogram"; | 
| c@25 | 341     d1.name = "Cyclic Tempogram"; | 
| Chris@43 | 342     d1.description = "Cyclic tempogram calculated by \"octave folding\" the DFT tempogram"; | 
| c@25 | 343     d1.unit = ""; | 
| c@25 | 344     d1.hasFixedBinCount = true; | 
| c@25 | 345     d1.binCount = m_cyclicTempogramOctaveDivider > 0 && !isnan(m_cyclicTempogramOctaveDivider) ? m_cyclicTempogramOctaveDivider : 0; | 
| c@25 | 346     d1.hasKnownExtents = false; | 
| c@25 | 347     d1.isQuantized = false; | 
| c@25 | 348     d1.sampleType = OutputDescriptor::FixedSampleRate; | 
| c@25 | 349     d_sampleRate = tempogramInputSampleRate/m_tempogramHopSize; | 
| c@25 | 350     d1.sampleRate = d_sampleRate > 0.0 && !isnan(d_sampleRate) ? d_sampleRate : 0; | 
| c@25 | 351     d1.hasDuration = false; | 
| c@25 | 352     list.push_back(d1); | 
| c@25 | 353 | 
| c@25 | 354     OutputDescriptor d2; | 
| c@25 | 355     d2.identifier = "tempogramDFT"; | 
| c@25 | 356     d2.name = "Tempogram via DFT"; | 
| Chris@43 | 357     d2.description = "Tempogram calculated using Discrete Fourier Transform method"; | 
| Chris@43 | 358     d2.unit = ""; // unit of bin contents, not of "bin label", so not bpm | 
| c@25 | 359     d2.hasFixedBinCount = true; | 
| c@25 | 360     d2.binCount = m_tempogramMaxBin - m_tempogramMinBin + 1; | 
| c@25 | 361     d2.hasKnownExtents = false; | 
| c@25 | 362     d2.isQuantized = false; | 
| c@25 | 363     d2.sampleType = OutputDescriptor::FixedSampleRate; | 
| c@25 | 364     d_sampleRate = tempogramInputSampleRate/m_tempogramHopSize; | 
| c@25 | 365     d2.sampleRate = d_sampleRate > 0.0 && !isnan(d_sampleRate) ? d_sampleRate : 0.0; | 
| c@25 | 366     for(int i = m_tempogramMinBin; i <= (int)m_tempogramMaxBin; i++){ | 
| c@25 | 367         float w = ((float)i/m_tempogramFftLength)*(tempogramInputSampleRate); | 
| c@25 | 368         d2.binNames.push_back(floatToString(w*60)); | 
| c@25 | 369     } | 
| c@25 | 370     d2.hasDuration = false; | 
| c@25 | 371     list.push_back(d2); | 
| c@25 | 372 | 
| c@21 | 373     OutputDescriptor d3; | 
| c@25 | 374     d3.identifier = "tempogramACT"; | 
| c@25 | 375     d3.name = "Tempogram via ACT"; | 
| Chris@43 | 376     d3.description = "Tempogram calculated using autocorrelation method"; | 
| Chris@43 | 377     d3.unit = ""; // unit of bin contents, not of "bin label", so not bpm | 
| c@21 | 378     d3.hasFixedBinCount = true; | 
| c@28 | 379     d3.binCount = m_tempogramMaxLag - m_tempogramMinLag + 1; | 
| c@21 | 380     d3.hasKnownExtents = false; | 
| c@21 | 381     d3.isQuantized = false; | 
| c@21 | 382     d3.sampleType = OutputDescriptor::FixedSampleRate; | 
| c@21 | 383     d_sampleRate = tempogramInputSampleRate/m_tempogramHopSize; | 
| c@25 | 384     d3.sampleRate = d_sampleRate > 0.0 && !isnan(d_sampleRate) ? d_sampleRate : 0.0; | 
| c@28 | 385     for(int lag = m_tempogramMaxLag; lag >= (int)m_tempogramMinLag; lag--){ | 
| c@28 | 386         d3.binNames.push_back(floatToString(60/(m_inputStepSize*(lag/m_inputSampleRate)))); | 
| c@25 | 387     } | 
| c@21 | 388     d3.hasDuration = false; | 
| c@21 | 389     list.push_back(d3); | 
| c@21 | 390 | 
| c@25 | 391     OutputDescriptor d4; | 
| c@25 | 392     d4.identifier = "nc"; | 
| c@25 | 393     d4.name = "Novelty Curve"; | 
| Chris@43 | 394     d4.description = "Novelty curve underlying the tempogram calculations"; | 
| c@25 | 395     d4.unit = ""; | 
| c@25 | 396     d4.hasFixedBinCount = true; | 
| c@25 | 397     d4.binCount = 1; | 
| c@25 | 398     d4.hasKnownExtents = false; | 
| c@25 | 399     d4.isQuantized = false; | 
| c@25 | 400     d4.sampleType = OutputDescriptor::FixedSampleRate; | 
| c@9 | 401     d_sampleRate = tempogramInputSampleRate; | 
| c@25 | 402     d4.sampleRate = d_sampleRate > 0 && !isnan(d_sampleRate) ? d_sampleRate : 0; | 
| c@25 | 403     d4.hasDuration = false; | 
| c@25 | 404     list.push_back(d4); | 
| c@18 | 405 | 
| c@0 | 406     return list; | 
| c@0 | 407 } | 
| c@0 | 408 | 
| c@20 | 409 bool | 
| c@20 | 410 TempogramPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize) | 
| c@20 | 411 { | 
| c@20 | 412     if (channels < getMinChannelCount() || | 
| c@20 | 413 	channels > getMaxChannelCount()) return false; | 
| c@20 | 414 | 
| c@20 | 415     // Real initialisation work goes here! | 
| c@20 | 416     m_inputBlockSize = blockSize; | 
| c@20 | 417     m_inputStepSize = stepSize; | 
| c@20 | 418 | 
| c@24 | 419     //m_spectrogram = Spectrogram(m_inputBlockSize/2 + 1); | 
| c@21 | 420     if (!handleParameterValues()) return false; | 
| c@19 | 421     //cout << m_cyclicTempogramOctaveDivider << endl; | 
| c@4 | 422 | 
| c@0 | 423     return true; | 
| c@0 | 424 } | 
| c@0 | 425 | 
| c@0 | 426 void | 
| c@14 | 427 TempogramPlugin::reset() | 
| c@0 | 428 { | 
| c@0 | 429     // Clear buffers, reset stored values, etc | 
| c@19 | 430     m_spectrogram.clear(); | 
| c@21 | 431     handleParameterValues(); | 
| c@0 | 432 } | 
| c@0 | 433 | 
| c@14 | 434 TempogramPlugin::FeatureSet | 
| c@14 | 435 TempogramPlugin::process(const float *const *inputBuffers, Vamp::RealTime timestamp) | 
| c@0 | 436 { | 
| c@23 | 437     int n = m_inputBlockSize/2 + 1; | 
| c@0 | 438     const float *in = inputBuffers[0]; | 
| c@3 | 439 | 
| c@9 | 440     //calculate magnitude of FrequencyDomain input | 
| c@22 | 441     vector<float> fftCoefficients; | 
| c@23 | 442     for (int i = 0; i < n; i++){ | 
| c@0 | 443         float magnitude = sqrt(in[2*i] * in[2*i] + in[2*i + 1] * in[2*i + 1]); | 
| c@29 | 444         magnitude = magnitude > m_noveltyCurveMinV ? magnitude : m_noveltyCurveMinV; | 
| c@22 | 445         fftCoefficients.push_back(magnitude); | 
| c@0 | 446     } | 
| c@22 | 447     m_spectrogram.push_back(fftCoefficients); | 
| c@24 | 448     //m_spectrogram.push_back(fftCoefficients); | 
| c@21 | 449 | 
| c@23 | 450     return FeatureSet(); | 
| c@0 | 451 } | 
| c@0 | 452 | 
| c@14 | 453 TempogramPlugin::FeatureSet | 
| c@14 | 454 TempogramPlugin::getRemainingFeatures() | 
| c@11 | 455 { | 
| c@0 | 456 | 
| c@18 | 457     float * hannWindow = new float[m_tempogramWindowLength]; | 
| c@20 | 458     for (int i = 0; i < (int)m_tempogramWindowLength; i++){ | 
| c@14 | 459         hannWindow[i] = 0.0; | 
| c@4 | 460     } | 
| c@11 | 461 | 
| c@1 | 462     FeatureSet featureSet; | 
| c@0 | 463 | 
| c@19 | 464     //initialise novelty curve processor | 
| c@23 | 465     int numberOfBlocks = m_spectrogram.size(); | 
| Chris@48 | 466 | 
| c@22 | 467     NoveltyCurveProcessor nc(m_inputSampleRate, m_inputBlockSize, m_noveltyCurveCompressionConstant); | 
| c@21 | 468     vector<float> noveltyCurve = nc.spectrogramToNoveltyCurve(m_spectrogram); //calculate novelty curvefrom magnitude data | 
| c@4 | 469 | 
| c@9 | 470     //push novelty curve data to featureset 1 and set timestamps | 
| c@23 | 471     for (int i = 0; i < numberOfBlocks; i++){ | 
| c@19 | 472         Feature noveltyCurveFeature; | 
| c@19 | 473         noveltyCurveFeature.values.push_back(noveltyCurve[i]); | 
| c@19 | 474         noveltyCurveFeature.hasTimestamp = false; | 
| c@25 | 475         featureSet[3].push_back(noveltyCurveFeature); | 
| c@21 | 476         assert(!isnan(noveltyCurveFeature.values.back())); | 
| c@4 | 477     } | 
| c@4 | 478 | 
| c@9 | 479     //window function for spectrogram | 
| c@18 | 480     WindowFunction::hanning(hannWindow, m_tempogramWindowLength); | 
| c@9 | 481 | 
| c@9 | 482     //initialise spectrogram processor | 
| c@18 | 483     SpectrogramProcessor spectrogramProcessor(m_tempogramWindowLength, m_tempogramFftLength, m_tempogramHopSize); | 
| c@9 | 484     //compute spectrogram from novelty curve data (i.e., tempogram) | 
| c@25 | 485     Tempogram tempogramDFT = spectrogramProcessor.process(&noveltyCurve[0], numberOfBlocks, hannWindow); | 
| c@18 | 486     delete []hannWindow; | 
| c@18 | 487     hannWindow = 0; | 
| c@0 | 488 | 
| c@25 | 489     int tempogramLength = tempogramDFT.size(); | 
| c@7 | 490 | 
| c@9 | 491     //push tempogram data to featureset 0 and set timestamps. | 
| c@7 | 492     for (int block = 0; block < tempogramLength; block++){ | 
| c@25 | 493         Feature tempogramDFTFeature; | 
| c@28 | 494 | 
| c@28 | 495         assert(tempogramDFT[block].size() == (m_tempogramFftLength/2 + 1)); | 
| c@28 | 496         for(int k = m_tempogramMinBin; k <= (int)m_tempogramMaxBin; k++){ | 
| c@28 | 497             tempogramDFTFeature.values.push_back(tempogramDFT[block][k]); | 
| c@28 | 498         } | 
| c@28 | 499         tempogramDFTFeature.hasTimestamp = false; | 
| c@28 | 500         featureSet[1].push_back(tempogramDFTFeature); | 
| c@28 | 501     } | 
| c@28 | 502 | 
| c@28 | 503     AutocorrelationProcessor autocorrelationProcessor(m_tempogramWindowLength, m_tempogramHopSize); | 
| c@28 | 504     Tempogram tempogramACT = autocorrelationProcessor.process(&noveltyCurve[0], numberOfBlocks); | 
| c@28 | 505 | 
| c@28 | 506     for (int block = 0; block < tempogramLength; block++){ | 
| c@25 | 507         Feature tempogramACTFeature; | 
| Chris@44 | 508 | 
| c@28 | 509         for(int k = m_tempogramMaxLag; k >= (int)m_tempogramMinLag; k--){ | 
| c@25 | 510             tempogramACTFeature.values.push_back(tempogramACT[block][k]); | 
| c@0 | 511         } | 
| c@25 | 512         tempogramACTFeature.hasTimestamp = false; | 
| c@25 | 513         featureSet[2].push_back(tempogramACTFeature); | 
| c@0 | 514     } | 
| c@0 | 515 | 
| c@18 | 516     //Calculate cyclic tempogram | 
| c@22 | 517     vector< vector<unsigned int> > logBins = calculateTempogramNearestNeighbourLogBins(); | 
| c@18 | 518 | 
| c@22 | 519     //assert((int)logBins.size() == m_cyclicTempogramOctaveDivider*m_cyclicTempogramNumberOfOctaves); | 
| c@18 | 520     for (int block = 0; block < tempogramLength; block++){ | 
| c@19 | 521         Feature cyclicTempogramFeature; | 
| c@18 | 522 | 
| c@23 | 523         for (int i = 0; i < m_cyclicTempogramOctaveDivider; i++){ | 
| c@18 | 524             float sum = 0; | 
| c@21 | 525 | 
| c@23 | 526             for (int j = 0; j < m_cyclicTempogramNumberOfOctaves; j++){ | 
| Chris@48 | 527                 sum += tempogramDFT[block][logBins[j][i]]; | 
| c@18 | 528             } | 
| c@19 | 529             cyclicTempogramFeature.values.push_back(sum/m_cyclicTempogramNumberOfOctaves); | 
| c@21 | 530             assert(!isnan(cyclicTempogramFeature.values.back())); | 
| c@18 | 531         } | 
| c@18 | 532 | 
| c@19 | 533         cyclicTempogramFeature.hasTimestamp = false; | 
| c@21 | 534         featureSet[0].push_back(cyclicTempogramFeature); | 
| c@18 | 535     } | 
| c@0 | 536 | 
| c@0 | 537     return featureSet; | 
| c@0 | 538 } | 
| c@22 | 539 | 
| c@22 | 540 vector< vector<unsigned int> > TempogramPlugin::calculateTempogramNearestNeighbourLogBins() const | 
| c@22 | 541 { | 
| c@22 | 542     vector< vector<unsigned int> > logBins; | 
| c@22 | 543 | 
| c@22 | 544     for (int octave = 0; octave < (int)m_cyclicTempogramNumberOfOctaves; octave++){ | 
| c@22 | 545         vector<unsigned int> octaveBins; | 
| Chris@47 | 546 | 
| c@22 | 547         for (int bin = 0; bin < (int)m_cyclicTempogramOctaveDivider; bin++){ | 
| c@22 | 548             float bpm = m_cyclicTempogramMinBPM*pow(2.0f, octave+(float)bin/m_cyclicTempogramOctaveDivider); | 
| c@22 | 549             octaveBins.push_back(bpmToBin(bpm)); | 
| c@22 | 550         } | 
| c@22 | 551         logBins.push_back(octaveBins); | 
| c@22 | 552     } | 
| c@22 | 553 | 
| c@22 | 554     return logBins; | 
| c@22 | 555 } | 
| c@22 | 556 | 
| c@22 | 557 unsigned int TempogramPlugin::bpmToBin(const float &bpm) const | 
| c@22 | 558 { | 
| c@22 | 559     float w = (float)bpm/60; | 
| c@22 | 560     float sampleRate = m_inputSampleRate/m_inputStepSize; | 
| c@22 | 561     int bin = floor((float)m_tempogramFftLength*w/sampleRate + 0.5); | 
| c@22 | 562 | 
| c@22 | 563     if(bin < 0) bin = 0; | 
| Chris@46 | 564     else if(bin > m_tempogramFftLength/2.0f) bin = m_tempogramFftLength/2.0f; | 
| c@22 | 565 | 
| c@22 | 566     return bin; | 
| c@22 | 567 } | 
| c@22 | 568 | 
| c@22 | 569 float TempogramPlugin::binToBPM(const int &bin) const | 
| c@22 | 570 { | 
| c@22 | 571     float sampleRate = m_inputSampleRate/m_inputStepSize; | 
| c@22 | 572 | 
| c@22 | 573     return (bin*sampleRate/m_tempogramFftLength)*60; | 
| c@22 | 574 } | 
| c@22 | 575 | 
| c@22 | 576 bool TempogramPlugin::handleParameterValues(){ | 
| c@22 | 577 | 
| Chris@42 | 578     if (m_tempogramLog2HopSize <= 0) { | 
| Chris@42 | 579 	cerr << "Tempogram log2 hop size " << m_tempogramLog2HopSize | 
| Chris@42 | 580 	     << " <= 0, failing initialise" << endl; | 
| Chris@42 | 581 	return false; | 
| Chris@42 | 582     } | 
| Chris@42 | 583     if (m_tempogramLog2FftLength <= 0) { | 
| Chris@42 | 584 	cerr << "Tempogram log2 fft length " << m_tempogramLog2FftLength | 
| Chris@42 | 585 	     << " <= 0, failing initialise" << endl; | 
| Chris@42 | 586 	return false; | 
| Chris@42 | 587     } | 
| c@22 | 588 | 
| Chris@42 | 589     if (m_tempogramMinBPM < 1) { | 
| Chris@42 | 590 	m_tempogramMinBPM = 1; | 
| Chris@42 | 591     } | 
| c@22 | 592     if (m_tempogramMinBPM >= m_tempogramMaxBPM){ | 
| c@22 | 593         m_tempogramMinBPM = 30; | 
| c@22 | 594         m_tempogramMaxBPM = 480; | 
| c@22 | 595     } | 
| c@22 | 596 | 
| c@29 | 597     m_noveltyCurveMinV = pow(10,(float)m_noveltyCurveMinDB/20); | 
| c@29 | 598 | 
| c@29 | 599     m_tempogramWindowLength = pow(2,m_tempogramLog2WindowLength); | 
| c@29 | 600     m_tempogramHopSize = pow(2,m_tempogramLog2HopSize); | 
| c@29 | 601     m_tempogramFftLength = pow(2,m_tempogramLog2FftLength); | 
| c@29 | 602 | 
| c@30 | 603     if (m_tempogramFftLength < m_tempogramWindowLength){ | 
| c@30 | 604         m_tempogramFftLength = m_tempogramWindowLength; | 
| c@30 | 605     } | 
| c@30 | 606 | 
| c@22 | 607     float tempogramInputSampleRate = (float)m_inputSampleRate/m_inputStepSize; | 
| c@28 | 608     m_tempogramMinBin = (max((int)floor(((m_tempogramMinBPM/60)/tempogramInputSampleRate)*m_tempogramFftLength), 0)); | 
| c@28 | 609     m_tempogramMaxBin = (min((int)ceil(((m_tempogramMaxBPM/60)/tempogramInputSampleRate)*m_tempogramFftLength), (int)(m_tempogramFftLength/2))); | 
| Chris@32 | 610 | 
| Chris@32 | 611     if (m_tempogramMaxBin < m_tempogramMinBin) { | 
| Chris@32 | 612 	cerr << "At audio sample rate " << m_inputSampleRate | 
| Chris@32 | 613 	     << ", tempogram sample rate " << tempogramInputSampleRate | 
| Chris@32 | 614 	     << " with bpm range " << m_tempogramMinBPM << " -> " | 
| Chris@32 | 615 	     << m_tempogramMaxBPM << ", min bin = " << m_tempogramMinBin | 
| Chris@32 | 616 	     << " > max bin " << m_tempogramMaxBin | 
| Chris@32 | 617 	     << ": can't proceed, failing initialise" << endl; | 
| Chris@32 | 618 	return false; | 
| Chris@32 | 619     } | 
| c@28 | 620 | 
| c@28 | 621     m_tempogramMinLag = max((int)ceil((60/(m_inputStepSize * m_tempogramMaxBPM))*m_inputSampleRate), 0); | 
| Chris@45 | 622     m_tempogramMaxLag = min((int)floor((60/(m_inputStepSize * m_tempogramMinBPM))*m_inputSampleRate), (int)m_tempogramWindowLength-1); | 
| Chris@32 | 623 | 
| Chris@32 | 624     if (m_tempogramMaxLag < m_tempogramMinLag) { | 
| Chris@32 | 625 	cerr << "At audio sample rate " << m_inputSampleRate | 
| Chris@32 | 626 	     << ", tempogram sample rate " << tempogramInputSampleRate | 
| Chris@42 | 627 	     << ", window length " << m_tempogramWindowLength | 
| Chris@32 | 628 	     << " with bpm range " << m_tempogramMinBPM << " -> " | 
| Chris@42 | 629 	     << m_tempogramMaxBPM << ", min lag = " << m_tempogramMinLag | 
| Chris@42 | 630 	     << " > max lag " << m_tempogramMaxLag | 
| Chris@32 | 631 	     << ": can't proceed, failing initialise" << endl; | 
| Chris@32 | 632 	return false; | 
| Chris@32 | 633     } | 
| c@22 | 634 | 
| Chris@47 | 635     m_cyclicTempogramMinBPM = max(binToBPM(m_tempogramMinBin), m_tempogramMinBPM); | 
| Chris@47 | 636     float cyclicTempogramMaxBPM = min(binToBPM(m_tempogramMaxBin), m_tempogramMaxBPM); | 
| Chris@47 | 637 | 
| c@22 | 638     m_cyclicTempogramNumberOfOctaves = floor(log2(cyclicTempogramMaxBPM/m_cyclicTempogramMinBPM)); | 
| Chris@42 | 639 | 
| Chris@42 | 640     if (m_cyclicTempogramNumberOfOctaves < 1) { | 
| Chris@42 | 641 	cerr << "At audio sample rate " << m_inputSampleRate | 
| Chris@42 | 642 	     << ", tempogram sample rate " << tempogramInputSampleRate | 
| Chris@42 | 643 	     << " with bpm range " << m_tempogramMinBPM << " -> " | 
| Chris@42 | 644 	     << m_tempogramMaxBPM << ", cyclic tempogram min bpm = " | 
| Chris@42 | 645 	     << m_cyclicTempogramMinBPM << " and max bpm = " | 
| Chris@42 | 646 	     << cyclicTempogramMaxBPM << " giving number of octaves = " | 
| Chris@42 | 647 	     << m_cyclicTempogramNumberOfOctaves | 
| Chris@42 | 648 	     << ": can't proceed, failing initialise" << endl; | 
| Chris@42 | 649 	return false; | 
| Chris@42 | 650     } | 
| c@22 | 651 | 
| c@22 | 652     return true; | 
| c@22 | 653 } | 
| c@22 | 654 | 
| c@22 | 655 string TempogramPlugin::floatToString(float value) const | 
| c@22 | 656 { | 
| c@22 | 657     ostringstream ss; | 
| c@22 | 658 | 
| c@22 | 659     if(!(ss << value)) throw runtime_error("TempogramPlugin::floatToString(): invalid conversion from float to string"); | 
| c@22 | 660     return ss.str(); | 
| c@22 | 661 } |