annotate src/Matcher.cpp @ 94:13be4158251d refactors

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