changeset 184:9b9cdfccbd14 noteagent

Wire up note agent code -- results are not very good, so far
author Chris Cannam
date Wed, 28 May 2014 14:54:01 +0100
parents e1718e64a921
children 78212f764251
files Makefile.inc src/AgentFeederPoly.h src/NoteHypothesis.cpp src/NoteHypothesis.h src/Silvet.cpp src/Silvet.h
diffstat 6 files changed, 259 insertions(+), 243 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.inc	Fri May 23 17:03:27 2014 +0100
+++ b/Makefile.inc	Wed May 28 14:54:01 2014 +0100
@@ -67,7 +67,9 @@
 
 # DO NOT DELETE
 
-src/Silvet.o: src/Silvet.h src/MedianFilter.h src/Instruments.h src/EM.h
+src/Silvet.o: src/Silvet.h src/MedianFilter.h src/Instruments.h
+src/Silvet.o: src/NoteHypothesis.h src/AgentHypothesis.h src/EM.h
+src/Silvet.o: src/AgentFeederPoly.h src/AgentFeeder.h
 src/Silvet.o: constant-q-cpp/src/dsp/Resampler.h
 src/EM.o: src/EM.h src/Instruments.h
 src/Instruments.o: src/Instruments.h data/include/templates.h
@@ -77,9 +79,17 @@
 src/Instruments.o: data/include/oboe.h data/include/tenorsax.h
 src/Instruments.o: data/include/violin.h data/include/piano1.h
 src/Instruments.o: data/include/piano2.h data/include/piano3.h
+src/NoteHypothesis.o: src/NoteHypothesis.h src/AgentHypothesis.h
+src/NoteHypothesis.o: src/AgentFeederPoly.h src/AgentFeeder.h
 src/libmain.o: src/Silvet.h src/MedianFilter.h src/Instruments.h
+src/libmain.o: src/NoteHypothesis.h src/AgentHypothesis.h
 bqvec/src/Allocators.o: bqvec/src/Allocators.h bqvec/src/VectorOps.h
 bqvec/src/Allocators.o: bqvec/src/Restrict.h
-src/Silvet.o: src/MedianFilter.h src/Instruments.h
+src/Silvet.o: src/MedianFilter.h src/Instruments.h src/NoteHypothesis.h
+src/Silvet.o: src/AgentHypothesis.h
+src/AgentFeeder.o: src/AgentHypothesis.h
+src/AgentFeederMono.o: src/AgentFeeder.h src/AgentHypothesis.h
+src/AgentFeederPoly.o: src/AgentFeeder.h src/AgentHypothesis.h
+src/NoteHypothesis.o: src/AgentHypothesis.h
 bqvec/src/Allocators.o: bqvec/src/VectorOps.h bqvec/src/Restrict.h
 bqvec/src/VectorOps.o: bqvec/src/Restrict.h
--- a/src/AgentFeederPoly.h	Fri May 23 17:03:27 2014 +0100
+++ b/src/AgentFeederPoly.h	Wed May 28 14:54:01 2014 +0100
@@ -20,8 +20,9 @@
 #include "AgentFeeder.h"
 
 #include <cassert>
+#include <stdexcept>
 
