changeset 23:1ae8041ae31b

Merge from branch "track"
author Chris Cannam
date Thu, 05 Jul 2012 08:29:20 +0100
parents 47355877a58d (current diff) a949c0278d7d (diff)
children 0a3c1ecff644
files
diffstat 6 files changed, 889 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CepstrumPitchTracker.cpp	Thu Jul 05 08:29:20 2012 +0100
@@ -0,0 +1,689 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+/*
+    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.
+*/
+
+#include "CepstrumPitchTracker.h"
+
+#include <vector>
+#include <algorithm>
+
+#include <cstdio>
+#include <cmath>
+#include <complex>
+
+using std::string;
+using std::vector;
+using Vamp::RealTime;
+
+CepstrumPitchTracker::Hypothesis::Hypothesis()
+{
+    m_state = New;
+    m_age = 0;
+}
+
+CepstrumPitchTracker::Hypothesis::~Hypothesis()
+{
+}
+
+bool
+CepstrumPitchTracker::Hypothesis::isWithinTolerance(Estimate s)
+{
+    if (m_pending.empty()) {
+        return true;
+    }
+
+    // check we are within a relatively close tolerance of the last
+    // candidate
+    Estimate last = m_pending[m_pending.size()-1];
+    double r = s.freq / last.freq;
+    int cents = lrint(1200.0 * (log(r) / log(2.0)));
+    if (cents < -60 || cents > 60) return false;
+
+    // and within a slightly bigger tolerance of the current mean
+    double meanFreq = getMeanFrequency();
+    r = s.freq / meanFreq;
+    cents = lrint(1200.0 * (log(r) / log(2.0)));
+    if (cents < -80 || cents > 80) return false;
+    
+    return true;
+}
+
+bool 
+CepstrumPitchTracker::Hypothesis::isSatisfied()
+{
+    if (m_pending.empty()) return false;
+    
+    double meanConfidence = 0.0;
+    for (int i = 0; i < m_pending.size(); ++i) {
+        meanConfidence += m_pending[i].confidence;
+    }
+    meanConfidence /= m_pending.size();
+
+    int lengthRequired = int(2.0 / meanConfidence + 0.5);
+    std::cerr << "meanConfidence = " << meanConfidence << ", lengthRequired = " << lengthRequired << ", length = " << m_pending.size() << std::endl;
+
+    return (m_pending.size() > lengthRequired);
+}
+
+void
+CepstrumPitchTracker::Hypothesis::advanceTime()
+{
+    ++m_age;
+}
+
+bool
+CepstrumPitchTracker::Hypothesis::test(Estimate s)
+{
+    bool accept = false;
+
+    switch (m_state) {
+
+    case New:
+        m_state = Provisional;
+        accept = true;
+        break;
+
+    case Provisional:
+        if (m_age > 3) {
+            m_state = Rejected;
+        } else if (isWithinTolerance(s)) {
+            accept = true;
+        }
+        break;
+        
+    case Satisfied:
+        if (m_age > 3) {
+            m_state = Expired;
+        } else if (isWithinTolerance(s)) {
+            accept = true;
+        }
+        break;
+
+    case Rejected:
+        break;
+
+    case Expired:
+        break;
+    }
+
+    if (accept) {
+        m_pending.push_back(s);
+        m_age = 0;
+        if (m_state == Provisional && isSatisfied()) {
+            m_state = Satisfied;
+        }
+    }
+
+    return accept && (m_state == Satisfied);
+}        
+
+CepstrumPitchTracker::Hypothesis::State
+CepstrumPitchTracker::Hypothesis::getState()
+{
+    return m_state;
+}
+
+int
+CepstrumPitchTracker::Hypothesis::getPendingLength()
+{
+    return m_pending.size();
+}
+
+CepstrumPitchTracker::Hypothesis::Estimates
+CepstrumPitchTracker::Hypothesis::getAcceptedEstimates()
+{
+    if (m_state == Satisfied || m_state == Expired) {
+        return m_pending;
+    } else {
+        return Estimates();
+    }
+}
+
+double
+CepstrumPitchTracker::Hypothesis::getMeanFrequency()
+{
+    double acc = 0.0;
+    for (int i = 0; i < m_pending.size(); ++i) {
+        acc += m_pending[i].freq;
+    }
+    acc /= m_pending.size();
+    return acc;
+}
+
+CepstrumPitchTracker::Hypothesis::Note
+CepstrumPitchTracker::Hypothesis::getAveragedNote()
+{
+    Note n;
+
+    if (!(m_state == Satisfied || m_state == Expired)) {
+        n.freq = 0.0;
+        n.time = RealTime::zeroTime;
+        n.duration = RealTime::zeroTime;
+        return n;
+    }
+
+    n.time = m_pending.begin()->time;
+
+    Estimates::iterator i = m_pending.end();
+    --i;
+    n.duration = i->time - n.time;
+
+    // just mean frequency for now, but this isn't at all right perceptually
+    n.freq = getMeanFrequency();
+    
+    return n;
+}
+
+void
+CepstrumPitchTracker::Hypothesis::addFeatures(FeatureSet &fs)
+{
+    for (int i = 0; i < m_pending.size(); ++i) {
+	Feature f;
+	f.hasTimestamp = true;
+	f.timestamp = m_pending[i].time;
+	f.values.push_back(m_pending[i].freq);
+	fs[0].push_back(f);
+    }
+
+    Feature nf;
+    nf.hasTimestamp = true;
+    nf.hasDuration = true;
+    Note n = getAveragedNote();
+    nf.timestamp = n.time;
+    nf.duration = n.duration;
+    nf.values.push_back(n.freq);
+    fs[1].push_back(nf);
+}
+
+CepstrumPitchTracker::CepstrumPitchTracker(float inputSampleRate) :
+    Plugin(inputSampleRate),
+    m_channels(0),
+    m_stepSize(256),
+    m_blockSize(1024),
+    m_fmin(50),
+    m_fmax(1000),
+    m_vflen(3),
+    m_binFrom(0),
+    m_binTo(0),
+    m_bins(0)
+{
+}
+
+CepstrumPitchTracker::~CepstrumPitchTracker()
+{
+}
+
+string
+CepstrumPitchTracker::getIdentifier() const
+{
+    return "cepstrum-pitch";
+}
+
+string
+CepstrumPitchTracker::getName() const
+{
+    return "Cepstrum Pitch Tracker";
+}
+
+string
+CepstrumPitchTracker::getDescription() const
+{
+    return "Estimate f0 of monophonic material using a cepstrum method.";
+}
+
+string
+CepstrumPitchTracker::getMaker() const
+{
+    return "Chris Cannam";
+}
+
+int
+CepstrumPitchTracker::getPluginVersion() const
+{
+    // Increment this each time you release a version that behaves
+    // differently from the previous one
+    return 1;
+}
+
+string
+CepstrumPitchTracker::getCopyright() const
+{
+    return "Freely redistributable (BSD license)";
+}
+
+CepstrumPitchTracker::InputDomain
+CepstrumPitchTracker::getInputDomain() const
+{
+    return FrequencyDomain;
+}
+
+size_t
+CepstrumPitchTracker::getPreferredBlockSize() const
+{
+    return 1024;
+}
+
+size_t 
+CepstrumPitchTracker::getPreferredStepSize() const
+{
+    return 256;
+}
+
+size_t
+CepstrumPitchTracker::getMinChannelCount() const
+{
+    return 1;
+}
+
+size_t
+CepstrumPitchTracker::getMaxChannelCount() const
+{
+    return 1;
+}
+
+CepstrumPitchTracker::ParameterList
+CepstrumPitchTracker::getParameterDescriptors() const
+{
+    ParameterList list;
+    return list;
+}
+
+float
+CepstrumPitchTracker::getParameter(string identifier) const
+{
+    return 0.f;
+}
+
+void
+CepstrumPitchTracker::setParameter(string identifier, float value) 
+{
+}
+
+CepstrumPitchTracker::ProgramList
+CepstrumPitchTracker::getPrograms() const
+{
+    ProgramList list;
+    return list;
+}
+
+string
+CepstrumPitchTracker::getCurrentProgram() const
+{
+    return ""; // no programs
+}
+
+void
+CepstrumPitchTracker::selectProgram(string name)
+{
+}
+
+CepstrumPitchTracker::OutputList
+CepstrumPitchTracker::getOutputDescriptors() const
+{
+    OutputList outputs;
+
+    int n = 0;
+
+    OutputDescriptor d;
+
+    d.identifier = "f0";
+    d.name = "Estimated f0";
+    d.description = "Estimated fundamental frequency";
+    d.unit = "Hz";
+    d.hasFixedBinCount = true;
+    d.binCount = 1;
+    d.hasKnownExtents = true;
+    d.minValue = m_fmin;
+    d.maxValue = m_fmax;
+    d.isQuantized = false;
+    d.sampleType = OutputDescriptor::FixedSampleRate;
+    d.sampleRate = (m_inputSampleRate / m_stepSize);
+    d.hasDuration = false;
+    outputs.push_back(d);
+
+    d.identifier = "notes";
+    d.name = "Notes";
+    d.description = "Derived fixed-pitch note frequencies";
+    d.unit = "Hz";
+    d.hasFixedBinCount = true;
+    d.binCount = 1;
+    d.hasKnownExtents = true;
+    d.minValue = m_fmin;
+    d.maxValue = m_fmax;
+    d.isQuantized = false;
+    d.sampleType = OutputDescriptor::FixedSampleRate;
+    d.sampleRate = (m_inputSampleRate / m_stepSize);
+    d.hasDuration = true;
+    outputs.push_back(d);
+
+    return outputs;
+}
+
+bool
+CepstrumPitchTracker::initialise(size_t channels, size_t stepSize, size_t blockSize)
+{
+    if (channels < getMinChannelCount() ||
+	channels > getMaxChannelCount()) return false;
+
+//    std::cerr << "CepstrumPitchTracker::initialise: channels = " << channels
+//	      << ", stepSize = " << stepSize << ", blockSize = " << blockSize
+//	      << std::endl;
+
+    m_channels = channels;
+    m_stepSize = stepSize;
+    m_blockSize = blockSize;
+
+    m_binFrom = int(m_inputSampleRate / m_fmax);
+    m_binTo = int(m_inputSampleRate / m_fmin); 
+
+    if (m_binTo >= (int)m_blockSize / 2) {
+        m_binTo = m_blockSize / 2 - 1;
+    }
+
+    m_bins = (m_binTo - m_binFrom) + 1;
+
+    reset();
+
+    return true;
+}
+
+void
+CepstrumPitchTracker::reset()
+{
+}
+
+void
+CepstrumPitchTracker::filter(const double *cep, double *data)
+{
+    for (int i = 0; i < m_bins; ++i) {
+        double v = 0;
+        int n = 0;
+        // average according to the vertical filter length
+        for (int j = -m_vflen/2; j <= m_vflen/2; ++j) {
+            int ix = i + m_binFrom + j;
+            if (ix >= 0 && ix < m_blockSize) {
+                v += cep[ix];
+                ++n;
+            }
+        }
+        data[i] = v / n;
+    }
+}
+
+CepstrumPitchTracker::FeatureSet
+CepstrumPitchTracker::process(const float *const *inputBuffers, RealTime timestamp)
+{
+    FeatureSet fs;
+
+    int bs = m_blockSize;
+    int hs = m_blockSize/2 + 1;
+
+    double *rawcep = new double[bs];
+    double *io = new double[bs];
+    double *logmag = new double[bs];
+
+    // The "inverse symmetric" method. Seems to be the most reliable
+        
+    for (int i = 0; i < hs; ++i) {
+
+	double power =
+	    inputBuffers[0][i*2  ] * inputBuffers[0][i*2  ] +
+	    inputBuffers[0][i*2+1] * inputBuffers[0][i*2+1];
+	double mag = sqrt(power);
+	
+	double lm = log(mag + 0.00000001);
+	
+	logmag[i] = lm;
+	if (i > 0) logmag[bs - i] = lm;
+    }
+
+    fft(bs, true, logmag, 0, rawcep, io);
+    
+    delete[] logmag;
+    delete[] io;
+
+    int n = m_bins;
+    double *data = new double[n];
+    filter(rawcep, data);
+    delete[] rawcep;
+
+    double abstot = 0.0;
+
+    for (int i = 0; i < n; ++i) {
+	abstot += fabs(data[i]);
+    }
+
+    double maxval = 0.0;
+    int maxbin = -1;
+
+    for (int i = 0; i < n; ++i) {
+        if (data[i] > maxval) {
+            maxval = data[i];
+            maxbin = i;
+        }
+    }
+
+    if (maxbin < 0) {
+        delete[] data;
+        return fs;
+    }
+
+    double nextPeakVal = 0.0;
+    for (int i = 1; i+1 < n; ++i) {
+        if (data[i] > data[i-1] &&
+            data[i] > data[i+1] &&
+            i != maxbin &&
+            data[i] > nextPeakVal) {
+            nextPeakVal = data[i];
+        }
+    }
+
+    double peakfreq = m_inputSampleRate / (maxbin + m_binFrom);
+
+    double confidence = 0.0;
+    if (nextPeakVal != 0.0) {
+        confidence = ((maxval / nextPeakVal) - 1.0) / 4.0;
+        if (confidence > 1.0) confidence = 1.0;
+    }
+
+    Hypothesis::Estimate e;
+    e.freq = peakfreq;
+    e.time = timestamp;
+    e.confidence = confidence;
+
+    m_accepted.advanceTime();
+
+    for (int i = 0; i < m_possible.size(); ++i) {
+        m_possible[i].advanceTime();
+    }
+
+    if (!m_accepted.test(e)) {
+
+        int candidate = -1;
+        bool accepted = false;
+
+        for (int i = 0; i < m_possible.size(); ++i) {
+            if (m_possible[i].test(e)) {
+                accepted = true;
+                if (m_possible[i].getState() == Hypothesis::Satisfied) {
+                    candidate = i;
+                }
+                break;
+            }
+        }
+
+        if (!accepted) {
+            Hypothesis h;
+            h.test(e); //!!! must succeed as h is new, so perhaps there should be a ctor for this
+            m_possible.push_back(h);
+        }
+
+        if (m_accepted.getState() == Hypothesis::Expired) {
+            m_accepted.addFeatures(fs);
+        }
+        
+        if (m_accepted.getState() == Hypothesis::Expired ||
+            m_accepted.getState() == Hypothesis::Rejected) {
+            if (candidate >= 0) {
+                m_accepted = m_possible[candidate];
+            } else {
+                m_accepted = Hypothesis();
+            }
+        }
+
+        // reap rejected/expired hypotheses from possible list
+        Hypotheses toReap = m_possible;
+        m_possible.clear();
+        for (int i = 0; i < toReap.size(); ++i) {
+            Hypothesis h = toReap[i];
+            if (h.getState() != Hypothesis::Rejected && 
+                h.getState() != Hypothesis::Expired) {
+                m_possible.push_back(h);
+            }
+        }
+    }  
+
+    std::cerr << "accepted length = " << m_accepted.getPendingLength()
+              << ", state = " << m_accepted.getState()
+              << ", hypothesis count = " << m_possible.size() << std::endl;
+
+    delete[] data;
+    return fs;
+}
+
+CepstrumPitchTracker::FeatureSet
+CepstrumPitchTracker::getRemainingFeatures()
+{
+    FeatureSet fs;
+    if (m_accepted.getState() == Hypothesis::Satisfied) {
+        m_accepted.addFeatures(fs);
+    }
+    return fs;
+}
+
+void
+CepstrumPitchTracker::fft(unsigned int n, bool inverse,
+                    double *ri, double *ii, double *ro, double *io)
+{
+    if (!ri || !ro || !io) return;
+
+    unsigned int bits;
+    unsigned int i, j, k, m;
+    unsigned int blockSize, blockEnd;
+
+    double tr, ti;
+
+    if (n < 2) return;
+    if (n & (n-1)) return;
+
+    double angle = 2.0 * M_PI;
+    if (inverse) angle = -angle;
+
+    for (i = 0; ; ++i) {
+	if (n & (1 << i)) {
+	    bits = i;
+	    break;
+	}
+    }
+
+    static unsigned int tableSize = 0;
+    static int *table = 0;
+
+    if (tableSize != n) {
+
+	delete[] table;
+
+	table = new int[n];
+
+	for (i = 0; i < n; ++i) {
+	
+	    m = i;
+
+	    for (j = k = 0; j < bits; ++j) {
+		k = (k << 1) | (m & 1);
+		m >>= 1;
+	    }
+
+	    table[i] = k;
+	}
+
+	tableSize = n;
+    }
+
+    if (ii) {
+	for (i = 0; i < n; ++i) {
+	    ro[table[i]] = ri[i];
+	    io[table[i]] = ii[i];
+	}
+    } else {
+	for (i = 0; i < n; ++i) {
+	    ro[table[i]] = ri[i];
+	    io[table[i]] = 0.0;
+	}
+    }
+
+    blockEnd = 1;
+
+    for (blockSize = 2; blockSize <= n; blockSize <<= 1) {
+
+	double delta = angle / (double)blockSize;
+	double sm2 = -sin(-2 * delta);
+	double sm1 = -sin(-delta);
+	double cm2 = cos(-2 * delta);
+	double cm1 = cos(-delta);
+	double w = 2 * cm1;
+	double ar[3], ai[3];
+
+	for (i = 0; i < n; i += blockSize) {
+
+	    ar[2] = cm2;
+	    ar[1] = cm1;
+
+	    ai[2] = sm2;
+	    ai[1] = sm1;
+
+	    for (j = i, m = 0; m < blockEnd; j++, m++) {
+
+		ar[0] = w * ar[1] - ar[2];
+		ar[2] = ar[1];
+		ar[1] = ar[0];
+
+		ai[0] = w * ai[1] - ai[2];
+		ai[2] = ai[1];
+		ai[1] = ai[0];
+
+		k = j + blockEnd;
+		tr = ar[0] * ro[k] - ai[0] * io[k];
+		ti = ar[0] * io[k] + ai[0] * ro[k];
+
+		ro[k] = ro[j] - tr;
+		io[k] = io[j] - ti;
+
+		ro[j] += tr;
+		io[j] += ti;
+	    }
+	}
+
+	blockEnd = blockSize;
+    }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CepstrumPitchTracker.h	Thu Jul 05 08:29:20 2012 +0100
@@ -0,0 +1,135 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+/*
+    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.
+*/
+
+#ifndef _CEPSTRUM_PITCH_H_
+#define _CEPSTRUM_PITCH_H_
+
+#include <vamp-sdk/Plugin.h>
+
+class CepstrumPitchTracker : public Vamp::Plugin
+{
+public:
+    CepstrumPitchTracker(float inputSampleRate);
+    virtual ~CepstrumPitchTracker();
+
+    std::string getIdentifier() const;
+    std::string getName() const;
+    std::string getDescription() const;
+    std::string getMaker() const;
+    int getPluginVersion() const;
+    std::string getCopyright() const;
+
+    InputDomain getInputDomain() const;
+    size_t getPreferredBlockSize() const;
+    size_t getPreferredStepSize() const;
+    size_t getMinChannelCount() const;
+    size_t getMaxChannelCount() const;
+
+    ParameterList getParameterDescriptors() const;
+    float getParameter(std::string identifier) const;
+    void setParameter(std::string identifier, float value);
+
+    ProgramList getPrograms() const;
+    std::string getCurrentProgram() const;
+    void selectProgram(std::string name);
+
+    OutputList getOutputDescriptors() const;
+
+    bool initialise(size_t channels, size_t stepSize, size_t blockSize);
+    void reset();
+
+    FeatureSet process(const float *const *inputBuffers,
+                       Vamp::RealTime timestamp);
+
+    FeatureSet getRemainingFeatures();
+
+protected:
+    size_t m_channels;
+    size_t m_stepSize;
+    size_t m_blockSize;
+    float m_fmin;
+    float m_fmax;
+    int m_vflen;
+
+    int m_binFrom;
+    int m_binTo;
+    int m_bins; // count of "interesting" bins, those returned in m_cepOutput
+
+    class Hypothesis {
+
+    public:
+        struct Estimate {
+            double freq;
+            Vamp::RealTime time;
+            double confidence;
+        };
+        typedef std::vector<Estimate> Estimates;
+
+        struct Note {
+            double freq;
+            Vamp::RealTime time;
+            Vamp::RealTime duration;
+        };
+        
+        Hypothesis();
+        ~Hypothesis();
+
+        enum State {
+            New,
+            Provisional,
+            Satisfied,
+            Rejected,
+            Expired
+        };
+
+        bool test(Estimate);
+
+        void advanceTime();
+
+        State getState();
+
+        int getPendingLength();
+        Estimates getAcceptedEstimates();
+        Note getAveragedNote();
+
+        void addFeatures(FeatureSet &fs);
+
+    private:
+        bool isWithinTolerance(Estimate);
+        bool isSatisfied();
+        double getMeanFrequency();
+
+        State m_state;
+        Estimates m_pending;
+        int m_age;
+    };
+
+    typedef std::vector<Hypothesis> Hypotheses;
+    Hypotheses m_possible;
+    Hypothesis m_accepted;
+
+    void filter(const double *in, double *out);
+    void fft(unsigned int n, bool inverse,
+             double *ri, double *ii, double *ro, double *io);
+};
+
+#endif
--- a/Makefile.inc	Mon Jun 25 14:32:05 2012 +0100
+++ b/Makefile.inc	Thu Jul 05 08:29:20 2012 +0100
@@ -8,9 +8,11 @@
 CXXFLAGS	:= $(CXXFLAGS) 
 LDFLAGS		:= $(LDFLAGS)
 
-HEADERS := SimpleCepstrum.h
+HEADERS := SimpleCepstrum.h \
+	   CepstrumPitchTracker.h
 
 SOURCES := SimpleCepstrum.cpp \
+	   CepstrumPitchTracker.cpp \
            libmain.cpp
 
 OBJECTS := $(SOURCES:.cpp=.o)
--- a/SimpleCepstrum.cpp	Mon Jun 25 14:32:05 2012 +0100
+++ b/SimpleCepstrum.cpp	Thu Jul 05 08:29:20 2012 +0100
@@ -38,7 +38,8 @@
     m_blockSize(1024),
     m_fmin(50),
     m_fmax(1000),
-    m_histlen(3),
+    m_histlen(1),
+    m_vflen(1),
     m_clamp(false),
     m_method(InverseSymmetric),
     m_binFrom(0),
@@ -159,11 +160,22 @@
     d.unit = "";
     d.minValue = 1;
     d.maxValue = 10;
-    d.defaultValue = 3;
+    d.defaultValue = 1;
     d.isQuantized = true;
     d.quantizeStep = 1;
     list.push_back(d);
 
+    d.identifier = "vflen";
+    d.name = "Vertical filter length";
+    d.description = "";
+    d.unit = "";
+    d.minValue = 1;
+    d.maxValue = 11;
+    d.defaultValue = 1;
+    d.isQuantized = true;
+    d.quantizeStep = 2;
+    list.push_back(d);
+
     d.identifier = "method";
     d.name = "Cepstrum transform method";
     d.unit = "";
@@ -199,6 +211,7 @@
     if (identifier == "fmin") return m_fmin;
     else if (identifier == "fmax") return m_fmax;
     else if (identifier == "histlen") return m_histlen;
+    else if (identifier == "vflen") return m_vflen;
     else if (identifier == "clamp") return (m_clamp ? 1 : 0);
     else if (identifier == "method") return (int)m_method;
     else return 0.f;
@@ -210,6 +223,7 @@
     if (identifier == "fmin") m_fmin = value;
     else if (identifier == "fmax") m_fmax = value;
     else if (identifier == "histlen") m_histlen = value;
+    else if (identifier == "vflen") m_vflen = value;
     else if (identifier == "clamp") m_clamp = (value > 0.5);
     else if (identifier == "method") m_method = Method(int(value + 0.5));
 }
