annotate 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
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@181 24
Chris@181 25 using Vamp::RealTime;
Chris@181 26
Chris@181 27 //#define DEBUG_NOTE_HYPOTHESIS 1
Chris@181 28
Chris@181 29 NoteHypothesis::NoteHypothesis()
Chris@181 30 {
Chris@181 31 m_state = New;
Chris@181 32 }
Chris@181 33
Chris@181 34 NoteHypothesis::~NoteHypothesis()
Chris@181 35 {
Chris@181 36 }
Chris@181 37
Chris@181 38 bool
Chris@181 39 NoteHypothesis::isWithinTolerance(Observation s) const
Chris@181 40 {
Chris@181 41 if (m_pending.empty()) {
Chris@181 42 return true;
Chris@181 43 }
Chris@181 44
Chris@181 45 // check we are within a relatively close tolerance of the last
Chris@181 46 // candidate
Chris@181 47 Observations::const_iterator i = m_pending.end();
Chris@181 48 --i;
Chris@181 49 Observation last = *i;
Chris@181 50 double r = s.value / last.value;
Chris@181 51 int cents = lrint(1200.0 * (log(r) / log(2.0)));
Chris@181 52 #ifdef DEBUG_NOTE_HYPOTHESIS
Chris@181 53 std::cerr << "isWithinTolerance: this " << s.value << " is " << cents
Chris@181 54 << " cents from prior " << last.value << std::endl;
Chris@181 55 #endif
Chris@181 56 if (cents < -60 || cents > 60) return false;
Chris@181 57
Chris@181 58 // and within a slightly bigger tolerance of the current mean
Chris@181 59 double meanFreq = getMeanFrequency();
Chris@181 60 r = s.value / meanFreq;
Chris@181 61 cents = lrint(1200.0 * (log(r) / log(2.0)));
Chris@181 62 #ifdef DEBUG_NOTE_HYPOTHESIS
Chris@181 63 std::cerr << "isWithinTolerance: this " << s.value << " is " << cents
Chris@181 64 << " cents from mean " << meanFreq << std::endl;
Chris@181 65 #endif
Chris@181 66 if (cents < -80 || cents > 80) return false;
Chris@181 67
Chris@181 68 return true;
Chris@181 69 }
Chris@181 70
Chris@181 71 bool
Chris@181 72 NoteHypothesis::isOutOfDateFor(Observation s) const
Chris@181 73 {
Chris@181 74 if (m_pending.empty()) return false;
Chris@181 75
Chris@181 76 Observations::const_iterator i = m_pending.end();
Chris@181 77 --i;
Chris@181 78 Observation last = *i;
Chris@181 79
Chris@181 80 #ifdef DEBUG_NOTE_HYPOTHESIS
Chris@181 81 std::cerr << "isOutOfDateFor: this " << s.time << " is "
Chris@181 82 << (s.time - last.time) << " from last " << last.time
Chris@181 83 << " (threshold " << RealTime::fromMilliseconds(40) << ")"
Chris@181 84 << std::endl;
Chris@181 85 #endif
Chris@181 86
Chris@181 87 return ((s.time - last.time) > RealTime::fromMilliseconds(40));
Chris@181 88 }
Chris@181 89
Chris@181 90 bool
Chris@181 91 NoteHypothesis::isSatisfied() const
Chris@181 92 {
Chris@181 93 if (m_pending.empty()) return false;
Chris@181 94
Chris@181 95 double meanConfidence = 0.0;
Chris@181 96 for (Observations::const_iterator i = m_pending.begin();
Chris@181 97 i != m_pending.end(); ++i) {
Chris@181 98 meanConfidence += i->confidence;
Chris@181 99 }
Chris@181 100 meanConfidence /= m_pending.size();
Chris@181 101
Chris@181 102 int lengthRequired = 100;
Chris@181 103 if (meanConfidence > 0.0) {
Chris@181 104 lengthRequired = int(2.0 / meanConfidence + 0.5);
Chris@181 105 }
Chris@181 106 // if (lengthRequired < 1) lengthRequired = 1;
Chris@181 107
Chris@181 108 #ifdef DEBUG_NOTE_HYPOTHESIS
Chris@181 109 std::cerr << "meanConfidence " << meanConfidence << ", lengthRequired " << lengthRequired << std::endl;
Chris@181 110 #endif
Chris@181 111
Chris@181 112 return ((int)m_pending.size() > lengthRequired);
Chris@181 113 }
Chris@181 114
Chris@181 115 bool
Chris@181 116 NoteHypothesis::accept(Observation s)
Chris@181 117 {
Chris@181 118 bool accept = false;
Chris@181 119
Chris@181 120 #ifdef DEBUG_NOTE_HYPOTHESIS
Chris@181 121 std::cerr << "NoteHypothesis[" << this << "]::accept: state " << m_state << "..." << std::endl;
Chris@181 122 #endif
Chris@181 123
Chris@181 124 static double negligibleConfidence = 0.0001;
Chris@181 125
Chris@181 126 if (s.confidence < negligibleConfidence) {
Chris@181 127 // avoid piling up a lengthy sequence of estimates that are
Chris@181 128 // all acceptable but are in total not enough to cause us to
Chris@181 129 // be satisfied
Chris@181 130 if (m_state == New) {
Chris@181 131 m_state = Rejected;
Chris@181 132 }
Chris@181 133 return false;
Chris@181 134 }
Chris@181 135
Chris@181 136 switch (m_state) {
Chris@181 137
Chris@181 138 case New:
Chris@181 139 m_state = Provisional;
Chris@181 140 accept = true;
Chris@181 141 break;
Chris@181 142
Chris@181 143 case Provisional:
Chris@181 144 if (isOutOfDateFor(s)) {
Chris@181 145 m_state = Rejected;
Chris@181 146 } else if (isWithinTolerance(s)) {
Chris@181 147 accept = true;
Chris@181 148 }
Chris@181 149 break;
Chris@181 150
Chris@181 151 case Satisfied:
Chris@181 152 if (isOutOfDateFor(s)) {
Chris@181 153 m_state = Expired;
Chris@181 154 } else if (isWithinTolerance(s)) {
Chris@181 155 accept = true;
Chris@181 156 }
Chris@181 157 break;
Chris@181 158
Chris@181 159 case Rejected:
Chris@181 160 break;
Chris@181 161
Chris@181 162 case Expired:
Chris@181 163 break;
Chris@181 164 }
Chris@181 165
Chris@181 166 if (accept) {
Chris@181 167 m_pending.insert(s);
Chris@181 168 if (m_state == Provisional && isSatisfied()) {
Chris@181 169 m_state = Satisfied;
Chris@181 170 }
Chris@181 171 }
Chris@181 172
Chris@181 173 #ifdef DEBUG_NOTE_HYPOTHESIS
Chris@181 174 std::cerr << "... -> " << m_state << " (pending: " << m_pending.size() << ")" << std::endl;
Chris@181 175 #endif
Chris@181 176
Chris@181 177 return accept;
Chris@181 178 }
Chris@181 179
Chris@181 180 NoteHypothesis::State
Chris@181 181 NoteHypothesis::getState() const
Chris@181 182 {
Chris@181 183 return m_state;
Chris@181 184 }
Chris@181 185
Chris@181 186 NoteHypothesis::Observations
Chris@181 187 NoteHypothesis::getAcceptedObservations() const
Chris@181 188 {
Chris@181 189 if (m_state == Satisfied || m_state == Expired) {
Chris@181 190 return m_pending;
Chris@181 191 } else {
Chris@181 192 return Observations();
Chris@181 193 }
Chris@181 194 }
Chris@181 195
Chris@181 196 double
Chris@181 197 NoteHypothesis::getMeanFrequency() const
Chris@181 198 {
Chris@181 199 double acc = 0.0;
Chris@181 200 if (m_pending.empty()) return acc;
Chris@181 201 for (Observations::const_iterator i = m_pending.begin();
Chris@181 202 i != m_pending.end(); ++i) {
Chris@181 203 acc += i->value;
Chris@181 204 }
Chris@181 205 acc /= m_pending.size();
Chris@181 206 return acc;
Chris@181 207 }
Chris@181 208
Chris@181 209 NoteHypothesis::Note
Chris@181 210 NoteHypothesis::getAveragedNote() const
Chris@181 211 {
Chris@181 212 Note n;
Chris@181 213
Chris@181 214 n.time = getStartTime();
Chris@181 215 n.duration = getDuration();
Chris@181 216
Chris@181 217 // just mean frequency for now, but this isn't at all right perceptually
Chris@181 218 n.freq = getMeanFrequency();
Chris@181 219
Chris@181 220 return n;
Chris@181 221 }
Chris@181 222
Chris@181 223 RealTime
Chris@181 224 NoteHypothesis::getStartTime() const
Chris@181 225 {
Chris@181 226 if (!(m_state == Satisfied || m_state == Expired)) {
Chris@181 227 return RealTime::zeroTime;
Chris@181 228 } else {
Chris@181 229 return m_pending.begin()->time;
Chris@181 230 }
Chris@181 231 }
Chris@181 232
Chris@181 233 RealTime
Chris@181 234 NoteHypothesis::getDuration() const
Chris@181 235 {
Chris@181 236 //!!! test this! it is wrong
Chris@181 237 if (!(m_state == Satisfied || m_state == Expired)) {
Chris@181 238 return RealTime::zeroTime;
Chris@181 239 } else {
Chris@181 240 RealTime start = m_pending.begin()->time;
Chris@181 241 Observations::const_iterator i = m_pending.end();
Chris@181 242 --i;
Chris@181 243 return i->time - start;
Chris@181 244 }
Chris@181 245 }
Chris@181 246
Chris@181 247 std::vector<double>
Chris@181 248 NoteHypothesis::sample(const std::set<NoteHypothesis> &notes,
Chris@181 249 RealTime startTime,
Chris@181 250 RealTime endTime,
Chris@181 251 RealTime interval)
Chris@181 252 {
Chris@181 253 //!!! where should this live? in AgentHypothesis? a Feeder class?
Chris@181 254
Chris@181 255 assert(interval > RealTime::zeroTime);
Chris@181 256
Chris@181 257 Observations obs = flatten(notes);
Chris@181 258 Observations::const_iterator oi = obs.begin();
Chris@181 259
Chris@181 260 // std::cerr << "sample: start " << startTime << " end " << endTime << " interval " << interval << std::endl;
Chris@181 261
Chris@181 262 // std::cerr << "sample: flatten gives " << obs.size() << " observations" << std::endl;
Chris@181 263
Chris@181 264 std::vector<double> samples;
Chris@181 265
Chris@181 266 RealTime obsInterval;
Chris@181 267
Chris@181 268 RealTime t = startTime;
Chris@181 269
Chris@181 270 while (oi != obs.end()) {
Chris@181 271
Chris@181 272 Observation o = *oi;
Chris@181 273
Chris@181 274 if (obsInterval == RealTime()) {
Chris@181 275 //!!! should pull out a function to establish this from the list
Chris@181 276 Observations::const_iterator oj = oi;
Chris@181 277 ++oj;
Chris@181 278 if (oj != obs.end()) {
Chris@181 279 obsInterval = oj->time - o.time;
Chris@181 280 }
Chris@181 281 }
Chris@181 282
Chris@181 283 // std::cerr << "t = " << t << ", o.time = " << o.time << ", interval = " << interval << ", obsInterval = " << obsInterval << std::endl;
Chris@181 284
Chris@181 285 if (t > endTime) {
Chris@181 286 break;
Chris@181 287 } else if (o.time > t) {
Chris@181 288 samples.push_back(0.0);
Chris@181 289 t = t + interval;
Chris@181 290 } else if (o.time + obsInterval <= t) {
Chris@181 291 ++oi;
Chris@181 292 } else {
Chris@181 293 samples.push_back(o.value);
Chris@181 294 t = t + interval;
Chris@181 295 }
Chris@181 296 }
Chris@181 297
Chris@181 298 while (1) {
Chris@181 299 // std::cerr << "t = " << t << std::endl;
Chris@181 300 if (t > endTime) {
Chris@181 301 break;
Chris@181 302 } else {
Chris@181 303 samples.push_back(0.0);
Chris@181 304 t = t + interval;
Chris@181 305 }
Chris@181 306 }
Chris@181 307
Chris@181 308 return samples;
Chris@181 309 }
Chris@181 310
Chris@181 311 std::vector<double>
Chris@181 312 NoteHypothesis::winnow(const Observations &obs,
Chris@181 313 RealTime startTime,
Chris@181 314 RealTime endTime,
Chris@181 315 RealTime interval)
Chris@181 316 {
Chris@181 317 AgentFeederPoly<NoteHypothesis> feeder;
Chris@181 318
Chris@181 319 #ifdef DEBUG_NOTE_HYPOTHESIS
Chris@181 320 std::cerr << "winnow: " << obs.size() << " input observations"
Chris@181 321 << std::endl;
Chris@181 322 int nonzero = 0;
Chris@181 323 #endif
Chris@181 324
Chris@181 325 for (Observations::const_iterator i = obs.begin();
Chris@181 326 i != obs.end(); ++i) {
Chris@181 327 if (i->value != 0.0) { // 0.0 is a special unvoiced value
Chris@181 328 feeder.feed(*i);
Chris@181 329 #ifdef DEBUG_NOTE_HYPOTHESIS
Chris@181 330 std::cerr << i->value << " ";
Chris@181 331 ++nonzero;
Chris@181 332 if (nonzero % 6 == 0) {
Chris@181 333 std::cerr << std::endl;
Chris@181 334 }
Chris@181 335 #endif
Chris@181 336 }
Chris@181 337 }
Chris@181 338
Chris@181 339 #ifdef DEBUG_NOTE_HYPOTHESIS
Chris@181 340 std::cerr << "winnow: " << nonzero << " non-zero"
Chris@181 341 << std::endl;
Chris@181 342 #endif
Chris@181 343
Chris@181 344 feeder.finish();
Chris@181 345
Chris@181 346 AgentFeederPoly<NoteHypothesis>::Hypotheses accepted =
Chris@181 347 feeder.getAcceptedHypotheses();
Chris@181 348
Chris@181 349 #ifdef DEBUG_NOTE_HYPOTHESIS
Chris@181 350 std::cerr << "winnow: " << accepted.size() << " accepted hypotheses"
Chris@181 351 << std::endl;
Chris@181 352 #endif
Chris@181 353
Chris@181 354 return NoteHypothesis::sample(accepted, startTime, endTime, interval);
Chris@181 355 }
Chris@181 356