-//#define DEBUG_FEEDER 1
+#define DEBUG_FEEDER 1
 
 /**
  * Take a series of observations or estimates (one at a time) and feed
@@ -39,10 +40,9 @@
 template <typename Hypothesis>
 class AgentFeederPoly : public AgentFeeder
 {
-public:
-    typedef std::set<Hypothesis> Hypotheses;
-    
 private:
+    typedef std::vector<Hypothesis> Hypotheses;
+
     struct State {
         Hypotheses provisional;
         Hypotheses satisfied;
@@ -55,10 +55,10 @@
 
     virtual void feed(AgentHypothesis::Observation o) {
 #ifdef DEBUG_FEEDER        
-        std::cerr << "feed: have observation [value = " << o.value << ", time = " << o.time << "]" << std::endl;
+        std::cerr << "\nfeed: have observation [value = " << o.value << ", time = " << o.time << "]" << std::endl;
 #endif
 
-        update(m_state, o);
+        m_state = update(m_state, o);
     }
 
     virtual void finish() {
@@ -67,16 +67,21 @@
 #endif
         for (typename Hypotheses::const_iterator i = m_state.satisfied.begin();
              i != m_state.satisfied.end(); ++i) {
-            m_state.completed.insert(*i);
+            m_state.completed.push_back(*i);
         }
     }
 
-    Hypotheses getAcceptedHypotheses() const {
-        return m_state.completed;
+    std::set<Hypothesis> getAcceptedHypotheses() const {
+        std::set<Hypothesis> hs;
+        for (typename Hypotheses::const_iterator i = m_state.completed.begin();
+             i != m_state.completed.end(); ++i) {
+            hs.insert(*i);
+        }
+        return hs;
     }
 
 private:
-    void update(State &s, AgentHypothesis::Observation o) {
+    State update(State s, AgentHypothesis::Observation o) {
 
         /*
           An observation can "belong" to any number of provisional
@@ -98,14 +103,14 @@
           must be discarded.
         */
 
+        State newState;
+
         // We only ever add to the completed hypotheses, never remove
         // anything from them. But we may remove from provisional (if
         // rejected or transferred to satisfied) and satisfied (when
         // completed).
-
-        Hypotheses toCompleted;
-        Hypotheses toSatisfied;
-        Hypotheses toProvisional;
+        
+        newState.completed = s.completed;
         
         bool swallowed = false;
 
@@ -126,6 +131,7 @@
                 // for expiry, because the state is only updated when
                 // accept() is called.
                 //!!! That looks like a limitation in the Hypothesis API
+                newState.satisfied.push_back(h);
 
             } else { // !swallowed
 
@@ -134,8 +140,11 @@
                     std::cerr << "accepted by satisfied hypothesis " << &(*i) << ", state is " << h.getState() << std::endl;
 #endif
                     swallowed = true;
+                    newState.satisfied.push_back(h);
                 } else if (h.getState() == Hypothesis::Expired) {
-                    toCompleted.insert(h);
+                    newState.completed.push_back(h);
+                } else {
+                    newState.satisfied.push_back(h);
                 }
             }
         }
@@ -147,6 +156,7 @@
 #endif
             // no provisional hypotheses have become satisfied, no new
             // ones have been introduced
+            newState.provisional = s.provisional;
 
         } else {
 
@@ -171,13 +181,13 @@
                 if (promoted == Hypothesis() &&
                     h.accept(o) &&
                     h.getState() == Hypothesis::Satisfied) {
-                    toSatisfied.insert(h);
+                    newState.satisfied.push_back(h);
 #ifdef DEBUG_FEEDER        
                     std::cerr << "promoting a hypothesis to satisfied, have " << newState.satisfied.size() << " satisfied now" << std::endl;
 #endif
                     promoted = h;
                 } else if (h.getState() != Hypothesis::Rejected) {
-                    // leave as provisional
+                    newState.provisional.push_back(h);
                 }
             }
 
@@ -189,50 +199,41 @@
                 h.accept(o);
 
                 if (h.getState() == Hypothesis::Provisional) {
-                    toProvisional.insert(h);
+                    newState.provisional.push_back(h);
                 } else if (h.getState() == Hypothesis::Satisfied) {
-                    toSatisfied.insert(h);
+                    newState.satisfied.push_back(h);
                 }
 
 #ifdef DEBUG_FEEDER        
                 std::cerr << "update: new hypothesis of state " << h.getState() << ", provisional count -> " << newState.provisional.size() << std::endl;
 #endif
+            } else {
 
+#ifdef DEBUG_FEEDER        
+                std::cerr << "a hypothesis became satisfied, reaping its observations" << std::endl;
+#endif
+                newState = reap(newState);
             }
         }
 
-        for (typename Hypotheses::const_iterator i = toCompleted.begin();
-             i != toCompleted.end(); ++i) {
-            s.satisfied.erase(*i);
-            s.completed.insert(*i);
-        }
-        for (typename Hypotheses::const_iterator i = toSatisfied.begin();
-             i != toSatisfied.end(); ++i) {
-            s.provisional.erase(*i);
-            s.satisfied.insert(*i);
-        }
-        for (typename Hypotheses::const_iterator i = toProvisional.begin();
-             i != toProvisional.end(); ++i) {
-            s.provisional.insert(*i);
-        }
-
-        reap(s);
+        return newState;
     }
 
