cannam@0
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
cannam@0
|
2
|
cannam@0
|
3 /*
|
cannam@0
|
4 Vamp feature extraction plugin using the MATCH audio alignment
|
cannam@0
|
5 algorithm.
|
cannam@0
|
6
|
cannam@0
|
7 Centre for Digital Music, Queen Mary, University of London.
|
cannam@0
|
8 This file copyright 2007 Simon Dixon, Chris Cannam and QMUL.
|
cannam@0
|
9
|
cannam@0
|
10 This program is free software; you can redistribute it and/or
|
cannam@0
|
11 modify it under the terms of the GNU General Public License as
|
cannam@0
|
12 published by the Free Software Foundation; either version 2 of the
|
cannam@0
|
13 License, or (at your option) any later version. See the file
|
cannam@0
|
14 COPYING included with this distribution for more information.
|
cannam@0
|
15 */
|
cannam@0
|
16
|
cannam@0
|
17 #include "Finder.h"
|
cannam@0
|
18
|
Chris@30
|
19 #include "Path.h"
|
Chris@30
|
20
|
Chris@30
|
21 #include <algorithm>
|
Chris@30
|
22
|
Chris@72
|
23 using namespace std;
|
cannam@0
|
24
|
Chris@72
|
25 Finder::Finder(Matcher *pm)
|
cannam@0
|
26 {
|
Chris@72
|
27 m_m = pm;
|
Chris@72
|
28 m_duration1 = -1;
|
Chris@72
|
29 m_duration2 = -1;
|
cannam@0
|
30 } // constructor
|
cannam@0
|
31
|
cannam@0
|
32 Finder::~Finder()
|
cannam@0
|
33 {
|
cannam@0
|
34 }
|
cannam@0
|
35
|
Chris@60
|
36 void
|
Chris@60
|
37 Finder::setDurations(int d1, int d2)
|
Chris@60
|
38 {
|
Chris@72
|
39 m_duration1 = d1;
|
Chris@72
|
40 m_duration2 = d2;
|
Chris@60
|
41 }
|
Chris@60
|
42
|
Chris@45
|
43 Matcher::Advance
|
cannam@0
|
44 Finder::getExpandDirection(int row, int col)
|
cannam@0
|
45 {
|
Chris@72
|
46 double min = m_m->getPathCost(row, col);
|
Chris@72
|
47
|
Chris@72
|
48 int bestRow = row;
|
Chris@72
|
49 int bestCol = col;
|
Chris@72
|
50
|
Chris@72
|
51 pair<int, int> rowRange = m_m->getRowRange(col);
|
Chris@72
|
52 if (rowRange.second > row+1) {
|
Chris@72
|
53 rowRange.second = row+1; // don't cheat by looking at future :)
|
Chris@72
|
54 }
|
Chris@72
|
55 for (int index = rowRange.first; index < rowRange.second; index++) {
|
Chris@72
|
56 double tmp = m_m->getPathCost(index, col);
|
cannam@0
|
57 if (tmp < min) {
|
cannam@0
|
58 min = tmp;
|
cannam@0
|
59 bestRow = index;
|
cannam@0
|
60 }
|
cannam@0
|
61 }
|
Chris@72
|
62
|
Chris@72
|
63 pair<int, int> colRange = m_m->getColRange(row);
|
Chris@72
|
64 if (colRange.second > col+1) {
|
Chris@72
|
65 colRange.second = col+1; // don't cheat by looking at future :)
|
Chris@72
|
66 }
|
Chris@72
|
67 for (int index = colRange.first; index < colRange.second; index++) {
|
Chris@72
|
68 double tmp = m_m->getPathCost(row, index);
|
cannam@0
|
69 if (tmp < min) {
|
cannam@0
|
70 min = tmp;
|
cannam@0
|
71 bestCol = index;
|
cannam@0
|
72 bestRow = row;
|
cannam@0
|
73 }
|
cannam@0
|
74 }
|
Chris@72
|
75
|
Chris@45
|
76 if (bestRow == row) {
|
Chris@45
|
77 if (bestCol == col) {
|
Chris@45
|
78 return Matcher::AdvanceBoth;
|
Chris@45
|
79 } else {
|
Chris@45
|
80 return Matcher::AdvanceThis;
|
Chris@45
|
81 }
|
Chris@45
|
82 } else if (bestCol == col) {
|
Chris@45
|
83 return Matcher::AdvanceOther;
|
Chris@45
|
84 } else {
|
Chris@46
|
85 return Matcher::AdvanceNone;
|
Chris@45
|
86 }
|
cannam@0
|
87
|
Chris@73
|
88 }
|
cannam@0
|
89
|
cannam@0
|
90 void
|
cannam@0
|
91 Finder::recalculatePathCostMatrix(int r1, int c1, int r2, int c2)
|
cannam@0
|
92 {
|
Chris@72
|
93 int prevRowStart = 0, prevRowStop = 0;
|
Chris@72
|
94
|
Chris@83
|
95 float diagonalWeight = m_m->getDiagonalWeight();
|
Chris@83
|
96
|
Chris@72
|
97 for (int r = r1; r <= r2; r++) {
|
Chris@72
|
98
|
Chris@72
|
99 pair<int, int> colRange = m_m->getColRange(r);
|
Chris@72
|
100
|
Chris@72
|
101 int rowStart = max(c1, colRange.first);
|
Chris@72
|
102 int rowStop = min(c2 + 1, colRange.second);
|
Chris@72
|
103
|
Chris@72
|
104 for (int c = rowStart; c < rowStop; c++) {
|
Chris@72
|
105
|
Chris@72
|
106 float newCost = m_m->getDistance(r, c);
|
Chris@72
|
107 Matcher::Advance dir = Matcher::AdvanceNone;
|
Chris@72
|
108
|
Chris@72
|
109 if (r > r1) { // not first row
|
Chris@72
|
110 double min = -1;
|
Chris@72
|
111 if ((c > prevRowStart) && (c <= prevRowStop)) {
|
Chris@72
|
112 // diagonal from (r-1,c-1)
|
Chris@83
|
113 min = m_m->getPathCost(r-1, c-1) + newCost * diagonalWeight;
|
Chris@72
|
114 dir = Matcher::AdvanceBoth;
|
Chris@72
|
115 }
|
Chris@72
|
116 if ((c >= prevRowStart) && (c < prevRowStop)) {
|
Chris@72
|
117 // vertical from (r-1,c)
|
Chris@72
|
118 double cost = m_m->getPathCost(r-1, c) + newCost;
|
Chris@72
|
119 if ((min < 0) || (cost < min)) {
|
Chris@72
|
120 min = cost;
|
Chris@72
|
121 dir = Matcher::AdvanceThis;
|
Chris@72
|
122 }
|
Chris@72
|
123 }
|
Chris@72
|
124 if (c > rowStart) {
|
Chris@72
|
125 // horizontal from (r,c-1)
|
Chris@72
|
126 double cost = m_m->getPathCost(r, c-1) + newCost;
|
Chris@72
|
127 if ((min < 0) || (cost < min)) {
|
Chris@72
|
128 min = cost;
|
Chris@72
|
129 dir = Matcher::AdvanceOther;
|
Chris@72
|
130 }
|
Chris@72
|
131 }
|
Chris@72
|
132
|
Chris@72
|
133 m_m->setPathCost(r, c, dir, min);
|
Chris@72
|
134
|
Chris@72
|
135 } else if (c > rowStart) { // first row
|
Chris@72
|
136 // horizontal from (r,c-1)
|
Chris@72
|
137 m_m->setPathCost(r, c, Matcher::AdvanceOther,
|
Chris@72
|
138 m_m->getPathCost(r, c-1) + newCost);
|
Chris@72
|
139 }
|
Chris@72
|
140 }
|
Chris@72
|
141
|
Chris@72
|
142 prevRowStart = rowStart;
|
Chris@72
|
143 prevRowStop = rowStop;
|
cannam@0
|
144 }
|
Chris@72
|
145 }
|
Chris@30
|
146
|
Chris@82
|
147 #ifdef PERFORM_ERROR_CHECKS
|
Chris@81
|
148 Finder::ErrorPosition
|
Chris@81
|
149 Finder::checkPathCostMatrix()
|
Chris@81
|
150 {
|
Chris@82
|
151 cerr << "Finder: Checking path-cost matrix..." << endl;
|
Chris@82
|
152
|
Chris@81
|
153 ErrorPosition err;
|
Chris@81
|
154
|
Chris@81
|
155 int r1 = 0;
|
Chris@81
|
156 int c1 = 0;
|
Chris@81
|
157 int r2 = m_m->getFrameCount() - 1;
|
Chris@81
|
158 int c2 = m_m->getOtherFrameCount() - 1;
|
Chris@81
|
159
|
Chris@81
|
160 if (r2 < r1 || c2 < c1) {
|
Chris@81
|
161 return err;
|
Chris@81
|
162 }
|
Chris@81
|
163
|
Chris@81
|
164 int prevRowStart = 0, prevRowStop = 0;
|
Chris@81
|
165
|
Chris@83
|
166 float diagonalWeight = m_m->getDiagonalWeight();
|
Chris@83
|
167
|
Chris@81
|
168 for (int r = r1; r <= r2; r++) {
|
Chris@81
|
169
|
Chris@81
|
170 pair<int, int> colRange = m_m->getColRange(r);
|
Chris@81
|
171
|
Chris@81
|
172 int rowStart = max(c1, colRange.first);
|
Chris@81
|
173 int rowStop = min(c2 + 1, colRange.second);
|
Chris@81
|
174
|
Chris@81
|
175 for (int c = rowStart; c < rowStop; c++) {
|
Chris@81
|
176
|
Chris@81
|
177 float newCost = m_m->getDistance(r, c);
|
Chris@81
|
178 double updateTo = -1.0;
|
Chris@81
|
179 Matcher::Advance dir = Matcher::AdvanceNone;
|
Chris@81
|
180
|
Chris@81
|
181 if (r > r1) { // not first row
|
Chris@81
|
182 double min = -1;
|
Chris@81
|
183 if ((c > prevRowStart) && (c <= prevRowStop)) {
|
Chris@81
|
184 // diagonal from (r-1,c-1)
|
Chris@83
|
185 min = m_m->getPathCost(r-1, c-1) + newCost * diagonalWeight;
|
Chris@81
|
186 err.prevCost = m_m->getPathCost(r-1, c-1);
|
Chris@83
|
187 err.distance = newCost * diagonalWeight;
|
Chris@81
|
188 dir = Matcher::AdvanceBoth;
|
Chris@81
|
189 }
|
Chris@81
|
190 if ((c >= prevRowStart) && (c < prevRowStop)) {
|
Chris@81
|
191 // vertical from (r-1,c)
|
Chris@81
|
192 double cost = m_m->getPathCost(r-1, c) + newCost;
|
Chris@81
|
193 if ((min < 0) || (cost < min)) {
|
Chris@81
|
194 min = cost;
|
Chris@81
|
195 err.prevCost = m_m->getPathCost(r-1, c);
|
Chris@81
|
196 err.distance = newCost;
|
Chris@81
|
197 dir = Matcher::AdvanceThis;
|
Chris@81
|
198 }
|
Chris@81
|
199 }
|
Chris@81
|
200 if (c > rowStart) {
|
Chris@81
|
201 // horizontal from (r,c-1)
|
Chris@81
|
202 double cost = m_m->getPathCost(r, c-1) + newCost;
|
Chris@81
|
203 if ((min < 0) || (cost < min)) {
|
Chris@81
|
204 min = cost;
|
Chris@81
|
205 err.prevCost = m_m->getPathCost(r, c-1);
|
Chris@81
|
206 err.distance = newCost;
|
Chris@81
|
207 dir = Matcher::AdvanceOther;
|
Chris@81
|
208 }
|
Chris@81
|
209 }
|
Chris@81
|
210
|
Chris@81
|
211 updateTo = min;
|
Chris@81
|
212
|
Chris@82
|
213 } else { // first row
|
Chris@82
|
214
|
Chris@82
|
215 if (c > rowStart) {
|
Chris@82
|
216 // horizontal from (r,c-1)
|
Chris@83
|
217 updateTo = m_m->getPathCost(r, c-1) + newCost;
|
Chris@83
|
218 err.prevCost = m_m->getPathCost(r, c-1);
|
Chris@83
|
219 err.distance = newCost;
|
Chris@82
|
220 dir = Matcher::AdvanceOther;
|
Chris@82
|
221 }
|
Chris@81
|
222 }
|
Chris@81
|
223
|
Chris@82
|
224 if (dir != Matcher::AdvanceNone) {
|
Chris@84
|
225 if (m_m->getAdvance(r, c) != dir) {
|
Chris@84
|
226 cerr << "WrongAdvance found" << endl;
|
Chris@84
|
227 err.type = ErrorPosition::WrongAdvance;
|
Chris@82
|
228 err.r = r;
|
Chris@82
|
229 err.c = c;
|
Chris@82
|
230 err.costWas = m_m->getPathCost(r, c);
|
Chris@82
|
231 err.costShouldBe = updateTo;
|
Chris@84
|
232 err.advanceWas = m_m->getAdvance(r, c);
|
Chris@84
|
233 err.advanceShouldBe = dir;
|
Chris@84
|
234 return err;
|
Chris@84
|
235 }
|
Chris@84
|
236 if (m_m->getPathCost(r, c) != updateTo) {
|
Chris@84
|
237 cerr << "WrongCost found" << endl;
|
Chris@84
|
238 err.type = ErrorPosition::WrongCost;
|
Chris@84
|
239 err.r = r;
|
Chris@84
|
240 err.c = c;
|
Chris@84
|
241 err.costWas = m_m->getPathCost(r, c);
|
Chris@84
|
242 err.costShouldBe = updateTo;
|
Chris@84
|
243 err.advanceWas = m_m->getAdvance(r, c);
|
Chris@84
|
244 err.advanceShouldBe = dir;
|
Chris@82
|
245 return err;
|
Chris@82
|
246 }
|
Chris@82
|
247 } else {
|
Chris@82
|
248 // AdvanceNone should occur only at r = r1, c = c1
|
Chris@82
|
249 if (r != r1 || c != c1) {
|
Chris@82
|
250 cerr << "AdvanceNone error found" << endl;
|
Chris@82
|
251 err.type = ErrorPosition::NoAdvance;
|
Chris@82
|
252 err.r = r;
|
Chris@82
|
253 err.c = c;
|
Chris@82
|
254 err.costWas = m_m->getPathCost(r, c);
|
Chris@82
|
255 err.costShouldBe = updateTo;
|
Chris@84
|
256 err.advanceWas = m_m->getAdvance(r, c);
|
Chris@84
|
257 err.advanceShouldBe = dir;
|
Chris@82
|
258 return err;
|
Chris@82
|
259 }
|
Chris@81
|
260 }
|
Chris@81
|
261 }
|
Chris@81
|
262
|
Chris@81
|
263 prevRowStart = rowStart;
|
Chris@81
|
264 prevRowStop = rowStop;
|
Chris@81
|
265 }
|
Chris@81
|
266
|
Chris@82
|
267 cerr << "No errors found" << endl;
|
Chris@81
|
268 return err;
|
Chris@82
|
269 }
|
Chris@82
|
270 #endif
|
Chris@81
|
271
|
Chris@30
|
272 int
|
Chris@31
|
273 Finder::retrievePath(bool smooth, vector<int> &pathx, vector<int> &pathy)
|
Chris@30
|
274 {
|
Chris@69
|
275 pathx.clear();
|
Chris@69
|
276 pathy.clear();
|
Chris@69
|
277
|
Chris@82
|
278 #ifdef PERFORM_ERROR_CHECKS
|
Chris@82
|
279 ErrorPosition err = checkPathCostMatrix();
|
Chris@82
|
280 if (err.type != ErrorPosition::NoError) {
|
Chris@82
|
281 cerr << "\nWARNING: Checking path-cost matrix returned mismatch:" << endl;
|
Chris@82
|
282 cerr << "Type: " << err.type << endl;
|
Chris@84
|
283 cerr << "At row " << err.r << ", column " << err.c
|
Chris@84
|
284 << "\nShould be advancing "
|
Chris@84
|
285 << Matcher::advanceToString(err.advanceShouldBe)
|
Chris@84
|
286 << ", advance in matrix is "
|
Chris@84
|
287 << Matcher::advanceToString(err.advanceWas)
|
Chris@83
|
288 << "\nPrev cost " << err.prevCost
|
Chris@82
|
289 << " plus distance " << err.distance << " gives "
|
Chris@84
|
290 << err.costShouldBe << ", matrix contains " << err.costWas
|
Chris@83
|
291 << endl;
|
Chris@83
|
292 cerr << "Note: diagonal weight = " << m_m->getDiagonalWeight() << endl;
|
Chris@83
|
293 cerr << endl;
|
Chris@82
|
294 }
|
Chris@82
|
295 #endif
|
Chris@82
|
296
|
Chris@72
|
297 int ex = m_m->getOtherFrameCount() - 1;
|
Chris@72
|
298 int ey = m_m->getFrameCount() - 1;
|
Chris@69
|
299
|
Chris@69
|
300 if (ex < 0 || ey < 0) {
|
Chris@69
|
301 return 0;
|
Chris@69
|
302 }
|
Chris@66
|
303
|
Chris@66
|
304 int x = ex;
|
Chris@66
|
305 int y = ey;
|
Chris@30
|
306
|
Chris@72
|
307 if (m_duration2 > 0 && m_duration2 < m_m->getOtherFrameCount()) {
|
Chris@72
|
308 x = m_duration2 - 1;
|
Chris@60
|
309 }
|
Chris@72
|
310 if (m_duration1 > 0 && m_duration1 < m_m->getFrameCount()) {
|
Chris@72
|
311 y = m_duration1 - 1;
|
Chris@60
|
312 }
|
Chris@79
|
313
|
Chris@79
|
314 // cerr << "before: x = " << x << ", y = " << y << endl;
|
Chris@60
|
315
|
Chris@72
|
316 if (!m_m->isAvailable(y, x)) {
|
Chris@66
|
317 // Path did not pass through the expected end point --
|
Chris@66
|
318 // probably means the pieces are substantially different in
|
Chris@66
|
319 // the later bits. Reset the expected end point to the end of
|
Chris@66
|
320 // both files including any trailing silence.
|
Chris@66
|
321 cerr << "NOTE: Path did not pass through expected end point, inputs are probably significantly different" << endl;
|
Chris@66
|
322 x = ex;
|
Chris@66
|
323 y = ey;
|
Chris@66
|
324 }
|
Chris@66
|
325
|
Chris@55
|
326 recalculatePathCostMatrix(0, 0, y, x);
|
Chris@55
|
327
|
Chris@66
|
328 // cerr << "start: x = " << x << ", y = " << y << endl;
|
Chris@66
|
329
|
Chris@72
|
330 while (m_m->isAvailable(y, x) && (x > 0 || y > 0)) {
|
Chris@30
|
331
|
Chris@33
|
332 // cerr << "x = " << x << ", y = " << y;
|
Chris@33
|
333
|
Chris@30
|
334 pathx.push_back(x);
|
Chris@30
|
335 pathy.push_back(y);
|
Chris@30
|
336
|
Chris@72
|
337 switch (m_m->getAdvance(y, x)) {
|
Chris@45
|
338 case Matcher::AdvanceThis:
|
Chris@70
|
339 // cerr << ", going down (dist = " << getDistance() << ")" << endl;
|
Chris@33
|
340 y--;
|
Chris@33
|
341 break;
|
Chris@45
|
342 case Matcher::AdvanceOther:
|
Chris@70
|
343 // cerr << ", going left (dist = " << getDistance() << ")" << endl;
|
Chris@33
|
344 x--;
|
Chris@33
|
345 break;
|
Chris@45
|
346 case Matcher::AdvanceBoth:
|
Chris@70
|
347 // cerr << ", going diag (dist = " << getDistance() << ")" << endl;
|
Chris@33
|
348 x--;
|
Chris@33
|
349 y--;
|
Chris@33
|
350 break;
|
Chris@45
|
351 case Matcher::AdvanceNone: // this would indicate a bug, but we wouldn't want to hang
|
Chris@69
|
352 cerr << "WARNING: Neither matcher advanced in path backtrack at (" << x << "," << y << ")" << endl;
|
Chris@33
|
353 if (x > y) {
|
Chris@33
|
354 x--;
|
Chris@33
|
355 } else {
|
Chris@33
|
356 y--;
|
Chris@33
|
357 }
|
Chris@33
|
358 break;
|
Chris@30
|
359 }
|
Chris@30
|
360 }
|
Chris@30
|
361
|
Chris@72
|
362 if (x > 0 || y > 0) {
|
Chris@72
|
363 cerr << "WARNING: Ran out of available path at (" << y << "," << x
|
Chris@72
|
364 << ")!" << endl;
|
Chris@72
|
365 }
|
Chris@72
|
366
|
Chris@72
|
367 reverse(pathx.begin(), pathx.end());
|
Chris@72
|
368 reverse(pathy.begin(), pathy.end());
|
Chris@30
|
369
|
Chris@31
|
370 if (smooth) {
|
Chris@31
|
371 int smoothedLen = Path().smooth(pathx, pathy, pathx.size());
|
Chris@31
|
372 return smoothedLen;
|
Chris@31
|
373 } else {
|
Chris@31
|
374 return pathx.size();
|
Chris@31
|
375 }
|
Chris@30
|
376 }
|
Chris@30
|
377
|
Chris@30
|
378
|