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@188
|
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@188
|
122 #ifdef DEBUG_NOTE_HYPOTHESIS
|
Chris@184
|
123 static void printState(NoteHypothesis::State s)
|
Chris@184
|
124 {
|
Chris@184
|
125 switch (s) {
|
Chris@184
|
126 case NoteHypothesis::New: cerr << "New"; break;
|
Chris@184
|
127 case NoteHypothesis::Provisional: cerr << "Provisional"; break;
|
Chris@184
|
128 case NoteHypothesis::Rejected: cerr << "Rejected"; break;
|
Chris@184
|
129 case NoteHypothesis::Satisfied: cerr << "Satisfied"; break;
|
Chris@184
|
130 case NoteHypothesis::Expired: cerr << "Expired"; break;
|
Chris@184
|
131 }
|
Chris@184
|
132 }
|
Chris@188
|
133 #endif
|
Chris@184
|
134
|
Chris@181
|
135 bool
|
Chris@181
|
136 NoteHypothesis::accept(Observation s)
|
Chris@181
|
137 {
|
Chris@181
|
138 bool accept = false;
|
Chris@181
|
139
|
Chris@181
|
140 #ifdef DEBUG_NOTE_HYPOTHESIS
|
Chris@184
|
141 cerr << "NoteHypothesis[" << this << "]::accept (value " << s.value << ", time " << s.time << ", confidence " << s.confidence << "): state ";
|
Chris@184
|
142 printState(m_state);
|
Chris@184
|
143 cerr << "..." << endl;
|
Chris@181
|
144 #endif
|
Chris@181
|
145
|
Chris@181
|
146 static double negligibleConfidence = 0.0001;
|
Chris@181
|
147
|
Chris@181
|
148 if (s.confidence < negligibleConfidence) {
|
Chris@181
|
149 // avoid piling up a lengthy sequence of estimates that are
|
Chris@181
|
150 // all acceptable but are in total not enough to cause us to
|
Chris@181
|
151 // be satisfied
|
Chris@181
|
152 if (m_state == New) {
|
Chris@181
|
153 m_state = Rejected;
|
Chris@181
|
154 }
|
Chris@181
|
155 return false;
|
Chris@181
|
156 }
|
Chris@181
|
157
|
Chris@181
|
158 switch (m_state) {
|
Chris@181
|
159
|
Chris@181
|
160 case New:
|
Chris@181
|
161 m_state = Provisional;
|
Chris@181
|
162 accept = true;
|
Chris@181
|
163 break;
|
Chris@181
|
164
|
Chris@181
|
165 case Provisional:
|
Chris@181
|
166 if (isOutOfDateFor(s)) {
|
Chris@181
|
167 m_state = Rejected;
|
Chris@181
|
168 } else if (isWithinTolerance(s)) {
|
Chris@181
|
169 accept = true;
|
Chris@181
|
170 }
|
Chris@181
|
171 break;
|
Chris@181
|
172
|
Chris@181
|
173 case Satisfied:
|
Chris@181
|
174 if (isOutOfDateFor(s)) {
|
Chris@181
|
175 m_state = Expired;
|
Chris@181
|
176 } else if (isWithinTolerance(s)) {
|
Chris@181
|
177 accept = true;
|
Chris@181
|
178 }
|
Chris@181
|
179 break;
|
Chris@181
|
180
|
Chris@181
|
181 case Rejected:
|
Chris@181
|
182 break;
|
Chris@181
|
183
|
Chris@181
|
184 case Expired:
|
Chris@181
|
185 break;
|
Chris@181
|
186 }
|
Chris@181
|
187
|
Chris@181
|
188 if (accept) {
|
Chris@184
|
189 #ifdef DEBUG_NOTE_HYPOTHESIS
|
Chris@184
|
190 cerr << "... accepting" << endl;
|
Chris@184
|
191 #endif
|
Chris@181
|
192 m_pending.insert(s);
|
Chris@181
|
193 if (m_state == Provisional && isSatisfied()) {
|
Chris@181
|
194 m_state = Satisfied;
|
Chris@181
|
195 }
|
Chris@184
|
196 } else {
|
Chris@184
|
197 #ifdef DEBUG_NOTE_HYPOTHESIS
|
Chris@184
|
198 cerr << "... not accepting" << endl;
|
Chris@184
|
199 #endif
|
Chris@181
|
200 }
|
Chris@181
|
201
|
Chris@181
|
202 #ifdef DEBUG_NOTE_HYPOTHESIS
|
Chris@184
|
203 cerr << "... -> ";
|
Chris@184
|
204 printState(m_state);
|
Chris@184
|
205 cerr << " (pending: " << m_pending.size() << ")" << endl;
|
Chris@181
|
206 #endif
|
Chris@181
|
207
|
Chris@181
|
208 return accept;
|
Chris@181
|
209 }
|
Chris@181
|
210
|
Chris@181
|
211 NoteHypothesis::State
|
Chris@181
|
212 NoteHypothesis::getState() const
|
Chris@181
|
213 {
|
Chris@181
|
214 return m_state;
|
Chris@181
|
215 }
|
Chris@181
|
216
|
Chris@181
|
217 NoteHypothesis::Observations
|
Chris@181
|
218 NoteHypothesis::getAcceptedObservations() const
|
Chris@181
|
219 {
|
Chris@181
|
220 if (m_state == Satisfied || m_state == Expired) {
|
Chris@181
|
221 return m_pending;
|
Chris@181
|
222 } else {
|
Chris@181
|
223 return Observations();
|
Chris@181
|
224 }
|
Chris@181
|
225 }
|
Chris@181
|
226
|
Chris@181
|
227 double
|
Chris@184
|
228 NoteHypothesis::getMedianFrequency() const
|
Chris@184
|
229 {
|
Chris@184
|
230 if (m_pending.empty()) return 0.0;
|
Chris@184
|
231 std::vector<double> freqs;
|
Chris@184
|
232 for (Observations::const_iterator i = m_pending.begin();
|
Chris@184
|
233 i != m_pending.end(); ++i) {
|
Chris@184
|
234 freqs.push_back(i->value);
|
Chris@184
|
235 }
|
Chris@184
|
236 std::sort(freqs.begin(), freqs.end());
|
Chris@184
|
237 return freqs[freqs.size()/2];
|
Chris@184
|
238 }
|
Chris@184
|
239
|
Chris@184
|
240 double
|
Chris@181
|
241 NoteHypothesis::getMeanFrequency() const
|
Chris@181
|
242 {
|
Chris@181
|
243 double acc = 0.0;
|
Chris@181
|
244 if (m_pending.empty()) return acc;
|
Chris@181
|
245 for (Observations::const_iterator i = m_pending.begin();
|
Chris@181
|
246 i != m_pending.end(); ++i) {
|
Chris@181
|
247 acc += i->value;
|
Chris@181
|
248 }
|
Chris@181
|
249 acc /= m_pending.size();
|
Chris@181
|
250 return acc;
|
Chris@181
|
251 }
|
Chris@181
|
252
|
Chris@184
|
253 double
|
Chris@184
|
254 NoteHypothesis::getMedianConfidence() const
|
Chris@184
|
255 {
|
Chris@184
|
256 if (m_pending.empty()) return 0.0;
|
Chris@184
|
257 std::vector<double> confs;
|
Chris@184
|
258 for (Observations::const_iterator i = m_pending.begin();
|
Chris@184
|
259 i != m_pending.end(); ++i) {
|
Chris@184
|
260 confs.push_back(i->confidence);
|
Chris@184
|
261 }
|
Chris@184
|
262 std::sort(confs.begin(), confs.end());
|
Chris@184
|
263 return confs[confs.size()/2];
|
Chris@184
|
264 }
|
Chris@184
|
265
|
Chris@181
|
266 NoteHypothesis::Note
|
Chris@181
|
267 NoteHypothesis::getAveragedNote() const
|
Chris@181
|
268 {
|
Chris@181
|
269 Note n;
|
Chris@181
|
270
|
Chris@181
|
271 n.time = getStartTime();
|
Chris@181
|
272 n.duration = getDuration();
|
Chris@184
|
273 n.freq = getMedianFrequency();
|
Chris@184
|
274 n.confidence = getMedianConfidence();
|
Chris@181
|
275
|
Chris@181
|
276 return n;
|
Chris@181
|
277 }
|
Chris@181
|
278
|
Chris@181
|
279 RealTime
|
Chris@181
|
280 NoteHypothesis::getStartTime() const
|
Chris@181
|
281 {
|
Chris@181
|
282 if (!(m_state == Satisfied || m_state == Expired)) {
|
Chris@181
|
283 return RealTime::zeroTime;
|
Chris@181
|
284 } else {
|
Chris@181
|
285 return m_pending.begin()->time;
|
Chris@181
|
286 }
|
Chris@181
|
287 }
|
Chris@181
|
288
|
Chris@181
|
289 RealTime
|
Chris@181
|
290 NoteHypothesis::getDuration() const
|
Chris@181
|
291 {
|
Chris@181
|
292 //!!! test this! it is wrong
|
Chris@181
|
293 if (!(m_state == Satisfied || m_state == Expired)) {
|
Chris@181
|
294 return RealTime::zeroTime;
|
Chris@181
|
295 } else {
|
Chris@181
|
296 RealTime start = m_pending.begin()->time;
|
Chris@181
|
297 Observations::const_iterator i = m_pending.end();
|
Chris@181
|
298 --i;
|
Chris@181
|
299 return i->time - start;
|
Chris@181
|
300 }
|
Chris@181
|
301 }
|