annotate src/NoteHypothesis.cpp @ 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 462b165c8c0f
rev   line source
Chris@181 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@181 2
Chris@181 3 /*
Chris@181 4 Silvet
Chris@181 5
Chris@181 6 A Vamp plugin for note transcription.
Chris@181 7 Centre for Digital Music, Queen Mary University of London.
Chris@181 8 This file Copyright 2012 Chris Cannam.
Chris@181 9
Chris@181 10 This program is free software; you can redistribute it and/or
Chris@181 11 modify it under the terms of the GNU General Public License as
Chris@181 12 published by the Free Software Foundation; either version 2 of the
Chris@181 13 License, or (at your option) any later version. See the file
Chris@181 14 COPYING included with this distribution for more information.
Chris@181 15 */
Chris@181 16
Chris@181 17 #include "NoteHypothesis.h"
Chris@181 18 #include "AgentFeederPoly.h"
Chris@181 19
Chris@181 20 #include <cmath>
Chris@181 21 #include <cassert>
Chris@181 22
Chris@181 23 #include <map>
Chris@184 24 #include <algorithm>
Chris@181 25
Chris@181 26 using Vamp::RealTime;
Chris@181 27
Chris@184 28 using std::cerr;
Chris@184 29 using std::endl;
Chris@184 30
Chris@184 31 #define DEBUG_NOTE_HYPOTHESIS 1
Chris@181 32
Chris@181 33 NoteHypothesis::NoteHypothesis()
Chris@181 34 {
Chris@181 35 m_state = New;
Chris@181 36 }
Chris@181 37
Chris@181 38 NoteHypothesis::~NoteHypothesis()
Chris@181 39 {
Chris@181 40 }
Chris@181 41
Chris@181 42 bool
Chris@181 43 NoteHypothesis::isWithinTolerance(Observation s) const
Chris@181 44 {
Chris@181 45 if (m_pending.empty()) {
Chris@181 46 return true;
Chris@181 47 }
Chris@181 48
Chris@181 49 // check we are within a relatively close tolerance of the last
Chris@181 50 // candidate
Chris@181 51 Observations::const_iterator i = m_pending.end();
Chris@181 52 --i;
Chris@181 53 Observation last = *i;
Chris@181 54 double r = s.value / last.value;
Chris@181 55 int cents = lrint(1200.0 * (log(r) / log(2.0)));
Chris@181 56 #ifdef DEBUG_NOTE_HYPOTHESIS
Chris@184 57 cerr << "isWithinTolerance: this " << s.value << " is " << cents
Chris@184 58 << " cents from prior " << last.value << endl;
Chris@181 59 #endif
Chris@181 60 if (cents < -60 || cents > 60) return false;
Chris@181 61
Chris@181 62 // and within a slightly bigger tolerance of the current mean
Chris@181 63 double meanFreq = getMeanFrequency();
Chris@181 64 r = s.value / meanFreq;
Chris@181 65 cents = lrint(1200.0 * (log(r) / log(2.0)));
Chris@181 66 #ifdef DEBUG_NOTE_HYPOTHESIS
Chris@184 67 cerr << "isWithinTolerance: this " << s.value << " is " << cents
Chris@184 68 << " cents from mean " << meanFreq << endl;
Chris@181 69 #endif
Chris@181 70 if (cents < -80 || cents > 80) return false;
Chris@181 71
Chris@181 72 return true;
Chris@181 73 }
Chris@181 74
Chris@181 75 bool
Chris@181 76 NoteHypothesis::isOutOfDateFor(Observation s) const
Chris@181 77 {
Chris@181 78 if (m_pending.empty()) return false;
Chris@181 79
Chris@181 80 Observations::const_iterator i = m_pending.end();
Chris@181 81 --i;
Chris@181 82 Observation last = *i;
Chris@181 83
Chris@181 84 #ifdef DEBUG_NOTE_HYPOTHESIS
Chris@184 85 cerr << "isOutOfDateFor: this " << s.time << " is "
Chris@181 86 << (s.time - last.time) << " from last " << last.time
Chris@181 87 << " (threshold " << RealTime::fromMilliseconds(40) << ")"
Chris@184 88 << endl;
Chris@181 89 #endif
Chris@181 90
Chris@181 91 return ((s.time - last.time) > RealTime::fromMilliseconds(40));
Chris@181 92 }
Chris@181 93
Chris@181 94 bool
Chris@181 95 NoteHypothesis::isSatisfied() const
Chris@181 96 {
Chris@181 97 if (m_pending.empty()) return false;
Chris@181 98
Chris@181 99 double meanConfidence = 0.0;
Chris@181 100 for (Observations::const_iterator i = m_pending.begin();
Chris@181 101 i != m_pending.end(); ++i) {
Chris@181 102 meanConfidence += i->confidence;
Chris@181 103 }
Chris@181 104 meanConfidence /= m_pending.size();
Chris@181 105
Chris@184 106 //!!! surely this depends on the hop size?
Chris@181 107 int lengthRequired = 100;
Chris@181 108 if (meanConfidence > 0.0) {
Chris@181 109 lengthRequired = int(2.0 / meanConfidence + 0.5);
Chris@181 110 }
Chris@184 111 //!!!
Chris@184 112 lengthRequired = lengthRequired / 2;
Chris@184 113 if (lengthRequired < 1) lengthRequired = 1;
Chris@181 114
Chris@181 115 #ifdef DEBUG_NOTE_HYPOTHESIS
Chris@184 116 cerr << "meanConfidence " << meanConfidence << ", lengthRequired " << lengthRequired << endl;
Chris@181 117 #endif
Chris@181 118
Chris@181 119 return ((int)m_pending.size() > lengthRequired);
Chris@181 120 }
Chris@181 121
Chris@184 122 static void printState(NoteHypothesis::State s)
Chris@184 123 {
Chris@184 124 switch (s) {
Chris@184 125 case NoteHypothesis::New: cerr << "New"; break;
Chris@184 126 case NoteHypothesis::Provisional: cerr << "Provisional"; break;
Chris@184 127 case NoteHypothesis::Rejected: cerr << "Rejected"; break;
Chris@184 128 case NoteHypothesis::Satisfied: cerr << "Satisfied"; break;
Chris@184 129 case NoteHypothesis::Expired: cerr << "Expired"; break;
Chris@184 130 }
Chris@184 131 }
Chris@184 132
Chris@181 133 bool
Chris@181 134 NoteHypothesis::accept(Observation s)
Chris@181 135 {
Chris@181 136 bool accept = false;
Chris@181 137
Chris@181 138 #ifdef DEBUG_NOTE_HYPOTHESIS
Chris@184 139 cerr << "NoteHypothesis[" << this << "]::accept (value " << s.value << ", time " << s.time << ", confidence " << s.confidence << "): state ";
Chris@184 140 printState(m_state);
Chris@184 141 cerr << "..." << endl;
Chris@181 142 #endif
Chris@181 143
Chris@181 144 static double negligibleConfidence = 0.0001;
Chris@181 145
Chris@181 146 if (s.confidence < negligibleConfidence) {
Chris@181 147 // avoid piling up a lengthy sequence of estimates that are
Chris@181 148 // all acceptable but are in total not enough to cause us to
Chris@181 149 // be satisfied
Chris@181 150 if (m_state == New) {
Chris@181 151 m_state = Rejected;
Chris@181 152 }
Chris@181 153 return false;
Chris@181 154 }
Chris@181 155
Chris@181 156 switch (m_state) {
Chris@181 157
Chris@181 158 case New:
Chris@181 159 m_state = Provisional;
Chris@181 160 accept = true;
Chris@181 161 break;
Chris@181 162
Chris@181 163 case Provisional:
Chris@181 164 if (isOutOfDateFor(s)) {
Chris@181 165 m_state = Rejected;
Chris@181 166 } else if (isWithinTolerance(s)) {
Chris@181 167 accept = true;
Chris@181 168 }
Chris@181 169 break;
Chris@181 170
Chris@181 171 case Satisfied:
Chris@181 172 if (isOutOfDateFor(s)) {
Chris@181 173 m_state = Expired;
Chris@181 174 } else if (isWithinTolerance(s)) {
Chris@181 175 accept = true;
Chris@181 176 }
Chris@181 177 break;
Chris@181 178
Chris@181 179 case Rejected:
Chris@181 180 break;
Chris@181 181
Chris@181 182 case Expired:
Chris@181 183 break;
Chris@181 184 }
Chris@181 185
Chris@181 186 if (accept) {
Chris@184 187 #ifdef DEBUG_NOTE_HYPOTHESIS
Chris@184 188 cerr << "... accepting" << endl;
Chris@184 189 #endif
Chris@181 190 m_pending.insert(s);
Chris@181 191 if (m_state == Provisional && isSatisfied()) {
Chris@181 192 m_state = Satisfied;
Chris@181 193 }
Chris@184 194 } else {
Chris@184 195 #ifdef DEBUG_NOTE_HYPOTHESIS
Chris@184 196 cerr << "... not accepting" << endl;
Chris@184 197 #endif
Chris@181 198 }
Chris@181 199
Chris@181 200 #ifdef DEBUG_NOTE_HYPOTHESIS
Chris@184 201 cerr << "... -> ";
Chris@184 202 printState(m_state);
Chris@184 203 cerr << " (pending: " << m_pending.size() << ")" << endl;
Chris@181 204 #endif
Chris@181 205
Chris@181 206 return accept;
Chris@181 207 }
Chris@181 208
Chris@181 209 NoteHypothesis::State
Chris@181 210 NoteHypothesis::getState() const
Chris@181 211 {
Chris@181 212 return m_state;
Chris@181 213 }
Chris@181 214
Chris@181 215 NoteHypothesis::Observations
Chris@181 216 NoteHypothesis::getAcceptedObservations() const
Chris@181 217 {
Chris@181 218 if (m_state == Satisfied || m_state == Expired) {
Chris@181 219 return m_pending;
Chris@181 220 } else {
Chris@181 221 return Observations();
Chris@181 222 }
Chris@181 223 }
Chris@181 224
Chris@181 225 double
Chris@184 226 NoteHypothesis::getMedianFrequency() const
Chris@184 227 {
Chris@184 228 if (m_pending.empty()) return 0.0;
Chris@184 229 std::vector<double> freqs;
Chris@184 230 for (Observations::const_iterator i = m_pending.begin();
Chris@184 231 i != m_pending.end(); ++i) {
Chris@184 232 freqs.push_back(i->value);
Chris@184 233 }
Chris@184 234 std::sort(freqs.begin(), freqs.end());
Chris@184 235 return freqs[freqs.size()/2];
Chris@184 236 }
Chris@184 237
Chris@184 238 double
Chris@181 239 NoteHypothesis::getMeanFrequency() const
Chris@181 240 {
Chris@181 241 double acc = 0.0;
Chris@181 242 if (m_pending.empty()) return acc;
Chris@181 243 for (Observations::const_iterator i = m_pending.begin();
Chris@181 244 i != m_pending.end(); ++i) {
Chris@181 245 acc += i->value;
Chris@181 246 }
Chris@181 247 acc /= m_pending.size();
Chris@181 248 return acc;
Chris@181 249 }
Chris@181 250
Chris@184 251 double
Chris@184 252 NoteHypothesis::getMedianConfidence() const
Chris@184 253 {
Chris@184 254 if (m_pending.empty()) return 0.0;
Chris@184 255 std::vector<double> confs;
Chris@184 256 for (Observations::const_iterator i = m_pending.begin();
Chris@184 257 i != m_pending.end(); ++i) {
Chris@184 258 confs.push_back(i->confidence);
Chris@184 259 }
Chris@184 260 std::sort(confs.begin(), confs.end());
Chris@184 261 return confs[confs.size()/2];
Chris@184 262 }
Chris@184 263
Chris@181 264 NoteHypothesis::Note
Chris@181 265 NoteHypothesis::getAveragedNote() const
Chris@181 266 {
Chris@181 267 Note n;
Chris@181 268
Chris@181 269 n.time = getStartTime();
Chris@181 270 n.duration = getDuration();
Chris@184 271 n.freq = getMedianFrequency();
Chris@184 272 n.confidence = getMedianConfidence();
Chris@181 273
Chris@181 274 return n;
Chris@181 275 }
Chris@181 276
Chris@181 277 RealTime
Chris@181 278 NoteHypothesis::getStartTime() const
Chris@181 279 {
Chris@181 280 if (!(m_state == Satisfied || m_state == Expired)) {
Chris@181 281 return RealTime::zeroTime;
Chris@181 282 } else {
Chris@181 283 return m_pending.begin()->time;
Chris@181 284 }
Chris@181 285 }
Chris@181 286
Chris@181 287 RealTime
Chris@181 288 NoteHypothesis::getDuration() const
Chris@181 289 {
Chris@181 290 //!!! test this! it is wrong
Chris@181 291 if (!(m_state == Satisfied || m_state == Expired)) {
Chris@181 292 return RealTime::zeroTime;
Chris@181 293 } else {
Chris@181 294 RealTime start = m_pending.begin()->time;
Chris@181 295 Observations::const_iterator i = m_pending.end();
Chris@181 296 --i;
Chris@181 297 return i->time - start;
Chris@181 298 }
Chris@181 299 }