Revision 32:c88a9972975b
| NoteHypothesis.cpp | ||
|---|---|---|
| 1 |
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
|
| 2 |
/* Copyright Chris Cannam - All Rights Reserved */ |
|
| 3 |
|
|
| 4 |
#include "NoteHypothesis.h" |
|
| 5 |
|
|
| 6 |
#include <cmath> |
|
| 7 |
|
|
| 8 |
#include "system/sysutils.h" |
|
| 9 |
|
|
| 10 |
namespace Turbot {
|
|
| 11 |
|
|
| 12 |
NoteHypothesis::NoteHypothesis() |
|
| 13 |
{
|
|
| 14 |
m_state = New; |
|
| 15 |
} |
|
| 16 |
|
|
| 17 |
NoteHypothesis::~NoteHypothesis() |
|
| 18 |
{
|
|
| 19 |
} |
|
| 20 |
|
|
| 21 |
bool |
|
| 22 |
NoteHypothesis::isWithinTolerance(Estimate s) const |
|
| 23 |
{
|
|
| 24 |
if (m_pending.empty()) {
|
|
| 25 |
return true; |
|
| 26 |
} |
|
| 27 |
|
|
| 28 |
// check we are within a relatively close tolerance of the last |
|
| 29 |
// candidate |
|
| 30 |
Estimate last = m_pending[m_pending.size()-1]; |
|
| 31 |
double r = s.freq / last.freq; |
|
| 32 |
int cents = lrint(1200.0 * (log(r) / log(2.0))); |
|
| 33 |
if (cents < -60 || cents > 60) return false; |
|
| 34 |
|
|
| 35 |
// and within a slightly bigger tolerance of the current mean |
|
| 36 |
double meanFreq = getMeanFrequency(); |
|
| 37 |
r = s.freq / meanFreq; |
|
| 38 |
cents = lrint(1200.0 * (log(r) / log(2.0))); |
|
| 39 |
if (cents < -80 || cents > 80) return false; |
|
| 40 |
|
|
| 41 |
return true; |
|
| 42 |
} |
|
| 43 |
|
|
| 44 |
bool |
|
| 45 |
NoteHypothesis::isOutOfDateFor(Estimate s) const |
|
| 46 |
{
|
|
| 47 |
if (m_pending.empty()) return false; |
|
| 48 |
|
|
| 49 |
return ((s.time - m_pending[m_pending.size()-1].time) > |
|
| 50 |
RealTime::fromMilliseconds(40)); |
|
| 51 |
} |
|
| 52 |
|
|
| 53 |
bool |
|
| 54 |
NoteHypothesis::isSatisfied() const |
|
| 55 |
{
|
|
| 56 |
if (m_pending.empty()) return false; |
|
| 57 |
|
|
| 58 |
double meanConfidence = 0.0; |
|
| 59 |
for (int i = 0; i < (int)m_pending.size(); ++i) {
|
|
| 60 |
meanConfidence += m_pending[i].confidence; |
|
| 61 |
} |
|
| 62 |
meanConfidence /= m_pending.size(); |
|
| 63 |
|
|
| 64 |
int lengthRequired = 10000; |
|
| 65 |
if (meanConfidence > 0.0) {
|
|
| 66 |
lengthRequired = int(2.0 / meanConfidence + 0.5); |
|
| 67 |
} |
|
| 68 |
|
|
| 69 |
return ((int)m_pending.size() > lengthRequired); |
|
| 70 |
} |
|
| 71 |
|
|
| 72 |
bool |
|
| 73 |
NoteHypothesis::accept(Estimate s) |
|
| 74 |
{
|
|
| 75 |
bool accept = false; |
|
| 76 |
|
|
| 77 |
switch (m_state) {
|
|
| 78 |
|
|
| 79 |
case New: |
|
| 80 |
m_state = Provisional; |
|
| 81 |
accept = true; |
|
| 82 |
break; |
|
| 83 |
|
|
| 84 |
case Provisional: |
|
| 85 |
if (isOutOfDateFor(s)) {
|
|
| 86 |
m_state = Rejected; |
|
| 87 |
} else if (isWithinTolerance(s)) {
|
|
| 88 |
accept = true; |
|
| 89 |
} |
|
| 90 |
break; |
|
| 91 |
|
|
| 92 |
case Satisfied: |
|
| 93 |
if (isOutOfDateFor(s)) {
|
|
| 94 |
m_state = Expired; |
|
| 95 |
} else if (isWithinTolerance(s)) {
|
|
| 96 |
accept = true; |
|
| 97 |
} |
|
| 98 |
break; |
|
| 99 |
|
|
| 100 |
case Rejected: |
|
| 101 |
break; |
|
| 102 |
|
|
| 103 |
case Expired: |
|
| 104 |
break; |
|
| 105 |
} |
|
| 106 |
|
|
| 107 |
if (accept) {
|
|
| 108 |
m_pending.push_back(s); |
|
| 109 |
if (m_state == Provisional && isSatisfied()) {
|
|
| 110 |
m_state = Satisfied; |
|
| 111 |
} |
|
| 112 |
} |
|
| 113 |
|
|
| 114 |
return accept; |
|
| 115 |
} |
|
| 116 |
|
|
| 117 |
NoteHypothesis::State |
|
| 118 |
NoteHypothesis::getState() const |
|
| 119 |
{
|
|
| 120 |
return m_state; |
|
| 121 |
} |
|
| 122 |
|
|
| 123 |
NoteHypothesis::Estimates |
|
| 124 |
NoteHypothesis::getAcceptedEstimates() const |
|
| 125 |
{
|
|
| 126 |
if (m_state == Satisfied || m_state == Expired) {
|
|
| 127 |
return m_pending; |
|
| 128 |
} else {
|
|
| 129 |
return Estimates(); |
|
| 130 |
} |
|
| 131 |
} |
|
| 132 |
|
|
| 133 |
double |
|
| 134 |
NoteHypothesis::getMeanFrequency() const |
|
| 135 |
{
|
|
| 136 |
double acc = 0.0; |
|
| 137 |
for (int i = 0; i < (int)m_pending.size(); ++i) {
|
|
| 138 |
acc += m_pending[i].freq; |
|
| 139 |
} |
|
| 140 |
acc /= m_pending.size(); |
|
| 141 |
return acc; |
|
| 142 |
} |
|
| 143 |
|
|
| 144 |
NoteHypothesis::Note |
|
| 145 |
NoteHypothesis::getAveragedNote() const |
|
| 146 |
{
|
|
| 147 |
Note n; |
|
| 148 |
|
|
| 149 |
if (!(m_state == Satisfied || m_state == Expired)) {
|
|
| 150 |
n.freq = 0.0; |
|
| 151 |
n.time = RealTime::zeroTime; |
|
| 152 |
n.duration = RealTime::zeroTime; |
|
| 153 |
return n; |
|
| 154 |
} |
|
| 155 |
|
|
| 156 |
n.time = m_pending.begin()->time; |
|
| 157 |
|
|
| 158 |
Estimates::const_iterator i = m_pending.end(); |
|
| 159 |
--i; |
|
| 160 |
n.duration = i->time - n.time; |
|
| 161 |
|
|
| 162 |
// just mean frequency for now, but this isn't at all right perceptually |
|
| 163 |
n.freq = getMeanFrequency(); |
|
| 164 |
|
|
| 165 |
return n; |
|
| 166 |
} |
|
| 167 |
|
|
| 168 |
} |
|
| 169 |
|
|
| NoteHypothesis.h | ||
|---|---|---|
| 1 |
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ |
|
| 2 |
/* Copyright Chris Cannam - All Rights Reserved */ |
|
| 3 |
|
|
| 4 |
#ifndef _NOTE_HYPOTHESIS_H_ |
|
| 5 |
#define _NOTE_HYPOTHESIS_H_ |
|
| 6 |
|
|
| 7 |
#include "base/RealTime.h" |
|
| 8 |
#include <vector> |
|
| 9 |
|
|
| 10 |
namespace Turbot {
|
|
| 11 |
|
|
| 12 |
/** |
|
| 13 |
* An agent used to test an incoming series of instantaneous pitch |
|
| 14 |
* estimates to see whether they fit a consistent single-note |
|
| 15 |
* relationship. Contains rules specific to testing note pitch and |
|
| 16 |
* timing. |
|
| 17 |
*/ |
|
| 18 |
|
|
| 19 |
class NoteHypothesis |
|
| 20 |
{
|
|
| 21 |
public: |
|
| 22 |
enum State {
|
|
| 23 |
|
|
| 24 |
/// Just constructed, will provisionally accept any estimate |
|
| 25 |
New, |
|
| 26 |
|
|
| 27 |
/// Accepted at least one estimate, but not enough evidence to satisfy |
|
| 28 |
Provisional, |
|
| 29 |
|
|
| 30 |
/// Could not find enough consistency in offered estimates |
|
| 31 |
Rejected, |
|
| 32 |
|
|
| 33 |
/// Have accepted enough consistent estimates to satisfy hypothesis |
|
| 34 |
Satisfied, |
|
| 35 |
|
|
| 36 |
/// Have been satisfied, but evidence has now changed: we're done |
|
| 37 |
Expired |
|
| 38 |
}; |
|
| 39 |
|
|
| 40 |
/** |
|
| 41 |
* Construct an empty hypothesis. This will be in New state and |
|
| 42 |
* will provisionally accept any estimate. |
|
| 43 |
*/ |
|
| 44 |
NoteHypothesis(); |
|
| 45 |
|
|
| 46 |
/** |
|
| 47 |
* Destroy the hypothesis |
|
| 48 |
*/ |
|
| 49 |
~NoteHypothesis(); |
|
| 50 |
|
|
| 51 |
struct Estimate {
|
|
| 52 |
double freq; |
|
| 53 |
RealTime time; |
|
| 54 |
double confidence; |
|
| 55 |
}; |
|
| 56 |
typedef std::vector<Estimate> Estimates; |
|
| 57 |
|
|
| 58 |
/** |
|
| 59 |
* Test the given estimate to see whether it is consistent with |
|
| 60 |
* this hypothesis, and adjust the hypothesis' internal state |
|
| 61 |
* accordingly. If the estimate is not inconsistent with the |
|
| 62 |
* hypothesis, return true. |
|
| 63 |
*/ |
|
| 64 |
bool accept(Estimate); |
|
| 65 |
|
|
| 66 |
/** |
|
| 67 |
* Return the current state of this hypothesis. |
|
| 68 |
*/ |
|
| 69 |
State getState() const; |
|
| 70 |
|
|
| 71 |
/** |
|
| 72 |
* If the hypothesis has been satisfied (i.e. is in Satisfied or |
|
| 73 |
* Expired state), return the series of estimates that it |
|
| 74 |
* accepted. Otherwise return an empty list |
|
| 75 |
*/ |
|
| 76 |
Estimates getAcceptedEstimates() const; |
|
| 77 |
|
|
| 78 |
struct Note {
|
|
| 79 |
double freq; |
|
| 80 |
RealTime time; |
|
| 81 |
RealTime duration; |
|
| 82 |
}; |
|
| 83 |
|
|
| 84 |
/** |
|
| 85 |
* Return a single note roughly matching this hypothesis |
|
| 86 |
*/ |
|
| 87 |
Note getAveragedNote() const; |
|
| 88 |
|
|
| 89 |
private: |
|
| 90 |
bool isWithinTolerance(Estimate) const; |
|
| 91 |
bool isOutOfDateFor(Estimate) const; |
|
| 92 |
bool isSatisfied() const; |
|
| 93 |
double getMeanFrequency() const; |
|
| 94 |
|
|
| 95 |
State m_state; |
|
| 96 |
Estimates m_pending; |
|
| 97 |
}; |
|
| 98 |
|
|
| 99 |
} |
|
| 100 |
|
|
| 101 |
#endif |
|
Also available in: Unified diff