-    void reap(State &s) {
+    State reap(State s) {
 
         // "When a hypothesis subsequently becomes satisfied, all
         // other provisional hypotheses containing any of its
         // observations must be discarded."
 
-        if (s.provisional.empty()) return;
+        if (s.provisional.empty()) return s;
 
         int reaped = 0;
 
-        Hypotheses toRemove = Hypotheses();;
+        Hypotheses prior = s.provisional;
+        s.provisional = Hypotheses();
 
-        for (typename Hypotheses::const_iterator hi = s.provisional.begin();
-             hi != s.provisional.end(); ++hi) {
+        for (typename Hypotheses::const_iterator hi = prior.begin();
+             hi != prior.end(); ++hi) {
                     
             const AgentHypothesis::Observations obs =
                 hi->getAcceptedObservations();
@@ -261,16 +262,12 @@
                 }
             }
 
-            if (!keep) {
-                toRemove.insert(*hi);
+            if (keep) {
+                s.provisional.push_back(*hi);
+            } else {
                 ++reaped;
             }
         }
-        
-        for (typename Hypotheses::const_iterator i = toRemove.begin();
-             i != toRemove.end(); ++i) {
-            s.provisional.erase(i);
-        }
 
 #ifdef DEBUG_FEEDER
         std::cerr << "reap: have "
@@ -279,6 +276,8 @@
                   << s.completed.size() << " completed, reaped "
                   << reaped << std::endl;
 #endif
+
+        return s;
     }
 };
 
--- a/src/NoteHypothesis.cpp	Fri May 23 17:03:27 2014 +0100
+++ b/src/NoteHypothesis.cpp	Wed May 28 14:54:01 2014 +0100
@@ -21,10 +21,14 @@
 #include <cassert>
 
 #include <map>
+#include <algorithm>
 
 using Vamp::RealTime;
 
-//#define DEBUG_NOTE_HYPOTHESIS 1
+using std::cerr;
+using std::endl;
+
+#define DEBUG_NOTE_HYPOTHESIS 1
 
 NoteHypothesis::NoteHypothesis()
 {
@@ -50,8 +54,8 @@
     double r = s.value / last.value;
     int cents = lrint(1200.0 * (log(r) / log(2.0)));
 #ifdef DEBUG_NOTE_HYPOTHESIS
-    std::cerr << "isWithinTolerance: this " << s.value << " is " << cents
-              << " cents from prior " << last.value << std::endl;
+    cerr << "isWithinTolerance: this " << s.value << " is " << cents
+              << " cents from prior " << last.value << endl;
 #endif
     if (cents < -60 || cents > 60) return false;
 
@@ -60,8 +64,8 @@
     r = s.value / meanFreq;
     cents = lrint(1200.0 * (log(r) / log(2.0)));
 #ifdef DEBUG_NOTE_HYPOTHESIS
-    std::cerr << "isWithinTolerance: this " << s.value << " is " << cents
-              << " cents from mean " << meanFreq << std::endl;
+    cerr << "isWithinTolerance: this " << s.value << " is " << cents
+              << " cents from mean " << meanFreq << endl;
 #endif
     if (cents < -80 || cents > 80) return false;
     
@@ -78,10 +82,10 @@
     Observation last = *i;
 
 #ifdef DEBUG_NOTE_HYPOTHESIS
-    std::cerr << "isOutOfDateFor: this " << s.time << " is "
+    cerr << "isOutOfDateFor: this " << s.time << " is "
               << (s.time - last.time) << " from last " << last.time
               << " (threshold " << RealTime::fromMilliseconds(40) << ")"
-              << std::endl;
+              << endl;
 #endif
 
     return ((s.time - last.time) > RealTime::fromMilliseconds(40));
@@ -99,26 +103,42 @@
     }
     meanConfidence /= m_pending.size();
 
