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> ¬es,
|
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
|