Mercurial > hg > qm-vamp-plugins
changeset 92:3602e755b696
* Add the Adaptive Spectrogram plugin -- but it isn't working correctly
yet. Also, when it does work, it will need to be refactored out into
the qm-dsp library
author | Chris Cannam <c.cannam@qmul.ac.uk> |
---|---|
date | Fri, 27 Feb 2009 10:45:10 +0000 |
parents | 93f7edb0564b |
children | 385bec9df059 |
files | libmain.cpp plugins/AdaptiveSpectrogram.cpp plugins/AdaptiveSpectrogram.h plugins/KeyDetect.h qm-vamp-plugins.pro |
diffstat | 5 files changed, 721 insertions(+), 30 deletions(-) [+] |
line wrap: on
line diff
--- a/libmain.cpp Fri Feb 27 10:26:38 2009 +0000 +++ b/libmain.cpp Fri Feb 27 10:45:10 2009 +0000 @@ -20,6 +20,7 @@ #include "plugins/SegmenterPlugin.h" #include "plugins/SimilarityPlugin.h" #include "plugins/BarBeatTrack.h" +#include "plugins/AdaptiveSpectrogram.h" static Vamp::PluginAdapter<BeatTracker> beatTrackerAdapter; static Vamp::PluginAdapter<OnsetDetector> onsetDetectorAdapter; @@ -31,6 +32,7 @@ static Vamp::PluginAdapter<SegmenterPlugin> segmenterPluginAdapter; static Vamp::PluginAdapter<SimilarityPlugin> similarityPluginAdapter; static Vamp::PluginAdapter<BarBeatTracker> barBeatTrackPluginAdapter; +static Vamp::PluginAdapter<AdaptiveSpectrogram> adaptiveSpectrogramAdapter; const VampPluginDescriptor *vampGetPluginDescriptor(unsigned int vampApiVersion, unsigned int index) @@ -48,6 +50,7 @@ case 7: return similarityPluginAdapter.getDescriptor(); case 8: return mfccPluginAdapter.getDescriptor(); case 9: return barBeatTrackPluginAdapter.getDescriptor(); + case 10: return adaptiveSpectrogramAdapter.getDescriptor(); default: return 0; } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/AdaptiveSpectrogram.cpp Fri Feb 27 10:45:10 2009 +0000 @@ -0,0 +1,635 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + QM Vamp Plugin Set + + Centre for Digital Music, Queen Mary, University of London. + All rights reserved. +*/ + +#include "AdaptiveSpectrogram.h" + +#include <cstdlib> +#include <cstring> + +#include <iostream> + +#include <dsp/transforms/FFT.h> + +using std::string; +using std::vector; +using std::cerr; +using std::endl; + +using Vamp::RealTime; + +AdaptiveSpectrogram::AdaptiveSpectrogram(float inputSampleRate) : + Plugin(inputSampleRate), + m_w(9), + m_n(2) +{ +} + +AdaptiveSpectrogram::~AdaptiveSpectrogram() +{ +} + +string +AdaptiveSpectrogram::getIdentifier() const +{ + return "adaptivespectrogram"; +} + +string +AdaptiveSpectrogram::getName() const +{ + return "Adaptive Spectrogram"; +} + +string +AdaptiveSpectrogram::getDescription() const +{ + return "Produce an adaptive spectrogram by adaptive selection from spectrograms at multiple resolutions"; +} + +string +AdaptiveSpectrogram::getMaker() const +{ + return "Queen Mary, University of London"; +} + +int +AdaptiveSpectrogram::getPluginVersion() const +{ + return 1; +} + +string +AdaptiveSpectrogram::getCopyright() const +{ + return "Plugin by Wen Xue and Chris Cannam. Copyright (c) 2009 Wen Xue and QMUL - All Rights Reserved"; +} + +size_t +AdaptiveSpectrogram::getPreferredStepSize() const +{ + return ((2 << m_w) << m_n) / 2; +} + +size_t +AdaptiveSpectrogram::getPreferredBlockSize() const +{ + return (2 << m_w) << m_n; +} + +bool +AdaptiveSpectrogram::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (channels < getMinChannelCount() || + channels > getMaxChannelCount()) return false; + + return true; +} + +void +AdaptiveSpectrogram::reset() +{ + +} + +AdaptiveSpectrogram::ParameterList +AdaptiveSpectrogram::getParameterDescriptors() const +{ + ParameterList list; + + ParameterDescriptor desc; + desc.identifier = "n"; + desc.name = "Number of resolutions"; + desc.description = "Number of consecutive powers of two to use as spectrogram resolutions, starting with the minimum resolution specified"; + desc.unit = ""; + desc.minValue = 1; + desc.maxValue = 10; + desc.defaultValue = 3; + desc.isQuantized = true; + desc.quantizeStep = 1; + list.push_back(desc); + + ParameterDescriptor desc2; + desc2.identifier = "w"; + desc2.name = "Smallest resolution"; + desc2.description = "Smallest of the consecutive powers of two to use as spectrogram resolutions"; + desc2.unit = ""; + desc2.minValue = 1; + desc2.maxValue = 14; + desc2.defaultValue = 10; + desc2.isQuantized = true; + desc2.quantizeStep = 1; + // I am so lazy + desc2.valueNames.push_back("2"); + desc2.valueNames.push_back("4"); + desc2.valueNames.push_back("8"); + desc2.valueNames.push_back("16"); + desc2.valueNames.push_back("32"); + desc2.valueNames.push_back("64"); + desc2.valueNames.push_back("128"); + desc2.valueNames.push_back("256"); + desc2.valueNames.push_back("512"); + desc2.valueNames.push_back("1024"); + desc2.valueNames.push_back("2048"); + desc2.valueNames.push_back("4096"); + desc2.valueNames.push_back("8192"); + desc2.valueNames.push_back("16384"); + list.push_back(desc2); + + return list; +} + +float +AdaptiveSpectrogram::getParameter(std::string id) const +{ + if (id == "n") return m_n+1; + else if (id == "w") return m_w+1; + return 0.f; +} + +void +AdaptiveSpectrogram::setParameter(std::string id, float value) +{ + if (id == "n") { + int n = lrintf(value); + if (n >= 1 && n <= 10) m_n = n-1; + } else if (id == "w") { + int w = lrintf(value); + if (w >= 1 && w <= 14) m_w = w-1; + } +} + +AdaptiveSpectrogram::OutputList +AdaptiveSpectrogram::getOutputDescriptors() const +{ + OutputList list; + + OutputDescriptor d; + d.identifier = "output"; + d.name = "Output"; + d.description = "The output of the plugin"; + d.unit = ""; + d.hasFixedBinCount = true; + d.binCount = ((2 << m_w) << m_n) / 2; + d.hasKnownExtents = false; + d.isQuantized = false; + d.sampleType = OutputDescriptor::FixedSampleRate; + d.sampleRate = m_inputSampleRate / ((2 << m_w) / 2); + d.hasDuration = false; + list.push_back(d); + + return list; +} + +AdaptiveSpectrogram::FeatureSet +AdaptiveSpectrogram::getRemainingFeatures() +{ + FeatureSet fs; + return fs; +} + +AdaptiveSpectrogram::FeatureSet +AdaptiveSpectrogram::process(const float *const *inputBuffers, RealTime ts) +{ + FeatureSet fs; + + int wid = (2 << m_w), WID = ((2 << m_w) << m_n); + int Res = log2(WID/wid)+1; + double ***specs = new double **[Res]; + int Wid = WID; + int wi = 0; + + cerr << "wid = " << wid << ", WID = " << WID << endl; + + double *tmpin = new double[WID]; + double *tmprout = new double[WID]; + double *tmpiout = new double[WID]; + + while (Wid >= wid) { + specs[wi] = new double *[WID/Wid]; + for (int i = 0; i < WID/Wid; ++i) { + specs[wi][i] = new double[Wid/2]; + int origin = WID/4 - Wid/4; // for 50% overlap + for (int j = 0; j < Wid; ++j) { + double mul = 0.50 - 0.50 * cos((2 * M_PI * j) / Wid); + tmpin[j] = inputBuffers[0][origin + i * Wid/2 + j] * mul; + } + FFT::process(Wid, false, tmpin, 0, tmprout, tmpiout); + for (int j = 0; j < Wid/2; ++j) { + double mag = + tmprout[j] * tmprout[j] + + tmpiout[j] * tmpiout[j]; + specs[wi][i][j] = sqrt(mag) / Wid; + } + } + Wid /= 2; + ++wi; + } + + int *spl = new int[WID/2]; + double *spec = new double[WID/2]; + + // This prefill makes it easy to see which elements are actually + // set by the MixSpectrogramBlock2 call. Turns out that, with + // 1024, 2048 and 4096 as our widths, the spl array has elements + // 0-4094 (incl) filled in and the spec array has elements 0-4095 + + for (int i = 0; i < WID/2; ++i) { + spl[i] = i; + spec[i] = i; + } + + MixSpectrogramBlock2(spl, spec, specs, WID/2, wid/2, false); + + Wid = WID; + wi = 0; + while (Wid >= wid) { + for (int i = 0; i < WID/Wid; ++i) { + delete[] specs[wi][i]; + } + delete[] specs[wi]; + Wid /= 2; + ++wi; + } + delete[] specs; + + std::cerr << "Results at " << ts << ":" << std::endl; +/* for (int i = 0; i < WID/2; ++i) { + if (spl[i] == i || spec[i] == i) { + std::cerr << "\n***\n"; + } + std::cerr << "[" << i << "] " << spl[i] << "," << spec[i] << " "; + } + std::cerr << std::endl; +*/ + vector<vector<float> > rmat(WID/wid); + for (int i = 0; i < WID/wid; ++i) { + rmat[i] = vector<float>(WID/2); + } + + int y = 0, h = WID/2; + int x = 0, w = WID/wid; + unpackResultMatrix(rmat, x, y, w, h, spl, spec, WID/2, WID); + + delete[] spec; + delete[] spl; + + for (int i = 0; i < rmat.size(); ++i) { + Feature f; + f.hasTimestamp = false; + f.values = rmat[i]; + fs[0].push_back(f); + } + +/* + if (m_stepSize == 0) { + cerr << "ERROR: AdaptiveSpectrogram::process: " + << "AdaptiveSpectrogram has not been initialised" + << endl; + return fs; + } +*/ + return fs; +} + +void +AdaptiveSpectrogram::unpackResultMatrix(vector<vector<float> > &rmat, + int x, int y, int w, int h, + int *spl, + double *spec, int sz, + int res + ) +{ + + cerr << "x = " << x << ", y = " << y << ", w = " << w << ", h = " << h + << ", sz = " << sz << ", *spl = " << *spl << ", *spec = " << *spec << ", res = " << res << endl; + + if (sz <= 1) { + + for (int i = 0; i < w; ++i) { + for (int j = 0; j < h; ++j) { +// rmat[x+i][y+j] = (off ? 0 : *spec); + if (rmat[x+i][y+j] != 0) { + cerr << "WARNING: Overwriting value " << rmat[x+i][y+j] + << " with " << res + i + j << " at " << x+i << "," << y+j << endl; + } +// cerr << "[" << x+i << "][" << y+j << "] <= " << res+i+j << endl; + rmat[x+i][y+j] = *spec; + } + } + +// cerr << " (done)" << endl; + return; + } +// cerr << endl; + + if (*spl == 0) { + + unpackResultMatrix(rmat, + x, y, + w, h/2, + spl + 1, + spec, + sz/2, + res); + + unpackResultMatrix(rmat, + x, y + h/2, + w, h/2, + spl + sz/2, + spec + sz/2, + sz/2, + res); + + } else if (*spl == 1) { + + unpackResultMatrix(rmat, + x, y, + w/2, h, + spl + 1, + spec, + sz/2, + res/2); + + unpackResultMatrix(rmat, + x + w/2, y, + w/2, h, + spl + sz/2, + spec + sz/2, + sz/2, + res/2); + + } else { + cerr << "ERROR: *spl = " << *spl << endl; + } +} + +//spl[Y-1] +//Specs[R0][x0:x0+x-1][Y0:Y0+Y-1] +//Specs[R0+1][2x0:2x0+2x-1][Y0/2:Y0/2+Y/2-1] +//... +//Specs[R0+?][Nx0:Nx0+Nx-1][Y0/N:Y0/N+Y/N-1] +//N=WID/wid + +/** + * DoCutSpectrogramBlock2 finds the optimal "cutting" and returns it in spl. + */ +double +AdaptiveSpectrogram::DoCutSpectrogramBlock2(int* spl, double*** Specs, int Y, int R0, + int x0, int Y0, int N, double& ene) +{ + double ent = 0; + + if (Y > N) { + + spl[0] = 0; + double ene1, ene2; + + ent += DoCutSpectrogramBlock2 + (&spl[1], Specs, Y/2, R0, x0, Y0, N, ene1); + + ent += DoCutSpectrogramBlock2 + (&spl[Y/2], Specs, Y/2, R0, x0, Y0+Y/2, N, ene2); + + ene = ene1+ene2; + + } else if (N == 1) { + + double tmp = Specs[R0][x0][Y0]; + ene = tmp; + ent = xlogx(tmp); + + } else { + // Y == N, the square case + + double enel, ener, enet, eneb, entl, entr, entt, entb; + int* tmpspl = new int[Y]; + + entl = DoCutSpectrogramBlock2 + (&spl[1], Specs, Y/2, R0+1, 2*x0, Y0/2, N/2, enel); + + entr = DoCutSpectrogramBlock2 + (&spl[Y/2], Specs, Y/2, R0+1, 2*x0+1, Y0/2, N/2, ener); + + entb = DoCutSpectrogramBlock2 + (&tmpspl[1], Specs, Y/2, R0, x0, Y0, N/2, eneb); + + entt = DoCutSpectrogramBlock2 + (&tmpspl[Y/2], Specs, Y/2, R0, x0, Y0+Y/2, N/2, enet); + + double + ene0 = enet + eneb, + ene1 = enel + ener, + ent0 = entt + entb, + ent1 = entl + entr; + + // normalize + + double eneres = 1 - (ene0+ene1)/2, norment0, norment1; + double a0 = 1 / (ene0+eneres), a1 = 1 / (ene1+eneres); + + // quasi-global normalization + + norment0 = (ent0 - ene0 * log(ene0+eneres)) / (ene0+eneres); + norment1 = (ent1 - ene1 * log(ene1+eneres)) / (ene1+eneres); + + // local normalization + + if (norment1 < norment0) { + spl[0] = 0; + ent = ent0, ene = ene0; + memcpy(&spl[1], &tmpspl[1], sizeof(int)*(Y-2)); + } else { + spl[0] = 1; + ent = ent1, ene = ene1; + } + } + return ent; +} + +/** + * DoMixSpectrogramBlock2 collects values from the multiple + * spectrograms Specs into a linear array Spec. + */ +double +AdaptiveSpectrogram::DoMixSpectrogramBlock2(int* spl, double* Spec, double*** Specs, int Y, + int R0, int x0, int Y0, bool normmix, int res, + double* e) +{ + if (Y == 1) { + + Spec[0] = Specs[R0][x0][Y0]*e[0]; + + } else { + + double le[32]; + + if (normmix && Y < (1<<res)) { + + for (int i = 0, j = 1, k = Y; + i < res; + i++, j *= 2, k /= 2) { + + double lle = 0; + + for (int fr = 0; fr < j; fr++) { + for (int n = 0; n < k; n++) { + lle += + Specs[i+R0][x0+fr][Y0+n] * + Specs[i+R0][x0+fr][Y0+n]; + } + } + + lle = sqrt(lle)*e[i]; + + if (i == 0) { + le[0] = lle; + } else if (lle > le[0]*2) { + le[i] = e[i]*le[0]*2/lle; + } else { + le[i] = e[i]; + } + } + + le[0] = e[0]; + + } else { + + memcpy(le, e, sizeof(double)*res); + } + + if (spl[0] == 0) { + + int newres; + if (Y >= (1<<res)) newres = res; + else newres = res-1; + + DoMixSpectrogramBlock2 + (&spl[1], Spec, Specs, Y/2, R0, x0, Y0, + normmix, newres, le); + + DoMixSpectrogramBlock2 + (&spl[Y/2], &Spec[Y/2], Specs, Y/2, R0, x0, Y0+Y/2, + normmix, newres, le); + + } else { + + DoMixSpectrogramBlock2 + (&spl[1], Spec, Specs, Y/2, R0+1, x0*2, Y0/2, + normmix, res-1, &le[1]); + + DoMixSpectrogramBlock2 + (&spl[Y/2], &Spec[Y/2], Specs, Y/2, R0+1, x0*2+1, Y0/2, + normmix, res-1, &le[1]); + } + } + + return 0; +} + +/** + * MixSpectrogramBlock2 calls the two Do...() to do the real work. + * + * At this point: + * spl is... what? the returned "cutting", organised how? + * Spec is... what? the returned spectrogram, organised how? + * Specs is an array of input spectrograms + * WID is the maximum window size + * wid is the minimum window size + * normmix is... what? + */ +double +AdaptiveSpectrogram::MixSpectrogramBlock2(int* spl, double* Spec, double*** Specs, int + WID, int wid, bool normmix) +{ + double ene[32]; + + // find the total energy and normalize + + for (int i = 0, Fr = 1, Wid = WID; Wid >= wid; i++, Fr *= 2, Wid /= 2) { + + double lene = 0; + + for (int fr = 0; fr < Fr; fr++) { + for (int k = 0; k < Wid; k++) { + lene += Specs[i][fr][k]*Specs[i][fr][k]; + } + } + + ene[i] = lene; + + if (lene != 0) { + double ilene = 1.0/lene; + for (int fr = 0; fr < Fr; fr++) { + for (int k = 0; k < Wid; k++) { + Specs[i][fr][k] = Specs[i][fr][k]*Specs[i][fr][k]*ilene; + } + } + } + } + + + double result = DoCutSpectrogramBlock2 + (spl, Specs, WID, 0, 0, 0, WID/wid, ene[31]); + + // de-normalize + + for (int i = 0, Fr = 1, Wid = WID; Wid >= wid; i++, Fr *= 2, Wid /= 2) { + double lene = ene[i]; + if (lene != 0) { + for (int fr = 0; fr < Fr; fr++) { + for (int k = 0; k < Wid; k++) { + Specs[i][fr][k] = sqrt(Specs[i][fr][k]*lene); + } + } + } + } + + double e[32]; + for (int i = 0; i < 32; i++) e[i] = 1; + + DoMixSpectrogramBlock2 + (spl, Spec, Specs, WID, 0, 0, 0, normmix, log2(WID/wid)+1, e); + + return result; +} + +/** + * MixSpectrogram2 does the work for Fr frames (the largest frame), + * which basically calls MixSpectrogramBlock2 Fr times. + * + * the 3-D array Specs is the multiple spectrograms calculated with + * window sizes between wid and WID, Specs[0] is the 0th spectrogram, + * etc. + * + * spl and Spec for all frames are returned by MixSpectrogram2, each + * as a 2-D array. + */ +double +AdaptiveSpectrogram::MixSpectrogram2(int** spl, double** Spec, double*** Specs, int Fr, + int WID, int wid, bool norm, bool normmix) +{ + // totally Fr frames of WID samples + // each frame is divided into wid VERTICAL parts + + int Res = log2(WID/wid)+1; + double*** lSpecs = new double**[Res]; + + for (int i = 0; i < Fr; i++) { + + for (int j = 0, nfr = 1; j < Res; j++, nfr *= 2) { + lSpecs[j] = &Specs[j][i*nfr]; + } + + MixSpectrogramBlock2(spl[i], Spec[i], lSpecs, WID, wid, norm); + } + + delete[] lSpecs; + return 0; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/AdaptiveSpectrogram.h Fri Feb 27 10:45:10 2009 +0000 @@ -0,0 +1,78 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + QM Vamp Plugin Set + + Centre for Digital Music, Queen Mary, University of London. + All rights reserved. +*/ + +#ifndef _ADAPTIVE_SPECTROGRAM_H_ +#define _ADAPTIVE_SPECTROGRAM_H_ + +#include <vamp-sdk/Plugin.h> +#include <cmath> +#include <vector> + +class AdaptiveSpectrogram : public Vamp::Plugin +{ +public: + AdaptiveSpectrogram(float inputSampleRate); + virtual ~AdaptiveSpectrogram(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + void reset(); + + InputDomain getInputDomain() const { return TimeDomain; } + + std::string getIdentifier() const; + std::string getName() const; + std::string getDescription() const; + std::string getMaker() const; + int getPluginVersion() const; + std::string getCopyright() const; + + size_t getPreferredStepSize() const; + size_t getPreferredBlockSize() const; + + ParameterList getParameterDescriptors() const; + float getParameter(std::string id) const; + void setParameter(std::string id, float value); + + OutputList getOutputDescriptors() const; + + FeatureSet process(const float *const *inputBuffers, + Vamp::RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + int m_w; + int m_n; + + inline double xlogx(double x) { + if (x == 0.0) return 0.0; + else return x * log(x); + } + + void unpackResultMatrix(std::vector<std::vector<float> > &rmat, + int x, int y, int w, int h, + int *spl, + double *spec, int specsz, int res); + + double DoCutSpectrogramBlock2(int* spl, double*** Specs, int Y, int R0, + int x0, int Y0, int N, double& ene); + + double DoMixSpectrogramBlock2(int* spl, double* Spec, double*** Specs, + int Y, int R0, int x0, int Y0, + bool normmix, int res, double* e); + + double MixSpectrogramBlock2(int* spl, double* Spec, double*** Specs, + int WID, int wid, bool normmix); + + double MixSpectrogram2(int** spl, double** Spec, double*** Specs, int Fr, + int WID, int wid, bool norm, bool normmix); +}; + + +#endif
--- a/plugins/KeyDetect.h Fri Feb 27 10:26:38 2009 +0000 +++ b/plugins/KeyDetect.h Fri Feb 27 10:45:10 2009 +0000 @@ -1,37 +1,10 @@ /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* - Vamp - - An API for audio analysis and feature extraction plugins. + QM Vamp Plugin Set Centre for Digital Music, Queen Mary, University of London. - Copyright 2006-2007 QMUL. - - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, copy, - modify, merge, publish, distribute, sublicense, and/or sell copies - of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR - ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the names of the Centre for - Digital Music; Queen Mary, University of London; and Chris Cannam - shall not be used in advertising or otherwise to promote the sale, - use or other dealings in this Software without prior written - authorization. + All rights reserved. */ #ifndef _GETMODE_PLUGIN_H_
--- a/qm-vamp-plugins.pro Fri Feb 27 10:26:38 2009 +0000 +++ b/qm-vamp-plugins.pro Fri Feb 27 10:45:10 2009 +0000 @@ -25,7 +25,8 @@ INCLUDEPATH += . plugins # Input -HEADERS += plugins/BarBeatTrack.h \ +HEADERS += plugins/AdaptiveSpectrogram.h \ + plugins/BarBeatTrack.h \ plugins/BeatTrack.h \ plugins/OnsetDetect.h \ plugins/ChromagramPlugin.h \ @@ -36,6 +37,7 @@ plugins/SimilarityPlugin.h \ plugins/TonalChangeDetect.h SOURCES += g2cstubs.c \ + plugins/AdaptiveSpectrogram.cpp \ plugins/BarBeatTrack.cpp \ plugins/BeatTrack.cpp \ plugins/OnsetDetect.cpp \