changeset 32:c88a9972975b

Add pitch note-hypothesis class
author Chris Cannam
date Fri, 13 Jul 2012 21:40:50 +0100
parents 2c175adf8736
children 5f2a57b1a75a
files NoteHypothesis.cpp NoteHypothesis.h
diffstat 2 files changed, 270 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/NoteHypothesis.cpp	Fri Jul 13 21:40:50 2012 +0100
@@ -0,0 +1,169 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+/* Copyright Chris Cannam - All Rights Reserved */
+
+#include "NoteHypothesis.h"
+
+#include <cmath>
+
+#include "system/sysutils.h"
+
+namespace Turbot {
+
+NoteHypothesis::NoteHypothesis()
+{
+    m_state = New;
+}
+
+NoteHypothesis::~NoteHypothesis()
+{
+}
+
+bool
+NoteHypothesis::isWithinTolerance(Estimate s) const
+{
+    if (m_pending.empty()) {
+        return true;
+    }
+
+    // check we are within a relatively close tolerance of the last
+    // candidate
+    Estimate last = m_pending[m_pending.size()-1];
+    double r = s.freq / last.freq;
+    int cents = lrint(1200.0 * (log(r) / log(2.0)));
+    if (cents < -60 || cents > 60) return false;
+
+    // and within a slightly bigger tolerance of the current mean
+    double meanFreq = getMeanFrequency();
+    r = s.freq / meanFreq;
+    cents = lrint(1200.0 * (log(r) / log(2.0)));
+    if (cents < -80 || cents > 80) return false;
+    
+    return true;
+}
+
+bool
+NoteHypothesis::isOutOfDateFor(Estimate s) const
+{
+    if (m_pending.empty()) return false;
+
+    return ((s.time - m_pending[m_pending.size()-1].time) > 
+            RealTime::fromMilliseconds(40));
+}
+
+bool 
+NoteHypothesis::isSatisfied() const
+{
+    if (m_pending.empty()) return false;
+    
+    double meanConfidence = 0.0;
+    for (int i = 0; i < (int)m_pending.size(); ++i) {
+        meanConfidence += m_pending[i].confidence;
+    }
+    meanConfidence /= m_pending.size();
+
+    int lengthRequired = 10000;
+    if (meanConfidence > 0.0) {
+        lengthRequired = int(2.0 / meanConfidence + 0.5);
+    }
+
+    return ((int)m_pending.size() > lengthRequired);
+}
+
+bool
+NoteHypothesis::accept(Estimate s)
+{
+    bool accept = 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.push_back(s);
+        if (m_state == Provisional && isSatisfied()) {
+            m_state = Satisfied;
+        }
+    }
+
+    return accept;
+}        
+
+NoteHypothesis::State
+NoteHypothesis::getState() const
+{
+    return m_state;
+}
+
+NoteHypothesis::Estimates
+NoteHypothesis::getAcceptedEstimates() const
+{
+    if (m_state == Satisfied || m_state == Expired) {
+        return m_pending;
+    } else {
+        return Estimates();
+    }
+}
+
+double
+NoteHypothesis::getMeanFrequency() const
+{
+    double acc = 0.0;
+    for (int i = 0; i < (int)m_pending.size(); ++i) {
+        acc += m_pending[i].freq;
+    }
+    acc /= m_pending.size();
+    return acc;
+}
+
+NoteHypothesis::Note
+NoteHypothesis::getAveragedNote() const
+{
+    Note n;
+
+    if (!(m_state == Satisfied || m_state == Expired)) {
+        n.freq = 0.0;
+        n.time = RealTime::zeroTime;
+        n.duration = RealTime::zeroTime;
+        return n;
+    }
+
+    n.time = m_pending.begin()->time;
+
+    Estimates::const_iterator i = m_pending.end();
+    --i;
+    n.duration = i->time - n.time;
+
+    // just mean frequency for now, but this isn't at all right perceptually
+    n.freq = getMeanFrequency();
+    
+    return n;
+}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/NoteHypothesis.h	Fri Jul 13 21:40:50 2012 +0100
@@ -0,0 +1,101 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+/* Copyright Chris Cannam - All Rights Reserved */
+
+#ifndef _NOTE_HYPOTHESIS_H_
+#define _NOTE_HYPOTHESIS_H_
+
+#include "base/RealTime.h"
+#include <vector>
+
+namespace Turbot {
+
+/**
+ * An agent used to test an incoming series of instantaneous pitch
+ * estimates to see whether they fit a consistent single-note
+ * relationship. Contains rules specific to testing note pitch and
+ * timing.
+ */
+
+class NoteHypothesis
+{
+public:
+    enum State {
+
+	/// Just constructed, will provisionally accept any estimate
+	New,
+
+	/// Accepted at least one estimate, but not enough evidence to satisfy
+	Provisional,
+
+	/// Could not find enough consistency in offered estimates
+	Rejected,
+
+	/// Have accepted enough consistent estimates to satisfy hypothesis
+	Satisfied,
+
+	/// Have been satisfied, but evidence has now changed: we're done
+	Expired
+    };
+    
+    /**
+     * Construct an empty hypothesis. This will be in New state and
+     * will provisionally accept any estimate.
+     */
+    NoteHypothesis();
+
+    /**
+     * Destroy the hypothesis
+     */
+    ~NoteHypothesis();
+
+    struct Estimate {
+	double freq;
+	RealTime time;
+	double confidence;
+    };
+    typedef std::vector<Estimate> Estimates;
+
+    /**
+     * Test the given estimate to see whether it is consistent with
+     * this hypothesis, and adjust the hypothesis' internal state
+     * accordingly. If the estimate is not inconsistent with the
+     * hypothesis, return true.
+     */
+    bool accept(Estimate);
+
+    /**
+     * Return the current state of this hypothesis.
+     */
+    State getState() const;
+
+    /**
+     * If the hypothesis has been satisfied (i.e. is in Satisfied or
+     * Expired state), return the series of estimates that it
+     * accepted. Otherwise return an empty list
+     */
+    Estimates getAcceptedEstimates() const;
+
+    struct Note {
+	double freq;
+	RealTime time;
+	RealTime duration;
+    };
+    
+    /**
+     * Return a single note roughly matching this hypothesis
+     */
+    Note getAveragedNote() const;
+    
+private:
+    bool isWithinTolerance(Estimate) const;
+    bool isOutOfDateFor(Estimate) const;
+    bool isSatisfied() const;
+    double getMeanFrequency() const;
+    
+    State m_state;
+    Estimates m_pending;
+};
+
+}
+
+#endif