diff src/NoteHypothesis.cpp @ 181:10e7c3ff575e noteagent

Experimental branch toward note-agent stuff (not actually plumbed in yet)
author Chris Cannam
date Fri, 23 May 2014 12:40:18 +0100
parents
children e1718e64a921
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/NoteHypothesis.cpp	Fri May 23 12:40:18 2014 +0100
@@ -0,0 +1,356 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+  Silvet
+
+  A Vamp plugin for note transcription.
+  Centre for Digital Music, Queen Mary University of London.
+  This file Copyright 2012 Chris Cannam.
+    
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU General Public License as
+  published by the Free Software Foundation; either version 2 of the
+  License, or (at your option) any later version.  See the file
+  COPYING included with this distribution for more information.
+*/
+
+#include "NoteHypothesis.h"
+#include "AgentFeederPoly.h"
+
+#include <cmath>
+#include <cassert>
+
+#include <map>
+
+using Vamp::RealTime;
+
+//#define DEBUG_NOTE_HYPOTHESIS 1
+
+NoteHypothesis::NoteHypothesis()
+{
+    m_state = New;
+}
+
+NoteHypothesis::~NoteHypothesis()
+{
+}
+
+bool
+NoteHypothesis::isWithinTolerance(Observation s) const
+{
+    if (m_pending.empty()) {
+        return true;
+    }
+
+    // check we are within a relatively close tolerance of the last
+    // candidate
+    Observations::const_iterator i = m_pending.end();
+    --i;
+    Observation last = *i;
+    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;
+#endif
+    if (cents < -60 || cents > 60) return false;
+
+    // and within a slightly bigger tolerance of the current mean
+    double meanFreq = getMeanFrequency();
+    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;
+#endif
+    if (cents < -80 || cents > 80) return false;
+    
+    return true;
+}
+
+bool
+NoteHypothesis::isOutOfDateFor(Observation s) const
+{
+    if (m_pending.empty()) return false;
+
+    Observations::const_iterator i = m_pending.end();
+    --i;
+    Observation last = *i;
+
+#ifdef DEBUG_NOTE_HYPOTHESIS
+    std::cerr << "isOutOfDateFor: this " << s.time << " is "
+              << (s.time - last.time) << " from last " << last.time
+              << " (threshold " << RealTime::fromMilliseconds(40) << ")"
+              << std::endl;
+#endif
+
+    return ((s.time - last.time) > RealTime::fromMilliseconds(40));
+}
+
+bool 
+NoteHypothesis::isSatisfied() const
+{
+    if (m_pending.empty()) return false;
+    
+    double meanConfidence = 0.0;
+    for (Observations::const_iterator i = m_pending.begin();
+         i != m_pending.end(); ++i) {
+        meanConfidence += i->confidence;
+    }
+    meanConfidence /= m_pending.size();
+
+    int lengthRequired = 100;
+    if (meanConfidence > 0.0) {
+        lengthRequired = int(2.0 / meanConfidence + 0.5);
+    }
+//    if (lengthRequired < 1) lengthRequired = 1;
+
+#ifdef DEBUG_NOTE_HYPOTHESIS
+    std::cerr << "meanConfidence " << meanConfidence << ", lengthRequired " << lengthRequired << std::endl;
+#endif
+
+    return ((int)m_pending.size() > lengthRequired);
+}
+
+bool
+NoteHypothesis::accept(Observation s)
+{
+    bool accept = false;
+
+#ifdef DEBUG_NOTE_HYPOTHESIS
+    std::cerr << "NoteHypothesis[" << this << "]::accept: state " << m_state << "..." << std::endl;
+#endif
+
+    static double negligibleConfidence = 0.0001;
+
+    if (s.confidence < negligibleConfidence) {
+        // avoid piling up a lengthy sequence of estimates that are
+        // all acceptable but are in total not enough to cause us to
+        // be satisfied
+        if (m_state == New) {
+            m_state = Rejected;
+        }
+        return false;
+    }
+
+    switch (m_state) {
+
+    case New:
+        m_state = Provisional;
+        accept = true;
+        break;
+
+    case Provisional:
+        if (isOutOfDateFor(s)) {
+            m_state = Rejected;
+        } else if (isWithinTolerance(s)) {
+            accept = true;
+        }
+        break;
+        
+    case Satisfied:
+        if (isOutOfDateFor(s)) {
+            m_state = Expired;
+        } else if (isWithinTolerance(s)) {
+            accept = true;
+        }
+        break;
+
+    case Rejected:
+        break;
+
+    case Expired:
+        break;
+    }
+
+    if (accept) {
+        m_pending.insert(s);
+        if (m_state == Provisional && isSatisfied()) {
+            m_state = Satisfied;
+        }
+    }
+
+#ifdef DEBUG_NOTE_HYPOTHESIS
+    std::cerr << "... -> " << m_state << " (pending: " << m_pending.size() << ")" << std::endl;
+#endif
+
+    return accept;
+}        
+
+NoteHypothesis::State
+NoteHypothesis::getState() const
+{
+    return m_state;
+}
+
+NoteHypothesis::Observations
+NoteHypothesis::getAcceptedObservations() const
+{
+    if (m_state == Satisfied || m_state == Expired) {
+        return m_pending;
+    } else {
+        return Observations();
+    }
+}
+
+double
+NoteHypothesis::getMeanFrequency() const
+{
+    double acc = 0.0;
+    if (m_pending.empty()) return acc;
+    for (Observations::const_iterator i = m_pending.begin();
+         i != m_pending.end(); ++i) {
+        acc += i->value;
+    }
+    acc /= m_pending.size();
+    return acc;
+}
+
+NoteHypothesis::Note
+NoteHypothesis::getAveragedNote() const
+{
+    Note n;
+
+    n.time = getStartTime();
+    n.duration = getDuration();
+
+    // just mean frequency for now, but this isn't at all right perceptually
+    n.freq = getMeanFrequency();
+    
+    return n;
+}
+
+RealTime
+NoteHypothesis::getStartTime() const
+{
+    if (!(m_state == Satisfied || m_state == Expired)) {
+        return RealTime::zeroTime;
+    } else {
+        return m_pending.begin()->time;
+    }
+}
+
+RealTime
+NoteHypothesis::getDuration() const
+{
+//!!! test this! it is wrong
+    if (!(m_state == Satisfied || m_state == Expired)) {
+        return RealTime::zeroTime;
+    } else {
+        RealTime start = m_pending.begin()->time;
+        Observations::const_iterator i = m_pending.end();
+        --i;
+        return i->time - start;
+    }
+}
+
+std::vector<double>
+NoteHypothesis::sample(const std::set<NoteHypothesis> &notes,
+                       RealTime startTime,
+                       RealTime endTime,
+                       RealTime interval)
+{
+    //!!! where should this live? in AgentHypothesis? a Feeder class?
+
+    assert(interval > RealTime::zeroTime);
+
+    Observations obs = flatten(notes);
+    Observations::const_iterator oi = obs.begin();
+
+//    std::cerr << "sample: start " << startTime << " end " << endTime << " interval " << interval << std::endl;
+
+//    std::cerr << "sample: flatten gives " << obs.size() << " observations" << std::endl;
+    
+    std::vector<double> samples;
+
+    RealTime obsInterval;
+
+    RealTime t = startTime;
+
+    while (oi != obs.end()) {
+
+        Observation o = *oi;
+
+        if (obsInterval == RealTime()) {
+            //!!! should pull out a function to establish this from the list
+            Observations::const_iterator oj = oi;
+            ++oj;
+            if (oj != obs.end()) {
+                obsInterval = oj->time - o.time;
+            }
+        }
+
+//        std::cerr << "t = " << t << ", o.time = " << o.time << ", interval = " << interval << ", obsInterval = " << obsInterval << std::endl;
+
+        if (t > endTime) {
+            break;
+        } else if (o.time > t) {
+            samples.push_back(0.0);
+            t = t + interval;
+        } else if (o.time + obsInterval <= t) {
+            ++oi;
+        } else {
+            samples.push_back(o.value);
+            t = t + interval;
+        }
+    }
+
+    while (1) {
+//        std::cerr << "t = " << t << std::endl;
+        if (t > endTime) {
+            break;
+        } else {
+            samples.push_back(0.0);
+            t = t + interval;
+        }
+    }
+
+    return samples;
+}
+
+std::vector<double>
+NoteHypothesis::winnow(const Observations &obs,
+                       RealTime startTime,
+                       RealTime endTime,
+                       RealTime interval)
+{
+    AgentFeederPoly<NoteHypothesis> feeder;
+    
+#ifdef DEBUG_NOTE_HYPOTHESIS
+    std::cerr << "winnow: " << obs.size() << " input observations"
+              << std::endl;
+    int nonzero = 0;
+#endif
+
+    for (Observations::const_iterator i = obs.begin();
+         i != obs.end(); ++i) {
+        if (i->value != 0.0) { // 0.0 is a special unvoiced value
+            feeder.feed(*i);
+#ifdef DEBUG_NOTE_HYPOTHESIS
+            std::cerr << i->value << "  ";
+            ++nonzero;
+            if (nonzero % 6 == 0) {
+                std::cerr << std::endl;
+            }
+#endif
+        }
+    }
+    
+#ifdef DEBUG_NOTE_HYPOTHESIS
+    std::cerr << "winnow: " << nonzero << " non-zero"
+              << std::endl;
+#endif
+
+    feeder.finish();
+
+    AgentFeederPoly<NoteHypothesis>::Hypotheses accepted =
+        feeder.getAcceptedHypotheses();
+    
+#ifdef DEBUG_NOTE_HYPOTHESIS
+    std::cerr << "winnow: " << accepted.size() << " accepted hypotheses"
+              << std::endl;
+#endif
+
+    return NoteHypothesis::sample(accepted, startTime, endTime, interval);
+}
+