+    //!!! surely this depends on the hop size?
     int lengthRequired = 100;
     if (meanConfidence > 0.0) {
         lengthRequired = int(2.0 / meanConfidence + 0.5);
     }
-//    if (lengthRequired < 1) lengthRequired = 1;
+    //!!! 
+    lengthRequired = lengthRequired / 2;
+    if (lengthRequired < 1) lengthRequired = 1;
 
 #ifdef DEBUG_NOTE_HYPOTHESIS
-    std::cerr << "meanConfidence " << meanConfidence << ", lengthRequired " << lengthRequired << std::endl;
+    cerr << "meanConfidence " << meanConfidence << ", lengthRequired " << lengthRequired << endl;
 #endif
 
     return ((int)m_pending.size() > lengthRequired);
 }
 
+static void printState(NoteHypothesis::State s)
+{
+    switch (s) {
+    case NoteHypothesis::New: cerr << "New"; break;
+    case NoteHypothesis::Provisional: cerr << "Provisional"; break;
+    case NoteHypothesis::Rejected: cerr << "Rejected"; break;
+    case NoteHypothesis::Satisfied: cerr << "Satisfied"; break;
+    case NoteHypothesis::Expired: cerr << "Expired"; break;
+    }
+}
+
 bool
 NoteHypothesis::accept(Observation s)
 {
     bool accept = false;
 
 #ifdef DEBUG_NOTE_HYPOTHESIS
-    std::cerr << "NoteHypothesis[" << this << "]::accept: state " << m_state << "..." << std::endl;
+    cerr << "NoteHypothesis[" << this << "]::accept (value " << s.value << ", time " << s.time << ", confidence " << s.confidence << "): state ";
+    printState(m_state);
+    cerr << "..." << endl;
 #endif
 
     static double negligibleConfidence = 0.0001;
@@ -164,14 +184,23 @@
     }
 
     if (accept) {
+#ifdef DEBUG_NOTE_HYPOTHESIS
+        cerr << "... accepting" << endl;
+#endif
         m_pending.insert(s);
         if (m_state == Provisional && isSatisfied()) {
             m_state = Satisfied;
         }
+    } else {
+#ifdef DEBUG_NOTE_HYPOTHESIS
+        cerr << "... not accepting" << endl;
+#endif
     }
 
 #ifdef DEBUG_NOTE_HYPOTHESIS
-    std::cerr << "... -> " << m_state << " (pending: " << m_pending.size() << ")" << std::endl;
+    cerr << "... -> ";
+    printState(m_state);
+    cerr << " (pending: " << m_pending.size() << ")" << endl;
 #endif
 
     return accept;
@@ -194,6 +223,19 @@
 }
 
 double
+NoteHypothesis::getMedianFrequency() const
+{
+    if (m_pending.empty()) return 0.0;
+    std::vector<double> freqs;
+    for (Observations::const_iterator i = m_pending.begin();
+         i != m_pending.end(); ++i) {
+        freqs.push_back(i->value);
+    }
+    std::sort(freqs.begin(), freqs.end());
+    return freqs[freqs.size()/2];
+}
+
+double
 NoteHypothesis::getMeanFrequency() const
 {
     double acc = 0.0;
@@ -206,6 +248,19 @@
     return acc;
 }
 
+double
+NoteHypothesis::getMedianConfidence() const
+{
+    if (m_pending.empty()) return 0.0;
+    std::vector<double> confs;
+    for (Observations::const_iterator i = m_pending.begin();
+         i != m_pending.end(); ++i) {
+        confs.push_back(i->confidence);
+    }
+    std::sort(confs.begin(), confs.end());
+    return confs[confs.size()/2];
+}
+
 NoteHypothesis::Note
 NoteHypothesis::getAveragedNote() const
 {
@@ -213,9 +268,8 @@
 
     n.time = getStartTime();
     n.duration = getDuration();
-
-    // just mean frequency for now, but this isn't at all right perceptually
-    n.freq = getMeanFrequency();
+    n.freq = getMedianFrequency();
+    n.confidence = getMedianConfidence();
     
     return n;
 }