@@ -265,7 +279,7 @@
     outputs.push_back(d);
 
     d.identifier = "peak";
-    d.name = "Peak value";
+    d.name = "Value at peak";
     d.unit = "";
     d.description = "Return the value found in the maximum-valued bin within the specified range of the cepstrum";
     m_pvOutput = n++;
@@ -285,6 +299,13 @@
     m_ppOutput = n++;
     outputs.push_back(d);
 
+    d.identifier = "peak_to_second_peak";
+    d.name = "Peak to second-peak ratio";
+    d.unit = "";
+    d.description = "Return the ratio of the value found in the peak bin within the specified range of the cepstrum, to the value found in the next highest peak";
+    m_pkoOutput = n++;
+    outputs.push_back(d);
+
     d.identifier = "total";
     d.name = "Total energy";
     d.unit = "";
@@ -402,7 +423,17 @@
     }
 
     for (int i = 0; i < m_bins; ++i) {
-        m_history[hix][i] = cep[i + m_binFrom];
+        double v = 0;
+        int n = 0;
+        // average according to the vertical filter length
+        for (int j = -m_vflen/2; j <= m_vflen/2; ++j) {
+            int ix = i + m_binFrom + j;
+            if (ix >= 0 && ix < m_blockSize) {
+                v += cep[ix];
+                ++n;
+            }
+        }
+        m_history[hix][i] = v / n;
     }
 
     for (int i = 0; i < m_bins; ++i) {
@@ -430,6 +461,17 @@
         }
     }
 
