Chris@32
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@37
|
2 /*
|
Chris@37
|
3 This file is Copyright (c) 2012 Chris Cannam
|
Chris@37
|
4
|
Chris@37
|
5 Permission is hereby granted, free of charge, to any person
|
Chris@37
|
6 obtaining a copy of this software and associated documentation
|
Chris@37
|
7 files (the "Software"), to deal in the Software without
|
Chris@37
|
8 restriction, including without limitation the rights to use, copy,
|
Chris@37
|
9 modify, merge, publish, distribute, sublicense, and/or sell copies
|
Chris@37
|
10 of the Software, and to permit persons to whom the Software is
|
Chris@37
|
11 furnished to do so, subject to the following conditions:
|
Chris@37
|
12
|
Chris@37
|
13 The above copyright notice and this permission notice shall be
|
Chris@37
|
14 included in all copies or substantial portions of the Software.
|
Chris@37
|
15
|
Chris@37
|
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
Chris@37
|
17 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
Chris@37
|
18 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
Chris@37
|
19 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
|
Chris@37
|
20 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
Chris@37
|
21 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
Chris@37
|
22 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
Chris@37
|
23 */
|
Chris@32
|
24
|
Chris@32
|
25 #include "NoteHypothesis.h"
|
Chris@32
|
26
|
Chris@32
|
27 #include <cmath>
|
Chris@32
|
28
|
Chris@35
|
29 using Vamp::RealTime;
|
Chris@32
|
30
|
Chris@66
|
31 NoteHypothesis::NoteHypothesis(float slack) :
|
Chris@66
|
32 m_state(New),
|
Chris@66
|
33 m_slack(slack)
|
Chris@32
|
34 {
|
Chris@32
|
35 }
|
Chris@32
|
36
|
Chris@32
|
37 NoteHypothesis::~NoteHypothesis()
|
Chris@32
|
38 {
|
Chris@32
|
39 }
|
Chris@32
|
40
|
Chris@32
|
41 bool
|
Chris@32
|
42 NoteHypothesis::isWithinTolerance(Estimate s) const
|
Chris@32
|
43 {
|
Chris@32
|
44 if (m_pending.empty()) {
|
Chris@32
|
45 return true;
|
Chris@32
|
46 }
|
Chris@32
|
47
|
Chris@32
|
48 // check we are within a relatively close tolerance of the last
|
Chris@32
|
49 // candidate
|
Chris@32
|
50 Estimate last = m_pending[m_pending.size()-1];
|
Chris@32
|
51 double r = s.freq / last.freq;
|
Chris@32
|
52 int cents = lrint(1200.0 * (log(r) / log(2.0)));
|
Chris@32
|
53 if (cents < -60 || cents > 60) return false;
|
Chris@32
|
54
|
Chris@32
|
55 // and within a slightly bigger tolerance of the current mean
|
Chris@32
|
56 double meanFreq = getMeanFrequency();
|
Chris@32
|
57 r = s.freq / meanFreq;
|
Chris@32
|
58 cents = lrint(1200.0 * (log(r) / log(2.0)));
|
Chris@32
|
59 if (cents < -80 || cents > 80) return false;
|
Chris@32
|
60
|
Chris@32
|
61 return true;
|
Chris@32
|
62 }
|
Chris@32
|
63
|
Chris@32
|
64 bool
|
Chris@32
|
65 NoteHypothesis::isOutOfDateFor(Estimate s) const
|
Chris@32
|
66 {
|
Chris@32
|
67 if (m_pending.empty()) return false;
|
Chris@32
|
68 return ((s.time - m_pending[m_pending.size()-1].time) >
|
Chris@66
|
69 RealTime::fromMilliseconds(m_slack));
|
Chris@32
|
70 }
|
Chris@32
|
71
|
Chris@32
|
72 bool
|
Chris@32
|
73 NoteHypothesis::isSatisfied() const
|
Chris@32
|
74 {
|
Chris@32
|
75 if (m_pending.empty()) return false;
|
Chris@32
|
76
|
Chris@32
|
77 double meanConfidence = 0.0;
|
Chris@32
|
78 for (int i = 0; i < (int)m_pending.size(); ++i) {
|
Chris@32
|
79 meanConfidence += m_pending[i].confidence;
|
Chris@32
|
80 }
|
Chris@32
|
81 meanConfidence /= m_pending.size();
|
Chris@32
|
82
|
Chris@59
|
83 int lengthRequired = 100;
|
Chris@32
|
84 if (meanConfidence > 0.0) {
|
Chris@32
|
85 lengthRequired = int(2.0 / meanConfidence + 0.5);
|
Chris@32
|
86 }
|
Chris@32
|
87
|
Chris@32
|
88 return ((int)m_pending.size() > lengthRequired);
|
Chris@32
|
89 }
|
Chris@32
|
90
|
Chris@32
|
91 bool
|
Chris@32
|
92 NoteHypothesis::accept(Estimate s)
|
Chris@32
|
93 {
|
Chris@32
|
94 bool accept = false;
|
Chris@32
|
95
|
Chris@58
|
96 static double negligibleConfidence = 0.0001;
|
Chris@58
|
97
|
Chris@58
|
98 if (s.confidence < negligibleConfidence) {
|
Chris@58
|
99 // avoid piling up a lengthy sequence of estimates that are
|
Chris@58
|
100 // all acceptable but are in total not enough to cause us to
|
Chris@58
|
101 // be satisfied
|
Chris@60
|
102 if (m_pending.empty()) {
|
Chris@60
|
103 m_state = Rejected;
|
Chris@60
|
104 }
|
Chris@58
|
105 return false;
|
Chris@58
|
106 }
|
Chris@58
|
107
|
Chris@32
|
108 switch (m_state) {
|
Chris@32
|
109
|
Chris@32
|
110 case New:
|
Chris@32
|
111 m_state = Provisional;
|
Chris@32
|
112 accept = true;
|
Chris@32
|
113 break;
|
Chris@32
|
114
|
Chris@32
|
115 case Provisional:
|
Chris@32
|
116 if (isOutOfDateFor(s)) {
|
Chris@32
|
117 m_state = Rejected;
|
Chris@32
|
118 } else if (isWithinTolerance(s)) {
|
Chris@32
|
119 accept = true;
|
Chris@32
|
120 }
|
Chris@32
|
121 break;
|
Chris@32
|
122
|
Chris@32
|
123 case Satisfied:
|
Chris@32
|
124 if (isOutOfDateFor(s)) {
|
Chris@32
|
125 m_state = Expired;
|
Chris@32
|
126 } else if (isWithinTolerance(s)) {
|
Chris@32
|
127 accept = true;
|
Chris@32
|
128 }
|
Chris@32
|
129 break;
|
Chris@32
|
130
|
Chris@32
|
131 case Rejected:
|
Chris@32
|
132 break;
|
Chris@32
|
133
|
Chris@32
|
134 case Expired:
|
Chris@32
|
135 break;
|
Chris@32
|
136 }
|
Chris@32
|
137
|
Chris@32
|
138 if (accept) {
|
Chris@32
|
139 m_pending.push_back(s);
|
Chris@32
|
140 if (m_state == Provisional && isSatisfied()) {
|
Chris@32
|
141 m_state = Satisfied;
|
Chris@32
|
142 }
|
Chris@32
|
143 }
|
Chris@32
|
144
|
Chris@32
|
145 return accept;
|
Chris@32
|
146 }
|
Chris@32
|
147
|
Chris@32
|
148 NoteHypothesis::State
|
Chris@32
|
149 NoteHypothesis::getState() const
|
Chris@32
|
150 {
|
Chris@32
|
151 return m_state;
|
Chris@32
|
152 }
|
Chris@32
|
153
|
Chris@32
|
154 NoteHypothesis::Estimates
|
Chris@32
|
155 NoteHypothesis::getAcceptedEstimates() const
|
Chris@32
|
156 {
|
Chris@32
|
157 if (m_state == Satisfied || m_state == Expired) {
|
Chris@32
|
158 return m_pending;
|
Chris@32
|
159 } else {
|
Chris@32
|
160 return Estimates();
|
Chris@32
|
161 }
|
Chris@32
|
162 }
|
Chris@32
|
163
|
Chris@52
|
164 RealTime
|
Chris@52
|
165 NoteHypothesis::getStartTime() const
|
Chris@52
|
166 {
|
Chris@52
|
167 if (!(m_state == Satisfied || m_state == Expired)) {
|
Chris@52
|
168 return RealTime::zeroTime;
|
Chris@52
|
169 } else {
|
Chris@52
|
170 return m_pending.begin()->time;
|
Chris@52
|
171 }
|
Chris@52
|
172 }
|
Chris@52
|
173
|
Chris@32
|
174 double
|
Chris@32
|
175 NoteHypothesis::getMeanFrequency() const
|
Chris@32
|
176 {
|
Chris@32
|
177 double acc = 0.0;
|
Chris@34
|
178 if (m_pending.empty()) return acc;
|
Chris@32
|
179 for (int i = 0; i < (int)m_pending.size(); ++i) {
|
Chris@32
|
180 acc += m_pending[i].freq;
|
Chris@32
|
181 }
|
Chris@32
|
182 acc /= m_pending.size();
|
Chris@32
|
183 return acc;
|
Chris@32
|
184 }
|
Chris@32
|
185
|
Chris@32
|
186 NoteHypothesis::Note
|
Chris@32
|
187 NoteHypothesis::getAveragedNote() const
|
Chris@32
|
188 {
|
Chris@32
|
189 Note n;
|
Chris@32
|
190
|
Chris@32
|
191 if (!(m_state == Satisfied || m_state == Expired)) {
|
Chris@32
|
192 n.freq = 0.0;
|
Chris@32
|
193 n.time = RealTime::zeroTime;
|
Chris@32
|
194 n.duration = RealTime::zeroTime;
|
Chris@32
|
195 return n;
|
Chris@32
|
196 }
|
Chris@32
|
197
|
Chris@32
|
198 n.time = m_pending.begin()->time;
|
Chris@32
|
199
|
Chris@32
|
200 Estimates::const_iterator i = m_pending.end();
|
Chris@32
|
201 --i;
|
Chris@32
|
202 n.duration = i->time - n.time;
|
Chris@32
|
203
|
Chris@32
|
204 // just mean frequency for now, but this isn't at all right perceptually
|
Chris@32
|
205 n.freq = getMeanFrequency();
|
Chris@32
|
206
|
Chris@32
|
207 return n;
|
Chris@32
|
208 }
|
Chris@32
|
209
|