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