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