Mercurial > hg > match-vamp
changeset 141:d6f22887283e adaptive_diagonals
Merge from branch refactors
author | Chris Cannam |
---|---|
date | Fri, 09 Jan 2015 17:20:52 +0000 |
parents | 50712e4b8c89 (diff) cfba9aec7569 (current diff) |
children | |
files | Makefile.inc Makefile.linux src/FeatureConditioner.h src/Finder.cpp src/MatchPipeline.cpp src/MatchPipeline.h src/MatchVampPlugin.cpp src/MatchVampPlugin.h test/TestFeatureExtractor.cpp test/expected.csv test/regressiontest.sh |
diffstat | 10 files changed, 504 insertions(+), 72 deletions(-) [+] |
line wrap: on
line diff
--- a/Makefile.inc Thu Jan 08 12:11:27 2015 +0000 +++ b/Makefile.inc Fri Jan 09 17:20:52 2015 +0000 @@ -37,12 +37,13 @@ # DO NOT DELETE src/DistanceMetric.o: src/DistanceMetric.h +src/Path.o: src/Path.h src/FeatureConditioner.o: src/FeatureConditioner.h -src/Path.o: src/Path.h src/MatchFeatureFeeder.o: src/MatchFeatureFeeder.h src/Matcher.h src/MatchFeatureFeeder.o: src/DistanceMetric.h src/Finder.h src/FeatureExtractor.o: src/FeatureExtractor.h src/Finder.o: src/Finder.h src/Matcher.h src/DistanceMetric.h src/Path.h +src/Finder.o: src/MedianFilter.h src/Matcher.o: src/Matcher.h src/DistanceMetric.h src/MatchPipeline.o: src/MatchPipeline.h src/Matcher.h src/DistanceMetric.h src/MatchPipeline.o: src/Finder.h src/FeatureExtractor.h @@ -50,7 +51,7 @@ src/MatchVampPlugin.o: src/MatchVampPlugin.h src/MatchPipeline.h src/MatchVampPlugin.o: src/Matcher.h src/DistanceMetric.h src/Finder.h src/MatchVampPlugin.o: src/FeatureExtractor.h src/FeatureConditioner.h -src/MatchVampPlugin.o: src/MatchFeatureFeeder.h src/Path.h +src/MatchVampPlugin.o: src/MatchFeatureFeeder.h src/MatchFeatureFeeder.o: src/Matcher.h src/DistanceMetric.h src/Finder.h src/Finder.o: src/Matcher.h src/DistanceMetric.h src/Matcher.o: src/DistanceMetric.h @@ -60,4 +61,6 @@ src/MatchVampPlugin.o: src/MatchPipeline.h src/Matcher.h src/DistanceMetric.h src/MatchVampPlugin.o: src/Finder.h src/FeatureExtractor.h src/MatchVampPlugin.o: src/FeatureConditioner.h src/MatchFeatureFeeder.h +test/TestFeatureConditioner.o: src/FeatureConditioner.h +test/TestDistanceMetric.o: src/DistanceMetric.h test/TestFeatureExtractor.o: src/FeatureExtractor.h
--- a/Makefile.linux Thu Jan 08 12:11:27 2015 +0000 +++ b/Makefile.linux Fri Jan 09 17:20:52 2015 +0000 @@ -1,6 +1,6 @@ -#CXXFLAGS += -fPIC -ffast-math -O3 -Wall -Werror -Wfloat-conversion -CXXFLAGS += -fPIC -g -Wall -Werror -DPERFORM_ERROR_CHECKS=1 +#CXXFLAGS += -fPIC -ffast-math -O3 -Wall -Werror +CXXFLAGS += -fPIC -g -Wall -Werror -Wfloat-conversion -DPERFORM_ERROR_CHECKS=1 LDFLAGS += -Wl,-Bstatic -lvamp-sdk -Wl,-Bdynamic PLUGIN_LDFLAGS += -shared -Wl,-Bsymbolic -Wl,-z,defs -lpthread -Wl,--version-script=vamp-plugin.map
--- a/src/Finder.cpp Thu Jan 08 12:11:27 2015 +0000 +++ b/src/Finder.cpp Fri Jan 09 17:20:52 2015 +0000 @@ -17,6 +17,7 @@ #include "Finder.h" #include "Path.h" +#include "MedianFilter.h" #include <algorithm> #include <iomanip> @@ -96,11 +97,19 @@ } void -Finder::recalculatePathCostMatrix(int r1, int c1, int r2, int c2) +Finder::recalculatePathCostMatrix(int r1, int c1, int r2, int c2) { + recalculatePathCostMatrix(r1, c1, r2, c2, m_m->getDiagonalWeight()); +} + +void +Finder::recalculatePathCostMatrix(int r1, int c1, int r2, int c2, + float diagonalWeight) +{ + cerr << "recalculatePathCostMatrix: (" << r1 << "," << c1 << ") -> (" + << r2 << "," << c2 << ")" << endl; + int prevRowStart = 0, prevRowStop = 0; - - float diagonalWeight = m_m->getDiagonalWeight(); for (int r = r1; r <= r2; r++) { @@ -311,7 +320,13 @@ for (int j = -4; j <= 0; ++j) { cerr << setw(w) << j; for (int i = -4; i <= 0; ++i) { - cerr << setw(ww) << m_m->getDistance(err.r + j, err.c + i); + int r = err.r + j; + int c = err.c + i; + float dist = 0.f; + if (r >= 0 && c >= 0) { + dist = m_m->getDistance(r, c); + } + cerr << setw(ww) << dist; } cerr << endl; } @@ -326,7 +341,13 @@ for (int j = -4; j <= 0; ++j) { cerr << setw(w) << j; for (int i = -4; i <= 0; ++i) { - cerr << setw(ww) << m_m->getPathCost(err.r + j, err.c + i); + int r = err.r + j; + int c = err.c + i; + double cost = 0.0; + if (r >= 0 && c >= 0) { + cost = m_m->getPathCost(r, c); + } + cerr << setw(ww) << cost; } cerr << endl; } @@ -335,25 +356,18 @@ } #endif -int -Finder::retrievePath(bool smooth, vector<int> &pathx, vector<int> &pathy) +void +Finder::getEndPoint(int &x, int &y) { - pathx.clear(); - pathy.clear(); - -#ifdef PERFORM_ERROR_CHECKS - checkAndReport(); -#endif - int ex = m_m->getOtherFrameCount() - 1; int ey = m_m->getFrameCount() - 1; if (ex < 0 || ey < 0) { - return 0; + return; } - int x = ex; - int y = ey; + x = ex; + y = ey; #ifdef DEBUG_FINDER cerr << "*** retrievePath: smooth = " << smooth << endl; @@ -377,11 +391,26 @@ y = ey; } - recalculatePathCostMatrix(0, 0, y, x); - #ifdef DEBUG_FINDER cerr << "*** retrievePath: start: x = " << x << ", y = " << y << endl; #endif +} + +void +Finder::retrievePath(vector<int> &pathx, + vector<int> &pathy, + vector<float> &distances) +{ + pathx.clear(); + pathy.clear(); + distances.clear(); + +#ifdef PERFORM_ERROR_CHECKS + checkAndReport(); +#endif + + int x, y; + getEndPoint(x, y); while (m_m->isAvailable(y, x) && (x > 0 || y > 0)) { @@ -389,6 +418,7 @@ pathx.push_back(x); pathy.push_back(y); + distances.push_back(m_m->getDistance(y, x)); switch (m_m->getAdvance(y, x)) { case Matcher::AdvanceThis: @@ -422,13 +452,146 @@ reverse(pathx.begin(), pathx.end()); reverse(pathy.begin(), pathy.end()); - - if (smooth) { - int smoothedLen = Path().smooth(pathx, pathy, pathx.size()); - return smoothedLen; - } else { - return pathx.size(); - } + reverse(distances.begin(), distances.end()); } +void +Finder::smooth(const vector<double> &mag1, const vector<double> &mag2) +{ + if (m_m->getDiagonalWeight() <= 1.0) { + cerr << "Finder::smooth: Diagonal weight is already " + << m_m->getDiagonalWeight() << ", adaptive smoothing will have " + << "no effect, skipping it" << endl; + return; + } + vector<double> confidence; + + vector<int> pathx, pathy; + vector<float> distances; + retrievePath(pathx, pathy, distances); + + int len = pathx.size(); + + for (int i = 0; i < len; ++i) { + + int x = pathx[i]; + int y = pathy[i]; + + double magSum = mag1[x] + mag2[y]; + double distance = distances[i]; + double c = magSum - distance * magSum; + confidence.push_back(c); + + /* + if (x != prevx) { + Feature f; + f.values.push_back(magSum); + returnFeatures[m_magOutNo].push_back(f); + + f.values.clear(); + f.values.push_back(distance); + returnFeatures[m_distOutNo].push_back(f); + } + + prevx = x; + prevy = y; + */ + } + + confidence = MedianFilter<double>::filter(3, confidence); + vector<double> filtered = MedianFilter<double>::filter(50, confidence); + for (int i = 0; i < len; ++i) { + confidence[i] -= filtered[i]; + if (confidence[i] < 0.f) { + confidence[i] = 0.f; + } + } + vector<double> deriv; + deriv.resize(len, 0.f); + for (int i = 1; i < len; ++i) { + deriv[i] = confidence[i] - confidence[i-1]; + } + vector<int> inflections; + for (int i = 1; i < len; ++i) { + if (deriv[i-1] > 0 && deriv[i] < 0) { + inflections.push_back(i); + } + } + + /* + for (int i = 0; i < len; ++i) { + + int x = pathx[i]; + int y = pathy[i]; + + if (x != prevx) { + Feature f; + double c = confidence[i]; + f.values.push_back(c); + returnFeatures[m_confidenceOutNo].push_back(f); + } + + prevx = x; + prevy = y; + } + */ + + map<int, int> pinpoints; + + for (int ii = 0; ii < int(inflections.size()); ++ii) { + + int i = inflections[ii]; + + int x = pathx[i]; + int y = pathy[i]; + + pinpoints[x] = y; + } + + cerr << "Pin points are:" << endl; + for (map<int, int>::const_iterator i = pinpoints.begin(); + i != pinpoints.end(); ++i) { + cerr << "[" << i->first << "," << i->second << "] "; + } + cerr << endl; + + if (pinpoints.size() < 2) return; + + int ex, ey; + getEndPoint(ex, ey); + + pair<int, int> prev(0, 0); + + for (map<int, int>::const_iterator i = pinpoints.begin(); + i != pinpoints.end(); ++i) { + + if (i == pinpoints.begin()) continue; + + map<int, int>::const_iterator j = i; + ++j; + if (j != pinpoints.end() && i->second == j->second) { + // skip this one + continue; + } + + pair<int, int> curr = *i; + + if (curr.first > ex || curr.second > ey) { + curr = pair<int, int>(ex, ey); + } + + recalculatePathCostMatrix(prev.second, prev.first, + curr.second, curr.first, + 1.0); + + prev = curr; + } + + recalculatePathCostMatrix(prev.second, prev.first, + m_m->getFrameCount() - 1, + m_m->getOtherFrameCount() - 1, + 1.0); +} + +
--- a/src/Finder.h Thu Jan 08 12:11:27 2015 +0000 +++ b/src/Finder.h Fri Jan 09 17:20:52 2015 +0000 @@ -18,6 +18,7 @@ #define _FINDER_H_ #include <vector> +#include <map> #include <iostream> #include "Matcher.h" @@ -54,17 +55,41 @@ void recalculatePathCostMatrix(int r1, int c1, int r2, int c2); /** + * Recalculate a rectangle of the path cost matrix using the given + * diagonal weight instead of the weight obtained from the Matcher + * parameters. + */ + void recalculatePathCostMatrix(int r1, int c1, int r2, int c2, + float diagonalWeight); + + /** * Track back after all of the matchers have been fed in order to * obtain the lowest cost path available. Path x and y coordinate * pairs are returned in corresponding elements of pathx and - * pathy. Return value is the length of the returned path: only - * this many elements from pathx and pathy are valid (any - * subsequent ones may be spurious). + * pathy, and the corresponding elements of the returned distance + * vector contain the distance between the pair of features + * selected at each step in the path. + */ + void retrievePath(std::vector<int> &pathx, + std::vector<int> &pathy, + std::vector<float> &distances); + + /** + * Using the provided magnitude arrays for the first and second + * inputs as an indication of salience, smooth the cost matrix by + * recalculating with locally adaptive diagonal weights in order + * to obtain a smoother path that retains the original locations + * for the most salient points. Subsequent calls to retrievePath + * will retrieve the smoothed version. The original can be + * restored with a call to recalculatePathCostMatrix(). * - * @param smooth whether to smooth the path before returning it + * Note that this is quite separate from Path::smooth() which + * smooths a path without trying to maintain locations for salient + * points. That function will achieve a smoother path, but may + * smooth out significant locations such as onsets. */ - int retrievePath(bool smooth, std::vector<int> &pathx, std::vector<int> &pathy); - + void smooth(const vector<double> &mag1, const vector<double> &mag2); + protected: #ifdef PERFORM_ERROR_CHECKS struct ErrorPosition { @@ -83,6 +108,8 @@ ErrorPosition checkPathCostMatrix(); void checkAndReport(); #endif + + void getEndPoint(int &x, int &y); Matcher *m_m; int m_duration1;
--- a/src/MatchPipeline.cpp Thu Jan 08 12:11:27 2015 +0000 +++ b/src/MatchPipeline.cpp Fri Jan 09 17:20:52 2015 +0000 @@ -99,6 +99,23 @@ f2 = m_f2; } +static double +magOf(const vector<double> &f) +{ + double mag = 0.0; + for (int j = 0; j < (int)f.size(); ++j) { + mag += f[j] * f[j]; + } + return sqrt(mag); +} + +void +MatchPipeline::extractFeatureMagnitudes(double &mag1, double &mag2) +{ + mag1 = magOf(m_f1); + mag2 = magOf(m_f2); +} + void MatchPipeline::extractConditionedFeatures(vector<double> &c1, vector<double> &c2) {
--- a/src/MatchPipeline.h Thu Jan 08 12:11:27 2015 +0000 +++ b/src/MatchPipeline.h Fri Jan 09 17:20:52 2015 +0000 @@ -74,11 +74,21 @@ * If a frame was just fed in at the first or second pipeline * stage, it can be retrieved from the second stage here. That is, * if you provided frequency-domain audio, extractFeatures will - * give you back the FeatureExtractor's features. + * give you back the FeatureExtractor's features; if you provided + * features, extractFeatures will simply give you those back. If + * you provided conditioned features, the return from this + * function is undefined. */ void extractFeatures(vector<double> &f1, vector<double> &f2); /** + * If a frame was just fed in at the first or second pipeline + * stage, you can obtain the magnitudes of its features for both + * inputs here. + */ + void extractFeatureMagnitudes(double &mag1, double &mag2); + + /** * Retrieve the conditioned features from the third pipeline stage. */ void extractConditionedFeatures(vector<double> &f1, vector<double> &f2);
--- a/src/MatchVampPlugin.cpp Thu Jan 08 12:11:27 2015 +0000 +++ b/src/MatchVampPlugin.cpp Fri Jan 09 17:20:52 2015 +0000 @@ -19,7 +19,6 @@ #include "Matcher.h" #include "MatchFeatureFeeder.h" #include "FeatureExtractor.h" -#include "Path.h" #include <vamp/vamp.h> #include <vamp-sdk/PluginAdapter.h> @@ -27,6 +26,11 @@ #include <vector> #include <algorithm> +#include <map> + +#include <cstdio> + +using namespace std; //static int extant = 0; @@ -66,10 +70,10 @@ m_defaultFcParams() { if (inputSampleRate < sampleRateMin) { - std::cerr << "MatchVampPlugin::MatchVampPlugin: input sample rate " + cerr << "MatchVampPlugin::MatchVampPlugin: input sample rate " << inputSampleRate << " < min supported rate " << sampleRateMin << ", plugin will refuse to initialise" - << std::endl; + << endl; } if (!m_serialisingMutexInitialised) { @@ -82,12 +86,11 @@ } m_pipeline = 0; -// std::cerr << "MatchVampPlugin::MatchVampPlugin(" << this << "): extant = " << ++extant << std::endl; } MatchVampPlugin::~MatchVampPlugin() { -// std::cerr << "MatchVampPlugin::~MatchVampPlugin(" << this << "): extant = " << --extant << std::endl; +// cerr << "MatchVampPlugin::~MatchVampPlugin(" << this << "): extant = " << --extant << endl; delete m_pipeline; @@ -209,7 +212,7 @@ desc.description = "Total frame energy threshold below which a feature will be regarded as silent"; desc.minValue = 0; desc.maxValue = 1; - desc.defaultValue = m_defaultFcParams.silenceThreshold; + desc.defaultValue = (float)m_defaultFcParams.silenceThreshold; desc.isQuantized = false; list.push_back(desc); @@ -259,7 +262,7 @@ } float -MatchVampPlugin::getParameter(std::string name) const +MatchVampPlugin::getParameter(string name) const { if (name == "serialise") { return m_serialise ? 1.0 : 0.0; @@ -280,14 +283,14 @@ } else if (name == "smooth") { return m_smooth ? 1.0 : 0.0; } else if (name == "silencethreshold") { - return m_fcParams.silenceThreshold; + return (float)m_fcParams.silenceThreshold; } return 0.0; } void -MatchVampPlugin::setParameter(std::string name, float value) +MatchVampPlugin::setParameter(string name, float value) { if (name == "serialise") { m_serialise = (value > 0.5); @@ -337,9 +340,9 @@ MatchVampPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize) { if (m_inputSampleRate < sampleRateMin) { - std::cerr << "MatchVampPlugin::MatchVampPlugin: input sample rate " + cerr << "MatchVampPlugin::MatchVampPlugin: input sample rate " << m_inputSampleRate << " < min supported rate " - << sampleRateMin << std::endl; + << sampleRateMin << endl; return false; } if (channels < getMinChannelCount() || @@ -364,6 +367,10 @@ delete m_pipeline; m_pipeline = 0; m_frameNo = 0; + + m_mag1.clear(); + m_mag2.clear(); + createMatchers(); m_begin = true; m_locked = false; @@ -497,6 +504,59 @@ m_cbFeaturesOutNo = list.size(); list.push_back(desc); + desc.identifier = "feature_distance"; + desc.name = "Feature Distance"; + desc.description = "Value of distance metric between features at each point-in-A along the chosen alignment path"; + desc.unit = ""; + desc.hasFixedBinCount = true; + desc.binCount = 1; + desc.hasKnownExtents = false; + desc.isQuantized = false; + desc.sampleType = OutputDescriptor::FixedSampleRate; + desc.sampleRate = outRate; + m_distOutNo = list.size(); + list.push_back(desc); + + desc.identifier = "feature_mag"; + desc.name = "Feature Magnitudes"; + desc.description = "Sum of magnitudes of feature pairs for each point-in-A along the chosen alignment path"; + desc.unit = ""; + desc.hasFixedBinCount = true; + desc.binCount = 1; + desc.hasKnownExtents = false; + desc.isQuantized = false; + desc.sampleType = OutputDescriptor::FixedSampleRate; + desc.sampleRate = outRate; + m_magOutNo = list.size(); + list.push_back(desc); +/* + desc.identifier = "confidence"; + desc.name = "Confidence"; + desc.description = "Confidence metric for the quality of match at each point-in-A along the chosen alignment path"; + desc.unit = ""; + desc.hasFixedBinCount = true; + desc.binCount = 1; + desc.hasKnownExtents = false; + desc.isQuantized = false; + desc.sampleType = OutputDescriptor::FixedSampleRate; + desc.sampleRate = outRate; + m_confidenceOutNo = list.size(); + list.push_back(desc); + + desc.identifier = "confidence_peaks"; + desc.name = "Confidence Peaks"; + desc.description = "Peak locations for the confidence metric"; + desc.unit = ""; + desc.hasFixedBinCount = true; + desc.binCount = 0; + desc.hasKnownExtents = false; + desc.isQuantized = false; + desc.sampleType = OutputDescriptor::FixedSampleRate; + desc.sampleRate = outRate; + m_confPeakOutNo = list.size(); + list.push_back(desc); +*/ + return list; } @@ -517,10 +577,16 @@ m_begin = false; } -// std::cerr << timestamp.toString(); +// cerr << timestamp.toString(); m_pipeline->feedFrequencyDomainAudio(inputBuffers[0], inputBuffers[1]); + + double m1, m2; + m_pipeline->extractFeatureMagnitudes(m1, m2); + m_mag1.push_back(m1); + m_mag2.push_back(m2); + FeatureSet returnFeatures; vector<double> f1, f2; @@ -572,18 +638,25 @@ FeatureSet returnFeatures; Finder *finder = m_pipeline->getFinder(); - std::vector<int> pathx; - std::vector<int> pathy; - int len = finder->retrievePath(m_smooth, pathx, pathy); + + if (m_smooth) { + finder->smooth(m_mag1, m_mag2); + } + + vector<int> pathx; + vector<int> pathy; + vector<float> distances; + finder->retrievePath(pathx, pathy, distances); int prevx = 0; int prevy = 0; + int len = pathx.size(); for (int i = 0; i < len; ++i) { int x = pathx[i]; int y = pathy[i]; - + Vamp::RealTime xt = Vamp::RealTime::frame2RealTime (x * m_stepSize, lrintf(m_inputSampleRate)); Vamp::RealTime yt = Vamp::RealTime::frame2RealTime @@ -654,31 +727,31 @@ /* for (int i = 0; i < len; ++i) { - std::cerr << i << ": [" << pathx[i] << "," << pathy[i] << "]" << std::endl; + cerr << i << ": [" << pathx[i] << "," << pathy[i] << "]" << endl; } - std::cerr << std::endl; - std::cerr << "File: A" << std::endl; - std::cerr << "Marks: -1" << std::endl; - std::cerr << "FixedPoints: true 0" << std::endl; - std::cerr << "0" << std::endl; - std::cerr << "0" << std::endl; - std::cerr << "0" << std::endl; - std::cerr << "0" << std::endl; - std::cerr << "File: B" << std::endl; - std::cerr << "Marks: 0" << std::endl; - std::cerr << "FixedPoints: true 0" << std::endl; - std::cerr << "0.02" << std::endl; - std::cerr << "0.02" << std::endl; + cerr << endl; + cerr << "File: A" << endl; + cerr << "Marks: -1" << endl; + cerr << "FixedPoints: true 0" << endl; + cerr << "0" << endl; + cerr << "0" << endl; + cerr << "0" << endl; + cerr << "0" << endl; + cerr << "File: B" << endl; + cerr << "Marks: 0" << endl; + cerr << "FixedPoints: true 0" << endl; + cerr << "0.02" << endl; + cerr << "0.02" << endl; - std::cerr << len << std::endl; + cerr << len << endl; for (int i = 0; i < len; ++i) { - std::cerr << pathx[i] << std::endl; + cerr << pathx[i] << endl; } - std::cerr << len << std::endl; + cerr << len << endl; for (int i = 0; i < len; ++i) { - std::cerr << pathy[i] << std::endl; + cerr << pathy[i] << endl; } */ }
--- a/src/MatchVampPlugin.h Thu Jan 08 12:11:27 2015 +0000 +++ b/src/MatchVampPlugin.h Fri Jan 09 17:20:52 2015 +0000 @@ -90,6 +90,9 @@ FeatureConditioner::Parameters m_fcParams; FeatureConditioner::Parameters m_defaultFcParams; + std::vector<double> m_mag1; + std::vector<double> m_mag2; + mutable int m_pathOutNo; mutable int m_abOutNo; mutable int m_baOutNo; @@ -99,6 +102,12 @@ mutable int m_bFeaturesOutNo; mutable int m_caFeaturesOutNo; mutable int m_cbFeaturesOutNo; + mutable int m_magOutNo; + mutable int m_distOutNo; +/* + mutable int m_confidenceOutNo; + mutable int m_confPeakOutNo; +*/ #ifdef _WIN32 static HANDLE m_serialisingMutex;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/MedianFilter.h Fri Jan 09 17:20:52 2015 +0000 @@ -0,0 +1,130 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + QM DSP Library + + Centre for Digital Music, Queen Mary, University of London. + This file Copyright 2010 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef MEDIAN_FILTER_H +#define MEDIAN_FILTER_H + +#include <algorithm> +#include <cassert> +#include <cmath> +#include <iostream> +#include <vector> + +template <typename T> +class MedianFilter +{ +public: + MedianFilter(int size, float percentile = 50.f) : + m_size(size), + m_frame(new T[size]), + m_sorted(new T[size]), + m_sortend(m_sorted + size - 1) { + setPercentile(percentile); + reset(); + } + + ~MedianFilter() { + delete[] m_frame; + delete[] m_sorted; + } + + void setPercentile(float p) { + m_index = int((m_size * p) / 100.f); + if (m_index >= m_size) m_index = m_size-1; + if (m_index < 0) m_index = 0; + } + + void push(T value) { + if (value != value) { + std::cerr << "WARNING: MedianFilter::push: attempt to push NaN" << std::endl; + return; // nan + } + drop(m_frame[0]); + const int sz1 = m_size-1; + for (int i = 0; i < sz1; ++i) m_frame[i] = m_frame[i+1]; + m_frame[m_size-1] = value; + put(value); + } + + T get() const { + return m_sorted[m_index]; + } + + int getSize() const { + return m_size; + } + + T getAt(float percentile) { + int ix = int((m_size * percentile) / 100.f); + if (ix >= m_size) ix = m_size-1; + if (ix < 0) ix = 0; + return m_sorted[ix]; + } + + void reset() { + for (int i = 0; i < m_size; ++i) m_frame[i] = 0; + for (int i = 0; i < m_size; ++i) m_sorted[i] = 0; + } + + static std::vector<T> filter(int size, const std::vector<T> &in) { + std::vector<T> out; + MedianFilter<T> f(size); + for (int i = 0; i < int(in.size()); ++i) { + f.push(in[i]); + T median = f.get(); + if (i >= size/2) out.push_back(median); + } + while (out.size() < in.size()) { + f.push(T()); + out.push_back(f.get()); + } + return out; + } + +private: + const int m_size; + T *const m_frame; + T *const m_sorted; + T *const m_sortend; + int m_index; + + void put(T value) { + // precondition: m_sorted contains m_size-1 values, packed at start + // postcondition: m_sorted contains m_size values, one of which is value + T *point = std::lower_bound(m_sorted, m_sortend, value); + const int n = m_sortend - point; + for (int i = n; i > 0; --i) point[i] = point[i-1]; + *point = value; + } + + void drop(T value) { + // precondition: m_sorted contains m_size values, one of which is value + // postcondition: m_sorted contains m_size-1 values, packed at start + T *point = std::lower_bound(m_sorted, m_sortend + 1, value); + if (*point != value) { + std::cerr << "WARNING: MedianFilter::drop: *point is " << *point + << ", expected " << value << std::endl; + } + const int n = m_sortend - point; + for (int i = 0; i < n; ++i) point[i] = point[i+1]; + *m_sortend = T(0); + } + + MedianFilter(const MedianFilter &); // not provided + MedianFilter &operator=(const MedianFilter &); // not provided +}; + +#endif +
--- a/test/TestFeatureExtractor.cpp Thu Jan 08 12:11:27 2015 +0000 +++ b/test/TestFeatureExtractor.cpp Fri Jan 09 17:20:52 2015 +0000 @@ -14,7 +14,7 @@ static int freq2mid(double freq) { - return round(57.0 + 12.0 * log(freq / 220.) / log(2.)); + return int(round(57.0 + 12.0 * log(freq / 220.) / log(2.))); } static int freq2chroma(double freq)