annotate src/Matcher.cpp @ 135:42381e437fcd refactors

The finder is supposed to use normalised path-cost when calculation expand direction (as in Java implementation). Also, provide a way to query the forward path.
author Chris Cannam
date Thu, 18 Dec 2014 17:56:54 +0000
parents 6636aca831c0
children cfba9aec7569
rev   line source
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 "Matcher.h"
cannam@0 18
cannam@0 19 #include <iostream>
cannam@0 20
cannam@4 21 #include <cstdlib>
Chris@16 22 #include <cassert>
cannam@4 23
Chris@72 24 using namespace std;
Chris@72 25
Chris@10 26 //#define DEBUG_MATCHER 1
Chris@10 27
Chris@104 28 Matcher::Matcher(Parameters parameters, Matcher *p) :
Chris@43 29 m_params(parameters),
Chris@43 30 m_metric(parameters.distanceNorm)
Chris@23 31 {
Chris@23 32 #ifdef DEBUG_MATCHER
Chris@104 33 cerr << "Matcher::Matcher(" << m_params.sampleRate << ", " << p << ")" << endl;
Chris@23 34 #endif
Chris@23 35
Chris@43 36 m_otherMatcher = p; // the first matcher will need this to be set later
Chris@43 37 m_firstPM = (!p);
Chris@43 38 m_frameCount = 0;
Chris@43 39 m_runCount = 0;
Chris@43 40 m_blockSize = 0;
cannam@0 41
Chris@43 42 m_blockSize = lrint(m_params.blockTime / m_params.hopTime);
Chris@15 43 #ifdef DEBUG_MATCHER
Chris@43 44 cerr << "Matcher: m_blockSize = " << m_blockSize << endl;
Chris@15 45 #endif
cannam@0 46
Chris@43 47 m_initialised = false;
Chris@23 48 }
cannam@0 49
cannam@0 50 Matcher::~Matcher()
cannam@0 51 {
Chris@10 52 #ifdef DEBUG_MATCHER
Chris@15 53 cerr << "Matcher(" << this << ")::~Matcher()" << endl;
Chris@10 54 #endif
cannam@0 55 }
cannam@0 56
cannam@0 57 void
cannam@0 58 Matcher::init()
cannam@0 59 {
Chris@43 60 if (m_initialised) return;
cannam@0 61
Chris@104 62 m_frames = vector<vector<double> >(m_blockSize);
cannam@0 63
Chris@43 64 m_distXSize = m_blockSize * 2;
Chris@45 65
Chris@41 66 size();
cannam@0 67
Chris@43 68 m_frameCount = 0;
Chris@43 69 m_runCount = 0;
Chris@38 70
Chris@43 71 m_initialised = true;
Chris@16 72 }
Chris@16 73
Chris@87 74 bool
Chris@87 75 Matcher::isInRange(int i, int j)
Chris@87 76 {
Chris@87 77 if (m_firstPM) {
Chris@87 78 return ((i >= 0) &&
Chris@87 79 (i < int(m_first.size())) &&
Chris@87 80 (j >= m_first[i]) &&
Chris@87 81 (j < int(m_first[i] + m_bestPathCost[i].size())));
Chris@87 82 } else {
Chris@87 83 return m_otherMatcher->isInRange(j, i);
Chris@87 84 }
Chris@87 85 }
Chris@87 86
Chris@87 87 bool
Chris@87 88 Matcher::isAvailable(int i, int j)
Chris@87 89 {
Chris@87 90 if (m_firstPM) {
Chris@87 91 if (isInRange(i, j)) {
Chris@87 92 return (m_bestPathCost[i][j - m_first[i]] >= 0);
Chris@87 93 } else {
Chris@87 94 return false;
Chris@87 95 }
Chris@87 96 } else {
Chris@87 97 return m_otherMatcher->isAvailable(j, i);
Chris@87 98 }
Chris@87 99 }
Chris@87 100
Chris@87 101 pair<int, int>
Chris@87 102 Matcher::getColRange(int i)
Chris@87 103 {
Chris@87 104 if (i < 0 || i >= int(m_first.size())) {
Chris@87 105 cerr << "ERROR: Matcher::getColRange(" << i << "): Index out of range"
Chris@87 106 << endl;
Chris@87 107 throw "Index out of range";
Chris@87 108 } else {
Chris@87 109 return pair<int, int>(m_first[i], m_last[i]);
Chris@87 110 }
Chris@87 111 }
Chris@87 112
Chris@87 113 pair<int, int>
Chris@87 114 Matcher::getRowRange(int i)
Chris@87 115 {
Chris@87 116 return m_otherMatcher->getColRange(i);
Chris@87 117 }
Chris@87 118
Chris@87 119 float
Chris@87 120 Matcher::getDistance(int i, int j)
Chris@87 121 {
Chris@87 122 if (m_firstPM) {
Chris@87 123 if (!isInRange(i, j)) {
Chris@87 124 cerr << "ERROR: Matcher::getDistance(" << i << ", " << j << "): "
Chris@87 125 << "Location is not in range" << endl;
Chris@87 126 throw "Distance not available";
Chris@87 127 }
Chris@87 128 float dist = m_distance[i][j - m_first[i]];
Chris@87 129 if (dist < 0) {
Chris@87 130 cerr << "ERROR: Matcher::getDistance(" << i << ", " << j << "): "
Chris@87 131 << "Location is in range, but distance ("
Chris@87 132 << dist << ") is invalid or has not been set" << endl;
Chris@87 133 throw "Distance not available";
Chris@87 134 }
Chris@87 135 return dist;
Chris@87 136 } else {
Chris@87 137 return m_otherMatcher->getDistance(j, i);
Chris@87 138 }
Chris@87 139 }
Chris@87 140
Chris@87 141 void
Chris@87 142 Matcher::setDistance(int i, int j, float distance)
Chris@87 143 {
Chris@87 144 if (m_firstPM) {
Chris@87 145 if (!isInRange(i, j)) {
Chris@87 146 cerr << "ERROR: Matcher::setDistance(" << i << ", " << j << ", "
Chris@87 147 << distance << "): Location is out of range" << endl;
Chris@87 148 throw "Indices out of range";
Chris@87 149 }
Chris@87 150 m_distance[i][j - m_first[i]] = distance;
Chris@87 151 } else {
Chris@87 152 m_otherMatcher->setDistance(j, i, distance);
Chris@87 153 }
Chris@87 154 }
Chris@87 155
Chris@87 156 double
Chris@135 157 Matcher::getNormalisedPathCost(int i, int j)
Chris@135 158 {
Chris@135 159 // normalised for path length. 1+ prevents division by zero here
Chris@135 160 return getPathCost(i, j) / (1 + i + j);
Chris@135 161 }
Chris@135 162
Chris@135 163 double
Chris@87 164 Matcher::getPathCost(int i, int j)
Chris@87 165 {
Chris@87 166 if (m_firstPM) {
Chris@87 167 if (!isAvailable(i, j)) {
Chris@87 168 if (!isInRange(i, j)) {
Chris@87 169 cerr << "ERROR: Matcher::getPathCost(" << i << ", " << j << "): "
Chris@87 170 << "Location is not in range" << endl;
Chris@87 171 } else {
Chris@87 172 cerr << "ERROR: Matcher::getPathCost(" << i << ", " << j << "): "
Chris@87 173 << "Location is in range, but pathCost ("
Chris@87 174 << m_bestPathCost[i][j - m_first[i]]
Chris@87 175 << ") is invalid or has not been set" << endl;
Chris@87 176 }
Chris@87 177 throw "Path cost not available";
Chris@87 178 }
Chris@87 179 return m_bestPathCost[i][j - m_first[i]];
Chris@87 180 } else {
Chris@87 181 return m_otherMatcher->getPathCost(j, i);
Chris@87 182 }
Chris@87 183 }
Chris@87 184
Chris@87 185 void
Chris@87 186 Matcher::setPathCost(int i, int j, Advance dir, double pathCost)
Chris@87 187 {
Chris@87 188 if (m_firstPM) {
Chris@87 189 if (!isInRange(i, j)) {
Chris@87 190 cerr << "ERROR: Matcher::setPathCost(" << i << ", " << j << ", "
Chris@87 191 << dir << ", " << pathCost
Chris@87 192 << "): Location is out of range" << endl;
Chris@87 193 throw "Indices out of range";
Chris@87 194 }
Chris@87 195 m_advance[i][j - m_first[i]] = dir;
Chris@87 196 m_bestPathCost[i][j - m_first[i]] = pathCost;
Chris@87 197 } else {
Chris@87 198 if (dir == AdvanceThis) {
Chris@87 199 dir = AdvanceOther;
Chris@87 200 } else if (dir == AdvanceOther) {
Chris@87 201 dir = AdvanceThis;
Chris@87 202 }
Chris@87 203 m_otherMatcher->setPathCost(j, i, dir, pathCost);
Chris@87 204 }
Chris@87 205
Chris@87 206 }
Chris@87 207
cannam@0 208 void
Chris@41 209 Matcher::size()
cannam@0 210 {
Chris@43 211 int distSize = (m_params.maxRunCount + 1) * m_blockSize;
Chris@71 212 m_bestPathCost.resize(m_distXSize, vector<double>(distSize, -1));
Chris@71 213 m_distance.resize(m_distXSize, vector<float>(distSize, -1));
Chris@45 214 m_advance.resize(m_distXSize, vector<Advance>(distSize, AdvanceNone));
Chris@43 215 m_first.resize(m_distXSize, 0);
Chris@43 216 m_last.resize(m_distXSize, 0);
Chris@38 217 }
cannam@0 218
Chris@23 219 void
Chris@72 220 Matcher::consumeFeatureVector(vector<double> feature)
Chris@23 221 {
Chris@43 222 if (!m_initialised) init();
Chris@43 223 int frameIndex = m_frameCount % m_blockSize;
Chris@43 224 m_frames[frameIndex] = feature;
Chris@23 225 calcAdvance();
Chris@21 226 }
Chris@21 227
Chris@21 228 void
Chris@21 229 Matcher::calcAdvance()
Chris@21 230 {
Chris@43 231 int frameIndex = m_frameCount % m_blockSize;
Chris@21 232
Chris@43 233 if (m_frameCount >= m_distXSize) {
Chris@43 234 m_distXSize *= 2;
Chris@41 235 size();
cannam@0 236 }
cannam@0 237
Chris@43 238 if (m_firstPM && (m_frameCount >= m_blockSize)) {
cannam@0 239
Chris@43 240 int len = m_last[m_frameCount - m_blockSize] -
Chris@43 241 m_first[m_frameCount - m_blockSize];
cannam@0 242
Chris@43 243 // We need to copy distance[m_frameCount-m_blockSize] to
Chris@43 244 // distance[m_frameCount], and then truncate
Chris@43 245 // distance[m_frameCount-m_blockSize] to its first len elements.
cannam@0 246 // Same for bestPathCost.
cannam@0 247
Chris@69 248 vector<float> dOld = m_distance[m_frameCount - m_blockSize];
Chris@71 249 vector<float> dNew(len, -1.f);
cannam@0 250
Chris@69 251 vector<double> bpcOld = m_bestPathCost[m_frameCount - m_blockSize];
Chris@71 252 vector<double> bpcNew(len, -1.0);
Chris@69 253
Chris@69 254 vector<Advance> adOld = m_advance[m_frameCount - m_blockSize];
Chris@69 255 vector<Advance> adNew(len, AdvanceNone);
Chris@69 256
Chris@69 257 for (int i = 0; i < len; ++i) {
Chris@69 258 dNew[i] = dOld[i];
Chris@69 259 bpcNew[i] = bpcOld[i];
Chris@69 260 adNew[i] = adOld[i];
Chris@69 261 }
Chris@45 262
Chris@69 263 m_distance[m_frameCount] = dOld;
Chris@69 264 m_distance[m_frameCount - m_blockSize] = dNew;
Chris@69 265
Chris@69 266 m_bestPathCost[m_frameCount] = bpcOld;
Chris@69 267 m_bestPathCost[m_frameCount - m_blockSize] = bpcNew;
Chris@69 268
Chris@69 269 m_advance[m_frameCount] = adOld;
Chris@69 270 m_advance[m_frameCount - m_blockSize] = adNew;
cannam@0 271 }
cannam@0 272
Chris@43 273 int stop = m_otherMatcher->m_frameCount;
Chris@43 274 int index = stop - m_blockSize;
Chris@86 275 if (index < 0) index = 0;
Chris@86 276
Chris@43 277 m_first[m_frameCount] = index;
Chris@43 278 m_last[m_frameCount] = stop;
cannam@0 279
cannam@0 280 for ( ; index < stop; index++) {
Chris@26 281
Chris@86 282 float distance = (float) m_metric.calcDistance
Chris@43 283 (m_frames[frameIndex],
Chris@45 284 m_otherMatcher->m_frames[index % m_blockSize]);
Chris@26 285
Chris@89 286 float diagDistance = distance * m_params.diagonalWeight;
Chris@89 287
Chris@86 288 if ((m_frameCount == 0) && (index == 0)) { // first element
Chris@86 289
Chris@86 290 updateValue(0, 0, AdvanceNone,
Chris@86 291 0,
Chris@86 292 distance);
Chris@86 293
Chris@86 294 } else if (m_frameCount == 0) { // first row
Chris@86 295
Chris@71 296 updateValue(0, index, AdvanceOther,
Chris@86 297 getPathCost(0, index-1),
Chris@86 298 distance);
Chris@86 299
Chris@86 300 } else if (index == 0) { // first column
Chris@86 301
Chris@71 302 updateValue(m_frameCount, index, AdvanceThis,
Chris@86 303 getPathCost(m_frameCount - 1, 0),
Chris@86 304 distance);
Chris@86 305
Chris@86 306 } else if (index == m_otherMatcher->m_frameCount - m_blockSize) {
Chris@86 307
cannam@0 308 // missing value(s) due to cutoff
cannam@0 309 // - no previous value in current row (resp. column)
cannam@0 310 // - no diagonal value if prev. dir. == curr. dirn
Chris@86 311
Chris@72 312 double min2 = getPathCost(m_frameCount - 1, index);
Chris@86 313
Chris@91 314 // cerr << "NOTE: missing value at i = " << m_frameCount << ", j = "
Chris@91 315 // << index << " (first = " << m_firstPM << ")" << endl;
Chris@86 316
Chris@43 317 // if ((m_firstPM && (first[m_frameCount - 1] == index)) ||
Chris@43 318 // (!m_firstPM && (m_last[index-1] < m_frameCount)))
Chris@86 319 if (m_first[m_frameCount - 1] == index) {
Chris@86 320
Chris@86 321 updateValue(m_frameCount, index, AdvanceThis,
Chris@86 322 min2, distance);
Chris@86 323
Chris@86 324 } else {
Chris@86 325
Chris@72 326 double min1 = getPathCost(m_frameCount - 1, index - 1);
Chris@89 327 if (min1 + diagDistance <= min2 + distance) {
Chris@86 328 updateValue(m_frameCount, index, AdvanceBoth,
Chris@86 329 min1, distance);
Chris@86 330 } else {
Chris@86 331 updateValue(m_frameCount, index, AdvanceThis,
Chris@86 332 min2, distance);
Chris@86 333 }
cannam@0 334 }
Chris@86 335
cannam@0 336 } else {
Chris@86 337
Chris@86 338 double min1 = getPathCost(m_frameCount, index - 1);
Chris@72 339 double min2 = getPathCost(m_frameCount - 1, index);
Chris@86 340 double min3 = getPathCost(m_frameCount - 1, index - 1);
Chris@87 341
Chris@89 342 double cost1 = min1 + distance;
Chris@89 343 double cost2 = min2 + distance;
Chris@89 344 double cost3 = min3 + diagDistance;
Chris@93 345
Chris@93 346 // Choosing is easy if there is a strict cheapest of the
Chris@93 347 // three. If two or more share the lowest cost, we choose
Chris@93 348 // in order of preference: cost3 (AdvanceBoth), cost2
Chris@94 349 // (AdvanceThis), cost1 (AdvanceOther) if we are the first
Chris@94 350 // matcher; and cost3 (AdvanceBoth), cost1 (AdvanceOther),
Chris@94 351 // cost2 (AdvanceThis) if we are the second matcher. That
Chris@94 352 // is, we always prioritise the diagonal followed by the
Chris@94 353 // first matcher.
Chris@94 354
Chris@94 355 if (( m_firstPM && (cost1 < cost2)) ||
Chris@94 356 (!m_firstPM && (cost1 <= cost2))) {
Chris@89 357 if (cost3 <= cost1) {
Chris@86 358 updateValue(m_frameCount, index, AdvanceBoth,
Chris@86 359 min3, distance);
Chris@86 360 } else {
Chris@86 361 updateValue(m_frameCount, index, AdvanceOther,
Chris@86 362 min1, distance);
Chris@86 363 }
cannam@0 364 } else {
Chris@89 365 if (cost3 <= cost2) {
Chris@86 366 updateValue(m_frameCount, index, AdvanceBoth,
Chris@86 367 min3, distance);
Chris@86 368 } else {
Chris@86 369 updateValue(m_frameCount, index, AdvanceThis,
Chris@86 370 min2, distance);
Chris@86 371 }
cannam@0 372 }
cannam@0 373 }
Chris@86 374
Chris@43 375 m_otherMatcher->m_last[index]++;
cannam@0 376 } // loop for row (resp. column)
cannam@0 377
Chris@43 378 m_frameCount++;
Chris@43 379 m_runCount++;
cannam@0 380
Chris@43 381 m_otherMatcher->m_runCount = 0;
Chris@21 382 }
cannam@0 383
cannam@0 384 void
Chris@96 385 Matcher::updateValue(int i, int j, Advance dir, double value, float distance)
cannam@0 386 {
Chris@96 387 float weighted = distance;
Chris@83 388 if (dir == AdvanceBoth) {
Chris@83 389 weighted *= m_params.diagonalWeight;
Chris@83 390 }
Chris@83 391
Chris@43 392 if (m_firstPM) {
Chris@45 393
Chris@96 394 setDistance(i, j, distance);
Chris@83 395 setPathCost(i, j, dir, value + weighted);
Chris@45 396
cannam@0 397 } else {
Chris@45 398
Chris@86 399 if (dir == AdvanceThis) dir = AdvanceOther;
Chris@86 400 else if (dir == AdvanceOther) dir = AdvanceThis;
Chris@84 401
Chris@43 402 int idx = i - m_otherMatcher->m_first[j];
Chris@45 403
Chris@69 404 if (idx == (int)m_otherMatcher->m_distance[j].size()) {
cannam@0 405 // This should never happen, but if we allow arbitrary
cannam@0 406 // pauses in either direction, and arbitrary lengths at
cannam@0 407 // end, it is better than a segmentation fault.
Chris@72 408 cerr << "Emergency resize: " << idx << " -> " << idx * 2 << endl;
Chris@71 409 m_otherMatcher->m_bestPathCost[j].resize(idx * 2, -1);
Chris@71 410 m_otherMatcher->m_distance[j].resize(idx * 2, -1);
Chris@46 411 m_otherMatcher->m_advance[j].resize(idx * 2, AdvanceNone);
cannam@0 412 }
Chris@45 413
Chris@96 414 m_otherMatcher->setDistance(j, i, distance);
Chris@83 415 m_otherMatcher->setPathCost(j, i, dir, value + weighted);
cannam@0 416 }
Chris@71 417 }
cannam@0 418
Chris@72 419 Matcher::Advance
Chris@72 420 Matcher::getAdvance(int i, int j)
Chris@72 421 {
Chris@72 422 if (m_firstPM) {
Chris@72 423 if (!isInRange(i, j)) {
Chris@72 424 cerr << "ERROR: Matcher::getAdvance(" << i << ", " << j << "): "
Chris@72 425 << "Location is not in range" << endl;
Chris@72 426 throw "Advance not available";
Chris@72 427 }
Chris@72 428 return m_advance[i][j - m_first[i]];
Chris@72 429 } else {
Chris@72 430 return m_otherMatcher->getAdvance(j, i);
Chris@72 431 }
Chris@72 432 }