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 }
|