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@0: #include "Tempogram.h" c@0: #include "FIRFilter.h" c@0: #include "WindowFunction.h" c@0: #include c@0: #include c@0: #include c@0: #include c@0: using Vamp::FFT; c@0: using namespace std; c@0: c@0: Tempogram::Tempogram(float inputSampleRate) : c@0: Plugin(inputSampleRate), c@0: m_blockSize(0), c@1: m_stepSize(0), c@0: compressionConstant(1000), //make param c@0: previousY(NULL), c@0: currentY(NULL), c@0: tN(1024), //make param c@0: thopSize(512), //make param c@0: fftInput(NULL), c@0: fftOutputReal(NULL), c@0: fftOutputImag(NULL), c@0: ncLength(0) 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@0: Tempogram::~Tempogram() c@0: { c@0: //delete stuff c@0: } c@0: c@0: string c@0: Tempogram::getIdentifier() const c@0: { c@0: return "tempogram"; c@0: } c@0: c@0: string c@0: Tempogram::getName() const c@0: { c@0: return "Tempogram"; c@0: } c@0: c@0: string c@0: Tempogram::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@0: Tempogram::getMaker() const c@0: { c@0: //Your name here c@0: return "Carl Bussey"; c@0: } c@0: c@0: int c@0: Tempogram::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@0: Tempogram::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@0: Tempogram::InputDomain c@0: Tempogram::getInputDomain() const c@0: { c@0: return FrequencyDomain; c@0: } c@0: c@0: size_t c@0: Tempogram::getPreferredBlockSize() const c@0: { c@0: return 0; // 0 means "I can handle any block size" c@0: } c@0: c@0: size_t c@0: Tempogram::getPreferredStepSize() const c@0: { c@0: return 0; // 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@0: Tempogram::getMinChannelCount() const c@0: { c@0: return 1; c@0: } c@0: c@0: size_t c@0: Tempogram::getMaxChannelCount() const c@0: { c@0: return 1; c@0: } c@0: c@0: Tempogram::ParameterList c@0: Tempogram::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@0: ParameterDescriptor C; c@0: C.identifier = "C"; c@0: C.name = "C"; c@0: C.description = "Spectrogram compression constant, C"; c@0: C.unit = ""; c@0: C.minValue = 2; c@0: C.maxValue = 10000; c@0: C.defaultValue = 1000; c@0: C.isQuantized = false; c@0: list.push_back(C); c@0: c@0: ParameterDescriptor tN; c@0: tN.identifier = "tN"; c@0: tN.name = "Tempogram FFT length"; c@0: tN.description = "Tempogram FFT length."; c@0: tN.unit = ""; c@0: tN.minValue = 128; c@0: tN.maxValue = 4096; c@0: tN.defaultValue = 1024; c@0: tN.isQuantized = true; c@0: tN.quantizeStep = 128; c@0: list.push_back(tN); c@0: c@0: return list; c@0: } c@0: c@0: float c@0: Tempogram::getParameter(string identifier) const c@0: { c@0: if (identifier == "C") { c@0: return compressionConstant; // return the ACTUAL current value of your parameter here! c@0: } c@0: if (identifier == "tN"){ c@0: return tN; c@0: } c@0: c@0: return 0; c@0: } c@0: c@0: void c@0: Tempogram::setParameter(string identifier, float value) c@0: { c@0: if (identifier == "C") { c@1: compressionConstant = value; // set the actual value of your parameter c@0: } c@0: if (identifier == "tN") { c@0: tN = value; c@0: } c@0: } c@0: c@0: Tempogram::ProgramList c@0: Tempogram::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@0: Tempogram::getCurrentProgram() const c@0: { c@0: return ""; // no programs c@0: } c@0: c@0: void c@0: Tempogram::selectProgram(string name) c@0: { c@0: } c@0: c@0: Tempogram::OutputList c@0: Tempogram::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@1: d.identifier = "tempogram"; c@0: d.name = "Cyclic Tempogram"; c@0: d.description = "Cyclic Tempogram"; c@0: d.unit = ""; c@1: d.hasFixedBinCount = true; c@1: d.binCount = tN; c@0: d.hasKnownExtents = false; c@0: d.isQuantized = false; c@1: d.sampleType = OutputDescriptor::FixedSampleRate; c@1: float d_sampleRate = m_inputSampleRate/(m_stepSize * thopSize); c@1: d.sampleRate = d_sampleRate > 0.0 && !isnan(d_sampleRate) ? d_sampleRate : 0.0; c@0: d.hasDuration = false; c@0: list.push_back(d); c@0: 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@1: d_sampleRate = m_inputSampleRate/m_stepSize; 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@0: Tempogram::initialise(size_t channels, size_t stepSize, size_t blockSize) c@0: { c@0: if (channels < getMinChannelCount() || c@0: channels > getMaxChannelCount()) return false; c@0: c@0: // Real initialisation work goes here! c@0: m_blockSize = blockSize; c@1: m_stepSize = stepSize; c@0: currentY = new float[m_blockSize]; c@0: previousY = new float[m_blockSize]; c@0: c@0: return true; c@0: } c@0: c@0: void c@0: Tempogram::reset() c@0: { c@0: // Clear buffers, reset stored values, etc c@0: } c@0: c@0: Tempogram::FeatureSet c@0: Tempogram::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@0: c@0: //Calculate log magnitude c@0: float sum = 0; c@0: for (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@0: currentY[i] = log(1+compressionConstant*magnitude); //should be 1+C*magnitude c@0: if(currentY[i] >= previousY[i]){ c@0: sum += (currentY[i] - previousY[i]); c@0: } c@0: } c@0: c@0: noveltyCurve.push_back(sum); c@0: c@0: float *tmpY = currentY; c@0: currentY = previousY; c@0: previousY = tmpY; c@0: c@0: ncTimestamps.push_back(timestamp); c@0: c@0: return FeatureSet(); c@0: } c@0: c@0: void c@0: Tempogram::initialiseForGRF(){ c@0: hannN = 129; c@0: hannWindow = new float[hannN]; c@0: hannWindowtN = new float[tN]; c@0: fftInput = new double[tN]; c@0: fftOutputReal = new double[tN]; c@0: fftOutputImag = new double[tN]; c@0: ncLength = noveltyCurve.size(); c@0: c@0: WindowFunction::hanning(hannWindow, hannN, true); c@0: } c@0: c@0: void c@0: Tempogram::cleanupForGRF(){ c@0: delete []hannWindow; c@0: hannWindow = NULL; c@0: delete []hannWindowtN; c@0: hannWindowtN = NULL; c@0: delete []fftInput; c@0: fftInput = NULL; c@0: delete []fftOutputReal; c@0: fftOutputReal = NULL; c@0: delete []fftOutputImag; c@0: fftOutputImag = NULL; c@0: } c@0: c@0: Tempogram::FeatureSet c@0: Tempogram::getRemainingFeatures() c@0: { c@0: //Make sure this is called at the beginning of the function c@0: initialiseForGRF(); c@1: FeatureSet featureSet; c@0: c@0: vector noveltyCurveLocalAverage(ncLength); c@0: c@0: FIRFilter *filter = new FIRFilter(ncLength, hannN); c@0: filter->process(&noveltyCurve[0], hannWindow, &noveltyCurveLocalAverage[0]); c@0: delete filter; c@0: c@0: for(int i = 0; i < ncLength; i++){ c@0: noveltyCurve[i] -= noveltyCurveLocalAverage[i]; c@0: noveltyCurve[i] = noveltyCurve[i] >= 0 ? noveltyCurve[i] : 0; c@1: Feature ncFeature; c@1: ncFeature.values.push_back(noveltyCurve[i]); c@1: featureSet[1].push_back(ncFeature); c@0: } c@0: c@0: int i=0; c@0: WindowFunction::hanning(hannWindowtN, tN); c@0: c@0: int index; c@1: int frameBeginOffset = floor(tN/2 + 0.5); c@0: int timestampInc = floor((((float)ncTimestamps[1].nsec - ncTimestamps[0].nsec)/1e9)*(thopSize) + 0.5); c@0: //cout << timestampInc << endl; c@0: c@0: while(i < ncLength){ c@0: Feature feature; c@0: c@1: for (int n = frameBeginOffset; n < tN; n++){ c@0: index = i + n - tN/2; c@0: assert (index >= 0); c@0: c@0: if(index < ncLength){ c@0: fftInput[n] = noveltyCurve[i + n] * hannWindowtN[n]; c@0: } c@0: else if(index >= ncLength){ c@0: fftInput[n] = 0.0; //pad the end with zeros c@0: } c@0: //cout << fftInput[n] << endl; c@0: } c@0: if (i+tN/2 > ncLength){ c@1: feature.timestamp = Vamp::RealTime::fromSeconds(ncTimestamps[i].sec + timestampInc); c@0: } c@0: else{ c@1: feature.timestamp = ncTimestamps[i + tN/2]; c@0: } c@0: c@0: FFT::forward(tN, fftInput, NULL, fftOutputReal, fftOutputImag); c@0: c@0: //TODO: sample at logarithmic spacing c@0: for(int k = 0; k < tN; k++){ c@1: float fftOutputPower = (fftOutputReal[k]*fftOutputReal[k] + fftOutputImag[k]*fftOutputImag[k]); //Magnitude or power? c@0: c@0: feature.values.push_back(fftOutputPower); c@0: } c@0: c@0: i += thopSize; c@1: frameBeginOffset = 0; c@0: c@0: feature.hasTimestamp = true; c@0: featureSet[0].push_back(feature); c@0: } c@0: c@0: //Make sure this is called at the end of the function c@0: cleanupForGRF(); c@0: c@0: return featureSet; c@0: }