diff TempogramPlugin.cpp @ 14:c11367df624d

* Renamed NoveltyCurve.* and Spectrogram.* to $(Name)Processor.* * Aligned novelty curve with audio - when performing FIRFilter::process(params), take inputLength after group delay. * Removed trail of Spectrogram. * General tidying!
author Carl Bussey <c.bussey@se10.qmul.ac.uk>
date Thu, 14 Aug 2014 10:31:49 +0100
parents Tempogram.cpp@7680cc4c0073
children 203551cbad47
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TempogramPlugin.cpp	Thu Aug 14 10:31:49 2014 +0100
@@ -0,0 +1,472 @@
+
+// This is a skeleton file for use in creating your own plugin
+// libraries.  Replace MyPlugin and myPlugin throughout with the name
+// of your first plugin class, and fill in the gaps as appropriate.
+
+
+#include "TempogramPlugin.h"
+#include <sstream>
+#include <stdexcept>
+
+using Vamp::FFT;
+using Vamp::RealTime;
+using namespace std;
+
+TempogramPlugin::TempogramPlugin(float inputSampleRate) :
+    Plugin(inputSampleRate),
+    m_blockSize(0),
+    m_stepSize(0),
+    m_compressionConstant(1000), //parameter
+    m_minDB(0),
+    m_log2WindowLength(10), //parameter
+    m_windowLength(pow((float)2,m_log2WindowLength)),
+    m_log2FftLength(m_log2WindowLength),
+    m_fftLength(m_windowLength),
+    m_log2HopSize(6), //parameter
+    m_hopSize(pow((float)2,m_log2HopSize)),
+    m_minBPM(30),
+    m_maxBPM(480),
+    m_minBin(0), //set in initialise()
+    m_maxBin(0) //set in initialise()
+
+    // Also be sure to set your plugin parameters (presumably stored
+    // in member variables) to their default values here -- the host
+    // will not do that for you
+{
+}
+
+TempogramPlugin::~TempogramPlugin()
+{
+    //delete stuff
+    cleanup();
+}
+
+string
+TempogramPlugin::getIdentifier() const
+{
+    return "tempogram";
+}
+
+string
+TempogramPlugin::getName() const
+{
+    return "Tempogram";
+}
+
+string
+TempogramPlugin::getDescription() const
+{
+    // Return something helpful here!
+    return "Cyclic Tempogram as described by Peter Grosche and Meinard Muller";
+}
+
+string
+TempogramPlugin::getMaker() const
+{
+    //Your name here
+    return "Carl Bussey";
+}
+
+int
+TempogramPlugin::getPluginVersion() const
+{
+    // Increment this each time you release a version that behaves
+    // differently from the previous one
+    return 1;
+}
+
+string
+TempogramPlugin::getCopyright() const
+{
+    // This function is not ideally named.  It does not necessarily
+    // need to say who made the plugin -- getMaker does that -- but it
+    // should indicate the terms under which it is distributed.  For
+    // example, "Copyright (year). All Rights Reserved", or "GPL"
+    return "";
+}
+
+TempogramPlugin::InputDomain
+TempogramPlugin::getInputDomain() const
+{
+    return FrequencyDomain;
+}
+
+size_t
+TempogramPlugin::getPreferredBlockSize() const
+{
+    return 2048; // 0 means "I can handle any block size"
+}
+
+size_t 
+TempogramPlugin::getPreferredStepSize() const
+{
+    return 1024; // 0 means "anything sensible"; in practice this
+              // means the same as the block size for TimeDomain
+              // plugins, or half of it for FrequencyDomain plugins
+}
+
+size_t
+TempogramPlugin::getMinChannelCount() const
+{
+    return 1;
+}
+
+size_t
+TempogramPlugin::getMaxChannelCount() const
+{
+    return 1;
+}
+
+TempogramPlugin::ParameterList
+TempogramPlugin::getParameterDescriptors() const
+{
+    ParameterList list;
+
+    // If the plugin has no adjustable parameters, return an empty
+    // list here (and there's no need to provide implementations of
+    // getParameter and setParameter in that case either).
+
+    // Note that it is your responsibility to make sure the parameters
+    // start off having their default values (e.g. in the constructor
+    // above).  The host needs to know the default value so it can do
+    // things like provide a "reset to default" function, but it will
+    // not explicitly set your parameters to their defaults for you if
+    // they have not changed in the mean time.
+
+    ParameterDescriptor d1;
+    d1.identifier = "C";
+    d1.name = "C";
+    d1.description = "Spectrogram compression constant, C, used when retrieving the novelty curve from the audio.";
+    d1.unit = "";
+    d1.minValue = 2;
+    d1.maxValue = 10000;
+    d1.defaultValue = 1000;
+    d1.isQuantized = false;
+    list.push_back(d1);
+
+    ParameterDescriptor d2;
+    d2.identifier = "log2TN";
+    d2.name = "Tempogram Window Length";
+    d2.description = "FFT window length when analysing the novelty curve and extracting the tempogram time-frequency function.";
+    d2.unit = "";
+    d2.minValue = 7;
+    d2.maxValue = 12;
+    d2.defaultValue = 10;
+    d2.isQuantized = true;
+    d2.quantizeStep = 1;
+    for (int i = d2.minValue; i <= d2.maxValue; i++){
+        d2.valueNames.push_back(floatToString(pow((float)2,(float)i)));
+    }
+    list.push_back(d2);
+    
+    ParameterDescriptor d3;
+    d3.identifier = "log2HopSize";
+    d3.name = "Tempogram Hopsize";
+    d3.description = "FFT hopsize when analysing the novelty curve and extracting the tempogram time-frequency function.";
+    d3.unit = "";
+    d3.minValue = 6;
+    d3.maxValue = 12;
+    d3.defaultValue = 6;
+    d3.isQuantized = true;
+    d3.quantizeStep = 1;
+    for (int i = d3.minValue; i <= d3.maxValue; i++){
+        d3.valueNames.push_back(floatToString(pow((float)2,(float)i)));
+    }
+    list.push_back(d3);
+    
+    ParameterDescriptor d4;
+    d4.identifier = "log2FftLength";
+    d4.name = "Tempogram FFT Length";
+    d4.description = "FFT length when analysing the novelty curve and extracting the tempogram time-frequency function. This parameter determines the amount of zero padding.";
+    d4.unit = "";
+    d4.minValue = 6;
+    d4.maxValue = 12;
+    d4.defaultValue = d2.defaultValue;
+    d4.isQuantized = true;
+    d4.quantizeStep = 1;
+    for (int i = d4.minValue; i <= d4.maxValue; i++){
+        d4.valueNames.push_back(floatToString(pow((float)2,(float)i)));
+    }
+    list.push_back(d4);
+    
+    ParameterDescriptor d5;
+    d5.identifier = "minBPM";
+    d5.name = "Minimum BPM";
+    d5.description = "The minimum BPM of the tempogram output bins.";
+    d5.unit = "";
+    d5.minValue = 0;
+    d5.maxValue = 2000;
+    d5.defaultValue = 30;
+    d5.isQuantized = true;
+    d5.quantizeStep = 5;
+    list.push_back(d5);
+    
+    ParameterDescriptor d6;
+    d6.identifier = "maxBPM";
+    d6.name = "Maximum BPM";
+    d6.description = "The minimum BPM of the tempogram output bins.";
+    d6.unit = "";
+    d6.minValue = 30;
+    d6.maxValue = 2000;
+    d6.defaultValue = 480;
+    d6.isQuantized = true;
+    d6.quantizeStep = 5;
+    list.push_back(d6);
+
+    return list;
+}
+
+float
+TempogramPlugin::getParameter(string identifier) const
+{
+    if (identifier == "C") {
+        return m_compressionConstant; // return the ACTUAL current value of your parameter here!
+    }
+    else if (identifier == "log2TN"){
+        return m_log2WindowLength;
+    }
+    else if (identifier == "log2HopSize"){
+        return m_log2HopSize;
+    }
+    else if (identifier == "log2FftLength"){
+        return m_log2FftLength;
+    }
+    else if (identifier == "minBPM") {
+        return m_minBPM;
+    }
+    else if (identifier == "maxBPM"){
+        return m_maxBPM;
+    }
+    
+    return 0;
+}
+
+void
+TempogramPlugin::setParameter(string identifier, float value) 
+{
+    
+    if (identifier == "C") {
+        m_compressionConstant = value; // set the actual value of your parameter
+    }
+    else if (identifier == "log2TN") {
+        m_windowLength = pow(2,value);
+        m_log2WindowLength = value;
+    }
+    else if (identifier == "log2HopSize"){
+        m_hopSize = pow(2,value);
+        m_log2HopSize = value;
+    }
+    else if (identifier == "log2HopFftLength"){
+        m_fftLength = pow(2,value);
+        m_log2FftLength = value;
+    }
+    else if (identifier == "minBPM") {
+        m_minBPM = value;
+    }
+    else if (identifier == "maxBPM"){
+        m_maxBPM = value;
+    }
+    
+}
+
+void TempogramPlugin::updateBPMParameters(){
+
+}
+
+TempogramPlugin::ProgramList
+TempogramPlugin::getPrograms() const
+{
+    ProgramList list;
+
+    // If you have no programs, return an empty list (or simply don't
+    // implement this function or getCurrentProgram/selectProgram)
+
+    return list;
+}
+
+string
+TempogramPlugin::getCurrentProgram() const
+{
+    return ""; // no programs
+}
+
+void
+TempogramPlugin::selectProgram(string name)
+{
+}
+
+string TempogramPlugin::floatToString(float value) const
+{
+    ostringstream ss;
+    
+    if(!(ss << value)) throw runtime_error("TempogramPlugin::floatToString(): invalid conversion from float to string");
+    return ss.str();
+}
+
+TempogramPlugin::OutputList
+TempogramPlugin::getOutputDescriptors() const
+{
+    OutputList list;
+
+    // See OutputDescriptor documentation for the possibilities here.
+    // Every plugin must have at least one output.
+    
+    OutputDescriptor d;
+    float d_sampleRate;
+    float tempogramInputSampleRate = (float)m_inputSampleRate/m_stepSize;
+    
+    d.identifier = "tempogram";
+    d.name = "Tempogram";
+    d.description = "Tempogram";
+    d.unit = "BPM";
+    d.hasFixedBinCount = true;
+    d.binCount = m_maxBin - m_minBin + 1;
+    d.hasKnownExtents = false;
+    d.isQuantized = false;
+    d.sampleType = OutputDescriptor::FixedSampleRate;
+    d_sampleRate = tempogramInputSampleRate/m_hopSize;
+    d.sampleRate = d_sampleRate > 0.0 && !isnan(d_sampleRate) ? d_sampleRate : 0.0;
+    for(int i = m_minBin; i <= (int)m_maxBin; i++){
+        float w = ((float)i/m_fftLength)*(tempogramInputSampleRate);
+        d.binNames.push_back(floatToString(w*60));
+    }
+    d.hasDuration = false;
+    list.push_back(d);
+    
+    d.identifier = "nc";
+    d.name = "Novelty Curve";
+    d.description = "Novelty Curve";
+    d.unit = "";
+    d.hasFixedBinCount = true;
+    d.binCount = 1;
+    d.hasKnownExtents = false;
+    d.isQuantized = false;
+    d.sampleType = OutputDescriptor::FixedSampleRate;
+    d_sampleRate = tempogramInputSampleRate;
+    d.sampleRate = d_sampleRate > 0 && !isnan(d_sampleRate) ? d_sampleRate : 0.0;
+    d.hasDuration = false;
+    list.push_back(d);
+    
+    return list;
+}
+
+bool
+TempogramPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize)
+{
+    if (channels < getMinChannelCount() ||
+	channels > getMaxChannelCount()) return false;
+    
+    // Real initialisation work goes here!
+    m_blockSize = blockSize;
+    m_stepSize = stepSize;
+    m_minDB = pow(10,(float)-74/20);
+    
+    if (m_fftLength < m_windowLength){
+        m_fftLength = m_windowLength;
+    }
+    if (m_minBPM > m_maxBPM){
+        m_minBPM = 30;
+        m_maxBPM = 480;
+    }
+    float tempogramInputSampleRate = (float)m_inputSampleRate/m_stepSize;
+    m_minBin = (unsigned int)(max(floor(((m_minBPM/60)/tempogramInputSampleRate)*m_fftLength), (float)0.0));
+    m_maxBin = (unsigned int)(min(ceil(((m_maxBPM/60)/tempogramInputSampleRate)*m_fftLength), (float)m_fftLength/2));
+    
+    m_spectrogram = SpectrogramTransposed(m_blockSize/2 + 1);
+    
+    return true;
+}
+
+void TempogramPlugin::cleanup(){
+
+}
+
+void
+TempogramPlugin::reset()
+{
+    // Clear buffers, reset stored values, etc
+    ncTimestamps.clear();
+    m_spectrogram = SpectrogramTransposed(m_blockSize/2 + 1);
+}
+
+TempogramPlugin::FeatureSet
+TempogramPlugin::process(const float *const *inputBuffers, Vamp::RealTime timestamp)
+{
+    size_t n = m_blockSize/2 + 1;
+    
+    FeatureSet featureSet;
+    Feature feature;
+    
+    const float *in = inputBuffers[0];
+
+    //calculate magnitude of FrequencyDomain input
+    for (unsigned int i = 0; i < n; i++){
+        float magnitude = sqrt(in[2*i] * in[2*i] + in[2*i + 1] * in[2*i + 1]);
+        magnitude = magnitude > m_minDB ? magnitude : m_minDB;
+        m_spectrogram[i].push_back(magnitude);
+    }
+    
+    ncTimestamps.push_back(timestamp); //save timestamp
+
+    return featureSet;
+}
+
+TempogramPlugin::FeatureSet
+TempogramPlugin::getRemainingFeatures()
+{
+    
+    float * hannWindow = new float[m_windowLength];
+    for (unsigned int i = 0; i < m_windowLength; i++){
+        hannWindow[i] = 0.0;
+    }
+    
+    FeatureSet featureSet;
+    
+    //initialise m_noveltyCurve processor
+    size_t numberOfBlocks = m_spectrogram[0].size();
+    NoveltyCurveProcessor nc(m_inputSampleRate, m_blockSize, numberOfBlocks, m_compressionConstant);
+    m_noveltyCurve = nc.spectrogramToNoveltyCurve(m_spectrogram); //calculate novelty curve from magnitude data
+    
+    //push novelty curve data to featureset 1 and set timestamps
+    for (unsigned int i = 0; i < numberOfBlocks; i++){
+        Feature feature;
+        feature.values.push_back(m_noveltyCurve[i]);
+        feature.hasTimestamp = false;
+        //feature.timestamp = ncTimestamps[i];
+        featureSet[1].push_back(feature);
+    }
+    
+    //window function for spectrogram
+    WindowFunction::hanning(hannWindow, m_windowLength);
+    
+    //initialise spectrogram processor
+    SpectrogramProcessor spectrogramProcessor(m_windowLength, m_fftLength, m_hopSize);
+    //compute spectrogram from novelty curve data (i.e., tempogram)
+    Spectrogram tempogram = spectrogramProcessor.process(&m_noveltyCurve[0], numberOfBlocks, hannWindow);
+    
+    int timePointer = m_hopSize-m_windowLength/2;
+    int tempogramLength = tempogram.size();
+    
+    //push tempogram data to featureset 0 and set timestamps.
+    for (int block = 0; block < tempogramLength; block++){
+        Feature feature;
+        
+        //int timeMS = floor(1000*(m_stepSize*timePointer)/m_inputSampleRate + 0.5);
+        
+        assert(tempogram[block].size() == (m_fftLength/2 + 1));
+        for(int k = m_minBin; k < (int)m_maxBin; k++){
+            feature.values.push_back(tempogram[block][k]);
+            //cout << tempogram[k][block] << endl;
+        }
+        feature.hasTimestamp = false;
+        //feature.timestamp = RealTime::fromMilliseconds(timeMS);
+        featureSet[0].push_back(feature);
+        
+        timePointer += m_hopSize;
+    }
+    
+    delete []hannWindow;
+    hannWindow = 0;
+    
+    return featureSet;
+}