+    double nextPeakVal = 0.0;
+
+    for (int i = 1; i+1 < n; ++i) {
+        if (data[i] > data[i-1] &&
+            data[i] > data[i+1] &&
+            i != maxbin &&
+            data[i] > nextPeakVal) {
+            nextPeakVal = data[i];
+        }
+    }
+
     Feature rf;
     if (maxval > 0.0) {
         rf.values.push_back(m_inputSampleRate / (maxbin + m_binFrom));
@@ -450,8 +492,10 @@
     double mean = total / n;
 
     double totsqr = 0;
+    double abstot = 0;
     for (int i = 0; i < n; ++i) {
         totsqr += data[i] * data[i];
+        abstot += fabs(data[i]);
     }
     double rms = sqrt(totsqr / n);
 
@@ -477,7 +521,7 @@
             ++i;
         }
     }
-    peakProportion = aroundPeak / sqrt(totsqr);
+    peakProportion = aroundPeak / abstot;
     Feature pp;
     pp.values.push_back(peakProportion);
     fs[m_ppOutput].push_back(pp);
@@ -494,6 +538,14 @@
     pv.values.push_back(maxval);
     fs[m_pvOutput].push_back(pv);
 
+    Feature pko;
+    if (nextPeakVal != 0.0) {
+        pko.values.push_back(maxval / nextPeakVal);
+    } else {
+        pko.values.push_back(0.0);
+    }
+    fs[m_pkoOutput].push_back(pko);
+
     Feature am;
     for (int i = 0; i < n; ++i) {
         if (data[i] < rms) am.values.push_back(0);
--- a/SimpleCepstrum.h	Mon Jun 25 14:32:05 2012 +0100
+++ b/SimpleCepstrum.h	Thu Jul 05 08:29:20 2012 +0100
@@ -69,6 +69,7 @@
     float m_fmin;
     float m_fmax;
     int m_histlen;
+    int m_vflen;
     bool m_clamp;
 
     enum Method {
@@ -91,6 +92,7 @@
     mutable int m_esOutput;
     mutable int m_ppOutput;
     mutable int m_totOutput;
+    mutable int m_pkoOutput;
 
     int m_binFrom;
     int m_binTo;
--- a/libmain.cpp	Mon Jun 25 14:32:05 2012 +0100
+++ b/libmain.cpp	Thu Jul 05 08:29:20 2012 +0100
@@ -4,8 +4,10 @@
 #include <vamp-sdk/PluginAdapter.h>
 
 #include "SimpleCepstrum.h"
+#include "CepstrumPitchTracker.h"
 
 static Vamp::PluginAdapter<SimpleCepstrum> cepPluginAdapter;
+static Vamp::PluginAdapter<CepstrumPitchTracker> cepitchPluginAdapter;
 
 const VampPluginDescriptor *
 vampGetPluginDescriptor(unsigned int version, unsigned int index)
@@ -14,6 +16,7 @@
 
     switch (index) {
     case  0: return cepPluginAdapter.getDescriptor();
+    case  1: return cepitchPluginAdapter.getDescriptor();
     default: return 0;
     }
 }