Mercurial > hg > pyin
changeset 0:99bac62ee2da
added PYIN sources, should be compileable
author | matthiasm |
---|---|
date | Wed, 27 Nov 2013 11:59:49 +0000 |
parents | |
children | 3dcef83df62a |
files | Makefile.inc Makefile.linux64 Makefile.osx MeanFilter.h MonoNote.cpp MonoNote.h MonoNoteHMM.cpp MonoNoteHMM.h MonoNoteParameters.cpp MonoNoteParameters.h MonoPitch.cpp MonoPitch.h MonoPitchHMM.cpp MonoPitchHMM.h PYIN.cpp PYIN.h SparseHMM.cpp SparseHMM.h VampYin.cpp VampYin.h Yin.cpp Yin.h YinUtil.cpp YinUtil.h libmain.cpp test/TestFFT.cpp test/TestMeanFilter.cpp test/TestMonoNote.cpp test/TestPredominoMethods.cpp test/TestYin.cpp test/TestYinUtil.cpp |
diffstat | 31 files changed, 3294 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile.inc Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,82 @@ + +PLUGIN_EXT ?= .so + +CXX ?= g++ +CC ?= gcc + +CFLAGS := $(CFLAGS) +CXXFLAGS := -I. $(CXXFLAGS) + +# LDFLAGS := $(LDFLAGS) -lvamp-sdk +# PLUGIN_LDFLAGS := $(LDFLAGS) $(PLUGIN_LDFLAGS) +# TEST_LDFLAGS := $(TEST_LDFLAGS) $(LDFLAGS) -lboost_unit_test_framework + +PLUGIN := yintony$(PLUGIN_EXT) + +SOURCES := PYIN.cpp \ + VampYin.cpp \ + Yin.cpp \ + YinUtil.cpp \ + MonoNote.cpp \ + MonoPitch.cpp \ + MonoNoteParameters.cpp \ + SparseHMM.cpp \ + MonoNoteHMM.cpp \ + MonoPitchHMM.cpp \ + +PLUGIN_MAIN := libmain.cpp + +TESTS := test/test-meanfilter \ + test/test-fft \ + test/test-yin \ + test/test-mononote + +OBJECTS := $(SOURCES:.cpp=.o) +OBJECTS := $(OBJECTS:.c=.o) + +PLUGIN_OBJECTS := $(OBJECTS) $(PLUGIN_MAIN:.cpp=.o) + +all: $(PLUGIN) $(TESTS) + for t in $(TESTS); do echo "Running $$t"; ./"$$t" || exit 1; done + +plugin: $(PLUGIN) + +$(PLUGIN): $(PLUGIN_OBJECTS) + $(CXX) -o $@ $^ $(PLUGIN_LDFLAGS) + +test/test-meanfilter: test/TestMeanFilter.o $(OBJECTS) + $(CXX) -o $@ $^ $(TEST_LDFLAGS) + +test/test-fft: test/TestFFT.o $(OBJECTS) + $(CXX) -o $@ $^ $(TEST_LDFLAGS) + +test/test-yin: test/TestYin.o $(OBJECTS) + $(CXX) -o $@ $^ $(TEST_LDFLAGS) + +test/test-mononote: test/TestMonoNote.o $(OBJECTS) + $(CXX) -o $@ $^ $(TEST_LDFLAGS) + +clean: + rm -f $(PLUGIN_OBJECTS) test/*.o + +distclean: clean + rm -f $(PLUGIN) $(TESTS) + +depend: + makedepend -Y -fMakefile.inc *.cpp test/*.cpp *.h test/*.h + +# DO NOT DELETE + +PYIN.o: PYIN.h +VampYin.o: VampYin.h +Yin.o: Yin.h +MonoNoteParameters.o: MonoNoteParameters.h +MonoNote.o: MonoNote.h +MonoPitch.o: MonoPitch.h +MonoPitchHMM.o: MonoPitchHMM.h +SparseHMM.o: SparseHMM.h +MonoNoteHMM.o: MonoNoteHMM.h +libmain.o: PYIN.h VampYin.h +test/TestMeanFilter.o: MeanFilter.h +test/TestYin.o: Yin.h +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile.linux64 Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,11 @@ + +CFLAGS := -Wall -O3 -fPIC -I../vamp-plugin-sdk/ +CXXFLAGS := $(CFLAGS) + +PLUGIN_LDFLAGS := -shared -Wl,-Bstatic -L../vamp-plugin-sdk -lvamp-sdk -Wl,-Bdynamic +TEST_LDFLAGS := -Wl,-Bstatic -L../vamp-plugin-sdk -lvamp-sdk -Wl,-Bdynamic -lboost_unit_test_framework + +PLUGIN_EXT := .so + +include Makefile.inc +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile.osx Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,11 @@ +ARCHFLAGS := +CFLAGS := $(ARCHFLAGS) -O3 -I../inst/include -I/usr/local/boost -Wall -fPIC +CXXFLAGS := $(CFLAGS) + +LDFLAGS := -lvamp-sdk $(ARCHFLAGS) +PLUGIN_LDFLAGS := -dynamiclib -lvamp-sdk +TEST_LDFLAGS := -lvamp-sdk -lboost_unit_test_framework +PLUGIN_EXT := .dylib + +include Makefile.inc +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MeanFilter.h Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,74 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + This file is Copyright (c) 2012 Chris Cannam + + 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 _MEAN_FILTER_H_ +#define _MEAN_FILTER_H_ + +class MeanFilter +{ +public: + /** + * Construct a non-causal mean filter with filter length flen, + * that replaces each sample N with the mean of samples + * [N-floor(F/2) .. N+floor(F/2)] where F is the filter length. + * Only odd F are supported. + */ + MeanFilter(int flen) : m_flen(flen) { } + ~MeanFilter() { } + + /** + * Filter the n samples in "in" and place the results in "out" + */ + void filter(const double *in, double *out, const int n) { + filterSubsequence(in, out, n, n, 0); + } + + /** + * Filter the n samples starting at the given offset in the + * m-element array "in" and place the results in the n-element + * array "out" + */ + void filterSubsequence(const double *in, double *out, + const int m, const int n, + const int offset) { + int half = m_flen/2; + for (int i = 0; i < n; ++i) { + double v = 0; + int n = 0; + for (int j = -half; j <= half; ++j) { + int ix = i + j + offset; + if (ix >= 0 && ix < m) { + v += in[ix]; + ++n; + } + } + out[i] = v / n; + } + } + +private: + int m_flen; +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MonoNote.cpp Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,48 @@ +#include "MonoNote.h" +#include <vector> + +#include <cstdio> +#include <cmath> +#include <complex> + +using std::vector; +using std::pair; + +MonoNote::MonoNote() : + hmm() +{ +} + +MonoNote::~MonoNote() +{ +} + +const vector<MonoNote::FrameOutput> +MonoNote::process(const vector<vector<pair<double, double> > > pitchProb) +{ + vector<vector<double> > obsProb; + for (size_t iFrame = 0; iFrame < pitchProb.size(); ++iFrame) + { + obsProb.push_back(hmm.calculateObsProb(pitchProb[iFrame])); + } + + vector<double> *scale = new vector<double>(pitchProb.size()); + + vector<MonoNote::FrameOutput> out; + + vector<int> path = hmm.decodeViterbi(obsProb, scale); + + for (size_t iFrame = 0; iFrame < path.size(); ++iFrame) + { + double currPitch = -1; + int stateKind = 0; + + currPitch = hmm.par.minPitch + (path[iFrame]/hmm.par.nSPP) * 1.0/hmm.par.nPPS; + stateKind = (path[iFrame]) % hmm.par.nSPP + 1; + + out.push_back(FrameOutput(iFrame, currPitch, stateKind)); + // std::cerr << path[iFrame] << " -- "<< pitchProb[iFrame][0].first << " -- "<< currPitch << " -- " << stateKind << std::endl; + } + delete scale; + return(out); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MonoNote.h Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,33 @@ +#ifndef _MONONOTE_H_ +#define _MONONOTE_H_ + +#include "MonoNoteHMM.h" +#include "MonoNoteParameters.h" + +#include <iostream> +#include <vector> +#include <exception> + +using std::vector; +using std::pair; + +class MonoNote { +public: + MonoNote(); + virtual ~MonoNote(); + + struct FrameOutput { + size_t frameNumber; + double pitch; + size_t noteState; // unvoiced, attack, stable, release, inter + FrameOutput() : frameNumber(0), pitch(-1.0), noteState(0) { } + FrameOutput(size_t _frameNumber, double _pitch, size_t _noteState) : + frameNumber(_frameNumber), pitch(_pitch), noteState(_noteState) { } + }; + // pitchProb is a frame-wise vector carrying a vector of pitch-probability pairs + const vector<FrameOutput> process(const vector<vector<pair<double, double> > > pitchProb); +private: + MonoNoteHMM hmm; +}; + +#endif \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MonoNoteHMM.cpp Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,206 @@ +#include "MonoNoteHMM.h" + +#include <boost/math/distributions.hpp> + +#include <cstdio> +#include <cmath> + +using std::vector; +using std::pair; + +MonoNoteHMM::MonoNoteHMM() : + par() +{ + build(); +} + +const vector<double> +MonoNoteHMM::calculateObsProb(const vector<pair<double, double> > pitchProb) +{ + // pitchProb is a list of pairs (pitches and their probabilities) + + size_t nCandidate = pitchProb.size(); + + // what is the probability of pitched + double pIsPitched = 0; + for (size_t iCandidate = 0; iCandidate < nCandidate; ++iCandidate) + { + // pIsPitched = pitchProb[iCandidate].second > pIsPitched ? pitchProb[iCandidate].second : pIsPitched; + pIsPitched += pitchProb[iCandidate].second; + } + + // pIsPitched = std::pow(pIsPitched, (1-par.priorWeight)) * std::pow(par.priorPitchedProb, par.priorWeight); + pIsPitched = pIsPitched * (1-par.priorWeight) + par.priorPitchedProb * par.priorWeight; + + vector<double> out = vector<double>(par.n); + double tempProbSum = 0; + for (size_t i = 0; i < par.n; ++i) + { + if (i % 4 != 2) + { + // std::cerr << getMidiPitch(i) << std::endl; + double tempProb = 0; + if (nCandidate > 0) + { + double minDist = 10000.0; + double minDistProb = 0; + size_t minDistCandidate = 0; + for (size_t iCandidate = 0; iCandidate < nCandidate; ++iCandidate) + { + double currDist = std::abs(getMidiPitch(i)-pitchProb[iCandidate].first); + if (currDist < minDist) + { + minDist = currDist; + minDistProb = pitchProb[iCandidate].second; + minDistCandidate = iCandidate; + } + } + tempProb = std::pow(minDistProb, par.yinTrust) * boost::math::pdf(pitchDistr[i], pitchProb[minDistCandidate].first); + } else { + tempProb = 1; + } + tempProbSum += tempProb; + out[i] = tempProb; + } + } + + for (size_t i = 0; i < par.n; ++i) + { + if (i % 4 != 2) + { + if (tempProbSum > 0) + { + out[i] = out[i] / tempProbSum * pIsPitched; + } + } else { + out[i] = (1-pIsPitched) / (par.nPPS * par.nS); + } + } + + return(out); +} + +void +MonoNoteHMM::build() +{ + // the states are organised as follows: + // 0-2. lowest pitch + // 0. attack state + // 1. stable state + // 2. silent state + // 3. inter state + // 4-6. second-lowest pitch + // 4. attack state + // ... + + // observation distributions + for (size_t iState = 0; iState < par.n; ++iState) + { + pitchDistr.push_back(boost::math::normal(0,1)); + if (iState % 4 == 2) + { + // silent state starts tracking + init.push_back(1.0/(par.nS * par.nPPS)); + } else { + init.push_back(0.0); + } + } + + for (size_t iPitch = 0; iPitch < (par.nS * par.nPPS); ++iPitch) + { + size_t index = iPitch * par.nSPP; + double mu = par.minPitch + iPitch * 1.0/par.nPPS; + pitchDistr[index] = boost::math::normal(mu, par.sigmaYinPitchAttack); + pitchDistr[index+1] = boost::math::normal(mu, par.sigmaYinPitchStable); + pitchDistr[index+2] = boost::math::normal(mu, 1.0); // dummy + pitchDistr[index+3] = boost::math::normal(mu, par.sigmaYinPitchInter); + } + + boost::math::normal noteDistanceDistr(0, par.sigma2Note); + + for (size_t iPitch = 0; iPitch < (par.nS * par.nPPS); ++iPitch) + { + // loop through all notes and set sparse transition probabilities + size_t index = iPitch * par.nSPP; + + // transitions from attack state + from.push_back(index); + to.push_back(index); + transProb.push_back(par.pAttackSelftrans); + + from.push_back(index); + to.push_back(index+1); + transProb.push_back(1-par.pAttackSelftrans); + + // transitions from stable state + from.push_back(index+1); + to.push_back(index+1); // to itself + transProb.push_back(par.pStableSelftrans); + + from.push_back(index+1); + to.push_back(index+2); // to silent + transProb.push_back(par.pStable2Silent); + + from.push_back(index+1); + to.push_back(index+3); // to inter-note + transProb.push_back(1-par.pStableSelftrans-par.pStable2Silent); + + // the "easy" transitions from silent state + from.push_back(index+2); + to.push_back(index+2); + transProb.push_back(par.pSilentSelftrans); + + // the "easy" inter state transition + from.push_back(index+3); + to.push_back(index+3); + transProb.push_back(par.pInterSelftrans); + + // the more complicated transitions from the silent and inter state + double probSumSilent = 0; + double probSumInter = 0; + vector<double> tempTransProbInter; + vector<double> tempTransProbSilent; + for (size_t jPitch = 0; jPitch < (par.nS * par.nPPS); ++jPitch) + { + int fromPitch = iPitch; + int toPitch = jPitch; + double semitoneDistance = std::abs(fromPitch - toPitch) * 1.0 / par.nPPS; + + // if (std::fmod(semitoneDistance, 1) == 0 && semitoneDistance > par.minSemitoneDistance) + if (semitoneDistance == 0 || (semitoneDistance > par.minSemitoneDistance && semitoneDistance < par.maxJump)) + { + size_t toIndex = jPitch * par.nSPP; // note attack index + + double tempWeightSilent = boost::math::pdf(noteDistanceDistr, semitoneDistance); + double tempWeightInter = semitoneDistance == 0 ? 0 : tempWeightSilent; + probSumSilent += tempWeightSilent; + probSumInter += tempWeightInter; + + tempTransProbSilent.push_back(tempWeightSilent); + tempTransProbInter.push_back(tempWeightInter); + + from.push_back(index+2); + to.push_back(toIndex); + from.push_back(index+3); + to.push_back(toIndex); + } + } + for (size_t i = 0; i < tempTransProbSilent.size(); ++i) + { + transProb.push_back((1-par.pSilentSelftrans) * tempTransProbSilent[i]/probSumSilent); + transProb.push_back((1-par.pInterSelftrans) * tempTransProbInter[i]/probSumInter); + } + } +} + +double +MonoNoteHMM::getMidiPitch(size_t index) +{ + return pitchDistr[index].mean(); +} + +double +MonoNoteHMM::getFrequency(size_t index) +{ + return 440 * pow(2, (pitchDistr[index].mean()-69)/12); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MonoNoteHMM.h Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,32 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + This file is Copyright (c) 2012 Matthias Mauch + +*/ + +#ifndef _MONONOTEHMM_H_ +#define _MONONOTEHMM_H_ + +#include "MonoNoteParameters.h" +#include "SparseHMM.h" + +#include <boost/math/distributions.hpp> + +#include <vector> +#include <cstdio> + +using std::vector; + +class MonoNoteHMM : public SparseHMM +{ +public: + MonoNoteHMM(); + const std::vector<double> calculateObsProb(const vector<pair<double, double> >); + double getMidiPitch(size_t index); + double getFrequency(size_t index); + void build(); + MonoNoteParameters par; + vector<boost::math::normal> pitchDistr; +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MonoNoteParameters.cpp Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,31 @@ +#include "MonoNoteParameters.h" + +MonoNoteParameters::MonoNoteParameters() : + minPitch(36), + nPPS(3), + nS(43), + nSPP(4), // states per pitch + n(0), + initPi(0), + pAttackSelftrans(0.99), + pStableSelftrans(0.99), + pStable2Silent(0.005), + pSilentSelftrans(0.99), + sigma2Note(1.5), + maxJump(13), + pInterSelftrans(0.99), + priorPitchedProb(.7), + priorWeight(0.5), + minSemitoneDistance(.5), + sigmaYinPitchAttack(4), + sigmaYinPitchStable(0.9), + sigmaYinPitchInter(4), + yinTrust(0.1) +{ + // just in case someone put in a silly value for pRelease2Unvoiced + n = nPPS * nS * nSPP; +} + +MonoNoteParameters::~MonoNoteParameters() +{ +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MonoNoteParameters.h Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,48 @@ +#ifndef _MONONOTEPARAMETERS_H_ +#define _MONONOTEPARAMETERS_H_ + +#include <iostream> +#include <vector> +#include <exception> + +using std::vector; + +class MonoNoteParameters +{ +public: + MonoNoteParameters(); + virtual ~MonoNoteParameters(); + + // model architecture parameters + size_t minPitch; // lowest pitch in MIDI notes + size_t nPPS; // number of pitches per semitone + size_t nS; // number of semitones + size_t nSPP; // number of states per pitch + size_t n; // number of states (will be calcualted from other parameters) + + // initial state probabilities + vector<double> initPi; + + // transition parameters + double pAttackSelftrans; + double pStableSelftrans; + double pStable2Silent; + double pSilentSelftrans; + double sigma2Note; // standard deviation of next note Gaussian distribution + double maxJump; + double pInterSelftrans; + + double priorPitchedProb; + double priorWeight; + + double minSemitoneDistance; // minimum distance for a transition + + double sigmaYinPitchAttack; + double sigmaYinPitchStable; + double sigmaYinPitchInter; + + double yinTrust; + +}; + +#endif \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MonoPitch.cpp Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,74 @@ +#include "MonoPitch.h" +#include "MonoPitchHMM.h" +#include <vector> + +#include <cstdio> +#include <cmath> +#include <complex> + +using std::vector; +using std::pair; + +MonoPitch::MonoPitch() : + hmm() +{ +} + +MonoPitch::~MonoPitch() +{ +} + +const vector<float> +MonoPitch::process(const vector<vector<pair<double, double> > > pitchProb) +{ + // std::cerr << "before observation prob calculation" << std::endl; + vector<vector<double> > obsProb; + for (size_t iFrame = 0; iFrame < pitchProb.size(); ++iFrame) + { + obsProb.push_back(hmm.calculateObsProb(pitchProb[iFrame])); + } + for (size_t i = 0; i < obsProb[0].size(); ++i) { + obsProb[0][i] = 0; + } + obsProb[0][obsProb[0].size()-1] = 1; + + // std::cerr << "after observation prob calculation" << std::endl; + + vector<double> *scale = new vector<double>(pitchProb.size()); + + vector<float> out; + + // std::cerr << "before Viterbi decoding" << obsProb.size() << "ng" << obsProb[1].size() << std::endl; + vector<int> path = hmm.decodeViterbi(obsProb, scale); + // std::cerr << "after Viterbi decoding" << std::endl; + + for (size_t iFrame = 0; iFrame < path.size(); ++iFrame) + { + // std::cerr << path[iFrame] << " " << hmm.m_freqs[path[iFrame]] << std::endl; + float hmmFreq = hmm.m_freqs[path[iFrame]]; + float bestFreq = 0; + float leastDist = 10000; + if (hmmFreq > 0) + { + // This was a Yin estimate, so try to get original pitch estimate back + // ... a bit hacky, since we could have direclty saved the frequency + // that was assigned to the HMM bin in hmm.calculateObsProb -- but would + // have had to rethink the interface of that method. + for (size_t iPitch = 0; iPitch < pitchProb[iFrame].size(); ++iPitch) + { + float freq = 440. * std::pow(2, (pitchProb[iFrame][iPitch].first - 69)/12); + float dist = std::abs(hmmFreq-freq); + if (dist < leastDist) + { + leastDist = dist; + bestFreq = freq; + } + } + } else { + bestFreq = hmmFreq; + } + out.push_back(bestFreq); + } + delete scale; + return(out); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MonoPitch.h Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,24 @@ +#ifndef _MONOPITCH_H_ +#define _MONOPITCH_H_ + +#include "MonoPitchHMM.h" + +#include <iostream> +#include <vector> +#include <exception> + +using std::vector; +using std::pair; + +class MonoPitch { +public: + MonoPitch(); + virtual ~MonoPitch(); + + // pitchProb is a frame-wise vector carrying a vector of pitch-probability pairs + const vector<float> process(const vector<vector<pair<double, double> > > pitchProb); +private: + MonoPitchHMM hmm; +}; + +#endif \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MonoPitchHMM.cpp Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,142 @@ +#include "MonoPitchHMM.h" + +#include <boost/math/distributions.hpp> + +#include <cstdio> +#include <cmath> + +using std::vector; +using std::pair; + +MonoPitchHMM::MonoPitchHMM() : +m_minFreq(55), +m_nBPS(10), +m_nPitch(0), +m_transitionWidth(0), +m_selfTrans(0.99), +m_yinTrust(.5), +m_freqs(0) +{ + m_transitionWidth = 5*(m_nBPS/2) + 1; + m_nPitch = 48 * m_nBPS; + m_freqs = vector<double>(2*m_nPitch+1); + for (size_t iPitch = 0; iPitch < m_nPitch; ++iPitch) + { + m_freqs[iPitch] = m_minFreq * std::pow(2, iPitch * 1.0 / (12 * m_nBPS)); + // m_freqs[iPitch+m_nPitch] = -2; + m_freqs[iPitch+m_nPitch] = -m_freqs[iPitch]; + // std::cerr << "pitch " << iPitch << " = " << m_freqs[iPitch] << std::endl; + } + m_freqs[2*m_nPitch] = -1; + build(); +} + +const vector<double> +MonoPitchHMM::calculateObsProb(const vector<pair<double, double> > pitchProb) +{ + vector<double> out = vector<double>(2*m_nPitch+1); + double probYinPitched = 0; + // BIN THE PITCHES + for (size_t iPair = 0; iPair < pitchProb.size(); ++iPair) + { + double freq = 440. * std::pow(2, (pitchProb[iPair].first - 69)/12); + if (freq <= m_minFreq) continue; + double d = 0; + double oldd = 1000; + for (size_t iPitch = 0; iPitch < m_nPitch; ++iPitch) + { + d = std::abs(freq-m_freqs[iPitch]); + if (oldd < d && iPitch > 0) + { + // previous bin must have been the closest + out[iPitch-1] = pitchProb[iPair].second; + probYinPitched += out[iPitch-1]; + break; + } + oldd = d; + } + } + + double probReallyPitched = m_yinTrust * probYinPitched; + for (size_t iPitch = 0; iPitch < m_nPitch; ++iPitch) + { + if (probYinPitched > 0) out[iPitch] *= (probReallyPitched/probYinPitched) ; + out[iPitch+m_nPitch] = (1 - probReallyPitched) / m_nPitch; + } + // out[2*m_nPitch] = m_yinTrust * (1 - probYinPitched); + return(out); +} + +void +MonoPitchHMM::build() +{ + // INITIAL VECTOR + init = vector<double>(2*m_nPitch+1); + init[2*m_nPitch] = 1; // force first frame to be unvoiced. + + // TRANSITIONS + for (size_t iPitch = 0; iPitch < m_nPitch; ++iPitch) + { + int theoreticalMinNextPitch = static_cast<int>(iPitch)-static_cast<int>(m_transitionWidth/2); + int minNextPitch = iPitch>m_transitionWidth/2 ? iPitch-m_transitionWidth/2 : 0; + int maxNextPitch = iPitch<m_nPitch-m_transitionWidth/2 ? iPitch+m_transitionWidth/2 : m_nPitch-1; + + // WEIGHT VECTOR + double weightSum = 0; + vector<double> weights; + for (size_t i = minNextPitch; i <= maxNextPitch; ++i) + { + if (i <= iPitch) + { + weights.push_back(i-theoreticalMinNextPitch+1); + // weights.push_back(i-theoreticalMinNextPitch+1+m_transitionWidth/2); + } else { + weights.push_back(iPitch-theoreticalMinNextPitch+1-(i-iPitch)); + // weights.push_back(iPitch-theoreticalMinNextPitch+1-(i-iPitch)+m_transitionWidth/2); + } + weightSum += weights[weights.size()-1]; + } + + // std::cerr << minNextPitch << " " << maxNextPitch << std::endl; + // TRANSITIONS TO CLOSE PITCH + for (size_t i = minNextPitch; i <= maxNextPitch; ++i) + { + from.push_back(iPitch); + to.push_back(i); + transProb.push_back(weights[i-minNextPitch] / weightSum * m_selfTrans); + + from.push_back(iPitch); + to.push_back(i+m_nPitch); + transProb.push_back(weights[i-minNextPitch] / weightSum * (1-m_selfTrans)); + + from.push_back(iPitch+m_nPitch); + to.push_back(i+m_nPitch); + transProb.push_back(weights[i-minNextPitch] / weightSum * m_selfTrans); + // transProb.push_back(weights[i-minNextPitch] / weightSum * 0.5); + + from.push_back(iPitch+m_nPitch); + to.push_back(i); + transProb.push_back(weights[i-minNextPitch] / weightSum * (1-m_selfTrans)); + // transProb.push_back(weights[i-minNextPitch] / weightSum * 0.5); + } + + // TRANSITION TO UNVOICED + // from.push_back(iPitch+m_nPitch); + // to.push_back(2*m_nPitch); + // transProb.push_back(1-m_selfTrans); + + // TRANSITION FROM UNVOICED TO PITCH + from.push_back(2*m_nPitch); + to.push_back(iPitch+m_nPitch); + transProb.push_back(1.0/m_nPitch); + } + // UNVOICED SELFTRANSITION + // from.push_back(2*m_nPitch); + // to.push_back(2*m_nPitch); + // transProb.push_back(m_selfTrans); + + // for (size_t i = 0; i < from.size(); ++i) { + // std::cerr << "P(["<< from[i] << " --> " << to[i] << "]) = " << transProb[i] << std::endl; + // } + +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MonoPitchHMM.h Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,36 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + This file is Copyright (c) 2012 Matthias Mauch + +*/ + +#ifndef _MONOPITCHHMM_H_ +#define _MONOPITCHHMM_H_ + +#include "SparseHMM.h" + +#include <boost/math/distributions.hpp> + +#include <vector> +#include <cstdio> + +using std::vector; + +class MonoPitchHMM : public SparseHMM +{ +public: + MonoPitchHMM(); + const std::vector<double> calculateObsProb(const vector<pair<double, double> >); + // double getMidiPitch(size_t index); + // double getFrequency(size_t index); + void build(); + double m_minFreq; // 82.40689f/2 + size_t m_nBPS; + size_t m_nPitch; + size_t m_transitionWidth; + double m_selfTrans; + double m_yinTrust; + vector<double> m_freqs; +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PYIN.cpp Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,547 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + This file is Copyright (c) 2012 Chris Cannam + + 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 "PYIN.h" +#include "MonoNote.h" +#include "MonoPitch.h" + +#include "vamp-sdk/FFT.h" + +#include <vector> +#include <algorithm> + +#include <cstdio> +#include <cmath> +#include <complex> + +using std::string; +using std::vector; +using Vamp::RealTime; + + +PYIN::PYIN(float inputSampleRate) : + Plugin(inputSampleRate), + m_channels(0), + m_stepSize(256), + m_blockSize(2048), + m_fmin(40), + m_fmax(700), + m_yin(2048, inputSampleRate, 0.0), + m_oF0Candidates(0), + m_oF0Probs(0), + m_oVoicedProb(0), + m_oCandidateSalience(0), + m_oSmoothedPitchTrack(0), + m_oNotes(0), + m_threshDistr(2.0f), + m_outputUnvoiced(0), + m_pitchProb(0), + m_timestamp(0) +{ +} + +PYIN::~PYIN() +{ +} + +string +PYIN::getIdentifier() const +{ + return "yintony"; +} + +string +PYIN::getName() const +{ + return "Yintony"; +} + +string +PYIN::getDescription() const +{ + return "Monophonic pitch and note tracking based on a probabilistic Yin extension."; +} + +string +PYIN::getMaker() const +{ + return "Matthias Mauch"; +} + +int +PYIN::getPluginVersion() const +{ + // Increment this each time you release a version that behaves + // differently from the previous one + return 1; +} + +string +PYIN::getCopyright() const +{ + return "GPL"; +} + +PYIN::InputDomain +PYIN::getInputDomain() const +{ + return TimeDomain; +} + +size_t +PYIN::getPreferredBlockSize() const +{ + return 2048; +} + +size_t +PYIN::getPreferredStepSize() const +{ + return 256; +} + +size_t +PYIN::getMinChannelCount() const +{ + return 1; +} + +size_t +PYIN::getMaxChannelCount() const +{ + return 1; +} + +PYIN::ParameterList +PYIN::getParameterDescriptors() const +{ + ParameterList list; + + ParameterDescriptor d; + + d.identifier = "threshdistr"; + d.name = "Yin threshold distribution"; + d.description = "."; + d.unit = ""; + d.minValue = 0.0f; + d.maxValue = 7.0f; + d.defaultValue = 2.0f; + d.isQuantized = true; + d.quantizeStep = 1.0f; + d.valueNames.push_back("Uniform"); + d.valueNames.push_back("Beta (mean 0.10)"); + d.valueNames.push_back("Beta (mean 0.15)"); + d.valueNames.push_back("Beta (mean 0.20)"); + d.valueNames.push_back("Beta (mean 0.30)"); + d.valueNames.push_back("Single Value 0.10"); + d.valueNames.push_back("Single Value 0.15"); + d.valueNames.push_back("Single Value 0.20"); + list.push_back(d); + + d.identifier = "outputunvoiced"; + d.valueNames.clear(); + d.name = "Output estimates classified as unvoiced?"; + d.description = "."; + d.unit = ""; + d.minValue = 0.0f; + d.maxValue = 2.0f; + d.defaultValue = 2.0f; + d.isQuantized = true; + d.quantizeStep = 1.0f; + d.valueNames.push_back("No"); + d.valueNames.push_back("Yes"); + d.valueNames.push_back("Yes, as negative frequencies"); + list.push_back(d); + + return list; +} + +float +PYIN::getParameter(string identifier) const +{ + if (identifier == "threshdistr") { + return m_threshDistr; + } + if (identifier == "outputunvoiced") { + return m_outputUnvoiced; + } + return 0.f; +} + +void +PYIN::setParameter(string identifier, float value) +{ + if (identifier == "threshdistr") + { + m_threshDistr = value; + } + if (identifier == "outputunvoiced") + { + m_outputUnvoiced = value; + } + +} + +PYIN::ProgramList +PYIN::getPrograms() const +{ + ProgramList list; + return list; +} + +string +PYIN::getCurrentProgram() const +{ + return ""; // no programs +} + +void +PYIN::selectProgram(string name) +{ +} + +PYIN::OutputList +PYIN::getOutputDescriptors() const +{ + OutputList outputs; + + OutputDescriptor d; + + int outputNumber = 0; + + d.identifier = "f0candidates"; + d.name = "F0 Candidates"; + d.description = "Estimated fundamental frequency candidates."; + d.unit = "Hz"; + d.hasFixedBinCount = false; + // d.binCount = 1; + d.hasKnownExtents = true; + d.minValue = m_fmin; + d.maxValue = 500; + d.isQuantized = false; + d.sampleType = OutputDescriptor::FixedSampleRate; + d.sampleRate = (m_inputSampleRate / m_stepSize); + d.hasDuration = false; + outputs.push_back(d); + m_oF0Candidates = outputNumber++; + + d.identifier = "f0probs"; + d.name = "Candidate Probabilities"; + d.description = "Probabilities of estimated fundamental frequency candidates."; + d.unit = ""; + d.hasFixedBinCount = false; + // d.binCount = 1; + d.hasKnownExtents = true; + d.minValue = 0; + d.maxValue = 1; + d.isQuantized = false; + d.sampleType = OutputDescriptor::FixedSampleRate; + d.sampleRate = (m_inputSampleRate / m_stepSize); + d.hasDuration = false; + outputs.push_back(d); + m_oF0Probs = outputNumber++; + + d.identifier = "voicedprob"; + d.name = "Voiced Probability"; + d.description = "Probability that the signal is voiced according to Probabilistic Yin."; + d.unit = ""; + d.hasFixedBinCount = true; + d.binCount = 1; + d.hasKnownExtents = true; + d.minValue = 0; + d.maxValue = 1; + d.isQuantized = false; + d.sampleType = OutputDescriptor::FixedSampleRate; + d.sampleRate = (m_inputSampleRate / m_stepSize); + d.hasDuration = false; + outputs.push_back(d); + m_oVoicedProb = outputNumber++; + + d.identifier = "candidatesalience"; + d.name = "Candidate Salience"; + d.description = "Candidate Salience"; + d.hasFixedBinCount = true; + d.binCount = m_blockSize / 2; + d.hasKnownExtents = true; + d.minValue = 0; + d.maxValue = 1; + d.isQuantized = false; + d.sampleType = OutputDescriptor::FixedSampleRate; + d.sampleRate = (m_inputSampleRate / m_stepSize); + d.hasDuration = false; + outputs.push_back(d); + m_oCandidateSalience = outputNumber++; + + d.identifier = "smoothedpitchtrack"; + d.name = "Smoothed Pitch Track"; + d.description = "."; + d.unit = "Hz"; + d.hasFixedBinCount = true; + d.binCount = 1; + d.hasKnownExtents = false; + // d.minValue = 0; + // d.maxValue = 1; + d.isQuantized = false; + d.sampleType = OutputDescriptor::FixedSampleRate; + d.sampleRate = (m_inputSampleRate / m_stepSize); + d.hasDuration = false; + outputs.push_back(d); + m_oSmoothedPitchTrack = outputNumber++; + + d.identifier = "notes"; + d.name = "Notes"; + d.description = "Derived fixed-pitch note frequencies"; + // d.unit = "MIDI unit"; + d.unit = "Hz"; + d.hasFixedBinCount = true; + d.binCount = 1; + d.hasKnownExtents = false; + d.isQuantized = false; + d.sampleType = OutputDescriptor::VariableSampleRate; + d.sampleRate = (m_inputSampleRate / m_stepSize); + d.hasDuration = true; + outputs.push_back(d); + m_oNotes = outputNumber++; + + d.identifier = "notepitchtrack"; + d.name = "Note pitch track."; + d.description = "Estimated fundamental frequencies during notes."; + d.unit = "Hz"; + d.hasFixedBinCount = true; + d.binCount = 1; + d.hasKnownExtents = true; + d.minValue = m_fmin; + d.maxValue = 75; + d.isQuantized = false; + d.sampleType = OutputDescriptor::VariableSampleRate; + // d.sampleRate = (m_inputSampleRate / m_stepSize); + d.hasDuration = false; + outputs.push_back(d); + m_oNotePitchTrack = outputNumber++; + + return outputs; +} + +bool +PYIN::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (channels < getMinChannelCount() || + channels > getMaxChannelCount()) return false; + + std::cerr << "PYIN::initialise: channels = " << channels + << ", stepSize = " << stepSize << ", blockSize = " << blockSize + << std::endl; + + m_channels = channels; + m_stepSize = stepSize; + m_blockSize = blockSize; + + reset(); + + return true; +} + +void +PYIN::reset() +{ + m_yin.setThresholdDistr(m_threshDistr); + m_yin.setFrameSize(m_blockSize); + + m_pitchProb.clear(); + m_timestamp.clear(); + + std::cerr << "PYIN::reset" + << ", blockSize = " << m_blockSize + << std::endl; +} + +PYIN::FeatureSet +PYIN::process(const float *const *inputBuffers, RealTime timestamp) +{ + timestamp = timestamp + Vamp::RealTime::frame2RealTime(m_blockSize/4, lrintf(m_inputSampleRate)); + FeatureSet fs; + + double *dInputBuffers = new double[m_blockSize]; + for (size_t i = 0; i < m_blockSize; ++i) dInputBuffers[i] = inputBuffers[0][i]; + + Yin::YinOutput yo = m_yin.processProbabilisticYin(dInputBuffers); + + Feature f; + f.hasTimestamp = true; + f.timestamp = timestamp; + for (size_t i = 0; i < yo.freqProb.size(); ++i) + { + f.values.push_back(yo.freqProb[i].first); + } + fs[m_oF0Candidates].push_back(f); + + f.values.clear(); + float voicedProb = 0; + for (size_t i = 0; i < yo.freqProb.size(); ++i) + { + f.values.push_back(yo.freqProb[i].second); + voicedProb += yo.freqProb[i].second; + } + fs[m_oF0Probs].push_back(f); + + f.values.clear(); + f.values.push_back(voicedProb); + fs[m_oVoicedProb].push_back(f); + + f.values.clear(); + float salienceSum = 0; + for (size_t iBin = 0; iBin < yo.salience.size(); ++iBin) + { + f.values.push_back(yo.salience[iBin]); + salienceSum += yo.salience[iBin]; + } + fs[m_oCandidateSalience].push_back(f); + + delete [] dInputBuffers; + + vector<pair<double, double> > tempPitchProb; + for (size_t iCandidate = 0; iCandidate < yo.freqProb.size(); ++iCandidate) + { + double tempPitch = 12 * std::log(yo.freqProb[iCandidate].first/440)/std::log(2.) + 69; + tempPitchProb.push_back(pair<double, double> + (tempPitch, yo.freqProb[iCandidate].second)); + } + m_pitchProb.push_back(tempPitchProb); + + m_timestamp.push_back(timestamp); + + return fs; +} + +PYIN::FeatureSet +PYIN::getRemainingFeatures() +{ + FeatureSet fs; + Feature f; + f.hasTimestamp = true; + f.hasDuration = false; + + // MONO-PITCH STUFF + MonoPitch mp; + std::cerr << "before viterbi" << std::endl; + vector<float> mpOut = mp.process(m_pitchProb); + // std::cerr << "after viterbi " << mpOut.size() << " "<< m_timestamp.size() << std::endl; + for (size_t iFrame = 0; iFrame < mpOut.size(); ++iFrame) + { + if (mpOut[iFrame] < 0 && (m_outputUnvoiced==0)) continue; + f.timestamp = m_timestamp[iFrame]; + f.values.clear(); + if (m_outputUnvoiced == 1) + { + f.values.push_back(abs(mpOut[iFrame])); + } else { + f.values.push_back(mpOut[iFrame]); + } + + fs[m_oSmoothedPitchTrack].push_back(f); + } + + // // MONO-NOTE STUFF + // MonoNote mn; + // + // vector<MonoNote::FrameOutput> mnOut = mn.process(m_pitchProb); + // + // f.hasTimestamp = true; + // f.hasDuration = true; + // f.values.clear(); + // f.values.push_back(0); + // + // Feature fNoteFreqTrack; + // fNoteFreqTrack.hasTimestamp = true; + // fNoteFreqTrack.hasDuration = false; + // + // + // int oldState = -1; + // int onsetFrame = 0; + // int framesInNote = 0; + // double pitchSum = 0; + // + // for (size_t iFrame = 0; iFrame < mnOut.size(); ++iFrame) + // { + // if (std::pow(2,(mnOut[onsetFrame].pitch - 69) / 12) * 440 >= m_fmin && mnOut[iFrame].noteState != 2 && oldState == 2) + // { + // // greedy pitch track + // vector<double> notePitchTrack; + // for (size_t i = onsetFrame; i <= iFrame; ++i) + // { + // fNoteFreqTrack.timestamp = m_timestamp[i]; + // + // bool hasPitch = 0; + // double bestProb = 0; + // double bestPitch = 0; + // + // for (int iCandidate = (int(m_pitchProb[i].size())) - 1; iCandidate != -1; --iCandidate) + // { + // // std::cerr << "writing :( " << std::endl; + // double tempPitch = m_pitchProb[i][iCandidate].first; + // if (std::abs(tempPitch-mnOut[onsetFrame].pitch) < 5 && m_pitchProb[i][iCandidate].second > bestProb) + // { + // bestProb = m_pitchProb[i][iCandidate].second; + // bestPitch = m_pitchProb[i][iCandidate].first; + // hasPitch = 1; + // } + // } + // if (hasPitch) { + // double tempFreq = std::pow(2,(bestPitch - 69) / 12) * 440; + // notePitchTrack.push_back(bestPitch); + // fNoteFreqTrack.values.clear(); + // fNoteFreqTrack.values.push_back(tempFreq); // convert back to Hz (Vamp hosts prefer that) + // fs[m_oNotePitchTrack].push_back(fNoteFreqTrack); + // } + // } + // + // // closing old note + // f.duration = m_timestamp[iFrame]-m_timestamp[onsetFrame]; + // double tempPitch = mnOut[onsetFrame].pitch; + // size_t notePitchTrackSize = notePitchTrack.size(); + // if (notePitchTrackSize > 2) { + // std::sort(notePitchTrack.begin(), notePitchTrack.end()); + // tempPitch = notePitchTrack[notePitchTrackSize/2]; // median + // } + // f.values[0] = std::pow(2,(tempPitch - 69) / 12) * 440; // convert back to Hz (Vamp hosts prefer that) + // fs[m_oNotes].push_back(f); + // + // + // } + // + // if (mnOut[iFrame].noteState == 1 && oldState != 1) + // { + // // open note + // onsetFrame = iFrame; + // f.timestamp = m_timestamp[iFrame]; + // pitchSum = 0; + // framesInNote = 0; + // } + // + // oldState = mnOut[iFrame].noteState; + // } + + + return fs; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PYIN.h Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,72 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + This file is Copyright (c) 2012 Matthias Mauch + +*/ + +#ifndef _PYIN_H_ +#define _PYIN_H_ + +#include <vamp-sdk/Plugin.h> + +#include "Yin.h" + +class PYIN : public Vamp::Plugin +{ +public: + PYIN(float inputSampleRate); + virtual ~PYIN(); + + 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; + Yin m_yin; + + mutable int m_oF0Candidates; + mutable int m_oF0Probs; + mutable int m_oVoicedProb; + mutable int m_oCandidateSalience; + mutable int m_oSmoothedPitchTrack; + mutable int m_oNotes; + mutable int m_oNotePitchTrack; + mutable float m_threshDistr; + mutable float m_outputUnvoiced; + mutable vector<vector<pair<double, double> > > m_pitchProb; + mutable vector<Vamp::RealTime> m_timestamp; +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SparseHMM.cpp Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,128 @@ +#include "SparseHMM.h" +#include <vector> +#include <cstdio> +#include <iostream> + +using std::vector; +using std::pair; + +const vector<double> +SparseHMM::calculateObsProb(const vector<pair<double, double> > data) +{ + // dummy (virtual?) implementation to be overloaded + return(vector<double>()); +} + +const std::vector<int> +SparseHMM::decodeViterbi(std::vector<vector<double> > obsProb, + vector<double> *scale) +{ + size_t nState = init.size(); + size_t nFrame = obsProb.size(); + + // check for consistency + size_t nTrans = transProb.size(); + + // declaring variables + std::vector<double> delta = std::vector<double>(nState); + std::vector<double> oldDelta = std::vector<double>(nState); + vector<vector<int> > psi; // "matrix" of remembered indices of the best transitions + vector<int> path = vector<int>(nFrame, nState-1); // the final output path (current assignment arbitrary, makes sense only for Chordino, where nChord-1 is the "no chord" label) + + double deltasum = 0; + + // initialise first frame + for (size_t iState = 0; iState < nState; ++iState) + { + oldDelta[iState] = init[iState] * obsProb[0][iState]; + // std::cerr << iState << " ----- " << init[iState] << std::endl; + deltasum += oldDelta[iState]; + } + + for (size_t iState = 0; iState < nState; ++iState) + { + oldDelta[iState] /= deltasum; // normalise (scale) + // std::cerr << oldDelta[iState] << std::endl; + } + + scale->push_back(1.0/deltasum); + psi.push_back(vector<int>(nState,0)); + + // rest of forward step + for (size_t iFrame = 1; iFrame < nFrame; ++iFrame) + { + deltasum = 0; + psi.push_back(vector<int>(nState,0)); + + // calculate best previous state for every current state + size_t fromState; + size_t toState; + double currentTransProb; + double currentValue; + + // this is the "sparse" loop + for (size_t iTrans = 0; iTrans < nTrans; ++iTrans) + { + fromState = from[iTrans]; + toState = to[iTrans]; + currentTransProb = transProb[iTrans]; + + currentValue = oldDelta[fromState] * currentTransProb; + if (currentValue > delta[toState]) + { + delta[toState] = currentValue; // will be multiplied by the right obs later! + psi[iFrame][toState] = fromState; + } + } + + for (size_t jState = 0; jState < nState; ++jState) + { + delta[jState] *= obsProb[iFrame][jState]; + deltasum += delta[jState]; + } + + if (deltasum > 0) + { + for (size_t iState = 0; iState < nState; ++iState) + { + oldDelta[iState] = delta[iState] / deltasum; // normalise (scale) + delta[iState] = 0; + } + scale->push_back(1.0/deltasum); + } else + { + std::cerr << "WARNING: Viterbi has been fed some zero probabilities, at least they become zero in combination with the model." << std::endl; + for (size_t iState = 0; iState < nState; ++iState) + { + oldDelta[iState] = 1.0/nState; + delta[iState] = 0; + } + scale->push_back(1.0); + } + } + + // initialise backward step + double bestValue = 0; + for (size_t iState = 0; iState < nState; ++iState) + { + double currentValue = oldDelta[iState]; + if (currentValue > bestValue) + { + bestValue = currentValue; + path[nFrame-1] = iState; + } + } + + // rest of backward step + for (int iFrame = nFrame-2; iFrame != -1; --iFrame) + { + path[iFrame] = psi[iFrame+1][path[iFrame+1]]; + } + + for (size_t iState = 0; iState < nState; ++iState) + { + // std::cerr << psi[2][iState] << std::endl; + } + + return path; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SparseHMM.h Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,28 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + This file is Copyright (c) 2012 Matthias Mauch + +*/ + +#ifndef _SPARSEHMM_H_ +#define _SPARSEHMM_H_ + +#include <vector> +#include <cstdio> + +using std::vector; +using std::pair; + +class SparseHMM +{ +public: + virtual const std::vector<double> calculateObsProb(const vector<pair<double, double> >); + const std::vector<int> decodeViterbi(std::vector<vector<double> > obs, + vector<double> *scale); + vector<double> init; + vector<size_t> from; + vector<size_t> to; + vector<double> transProb; +}; + +#endif \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VampYin.cpp Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,366 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +#include "VampYin.h" +#include "MonoNote.h" + +#include "vamp-sdk/FFT.h" + +#include <vector> +#include <algorithm> + +#include <cstdio> +#include <cmath> +#include <complex> + +using std::string; +using std::vector; +using Vamp::RealTime; + + +VampYin::VampYin(float inputSampleRate) : + Plugin(inputSampleRate), + m_channels(0), + m_stepSize(256), + m_blockSize(2048), + m_fmin(40), + m_fmax(1000), + m_yin(2048, inputSampleRate, 0.0), + m_outNoF0(0), + m_outNoPeriodicity(0), + m_outNoRms(0), + m_outNoSalience(0), + m_yinParameter(0.15f), + m_outputUnvoiced(0.0f) +{ +} + +VampYin::~VampYin() +{ +} + +string +VampYin::getIdentifier() const +{ + return "yin"; +} + +string +VampYin::getName() const +{ + return "Yin"; +} + +string +VampYin::getDescription() const +{ + return "A vamp implementation of the Yin algorithm for monophonic frequency estimation."; +} + +string +VampYin::getMaker() const +{ + return "Matthias Mauch"; +} + +int +VampYin::getPluginVersion() const +{ + // Increment this each time you release a version that behaves + // differently from the previous one + return 1; +} + +string +VampYin::getCopyright() const +{ + return "GPL"; +} + +VampYin::InputDomain +VampYin::getInputDomain() const +{ + return TimeDomain; +} + +size_t +VampYin::getPreferredBlockSize() const +{ + return 2048; +} + +size_t +VampYin::getPreferredStepSize() const +{ + return 256; +} + +size_t +VampYin::getMinChannelCount() const +{ + return 1; +} + +size_t +VampYin::getMaxChannelCount() const +{ + return 1; +} + +VampYin::ParameterList +VampYin::getParameterDescriptors() const +{ + ParameterList list; + + ParameterDescriptor d; + d.identifier = "yinThreshold"; + d.name = "Yin threshold"; + d.description = "The greedy Yin search for a low value difference function is done once a dip lower than this threshold is reached."; + d.unit = ""; + d.minValue = 0.025f; + d.maxValue = 1.0f; + d.defaultValue = 0.15f; + d.isQuantized = true; + d.quantizeStep = 0.025f; + + list.push_back(d); + + // d.identifier = "removeunvoiced"; + // d.name = "Remove pitches classified as unvoiced."; + // d.description = "If ticked, then the pitch estimator will return the most likely pitch, even if it 'thinks' there isn't any."; + // d.unit = ""; + // d.minValue = 0.0f; + // d.maxValue = 1.0f; + // d.defaultValue = 0.0f; + // d.isQuantized = true; + // d.quantizeStep = 1.0f; + // d.valueNames.clear(); + // list.push_back(d); + + d.identifier = "outputunvoiced"; + d.valueNames.clear(); + d.name = "Output estimates classified as unvoiced?"; + d.description = "."; + d.unit = ""; + d.minValue = 0.0f; + d.maxValue = 2.0f; + d.defaultValue = 2.0f; + d.isQuantized = true; + d.quantizeStep = 1.0f; + d.valueNames.push_back("No"); + d.valueNames.push_back("Yes"); + d.valueNames.push_back("Yes, as negative frequencies"); + list.push_back(d); + + return list; +} + +float +VampYin::getParameter(string identifier) const +{ + if (identifier == "yinThreshold") { + return m_yinParameter; + } + if (identifier == "outputunvoiced") { + return m_outputUnvoiced; + } + return 0.f; +} + +void +VampYin::setParameter(string identifier, float value) +{ + if (identifier == "yinThreshold") + { + m_yinParameter = value; + } + if (identifier == "outputunvoiced") + { + m_outputUnvoiced = value; + } +} + +VampYin::ProgramList +VampYin::getPrograms() const +{ + ProgramList list; + return list; +} + +string +VampYin::getCurrentProgram() const +{ + return ""; // no programs +} + +void +VampYin::selectProgram(string name) +{ +} + +VampYin::OutputList +VampYin::getOutputDescriptors() const +{ + OutputList outputs; + + OutputDescriptor d; + + int outputNumber = 0; + + 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 = 500; + d.isQuantized = false; + d.sampleType = OutputDescriptor::FixedSampleRate; + d.sampleRate = (m_inputSampleRate / m_stepSize); + d.hasDuration = false; + outputs.push_back(d); + m_outNoF0 = outputNumber++; + + d.identifier = "periodicity"; + d.name = "Periodicity"; + d.description = "by-product of Yin f0 estimation"; + d.unit = ""; + d.hasFixedBinCount = true; + d.binCount = 1; + d.hasKnownExtents = true; + d.minValue = 0; + d.maxValue = 1; + d.isQuantized = false; + d.sampleType = OutputDescriptor::FixedSampleRate; + d.sampleRate = (m_inputSampleRate / m_stepSize); + d.hasDuration = false; + outputs.push_back(d); + m_outNoPeriodicity = outputNumber++; + + d.identifier = "rms"; + d.name = "root mean square"; + d.description = "Root mean square of the waveform."; + d.unit = ""; + d.hasFixedBinCount = true; + d.binCount = 1; + d.hasKnownExtents = true; + d.minValue = 0; + d.maxValue = 1; + d.isQuantized = false; + d.sampleType = OutputDescriptor::FixedSampleRate; + d.sampleRate = (m_inputSampleRate / m_stepSize); + d.hasDuration = false; + outputs.push_back(d); + m_outNoRms = outputNumber++; + + d.identifier = "salience"; + d.name = "Salience"; + d.description = "Yin Salience"; + d.hasFixedBinCount = true; + d.binCount = m_blockSize / 2; + d.hasKnownExtents = true; + d.minValue = 0; + d.maxValue = 1; + d.isQuantized = false; + d.sampleType = OutputDescriptor::FixedSampleRate; + d.sampleRate = (m_inputSampleRate / m_stepSize); + d.hasDuration = false; + outputs.push_back(d); + m_outNoSalience = outputNumber++; + + return outputs; +} + +bool +VampYin::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (channels < getMinChannelCount() || + channels > getMaxChannelCount()) return false; + + std::cerr << "VampYin::initialise: channels = " << channels + << ", stepSize = " << stepSize << ", blockSize = " << blockSize + << std::endl; + + m_channels = channels; + m_stepSize = stepSize; + m_blockSize = blockSize; + + reset(); + + return true; +} + +void +VampYin::reset() +{ + m_yin.setThreshold(m_yinParameter); + m_yin.setFrameSize(m_blockSize); + + std::cerr << "VampYin::reset: yin threshold set to " << (m_yinParameter) + << ", blockSize = " << m_blockSize + << std::endl; +} + +VampYin::FeatureSet +VampYin::process(const float *const *inputBuffers, RealTime timestamp) +{ + timestamp = timestamp + Vamp::RealTime::frame2RealTime(m_blockSize/4, lrintf(m_inputSampleRate)); + FeatureSet fs; + + double *dInputBuffers = new double[m_blockSize]; + for (size_t i = 0; i < m_blockSize; ++i) dInputBuffers[i] = inputBuffers[0][i]; + + Yin::YinOutput yo = m_yin.process(dInputBuffers); + // std::cerr << "f0 in VampYin: " << yo.f0 << std::endl; + Feature f; + f.hasTimestamp = true; + f.timestamp = timestamp; + if (m_outputUnvoiced == 0.0f) + { + // std::cerr << "f0 in VampYin: " << yo.f0 << std::endl; + if (yo.f0 > 0 && yo.f0 < m_fmax && yo.f0 > m_fmin) { + f.values.push_back(yo.f0); + fs[m_outNoF0].push_back(f); + } + } else if (m_outputUnvoiced == 1.0f) + { + if (abs(yo.f0) < m_fmax && abs(yo.f0) > m_fmin) { + f.values.push_back(abs(yo.f0)); + fs[m_outNoF0].push_back(f); + } + } else + { + if (abs(yo.f0) < m_fmax && abs(yo.f0) > m_fmin) { + f.values.push_back(yo.f0); + fs[m_outNoF0].push_back(f); + } + } + + f.values.clear(); + f.values.push_back(yo.rms); + fs[m_outNoRms].push_back(f); + + f.values.clear(); + for (size_t iBin = 0; iBin < yo.salience.size(); ++iBin) + { + f.values.push_back(yo.salience[iBin]); + } + fs[m_outNoSalience].push_back(f); + + f.values.clear(); + // f.values[0] = yo.periodicity; + f.values.push_back(yo.periodicity); + fs[m_outNoPeriodicity].push_back(f); + + delete [] dInputBuffers; + + return fs; +} + +VampYin::FeatureSet +VampYin::getRemainingFeatures() +{ + FeatureSet fs; + return fs; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VampYin.h Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,67 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + This file is Copyright (c) 2012 Matthias Mauch + +*/ + +#ifndef _VAMPYIN_H_ +#define _VAMPYIN_H_ + +#include <vamp-sdk/Plugin.h> + +#include "Yin.h" + +class VampYin : public Vamp::Plugin +{ +public: + VampYin(float inputSampleRate); + virtual ~VampYin(); + + 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; + Yin m_yin; + + mutable int m_outNoF0; + mutable int m_outNoPeriodicity; + mutable int m_outNoRms; + mutable int m_outNoSalience; + mutable float m_yinParameter; + mutable float m_outputUnvoiced; +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Yin.cpp Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,135 @@ +#include "Yin.h" + +#include "vamp-sdk/FFT.h" +#include "MeanFilter.h" +#include "YinUtil.h" + +#include <vector> +#include <cstdlib> +#include <cstdio> +#include <cmath> +#include <complex> + +using std::vector; + +Yin::Yin(size_t frameSize, size_t inputSampleRate, double thresh) : + m_frameSize(frameSize), + m_inputSampleRate(inputSampleRate), + m_thresh(thresh), + m_threshDistr(2), + m_yinBufferSize(frameSize/2) +{ + if (frameSize & (frameSize-1)) { + throw "N must be a power of two"; + } +} + +Yin::~Yin() +{ +} + +Yin::YinOutput +Yin::process(const double *in) const { + + double* yinBuffer = new double[m_yinBufferSize]; + + // calculate aperiodicity function for all periods + YinUtil::fastDifference(in, yinBuffer, m_yinBufferSize); + YinUtil::cumulativeDifference(yinBuffer, m_yinBufferSize); + + int tau = 0; + tau = YinUtil::absoluteThreshold(yinBuffer, m_yinBufferSize, m_thresh); + + double interpolatedTau; + double aperiodicity; + double f0; + + if (tau!=0) + { + interpolatedTau = YinUtil::parabolicInterpolation(yinBuffer, abs(tau), m_yinBufferSize); + f0 = m_inputSampleRate * (1.0 / interpolatedTau); + } else { + interpolatedTau = 0; + f0 = 0; + } + double rms = std::sqrt(YinUtil::sumSquare(in, 0, m_yinBufferSize)/m_yinBufferSize); + aperiodicity = yinBuffer[abs(tau)]; + // std::cerr << aperiodicity << std::endl; + if (tau < 0) f0 = -f0; + + Yin::YinOutput yo(f0, 1-aperiodicity, rms); + for (size_t iBuf = 0; iBuf < m_yinBufferSize; ++iBuf) + { + yo.salience.push_back(yinBuffer[iBuf] < 1 ? 1-yinBuffer[iBuf] : 0); // why are the values sometimes < 0 if I don't check? + } + + delete [] yinBuffer; + return yo; +} + +Yin::YinOutput +Yin::processProbabilisticYin(const double *in) const { + + double* yinBuffer = new double[m_yinBufferSize]; + + // calculate aperiodicity function for all periods + YinUtil::fastDifference(in, yinBuffer, m_yinBufferSize); + YinUtil::cumulativeDifference(yinBuffer, m_yinBufferSize); + + vector<double> peakProbability = YinUtil::yinProb(yinBuffer, m_threshDistr, m_yinBufferSize); + + // calculate overall "probability" from peak probability + double probSum = 0; + for (size_t iBin = 0; iBin < m_yinBufferSize; ++iBin) + { + probSum += peakProbability[iBin]; + } + + Yin::YinOutput yo(0,0,0); + for (size_t iBuf = 0; iBuf < m_yinBufferSize; ++iBuf) + { + yo.salience.push_back(peakProbability[iBuf]); + if (peakProbability[iBuf] > 0) + { + double currentF0 = + m_inputSampleRate * (1.0 / + YinUtil::parabolicInterpolation(yinBuffer, iBuf, m_yinBufferSize)); + yo.freqProb.push_back(pair<double, double>(currentF0, peakProbability[iBuf])); + } + } + + // std::cerr << yo.freqProb.size() << std::endl; + + delete [] yinBuffer; + return yo; +} + + +int +Yin::setThreshold(double parameter) +{ + m_thresh = static_cast<float>(parameter); + return 0; +} + +int +Yin::setThresholdDistr(float parameter) +{ + m_threshDistr = static_cast<size_t>(parameter); + return 0; +} + +int +Yin::setFrameSize(size_t parameter) +{ + m_frameSize = parameter; + m_yinBufferSize = m_frameSize/2; + return 0; +} + +// int +// Yin::setRemoveUnvoiced(bool parameter) +// { +// m_removeUnvoiced = parameter; +// return 0; +// }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Yin.h Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,80 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + This file is Copyright (c) 2012 Chris Cannam + + 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 _YIN_H_ +#define _YIN_H_ + +#include "vamp-sdk/FFT.h" +#include "MeanFilter.h" + +#include <cmath> + +#include <iostream> +#include <vector> +#include <exception> + +using std::vector; +using std::pair; + + + +class Yin +{ +public: + Yin(size_t frameSize, size_t inputSampleRate, double thresh = 0.2); + virtual ~Yin(); + + struct YinOutput { + double f0; + double periodicity; + double rms; + vector<double> salience; + vector<pair<double, double> > freqProb; + YinOutput() : f0(0), periodicity(0), rms(0), + salience(vector<double>(0)), freqProb(vector<pair<double, double> >(0)) { } + YinOutput(double _f, double _p, double _r) : + f0(_f), periodicity(_p), rms(_r), + salience(vector<double>(0)), freqProb(vector<pair<double, double> >(0)) { } + YinOutput(double _f, double _p, double _r, vector<double> _salience) : + f0(_f), periodicity(_p), rms(_r), salience(_salience), + freqProb(vector<pair<double, double> >(0)) { } + }; + + int setThreshold(double parameter); + int setThresholdDistr(float parameter); + int setFrameSize(size_t frameSize); + // int setRemoveUnvoiced(bool frameSize); + YinOutput process(const double *in) const; + YinOutput processProbabilisticYin(const double *in) const; + +private: + mutable size_t m_frameSize; + mutable size_t m_inputSampleRate; + mutable double m_thresh; + mutable size_t m_threshDistr; + mutable size_t m_yinBufferSize; + // mutable bool m_removeUnvoiced; +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/YinUtil.cpp Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,343 @@ +#include "YinUtil.h" + +#include <vector> + +#include <cstdio> +#include <cmath> +#include <algorithm> + +#include <boost/math/distributions.hpp> + +void +YinUtil::fastDifference(const double *in, double *yinBuffer, const size_t yinBufferSize) +{ + + // DECLARE AND INITIALISE + // initialisation of most of the arrays here was done in a separate function, + // with all the arrays as members of the class... moved them back here. + + size_t frameSize = 2 * yinBufferSize; + + for (size_t j = 0; j < yinBufferSize; ++j) + { + yinBuffer[j] = 0.; + } + + double *audioTransformedReal = new double[frameSize]; + double *audioTransformedImag = new double[frameSize]; + double *nullImag = new double[frameSize]; + double *kernel = new double[frameSize]; + double *kernelTransformedReal = new double[frameSize]; + double *kernelTransformedImag = new double[frameSize]; + double *yinStyleACFReal = new double[frameSize]; + double *yinStyleACFImag = new double[frameSize]; + double *powerTerms = new double[yinBufferSize]; + + for (size_t j = 0; j < yinBufferSize; ++j) + { + powerTerms[j] = 0.; + } + + for (size_t j = 0; j < frameSize; ++j) + { + nullImag[j] = 0.; + audioTransformedReal[j] = 0.; + audioTransformedImag[j] = 0.; + kernel[j] = 0.; + kernelTransformedReal[j] = 0.; + kernelTransformedImag[j] = 0.; + yinStyleACFReal[j] = 0.; + yinStyleACFImag[j] = 0.; + } + + // POWER TERM CALCULATION + // ... for the power terms in equation (7) in the Yin paper + powerTerms[0] = 0.0; + for (size_t j = 0; j < yinBufferSize; ++j) { + powerTerms[0] += in[j] * in[j]; + } + + // now iteratively calculate all others (saves a few multiplications) + for (size_t tau = 1; tau < yinBufferSize; ++tau) { + powerTerms[tau] = powerTerms[tau-1] - in[tau-1] * in[tau-1] + in[tau+yinBufferSize] * in[tau+yinBufferSize]; + } + + // YIN-STYLE AUTOCORRELATION via FFT + // 1. data + Vamp::FFT::forward(frameSize, in, nullImag, audioTransformedReal, audioTransformedImag); + + // 2. half of the data, disguised as a convolution kernel + for (size_t j = 0; j < yinBufferSize; ++j) { + kernel[j] = in[yinBufferSize-1-j]; + kernel[j+yinBufferSize] = 0; + } + Vamp::FFT::forward(frameSize, kernel, nullImag, kernelTransformedReal, kernelTransformedImag); + + // 3. convolution via complex multiplication -- written into + for (size_t j = 0; j < frameSize; ++j) { + yinStyleACFReal[j] = audioTransformedReal[j]*kernelTransformedReal[j] - audioTransformedImag[j]*kernelTransformedImag[j]; // real + yinStyleACFImag[j] = audioTransformedReal[j]*kernelTransformedImag[j] + audioTransformedImag[j]*kernelTransformedReal[j]; // imaginary + } + Vamp::FFT::inverse(frameSize, yinStyleACFReal, yinStyleACFImag, audioTransformedReal, audioTransformedImag); + + // CALCULATION OF difference function + // ... according to (7) in the Yin paper. + for (size_t j = 0; j < yinBufferSize; ++j) { + // taking only the real part + yinBuffer[j] = powerTerms[0] + powerTerms[j] - 2 * audioTransformedReal[j+yinBufferSize-1]; + } + delete [] audioTransformedReal; + delete [] audioTransformedImag; + delete [] nullImag; + delete [] kernel; + delete [] kernelTransformedReal; + delete [] kernelTransformedImag; + delete [] yinStyleACFReal; + delete [] yinStyleACFImag; + delete [] powerTerms; +} + +void +YinUtil::cumulativeDifference(double *yinBuffer, const size_t yinBufferSize) +{ + size_t tau; + + yinBuffer[0] = 1; + + double runningSum = 0; + + for (tau = 1; tau < yinBufferSize; ++tau) { + runningSum += yinBuffer[tau]; + if (runningSum == 0) + { + yinBuffer[tau] = 1; + } else { + yinBuffer[tau] *= tau / runningSum; + } + } +} + +int +YinUtil::absoluteThreshold(const double *yinBuffer, const size_t yinBufferSize, const double thresh) +{ + size_t tau; + size_t minTau = 0; + double minVal = 1000.; + + // using Joren Six's "loop construct" from TarsosDSP + tau = 2; + while (tau < yinBufferSize) + { + if (yinBuffer[tau] < thresh) + { + while (tau+1 < yinBufferSize && yinBuffer[tau+1] < yinBuffer[tau]) + { + ++tau; + } + return tau; + } else { + if (yinBuffer[tau] < minVal) + { + minVal = yinBuffer[tau]; + minTau = tau; + } + } + ++tau; + } + if (minTau > 0) + { + return -minTau; + } + return 0; +} + + +std::vector<double> +YinUtil::yinProb(const double *yinBuffer, const size_t prior, const size_t yinBufferSize) +{ + double minWeight = 0.01; + size_t tau; + std::vector<float> thresholds; + std::vector<float> distribution; + std::vector<double> peakProb = std::vector<double>(yinBufferSize); + float uniformDist[100] = {0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000,0.0100000}; + float betaDist1[100] = {0.028911,0.048656,0.061306,0.068539,0.071703,0.071877,0.069915,0.066489,0.062117,0.057199,0.052034,0.046844,0.041786,0.036971,0.032470,0.028323,0.024549,0.021153,0.018124,0.015446,0.013096,0.011048,0.009275,0.007750,0.006445,0.005336,0.004397,0.003606,0.002945,0.002394,0.001937,0.001560,0.001250,0.000998,0.000792,0.000626,0.000492,0.000385,0.000300,0.000232,0.000179,0.000137,0.000104,0.000079,0.000060,0.000045,0.000033,0.000024,0.000018,0.000013,0.000009,0.000007,0.000005,0.000003,0.000002,0.000002,0.000001,0.000001,0.000001,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000}; + float betaDist2[100] = {0.012614,0.022715,0.030646,0.036712,0.041184,0.044301,0.046277,0.047298,0.047528,0.047110,0.046171,0.044817,0.043144,0.041231,0.039147,0.036950,0.034690,0.032406,0.030133,0.027898,0.025722,0.023624,0.021614,0.019704,0.017900,0.016205,0.014621,0.013148,0.011785,0.010530,0.009377,0.008324,0.007366,0.006497,0.005712,0.005005,0.004372,0.003806,0.003302,0.002855,0.002460,0.002112,0.001806,0.001539,0.001307,0.001105,0.000931,0.000781,0.000652,0.000542,0.000449,0.000370,0.000303,0.000247,0.000201,0.000162,0.000130,0.000104,0.000082,0.000065,0.000051,0.000039,0.000030,0.000023,0.000018,0.000013,0.000010,0.000007,0.000005,0.000004,0.000003,0.000002,0.000001,0.000001,0.000001,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000}; + float betaDist3[100] = {0.006715,0.012509,0.017463,0.021655,0.025155,0.028031,0.030344,0.032151,0.033506,0.034458,0.035052,0.035331,0.035332,0.035092,0.034643,0.034015,0.033234,0.032327,0.031314,0.030217,0.029054,0.027841,0.026592,0.025322,0.024042,0.022761,0.021489,0.020234,0.019002,0.017799,0.016630,0.015499,0.014409,0.013362,0.012361,0.011407,0.010500,0.009641,0.008830,0.008067,0.007351,0.006681,0.006056,0.005475,0.004936,0.004437,0.003978,0.003555,0.003168,0.002814,0.002492,0.002199,0.001934,0.001695,0.001481,0.001288,0.001116,0.000963,0.000828,0.000708,0.000603,0.000511,0.000431,0.000361,0.000301,0.000250,0.000206,0.000168,0.000137,0.000110,0.000088,0.000070,0.000055,0.000043,0.000033,0.000025,0.000019,0.000014,0.000010,0.000007,0.000005,0.000004,0.000002,0.000002,0.000001,0.000001,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000}; + float betaDist4[100] = {0.003996,0.007596,0.010824,0.013703,0.016255,0.018501,0.020460,0.022153,0.023597,0.024809,0.025807,0.026607,0.027223,0.027671,0.027963,0.028114,0.028135,0.028038,0.027834,0.027535,0.027149,0.026687,0.026157,0.025567,0.024926,0.024240,0.023517,0.022763,0.021983,0.021184,0.020371,0.019548,0.018719,0.017890,0.017062,0.016241,0.015428,0.014627,0.013839,0.013068,0.012315,0.011582,0.010870,0.010181,0.009515,0.008874,0.008258,0.007668,0.007103,0.006565,0.006053,0.005567,0.005107,0.004673,0.004264,0.003880,0.003521,0.003185,0.002872,0.002581,0.002312,0.002064,0.001835,0.001626,0.001434,0.001260,0.001102,0.000959,0.000830,0.000715,0.000612,0.000521,0.000440,0.000369,0.000308,0.000254,0.000208,0.000169,0.000136,0.000108,0.000084,0.000065,0.000050,0.000037,0.000027,0.000019,0.000014,0.000009,0.000006,0.000004,0.000002,0.000001,0.000001,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000}; + // float betaDist4[100] = {0.0000119,0.0000470,0.0001048,0.0001843,0.0002850,0.0004061,0.0005469,0.0007066,0.0008846,0.0010801,0.0012924,0.0015208,0.0017645,0.0020229,0.0022952,0.0025807,0.0028787,0.0031885,0.0035093,0.0038404,0.0041811,0.0045307,0.0048884,0.0052536,0.0056256,0.0060035,0.0063867,0.0067744,0.0071660,0.0075608,0.0079579,0.0083567,0.0087564,0.0091564,0.0095560,0.0099543,0.0103507,0.0107444,0.0111348,0.0115212,0.0119027,0.0122787,0.0126484,0.0130112,0.0133663,0.0137131,0.0140506,0.0143784,0.0146956,0.0150015,0.0152954,0.0155766,0.0158443,0.0160979,0.0163366,0.0165597,0.0167665,0.0169563,0.0171282,0.0172817,0.0174160,0.0175304,0.0176241,0.0176965,0.0177468,0.0177743,0.0177782,0.0177579,0.0177127,0.0176418,0.0175444,0.0174200,0.0172677,0.0170868,0.0168767,0.0166365,0.0163657,0.0160634,0.0157289,0.0153615,0.0149606,0.0145253,0.0140550,0.0135489,0.0130063,0.0124265,0.0118088,0.0111525,0.0104568,0.0097210,0.0089444,0.0081263,0.0072659,0.0063626,0.0054155,0.0044241,0.0033876,0.0023052,0.0011762,0.0000000}; + float single10[100] = {0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,1.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000}; + float single15[100] = {0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,1.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000}; + float single20[100] = {0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,1.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000}; + + // float betaDist4[100] = {0.00001,0.00005,0.00010,0.00018,0.00029,0.00041,0.00055,0.00071,0.00088,0.00108,0.00129,0.00152,0.00176,0.00202,0.00230,0.00258,0.00288,0.00319,0.00351,0.00384,0.00418,0.00453,0.00489,0.00525,0.00563,0.00600,0.00639,0.00677,0.00717,0.00756,0.00796,0.00836,0.00876,0.00916,0.00956,0.00995,0.01035,0.01074,0.01113,0.01152,0.01190,0.01228,0.01265,0.01301,0.01337,0.01371,0.01405,0.01438,0.01470,0.01500,0.01530,0.01558,0.01584,0.01610,0.01634,0.01656,0.01677,0.01696,0.01713,0.01728,0.01742,0.01753,0.01762,0.01770,0.01775,0.01777,0.01778,0.01776,0.01771,0.01764,0.01754,0.01742,0.01727,0.01709,0.01688,0.01664,0.01637,0.01606,0.01573,0.01536,0.01496,0.01453,0.01405,0.01355,0.01301,0.01243,0.01181,0.01115,0.01046,0.00972,0.00894,0.00813,0.00727,0.00636,0.00542,0.00442,0.00339,0.00231,0.00118,0.00000}; + // float uniformDist[40] = {0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500,0.02500}; + // float betaDist1[40] = {0.00632,0.02052,0.03731,0.05327,0.06644,0.07587,0.08133,0.08305,0.08153,0.07743,0.07144,0.06421,0.05633,0.04831,0.04052,0.03326,0.02671,0.02098,0.01611,0.01209,0.00884,0.00629,0.00436,0.00292,0.00189,0.00118,0.00070,0.00040,0.00021,0.00011,0.00005,0.00002,0.00001,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000}; + // float betaDist2[40] = {0.00148,0.00535,0.01081,0.01722,0.02404,0.03083,0.03724,0.04301,0.04794,0.05191,0.05485,0.05672,0.05756,0.05740,0.05633,0.05443,0.05183,0.04864,0.04499,0.04102,0.03683,0.03256,0.02832,0.02419,0.02028,0.01664,0.01334,0.01042,0.00789,0.00577,0.00404,0.00269,0.00168,0.00096,0.00049,0.00021,0.00007,0.00001,0.00000,0.00000}; + // float betaDist3[40] = {0.00045,0.00169,0.00361,0.00608,0.00897,0.01219,0.01563,0.01920,0.02280,0.02637,0.02981,0.03308,0.03609,0.03882,0.04120,0.04320,0.04479,0.04594,0.04664,0.04688,0.04664,0.04594,0.04479,0.04320,0.04120,0.03882,0.03609,0.03308,0.02981,0.02637,0.02280,0.01920,0.01563,0.01219,0.00897,0.00608,0.00361,0.00169,0.00045,0.00000}; + // float betaDist4[40] = {0.00018,0.00071,0.00156,0.00270,0.00410,0.00574,0.00758,0.00961,0.01178,0.01407,0.01646,0.01891,0.02140,0.02390,0.02638,0.02882,0.03118,0.03343,0.03556,0.03752,0.03930,0.04086,0.04218,0.04323,0.04397,0.04439,0.04445,0.04413,0.04339,0.04221,0.04057,0.03842,0.03576,0.03253,0.02873,0.02432,0.01926,0.01355,0.00713,0.00000}; + // float single10[40] = {0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,1.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000}; + // float single15[40] = {0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,1.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000}; + // float single20[40] = {0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,1.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000,0.00000}; + + size_t nThreshold = 100; + int nThresholdInt = nThreshold; + + for (int i = 0; i < nThresholdInt; ++i) + { + switch (prior) { + case 0: + distribution.push_back(uniformDist[i]); + break; + case 1: + distribution.push_back(betaDist1[i]); + break; + case 2: + distribution.push_back(betaDist2[i]); + break; + case 3: + distribution.push_back(betaDist3[i]); + break; + case 4: + distribution.push_back(betaDist4[i]); + break; + case 5: + distribution.push_back(single10[i]); + break; + case 6: + distribution.push_back(single15[i]); + break; + case 7: + distribution.push_back(single20[i]); + break; + default: + distribution.push_back(uniformDist[i]); + } + thresholds.push_back(0.01 + i*0.01); + } + + // double minYin = 2936; + // for (size_t i = 2; i < yinBufferSize; ++i) + // { + // if (yinBuffer[i] < minYin) + // { + // minYin = yinBuffer[i]; + // } + // } + // if (minYin < 0.01) std::cerr << "min Yin buffer element: " << minYin << std::endl; + + + int currThreshInd = nThreshold-1; + tau = 2; + + // double factor = 1.0 / (0.25 * (nThresholdInt+1) * (nThresholdInt + 1)); // factor to scale down triangular weight + size_t minInd = 0; + float minVal = 42.f; + while (currThreshInd != -1 && tau < yinBufferSize) + { + if (yinBuffer[tau] < thresholds[currThreshInd]) + { + while (tau + 1 < yinBufferSize && yinBuffer[tau+1] < yinBuffer[tau]) + { + tau++; + } + // tau is now local minimum + // std::cerr << tau << " " << currThreshInd << " "<< thresholds[currThreshInd] << " " << distribution[currThreshInd] << std::endl; + if (yinBuffer[tau] < minVal && tau > 2){ + minVal = yinBuffer[tau]; + minInd = tau; + } + peakProb[tau] += distribution[currThreshInd]; + currThreshInd--; + } else { + tau++; + } + } + double nonPeakProb = 1; + for (size_t i = 0; i < yinBufferSize; ++i) + { + nonPeakProb -= peakProb[i]; + } + // std::cerr << nonPeakProb << std::endl; + if (minInd > 0) + { + // std::cerr << "min set " << minVal << " " << minInd << " " << nonPeakProb << std::endl; + peakProb[minInd] += nonPeakProb * minWeight; + } + + return peakProb; +} + +double +YinUtil::parabolicInterpolation(const double *yinBuffer, const size_t tau, const size_t yinBufferSize) +{ + // this is taken almost literally from Joren Six's Java implementation + if (tau == yinBufferSize) // not valid anyway. + { + return static_cast<double>(tau); + } + + double betterTau = 0.0; + size_t x0; + size_t x2; + + if (tau < 1) + { + x0 = tau; + } else { + x0 = tau - 1; + } + + if (tau + 1 < yinBufferSize) + { + x2 = tau + 1; + } else { + x2 = tau; + } + + if (x0 == tau) + { + if (yinBuffer[tau] <= yinBuffer[x2]) + { + betterTau = tau; + } else { + betterTau = x2; + } + } + else if (x2 == tau) + { + if (yinBuffer[tau] <= yinBuffer[x0]) + { + betterTau = tau; + } + else + { + betterTau = x0; + } + } + else + { + float s0, s1, s2; + s0 = yinBuffer[x0]; + s1 = yinBuffer[tau]; + s2 = yinBuffer[x2]; + // fixed AUBIO implementation, thanks to Karl Helgason: + // (2.0f * s1 - s2 - s0) was incorrectly multiplied with -1 + betterTau = tau + (s2 - s0) / (2 * (2 * s1 - s2 - s0)); + + // std::cerr << tau << " --> " << betterTau << std::endl; + + } + return betterTau; +} + +double +YinUtil::sumSquare(const double *in, const size_t start, const size_t end) +{ + double out = 0; + for (size_t i = start; i < end; ++i) + { + out += in[i] * in[i]; + } + return out; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/YinUtil.h Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,52 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + This file is Copyright (c) 2012 Chris Cannam + + 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 _YINUTIL_H_ +#define _YINUTIL_H_ + +#include "vamp-sdk/FFT.h" +#include "MeanFilter.h" + +#include <cmath> + +#include <iostream> +#include <vector> +#include <exception> + +using std::vector; + +class YinUtil +{ +public: + static double sumSquare(const double *in, const size_t startInd, const size_t endInd); + static void difference(const double *in, double *yinBuffer, const size_t yinBufferSize); + static void fastDifference(const double *in, double *yinBuffer, const size_t yinBufferSize); + static void cumulativeDifference(double *yinBuffer, const size_t yinBufferSize); + static int absoluteThreshold(const double *yinBuffer, const size_t yinBufferSize, const double thresh); + static vector<double> yinProb(const double *yinBuffer, const size_t prior, const size_t yinBufferSize); + static double parabolicInterpolation(const double *yinBuffer, const size_t tau, + const size_t yinBufferSize); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libmain.cpp Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,46 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + This file is Copyright (c) 2012 Chris Cannam + + 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 <vamp/vamp.h> +#include <vamp-sdk/PluginAdapter.h> + +#include "PYIN.h" +#include "VampYin.h" + +static Vamp::PluginAdapter<PYIN> yintonyPluginAdapter; +static Vamp::PluginAdapter<VampYin> vampyinPluginAdapter; + +const VampPluginDescriptor * +vampGetPluginDescriptor(unsigned int version, unsigned int index) +{ + if (version < 1) return 0; + + switch (index) { + case 0: return yintonyPluginAdapter.getDescriptor(); + case 1: return vampyinPluginAdapter.getDescriptor(); + default: return 0; + } +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/TestFFT.cpp Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,177 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + This file is Copyright (c) 2012 Chris Cannam + + 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. +*/ + +/* + This unit test suite for the Vamp SDK FFT implementation is included + here mostly for illustrative purposes! +*/ + +#include "vamp-sdk/FFT.h" + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN + +#include <boost/test/unit_test.hpp> + +BOOST_AUTO_TEST_SUITE(TestFFT) + +#define COMPARE_CONST(a, n) \ + for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \ + BOOST_CHECK_SMALL(a[cmp_i] - n, 1e-14); \ + } + +#define COMPARE_ARRAY(a, b) \ + for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \ + BOOST_CHECK_SMALL(a[cmp_i] - b[cmp_i], 1e-14); \ + } + +BOOST_AUTO_TEST_CASE(dc) +{ + // DC-only signal. The DC bin is purely real + double in[] = { 1, 1, 1, 1 }; + double re[4], im[4]; + Vamp::FFT::forward(4, in, 0, re, im); + BOOST_CHECK_EQUAL(re[0], 4.0); + BOOST_CHECK_EQUAL(re[1], 0.0); + BOOST_CHECK_EQUAL(re[2], 0.0); + COMPARE_CONST(im, 0.0); + double back[4]; + double backim[4]; + Vamp::FFT::inverse(4, re, im, back, backim); + COMPARE_ARRAY(back, in); +} + +BOOST_AUTO_TEST_CASE(sine) +{ + // Sine. Output is purely imaginary + double in[] = { 0, 1, 0, -1 }; + double re[4], im[4]; + Vamp::FFT::forward(4, in, 0, re, im); + COMPARE_CONST(re, 0.0); + BOOST_CHECK_EQUAL(im[0], 0.0); + BOOST_CHECK_EQUAL(im[1], -2.0); + BOOST_CHECK_EQUAL(im[2], 0.0); + double back[4]; + double backim[4]; + Vamp::FFT::inverse(4, re, im, back, backim); + COMPARE_ARRAY(back, in); +} + +BOOST_AUTO_TEST_CASE(cosine) +{ + // Cosine. Output is purely real + double in[] = { 1, 0, -1, 0 }; + double re[4], im[4]; + Vamp::FFT::forward(4, in, 0, re, im); + BOOST_CHECK_EQUAL(re[0], 0.0); + BOOST_CHECK_EQUAL(re[1], 2.0); + BOOST_CHECK_EQUAL(re[2], 0.0); + COMPARE_CONST(im, 0.0); + double back[4]; + double backim[4]; + Vamp::FFT::inverse(4, re, im, back, backim); + COMPARE_ARRAY(back, in); +} + +BOOST_AUTO_TEST_CASE(sineCosine) +{ + // Sine and cosine mixed + double in[] = { 0.5, 1, -0.5, -1 }; + double re[4], im[4]; + Vamp::FFT::forward(4, in, 0, re, im); + BOOST_CHECK_EQUAL(re[0], 0.0); + BOOST_CHECK_CLOSE(re[1], 1.0, 1e-12); + BOOST_CHECK_EQUAL(re[2], 0.0); + BOOST_CHECK_EQUAL(im[0], 0.0); + BOOST_CHECK_CLOSE(im[1], -2.0, 1e-12); + BOOST_CHECK_EQUAL(im[2], 0.0); + double back[4]; + double backim[4]; + Vamp::FFT::inverse(4, re, im, back, backim); + COMPARE_ARRAY(back, in); +} + +BOOST_AUTO_TEST_CASE(nyquist) +{ + double in[] = { 1, -1, 1, -1 }; + double re[4], im[4]; + Vamp::FFT::forward(4, in, 0, re, im); + BOOST_CHECK_EQUAL(re[0], 0.0); + BOOST_CHECK_EQUAL(re[1], 0.0); + BOOST_CHECK_EQUAL(re[2], 4.0); + COMPARE_CONST(im, 0.0); + double back[4]; + double backim[4]; + Vamp::FFT::inverse(4, re, im, back, backim); + COMPARE_ARRAY(back, in); +} + +BOOST_AUTO_TEST_CASE(dirac) +{ + double in[] = { 1, 0, 0, 0 }; + double re[4], im[4]; + Vamp::FFT::forward(4, in, 0, re, im); + BOOST_CHECK_EQUAL(re[0], 1.0); + BOOST_CHECK_EQUAL(re[1], 1.0); + BOOST_CHECK_EQUAL(re[2], 1.0); + COMPARE_CONST(im, 0.0); + double back[4]; + double backim[4]; + Vamp::FFT::inverse(4, re, im, back, backim); + COMPARE_ARRAY(back, in); +} + +BOOST_AUTO_TEST_CASE(forwardArrayBounds) +{ + // initialise bins to something recognisable, so we can tell + // if they haven't been written + double in[] = { 1, 1, -1, -1 }; + double re[] = { 999, 999, 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999, 999, 999 }; + Vamp::FFT::forward(4, in, 0, re+1, im+1); + // And check we haven't overrun the arrays + BOOST_CHECK_EQUAL(re[0], 999.0); + BOOST_CHECK_EQUAL(im[0], 999.0); + BOOST_CHECK_EQUAL(re[5], 999.0); + BOOST_CHECK_EQUAL(im[5], 999.0); +} + +BOOST_AUTO_TEST_CASE(inverseArrayBounds) +{ + // initialise bins to something recognisable, so we can tell + // if they haven't been written + double re[] = { 0, 1, 0 }; + double im[] = { 0, -2, 0 }; + double outre[] = { 999, 999, 999, 999, 999, 999 }; + double outim[] = { 999, 999, 999, 999, 999, 999 }; + Vamp::FFT::forward(4, re, im, outre+1, outim+1); + // And check we haven't overrun the arrays + BOOST_CHECK_EQUAL(outre[0], 999.0); + BOOST_CHECK_EQUAL(outim[0], 999.0); + BOOST_CHECK_EQUAL(outre[5], 999.0); + BOOST_CHECK_EQUAL(outim[5], 999.0); +} + +BOOST_AUTO_TEST_SUITE_END() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/TestMeanFilter.cpp Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,91 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + This file is Copyright (c) 2012 Chris Cannam + + 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 "MeanFilter.h" + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN + +#include <boost/test/unit_test.hpp> + +BOOST_AUTO_TEST_SUITE(TestMeanFilter) + +BOOST_AUTO_TEST_CASE(simpleFilter) +{ + double in[] = { 1.0, 2.0, 3.0 }; + double out[3]; + MeanFilter(3).filter(in, out, 3); + BOOST_CHECK_EQUAL(out[0], 1.5); + BOOST_CHECK_EQUAL(out[1], 2.0); + BOOST_CHECK_EQUAL(out[2], 2.5); +} + +BOOST_AUTO_TEST_CASE(simpleSubset) +{ + double in[] = { 1.0, 2.0, 3.0, 4.0, 5.0 }; + double out[3]; + MeanFilter(3).filterSubsequence(in, out, 5, 3, 1); + BOOST_CHECK_EQUAL(out[0], 2.0); + BOOST_CHECK_EQUAL(out[1], 3.0); + BOOST_CHECK_EQUAL(out[2], 4.0); +} + +BOOST_AUTO_TEST_CASE(flenExceedsArraySize) +{ + double in[] = { 1.0, 2.0, 3.0 }; + double out[3]; + MeanFilter(5).filter(in, out, 3); + BOOST_CHECK_EQUAL(out[0], 2.0); + BOOST_CHECK_EQUAL(out[1], 2.0); + BOOST_CHECK_EQUAL(out[2], 2.0); +} + +BOOST_AUTO_TEST_CASE(flenIs1) +{ + double in[] = { 1.0, 2.0, 3.0 }; + double out[3]; + MeanFilter(1).filter(in, out, 3); + BOOST_CHECK_EQUAL(out[0], in[0]); + BOOST_CHECK_EQUAL(out[1], in[1]); + BOOST_CHECK_EQUAL(out[2], in[2]); +} + +BOOST_AUTO_TEST_CASE(arraySizeIs1) +{ + double in[] = { 1.0 }; + double out[1]; + MeanFilter(3).filter(in, out, 1); + BOOST_CHECK_EQUAL(out[0], in[0]); +} + +BOOST_AUTO_TEST_CASE(subsequenceLengthIs1) +{ + double in[] = { 1.0, 2.0, 3.0 }; + double out[1]; + MeanFilter(3).filterSubsequence(in, out, 3, 1, 2); + BOOST_CHECK_EQUAL(out[0], 2.5); +} + +BOOST_AUTO_TEST_SUITE_END() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/TestMonoNote.cpp Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,43 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +#include "MonoNote.h" + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN + +#include <boost/test/unit_test.hpp> + +using std::vector; +using std::pair; + +BOOST_AUTO_TEST_SUITE(TestMonoNote) + +BOOST_AUTO_TEST_CASE(instantiate) +{ + MonoNote mn; + vector<vector<pair<double, double> > > pitchProb; + size_t n = 8; + + vector<double> pitch; + pitch.push_back(48); + pitch.push_back(51); + pitch.push_back(50); + pitch.push_back(51); + pitch.push_back(60); + pitch.push_back(71); + pitch.push_back(51); + pitch.push_back(62); + + for (size_t i = 0; i < n; ++i) + { + vector<pair<double, double> > temp; + temp.push_back(pair<double, double>(pitch[i], 1.0)); + pitchProb.push_back(temp); + } + + mn.process(pitchProb); +} + + +BOOST_AUTO_TEST_SUITE_END() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/TestPredominoMethods.cpp Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,132 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +#include "PredominoMethods.h" + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN + +#include <boost/test/unit_test.hpp> +#include <boost/test/floating_point_comparison.hpp> + +BOOST_AUTO_TEST_SUITE(TestPredominoMethods) + +BOOST_AUTO_TEST_CASE(pickNPeaks) +{ + vector<float> invec; + invec.push_back(0); + invec.push_back(1); + invec.push_back(2); + invec.push_back(1); + invec.push_back(3); + invec.push_back(3); + invec.push_back(4); + invec.push_back(0); + invec.push_back(1); + invec.push_back(0); + + vector<size_t> outvec = PredominoMethods::pickNPeaks(invec, 2); + BOOST_CHECK_EQUAL(outvec[0], 2); + BOOST_CHECK_EQUAL(outvec[1], 6); + +} + +BOOST_AUTO_TEST_CASE(zeroPad) +{ + size_t nIn = 4; + size_t nPadded = 8; + float *in = new float[nIn]; + in[0] = 1; in[1] = 2; in[2] = 3; in[3] = 4; + float *out = new float[nPadded]; + PredominoMethods::zeroPad(in, out, nIn, nPadded); + + BOOST_CHECK_EQUAL(out[0], 3); + BOOST_CHECK_EQUAL(out[1], 4); + for (size_t i = 2; i < nPadded - nIn/2; ++i) { + BOOST_CHECK_EQUAL(out[i], 0); + } + BOOST_CHECK_EQUAL(out[6], 1); + BOOST_CHECK_EQUAL(out[7], 2); + + delete [] in; + delete [] out; +} + +BOOST_AUTO_TEST_CASE(applyWindow) +{ + size_t n = 4; + vector<float> in; + vector<float> win; + for (size_t i = 0; i < n; ++i) { + in.push_back(i); + win.push_back(i); + } + PredominoMethods::applyWindow(&in, win); + for (size_t i = 0; i < n; ++i) { + BOOST_CHECK_EQUAL(in[i], i*i); + } +} + +BOOST_AUTO_TEST_CASE(hannWindow4) +{ + size_t n = 4; + vector<float> hw = PredominoMethods::hannWindow(n); + // for (size_t i = 0; i < n; ++i) { + // std::cerr << hw[i] << std::endl; + // } + + BOOST_CHECK_CLOSE_FRACTION(hw[0], 0.1382, .0001); + BOOST_CHECK_CLOSE_FRACTION(hw[1], 0.3618, .0001); + BOOST_CHECK_CLOSE_FRACTION(hw[2], 0.3618, .0001); + BOOST_CHECK_CLOSE_FRACTION(hw[3], 0.1382, .0001); +} + +BOOST_AUTO_TEST_CASE(parabolicInterpolation) +{ + vector<float> mag1; + mag1.push_back(1); mag1.push_back(2); mag1.push_back(1); + vector<size_t> peakIndices1; + peakIndices1.push_back(1); + vector<float> interpolated1 = PredominoMethods::parabolicInterpolation(mag1, peakIndices1); + BOOST_CHECK_CLOSE_FRACTION(interpolated1[0], 0, 0.001); + + vector<float> mag2; + mag2.push_back(1); mag2.push_back(1); mag1.push_back(0); + vector<size_t> peakIndices2; + peakIndices2.push_back(1); + vector<float> interpolated2 = PredominoMethods::parabolicInterpolation(mag2, peakIndices2); + BOOST_CHECK_CLOSE_FRACTION(interpolated2[0], -0.5, 0.001); +} + +BOOST_AUTO_TEST_CASE(convertMagnitude2dB) +{ + vector<float> mag; + mag.push_back(1.01); + vector<float> magdb = PredominoMethods::convertMagnitude2dB(mag); + BOOST_CHECK_CLOSE_FRACTION(magdb[0], 0.0864, 0.001); +} + +BOOST_AUTO_TEST_CASE(generateOutputFrequencies) +{ + vector<float> f = PredominoMethods::generateOutputFrequencies(110, 2, 50); + BOOST_CHECK_CLOSE_FRACTION(f[0], 110, 0.001); + BOOST_CHECK_CLOSE_FRACTION(f[1], 113.2232, 0.001); +} + +BOOST_AUTO_TEST_CASE(timedomain2magnitude) +{ + float *in = new float[4]; + in[0] = 1; + in[1] = 2; + in[2] = 3; + in[3] = 2; + vector<float> mag = PredominoMethods::timedomain2magnitude(in, 4, 0); + // for (size_t i = 0; i < 4; ++i) std::cerr << mag[i] << std::endl; + delete [] in; + BOOST_CHECK_CLOSE_FRACTION(mag[0], 8, 0.001); + BOOST_CHECK_CLOSE_FRACTION(mag[1], 2, 0.001); + BOOST_CHECK_CLOSE_FRACTION(mag[2], 0, 0.001); + BOOST_CHECK_CLOSE_FRACTION(mag[3], 2, 0.001); +} + +BOOST_AUTO_TEST_SUITE_END() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/TestYin.cpp Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,41 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +#include "Yin.h" + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN + +#include <boost/test/unit_test.hpp> + +BOOST_AUTO_TEST_SUITE(TestYin) + +BOOST_AUTO_TEST_CASE(implicitYinOutput) +{ + // this test is just to make sure a YinOutput initialises + // -- the actual reason for me to write this is to have written + // a test, so let's plough on... + Yin::YinOutput out; + + BOOST_CHECK_EQUAL(out.f0, 0); + BOOST_CHECK_EQUAL(out.periodicity, 0); + BOOST_CHECK_EQUAL(out.rms, 0); +} + +BOOST_AUTO_TEST_CASE(sine128) +{ + + // a very short frame (8) with maximum frequency (128), + // assuming a sampling rate of 256 1/sec. + // Yin should get it approximately right... + // but because of post-processing we want to be tolerant + + double in[] = { 1, -1, 1, -1, 1, -1, 1, -1 }; + Yin y(8, 256); + Yin::YinOutput yo = y.process(in); + + BOOST_CHECK(yo.f0 > 114); + BOOST_CHECK(yo.f0 < 142); +} + +BOOST_AUTO_TEST_SUITE_END() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/TestYinUtil.cpp Wed Nov 27 11:59:49 2013 +0000 @@ -0,0 +1,94 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +#include "YinUtil.h" + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN + +#include <boost/test/unit_test.hpp> + +BOOST_AUTO_TEST_SUITE(TestYin) + +BOOST_AUTO_TEST_CASE(difference1) +{ + // difference always needs to be non-negative + double in[] = { 1, -1, 1, -1, 1, -1, 1, -1 }; + double yinBuffer[4]; + + YinUtil::difference(in, yinBuffer, 4); + + BOOST_CHECK(yinBuffer[0] >= 0); + BOOST_CHECK(yinBuffer[1] >= 0); + BOOST_CHECK(yinBuffer[2] >= 0); + BOOST_CHECK(yinBuffer[3] >= 0); +} + +// BOOST_AUTO_TEST_CASE(difference2) +// { +// // difference for fast sinusoid +// double in[] = { 1, -1, 1, -1, 1, -1, 1, -1 }; +// double yinBuffer[4]; +// +// Yin y(8, 256); +// +// y.difference(in, yinBuffer); +// +// BOOST_CHECK_EQUAL(yinBuffer[0], 0); +// BOOST_CHECK_EQUAL(yinBuffer[1], 16); +// BOOST_CHECK_EQUAL(yinBuffer[2], 0); +// BOOST_CHECK_EQUAL(yinBuffer[3], 16); +// } +// +// BOOST_AUTO_TEST_CASE(difference3) +// { +// // difference for contaminated fast sinusoid +// double in[] = { 2, -1, 1, -1, 1, -1, 1, -1 }; +// double yinBuffer[4]; +// +// Yin y(8, 256); +// +// y.difference(in, yinBuffer); +// +// BOOST_CHECK_EQUAL(yinBuffer[0], 0); +// BOOST_CHECK_EQUAL(yinBuffer[1], 21); +// BOOST_CHECK_EQUAL(yinBuffer[2], 1); +// BOOST_CHECK_EQUAL(yinBuffer[3], 21); +// } +// +// BOOST_AUTO_TEST_CASE(difference4) +// { +// // +// double in[2048]; +// double yinBuffer[1024]; +// for (size_t i = 0; i < 2048; i = i + 2) { +// in[i] = 1; +// in[i+1] = -1; +// } +// Yin y(2048, 44100); +// +// y.difference(in, yinBuffer); +// +// BOOST_CHECK(yinBuffer[0] >= 0); +// BOOST_CHECK(yinBuffer[1] >= 0); +// } +// +// BOOST_AUTO_TEST_CASE(cumulativeDifference1) +// { +// // test against matlab implementation +// double yinBuffer[] = {0, 21, 1, 21}; +// +// Yin y(8, 256); +// +// y.cumulativeDifference(yinBuffer); +// +// BOOST_CHECK_EQUAL(yinBuffer[0], 1); +// BOOST_CHECK_EQUAL(yinBuffer[1], 1); // this is just chance! +// BOOST_CHECK(yinBuffer[2] >= 0.0909 && yinBuffer[2] < 0.091); +// BOOST_CHECK(yinBuffer[3] >= 1.4651 && yinBuffer[3] < 1.466); +// +// } + + + +BOOST_AUTO_TEST_SUITE_END() +