--- a/src/NoteHypothesis.h	Fri May 23 17:03:27 2014 +0100
+++ b/src/NoteHypothesis.h	Wed May 28 14:54:01 2014 +0100
@@ -47,21 +47,33 @@
     virtual Observations getAcceptedObservations() const;
 
     struct Note {
-        Note() : freq(0), time(), duration() { }
-        Note(double _f, Vamp::RealTime _t, Vamp::RealTime _d) :
-            freq(_f), time(_t), duration(_d) { }
+        Note() : freq(0), time(), duration(), confidence(1.0) { }
+        Note(double _f, Vamp::RealTime _t, Vamp::RealTime _d, double _i) :
+            freq(_f), time(_t), duration(_d), confidence(_i) { }
         bool operator==(const Note &e) const {
-            return e.freq == freq && e.time == time && e.duration == duration;
+            return e.freq == freq && e.time == time && 
+                e.duration == duration && e.confidence == confidence;
         }
 	double freq;
 	Vamp::RealTime time;
 	Vamp::RealTime duration;
+        double confidence;
     };
     
     /**
      * Return the mean frequency of the accepted observations
      */
     double getMeanFrequency() const;
+    
+    /**
+     * Return the median frequency of the accepted observations
+     */
+    double getMedianFrequency() const;
+    
+    /**
+     * Return the median confidence of the accepted observations
+     */
+    double getMedianConfidence() const;
 
     /**
      * Return a single note roughly matching this hypothesis
@@ -85,7 +97,25 @@
     }
 
     bool operator<(const NoteHypothesis &other) const {
-        return getStartTime() < other.getStartTime();
+        if (getStartTime() != other.getStartTime()) {
+            return getStartTime() < other.getStartTime();
+        } else if (m_state != other.m_state) {
+            return m_state < other.m_state;
+        } else if (m_pending.size() != other.m_pending.size()) {
+            return m_pending.size() < other.m_pending.size();
+        } else {
+            Observations::const_iterator i = m_pending.begin();
+            Observations::const_iterator j = other.m_pending.begin();
+            while (i != m_pending.end()) {
+                if (*i == *j) {
+                    ++i;
+                    ++j;
+                } else {
+                    return *i < *j;
+                }
+            }
+            return false;
+        }
     }
 
 private:
--- a/src/Silvet.cpp	Fri May 23 17:03:27 2014 +0100
+++ b/src/Silvet.cpp	Wed May 28 14:54:01 2014 +0100
@@ -19,6 +19,9 @@
 #include <cq/CQSpectrogram.h>
 
 #include "MedianFilter.h"
+#include "AgentFeederPoly.h"
+#include "NoteHypothesis.h"
+
 #include "constant-q-cpp/src/dsp/Resampler.h"
 
 #include <vector>
@@ -42,7 +45,8 @@
     m_hqMode(true),
     m_fineTuning(false),
     m_instrument(0),
-    m_colsPerSec(50)
+    m_colsPerSec(50),
+    m_agentFeeder(0)
 {
 }
 
@@ -53,6 +57,7 @@
     for (int i = 0; i < (int)m_postFilter.size(); ++i) {
         delete m_postFilter[i];
     }
+    delete m_agentFeeder;
 }
 
 string
@@ -353,6 +358,7 @@
 {
     delete m_resampler;
     delete m_cq;
+    delete m_agentFeeder;
 
     if (m_inputSampleRate != processingSampleRate) {
 	m_resampler = new Resampler(m_inputSampleRate, processingSampleRate);
@@ -393,15 +399,18 @@
     for (int i = 0; i < m_instruments[0].templateNoteCount; ++i) {
         m_postFilter.push_back(new MedianFilter<double>(3));
     }
-    m_pianoRoll.clear();
-    m_columnCount = 0;
+
+    m_columnCountIn = 0;
+    m_columnCountOut = 0;
     m_startTime = RealTime::zeroTime;
+
+    m_agentFeeder = new AgentFeederPoly<NoteHypothesis>();
 }
 
 Silvet::FeatureSet
 Silvet::process(const float *const *inputBuffers, Vamp::RealTime timestamp)
 {
-    if (m_columnCount == 0) {
+    if (m_columnCountIn == 0) {
         m_startTime = timestamp;
     }
     
@@ -423,7 +432,17 @@
 Silvet::getRemainingFeatures()
 {
     Grid cqout = m_cq->getRemainingOutput();
+
     FeatureSet fs = transcribe(cqout);
+
+    m_agentFeeder->finish();
+
+    FeatureList noteFeatures = obtainNotes();
+    for (FeatureList::const_iterator fi = noteFeatures.begin();
+         fi != noteFeatures.end(); ++fi) {
+        fs[m_notesOutputNo].push_back(*fi);
+    }
+
     return fs;
 }
 
@@ -453,7 +472,7 @@
     //!!! pitches or notes? [terminology]
     Grid localPitches(width, vector<double>(pack.templateNoteCount, 0.0));
 
-    bool wantShifts = m_hqMode && m_fineTuning;
+    bool wantShifts = m_hqMode;
     int shiftCount = 1;
     if (wantShifts) {
         shiftCount = pack.templateMaxShift * 2 + 1;
@@ -513,16 +532,13 @@
             for (int j = 0; j < pack.templateNoteCount; ++j) {
                 m_postFilter[j]->push(0.0);
             }
-            m_pianoRoll.push_back(map<int, double>());
-            if (wantShifts) {
-                m_pianoRollShifts.push_back(map<int, int>());
-            }
             continue;
         }
 
-        postProcess(localPitches[i], localBestShifts[i], wantShifts);
+        postProcess(localPitches[i], localBestShifts[i], 
+                    wantShifts, shiftCount);
         
-        FeatureList noteFeatures = noteTrack(shiftCount);
+        FeatureList noteFeatures = obtainNotes();
 
         for (FeatureList::const_iterator fi = noteFeatures.begin();
              fi != noteFeatures.end(); ++fi) {
@@ -556,13 +572,13 @@
 
     for (int i = 0; i < width; ++i) {
 
-        if (m_columnCount < latentColumns) {
-            ++m_columnCount;
+        if (m_columnCountIn < latentColumns) {
+            ++m_columnCountIn;
             continue;
         }
 
-        int prevSampleNo = (m_columnCount - 1) * m_cq->getColumnHop();
-        int sampleNo = m_columnCount * m_cq->getColumnHop();
+        int prevSampleNo = (m_columnCountIn - 1) * m_cq->getColumnHop();
+        int sampleNo = m_columnCountIn * m_cq->getColumnHop();
 
         bool select = (sampleNo / spacing != prevSampleNo / spacing);
 
@@ -611,7 +627,7 @@
             out.push_back(outCol);
         }
 
-        ++m_columnCount;
+        ++m_columnCountIn;
     }
 
     return out;
@@ -620,7 +636,8 @@
 void
 Silvet::postProcess(const vector<double> &pitches,
                     const vector<int> &bestShifts,
-                    bool wantShifts)
+                    bool wantShifts,
+                    int shiftCount)
 {
     const InstrumentPack &pack = m_instruments[m_instrument];
 
@@ -631,178 +648,82 @@
         filtered.push_back(m_postFilter[j]->get());
     }
 
-    // Threshold for level and reduce number of candidate pitches
+    double threshold = 1;
 
-    int polyphony = 5;
-
-    //!!! make this a parameter (was 4.8, try adjusting, compare levels against matlab code)
-    double threshold = 6;
-//    double threshold = 4.8;
-
-    typedef std::multimap<double, int> ValueIndexMap;
-
-    ValueIndexMap strengths;
+    double columnDuration = 1.0 / m_colsPerSec;
+    int postFilterLatency = int(m_postFilter[0]->getSize() / 2);
+    RealTime t = RealTime::fromSeconds
+        (columnDuration * (m_columnCountOut - postFilterLatency) + 0.02);
 
     for (int j = 0; j < pack.templateNoteCount; ++j) {
+
         double strength = filtered[j];
-        if (strength < threshold) continue;
-        strengths.insert(ValueIndexMap::value_type(strength, j));
+        if (strength < threshold) {
+            continue;
+        }
+
+        double freq;
+        if (wantShifts) {
+            freq = noteFrequency(j, bestShifts[j], shiftCount);
+        } else {
+            freq = noteFrequency(j, 0, shiftCount);
+        }
+
+        double confidence = strength / 50.0; //!!!???
+        if (confidence > 1.0) confidence = 1.0;
+
+        AgentHypothesis::Observation obs(freq, t, confidence);
+        m_agentFeeder->feed(obs);
     }
 
-    ValueIndexMap::const_iterator si = strengths.end();
-
-    map<int, double> active;
-    map<int, int> activeShifts;
-
-    while (int(active.size()) < polyphony && si != strengths.begin()) {
-
-        --si;
-
-        double strength = si->first;
-        int j = si->second;
-
-        active[j] = strength;
-
-        if (wantShifts) {
-            activeShifts[j] = bestShifts[j];
-        }
-    }
-
-    m_pianoRoll.push_back(active);
-
-    if (wantShifts) {
-        m_pianoRollShifts.push_back(activeShifts);
-    }
+    m_columnCountOut ++;
 }
 
 Vamp::Plugin::FeatureList
-Silvet::noteTrack(int shiftCount)
+Silvet::obtainNotes()
 {        
-    // Minimum duration pruning, and conversion to notes. We can only
-    // report notes that have just ended (i.e. that are absent in the
-    // latest active set but present in the prior set in the piano
-    // roll) -- any notes that ended earlier will have been reported
-    // already, and if they haven't ended, we don't know their
-    // duration.
-
-    int width = m_pianoRoll.size() - 1;
-
-    const map<int, double> &active = m_pianoRoll[width];
-
-    double columnDuration = 1.0 / m_colsPerSec;
-
-    // only keep notes >= 100ms or thereabouts
-    int durationThreshold = floor(0.1 / columnDuration); // columns
-    if (durationThreshold < 1) durationThreshold = 1;
-
     FeatureList noteFeatures;
 
-    if (width < durationThreshold + 1) {
+    typedef AgentFeederPoly<NoteHypothesis> NoteFeeder;
+
+    NoteFeeder *feeder = dynamic_cast<NoteFeeder *>(m_agentFeeder);
+
+    if (!feeder) {
+        cerr << "INTERNAL ERROR: Feeder is not a poly-note-hypothesis-feeder!"
+             << endl;
         return noteFeatures;
     }
-    
-    //!!! try: repeated note detection? (look for change in first derivative of the pitch matrix)
 
-    for (map<int, double>::const_iterator ni = m_pianoRoll[width-1].begin();
-         ni != m_pianoRoll[width-1].end(); ++ni) {
+    std::set<NoteHypothesis> hh = feeder->getAcceptedHypotheses();
 
-        int note = ni->first;
-        
-        if (active.find(note) != active.end()) {
-            // the note is still playing
-            continue;
+    //!!! inefficient
+    for (std::set<NoteHypothesis>::const_iterator hi = hh.begin();
+         hi != hh.end(); ++hi) { 
+
+        NoteHypothesis h(*hi);
+
+        if (m_emitted.find(h) != m_emitted.end()) {
+            continue; // already returned this one
         }
 
-        // the note was playing but just ended
-        int end = width;
-        int start = end-1;
+        m_emitted.insert(h);
 
-        while (m_pianoRoll[start].find(note) != m_pianoRoll[start].end()) {
-            --start;
-        }
-        ++start;
+        NoteHypothesis::Note n = h.getAveragedNote();
 
-        if ((end - start) < durationThreshold) {
-            continue;
-        }
+        int velocity = n.confidence * 127;
+        if (velocity > 127) velocity = 127;
 
-        emitNote(start, end, note, shiftCount, noteFeatures);
+        Feature f;
+        f.hasTimestamp = true;
+        f.hasDuration = true;
+        f.timestamp = n.time;
+        f.duration = n.duration;
+        f.values.clear();
+        f.values.push_back(n.freq);
+        f.values.push_back(velocity);
+//        f.label = noteName(note, partShift, shiftCount);
+        noteFeatures.push_back(f);
     }
 
-//    cerr << "returning " << noteFeatures.size() << " complete note(s) " << endl;
-
     return noteFeatures;
 }
-
-void
-Silvet::emitNote(int start, int end, int note, int shiftCount,
-                 FeatureList &noteFeatures)
-{
-    int partStart = start;
-    int partShift = 0;
-    int partVelocity = 0;
-
-    Feature f;
-    f.hasTimestamp = true;
-    f.hasDuration = true;
-
-    double columnDuration = 1.0 / m_colsPerSec;
-    int postFilterLatency = int(m_postFilter[0]->getSize() / 2);
-    int partThreshold = floor(0.05 / columnDuration);
-
-    for (int i = start; i != end; ++i) {
-        
-        double strength = m_pianoRoll[i][note];
-
-        int shift = 0;
-
-        if (shiftCount > 1) {
-
-            shift = m_pianoRollShifts[i][note];
-
-            if (i == partStart) {
-                partShift = shift;
-            }
-
-            if (i > partStart + partThreshold && shift != partShift) {
-                
-//                cerr << "i = " << i << ", partStart = " << partStart << ", shift = " << shift << ", partShift = " << partShift << endl;
-
-                // pitch has changed, emit an intermediate note
-                f.timestamp = RealTime::fromSeconds
-                    (columnDuration * (partStart - postFilterLatency) + 0.02);
-                f.duration = RealTime::fromSeconds
-                    (columnDuration * (i - partStart));
-                f.values.clear();
-                f.values.push_back
-                    (noteFrequency(note, partShift, shiftCount));
-                f.values.push_back(partVelocity);
-                f.label = noteName(note, partShift, shiftCount);
-                noteFeatures.push_back(f);
-                partStart = i;
-                partShift = shift;
-                partVelocity = 0;
-            }
-        }
-
-        int v = strength * 2;
-        if (v > 127) v = 127;
-
-        if (v > partVelocity) {
-            partVelocity = v;
-        }
-    }
-
-    if (end >= partStart + partThreshold) {
-        f.timestamp = RealTime::fromSeconds
-            (columnDuration * (partStart - postFilterLatency) + 0.02);
-        f.duration = RealTime::fromSeconds
-            (columnDuration * (end - partStart));
-        f.values.clear();
-        f.values.push_back
-            (noteFrequency(note, partShift, shiftCount));
-        f.values.push_back(partVelocity);
-        f.label = noteName(note, partShift, shiftCount);
-        noteFeatures.push_back(f);
-    }
-}
--- a/src/Silvet.h	Fri May 23 17:03:27 2014 +0100
+++ b/src/Silvet.h	Wed May 28 14:54:01 2014 +0100
@@ -24,6 +24,7 @@
 
 #include "MedianFilter.h"
 #include "Instruments.h"
+#include "NoteHypothesis.h"
 
 using std::string;
 using std::vector;
@@ -32,6 +33,7 @@
 
 class Resampler;
 class CQSpectrogram;
+class AgentFeeder;
 
 class Silvet : public Vamp::Plugin
 {
@@ -84,19 +86,18 @@
     typedef vector<vector<double> > Grid;
 
     vector<MedianFilter<double> *> m_postFilter;
-    vector<map<int, double> > m_pianoRoll;
-    vector<map<int, int> > m_pianoRollShifts;
+
+    AgentFeeder *m_agentFeeder;
+    std::set<NoteHypothesis> m_emitted;
 
     Grid preProcess(const Grid &);
 
     void postProcess(const vector<double> &pitches,
                      const vector<int> &bestShifts,
-                     bool wantShifts); // -> piano roll column
+                     bool wantShifts,
+                     int shiftCount); // -> feeder
 
-    FeatureList noteTrack(int shiftCount);
-
-    void emitNote(int start, int end, int note, int shiftCount,
-                  FeatureList &noteFeatures);
+    FeatureList obtainNotes();
 
     FeatureSet transcribe(const Grid &);
 
@@ -104,7 +105,8 @@
     float noteFrequency(int n, int shift, int shiftCount) const;
 
     int m_blockSize;
-    int m_columnCount;
+    int m_columnCountIn;
+    int m_columnCountOut;
     Vamp::RealTime m_startTime;
 
     mutable int m_notesOutputNo;