cannam@0: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ cannam@0: cannam@0: /* cannam@0: Vamp feature extraction plugin using the MATCH audio alignment cannam@0: algorithm. cannam@0: cannam@0: Centre for Digital Music, Queen Mary, University of London. cannam@0: This file copyright 2007 Simon Dixon, Chris Cannam and QMUL. cannam@0: cannam@0: This program is free software; you can redistribute it and/or cannam@0: modify it under the terms of the GNU General Public License as cannam@0: published by the Free Software Foundation; either version 2 of the cannam@0: License, or (at your option) any later version. See the file cannam@0: COPYING included with this distribution for more information. cannam@0: */ cannam@0: cannam@0: #include "Matcher.h" cannam@0: cannam@0: #include cannam@0: cannam@4: #include Chris@16: #include cannam@4: Chris@72: using namespace std; Chris@72: Chris@10: //#define DEBUG_MATCHER 1 Chris@10: Chris@143: Matcher::Matcher(Parameters parameters, DistanceMetric::Parameters dparams, Chris@143: Matcher *p) : Chris@43: m_params(parameters), Chris@143: m_metric(dparams) Chris@23: { Chris@23: #ifdef DEBUG_MATCHER Chris@143: cerr << "*** Matcher: hopTime = " << parameters.hopTime Chris@140: << ", blockTime = " << parameters.blockTime Chris@140: << ", maxRunCount = " << parameters.maxRunCount Chris@140: << ", diagonalWeight = " << parameters.diagonalWeight << endl; Chris@23: #endif Chris@140: Chris@43: m_otherMatcher = p; // the first matcher will need this to be set later Chris@43: m_firstPM = (!p); Chris@43: m_frameCount = 0; Chris@43: m_runCount = 0; Chris@43: m_blockSize = 0; cannam@0: Chris@43: m_blockSize = lrint(m_params.blockTime / m_params.hopTime); Chris@15: #ifdef DEBUG_MATCHER Chris@43: cerr << "Matcher: m_blockSize = " << m_blockSize << endl; Chris@15: #endif cannam@0: Chris@43: m_initialised = false; Chris@23: } cannam@0: cannam@0: Matcher::~Matcher() cannam@0: { Chris@10: #ifdef DEBUG_MATCHER Chris@15: cerr << "Matcher(" << this << ")::~Matcher()" << endl; Chris@10: #endif cannam@0: } cannam@0: cannam@0: void cannam@0: Matcher::init() cannam@0: { Chris@43: if (m_initialised) return; cannam@0: Chris@104: m_frames = vector >(m_blockSize); cannam@0: Chris@43: m_distXSize = m_blockSize * 2; Chris@45: Chris@41: size(); cannam@0: Chris@43: m_frameCount = 0; Chris@43: m_runCount = 0; Chris@38: Chris@43: m_initialised = true; Chris@16: } Chris@16: Chris@87: bool Chris@154: Matcher::isRowAvailable(int i) Chris@154: { Chris@154: if (i < 0 || i >= int(m_first.size())) return false; Chris@154: Chris@154: for (int j = m_first[i]; j < int(m_first[i] + m_bestPathCost[i].size()); ++j) { Chris@154: if (isAvailable(i, j)) { Chris@154: return true; Chris@154: } Chris@154: } Chris@154: Chris@154: return false; Chris@154: } Chris@154: Chris@154: bool Chris@154: Matcher::isColAvailable(int i) Chris@154: { Chris@154: return m_otherMatcher->isRowAvailable(i); Chris@154: } Chris@154: Chris@154: bool Chris@87: Matcher::isInRange(int i, int j) Chris@87: { Chris@87: if (m_firstPM) { Chris@87: return ((i >= 0) && Chris@87: (i < int(m_first.size())) && Chris@87: (j >= m_first[i]) && Chris@87: (j < int(m_first[i] + m_bestPathCost[i].size()))); Chris@87: } else { Chris@87: return m_otherMatcher->isInRange(j, i); Chris@87: } Chris@87: } Chris@87: Chris@87: bool Chris@87: Matcher::isAvailable(int i, int j) Chris@87: { Chris@87: if (m_firstPM) { Chris@87: if (isInRange(i, j)) { Chris@87: return (m_bestPathCost[i][j - m_first[i]] >= 0); Chris@87: } else { Chris@87: return false; Chris@87: } Chris@87: } else { Chris@87: return m_otherMatcher->isAvailable(j, i); Chris@87: } Chris@87: } Chris@87: Chris@87: pair Chris@87: Matcher::getColRange(int i) Chris@87: { Chris@87: if (i < 0 || i >= int(m_first.size())) { Chris@87: cerr << "ERROR: Matcher::getColRange(" << i << "): Index out of range" Chris@87: << endl; Chris@87: throw "Index out of range"; Chris@87: } else { Chris@87: return pair(m_first[i], m_last[i]); Chris@87: } Chris@87: } Chris@87: Chris@87: pair Chris@87: Matcher::getRowRange(int i) Chris@87: { Chris@87: return m_otherMatcher->getColRange(i); Chris@87: } Chris@87: Chris@87: float Chris@87: Matcher::getDistance(int i, int j) Chris@87: { Chris@87: if (m_firstPM) { Chris@87: if (!isInRange(i, j)) { Chris@87: cerr << "ERROR: Matcher::getDistance(" << i << ", " << j << "): " Chris@87: << "Location is not in range" << endl; Chris@87: throw "Distance not available"; Chris@87: } Chris@87: float dist = m_distance[i][j - m_first[i]]; Chris@87: if (dist < 0) { Chris@87: cerr << "ERROR: Matcher::getDistance(" << i << ", " << j << "): " Chris@87: << "Location is in range, but distance (" Chris@87: << dist << ") is invalid or has not been set" << endl; Chris@87: throw "Distance not available"; Chris@87: } Chris@87: return dist; Chris@87: } else { Chris@87: return m_otherMatcher->getDistance(j, i); Chris@87: } Chris@87: } Chris@87: Chris@87: void Chris@87: Matcher::setDistance(int i, int j, float distance) Chris@87: { Chris@87: if (m_firstPM) { Chris@87: if (!isInRange(i, j)) { Chris@87: cerr << "ERROR: Matcher::setDistance(" << i << ", " << j << ", " Chris@87: << distance << "): Location is out of range" << endl; Chris@87: throw "Indices out of range"; Chris@87: } Chris@87: m_distance[i][j - m_first[i]] = distance; Chris@87: } else { Chris@87: m_otherMatcher->setDistance(j, i, distance); Chris@87: } Chris@87: } Chris@87: Chris@87: double Chris@135: Matcher::getNormalisedPathCost(int i, int j) Chris@135: { Chris@135: // normalised for path length. 1+ prevents division by zero here Chris@135: return getPathCost(i, j) / (1 + i + j); Chris@135: } Chris@135: Chris@135: double Chris@87: Matcher::getPathCost(int i, int j) Chris@87: { Chris@87: if (m_firstPM) { Chris@87: if (!isAvailable(i, j)) { Chris@87: if (!isInRange(i, j)) { Chris@87: cerr << "ERROR: Matcher::getPathCost(" << i << ", " << j << "): " Chris@87: << "Location is not in range" << endl; Chris@87: } else { Chris@87: cerr << "ERROR: Matcher::getPathCost(" << i << ", " << j << "): " Chris@87: << "Location is in range, but pathCost (" Chris@87: << m_bestPathCost[i][j - m_first[i]] Chris@87: << ") is invalid or has not been set" << endl; Chris@87: } Chris@87: throw "Path cost not available"; Chris@87: } Chris@87: return m_bestPathCost[i][j - m_first[i]]; Chris@87: } else { Chris@87: return m_otherMatcher->getPathCost(j, i); Chris@87: } Chris@87: } Chris@87: Chris@87: void Chris@87: Matcher::setPathCost(int i, int j, Advance dir, double pathCost) Chris@87: { Chris@87: if (m_firstPM) { Chris@87: if (!isInRange(i, j)) { Chris@87: cerr << "ERROR: Matcher::setPathCost(" << i << ", " << j << ", " Chris@87: << dir << ", " << pathCost Chris@87: << "): Location is out of range" << endl; Chris@87: throw "Indices out of range"; Chris@87: } Chris@87: m_advance[i][j - m_first[i]] = dir; Chris@87: m_bestPathCost[i][j - m_first[i]] = pathCost; Chris@87: } else { Chris@87: if (dir == AdvanceThis) { Chris@87: dir = AdvanceOther; Chris@87: } else if (dir == AdvanceOther) { Chris@87: dir = AdvanceThis; Chris@87: } Chris@87: m_otherMatcher->setPathCost(j, i, dir, pathCost); Chris@87: } Chris@87: Chris@87: } Chris@87: cannam@0: void Chris@41: Matcher::size() cannam@0: { Chris@43: int distSize = (m_params.maxRunCount + 1) * m_blockSize; Chris@71: m_bestPathCost.resize(m_distXSize, vector(distSize, -1)); Chris@71: m_distance.resize(m_distXSize, vector(distSize, -1)); Chris@45: m_advance.resize(m_distXSize, vector(distSize, AdvanceNone)); Chris@43: m_first.resize(m_distXSize, 0); Chris@43: m_last.resize(m_distXSize, 0); Chris@38: } cannam@0: Chris@23: void Chris@72: Matcher::consumeFeatureVector(vector feature) Chris@23: { Chris@43: if (!m_initialised) init(); Chris@43: int frameIndex = m_frameCount % m_blockSize; Chris@43: m_frames[frameIndex] = feature; Chris@23: calcAdvance(); Chris@21: } Chris@21: Chris@21: void Chris@21: Matcher::calcAdvance() Chris@21: { Chris@43: int frameIndex = m_frameCount % m_blockSize; Chris@21: Chris@43: if (m_frameCount >= m_distXSize) { Chris@43: m_distXSize *= 2; Chris@41: size(); cannam@0: } cannam@0: Chris@43: if (m_firstPM && (m_frameCount >= m_blockSize)) { cannam@0: Chris@43: int len = m_last[m_frameCount - m_blockSize] - Chris@43: m_first[m_frameCount - m_blockSize]; cannam@0: Chris@43: // We need to copy distance[m_frameCount-m_blockSize] to Chris@43: // distance[m_frameCount], and then truncate Chris@43: // distance[m_frameCount-m_blockSize] to its first len elements. cannam@0: // Same for bestPathCost. cannam@0: Chris@69: vector dOld = m_distance[m_frameCount - m_blockSize]; Chris@71: vector dNew(len, -1.f); cannam@0: Chris@69: vector bpcOld = m_bestPathCost[m_frameCount - m_blockSize]; Chris@71: vector bpcNew(len, -1.0); Chris@69: Chris@69: vector adOld = m_advance[m_frameCount - m_blockSize]; Chris@69: vector adNew(len, AdvanceNone); Chris@69: Chris@69: for (int i = 0; i < len; ++i) { Chris@69: dNew[i] = dOld[i]; Chris@69: bpcNew[i] = bpcOld[i]; Chris@69: adNew[i] = adOld[i]; Chris@69: } Chris@45: Chris@69: m_distance[m_frameCount] = dOld; Chris@69: m_distance[m_frameCount - m_blockSize] = dNew; Chris@69: Chris@69: m_bestPathCost[m_frameCount] = bpcOld; Chris@69: m_bestPathCost[m_frameCount - m_blockSize] = bpcNew; Chris@69: Chris@69: m_advance[m_frameCount] = adOld; Chris@69: m_advance[m_frameCount - m_blockSize] = adNew; cannam@0: } cannam@0: Chris@43: int stop = m_otherMatcher->m_frameCount; Chris@43: int index = stop - m_blockSize; Chris@86: if (index < 0) index = 0; Chris@86: Chris@43: m_first[m_frameCount] = index; Chris@43: m_last[m_frameCount] = stop; cannam@0: cannam@0: for ( ; index < stop; index++) { Chris@26: Chris@86: float distance = (float) m_metric.calcDistance Chris@43: (m_frames[frameIndex], Chris@45: m_otherMatcher->m_frames[index % m_blockSize]); Chris@26: Chris@89: float diagDistance = distance * m_params.diagonalWeight; Chris@89: Chris@86: if ((m_frameCount == 0) && (index == 0)) { // first element Chris@86: Chris@86: updateValue(0, 0, AdvanceNone, Chris@86: 0, Chris@86: distance); Chris@86: Chris@86: } else if (m_frameCount == 0) { // first row Chris@86: Chris@71: updateValue(0, index, AdvanceOther, Chris@86: getPathCost(0, index-1), Chris@86: distance); Chris@86: Chris@86: } else if (index == 0) { // first column Chris@86: Chris@71: updateValue(m_frameCount, index, AdvanceThis, Chris@86: getPathCost(m_frameCount - 1, 0), Chris@86: distance); Chris@86: Chris@86: } else if (index == m_otherMatcher->m_frameCount - m_blockSize) { Chris@86: cannam@0: // missing value(s) due to cutoff cannam@0: // - no previous value in current row (resp. column) cannam@0: // - no diagonal value if prev. dir. == curr. dirn Chris@86: Chris@72: double min2 = getPathCost(m_frameCount - 1, index); Chris@86: Chris@91: // cerr << "NOTE: missing value at i = " << m_frameCount << ", j = " Chris@91: // << index << " (first = " << m_firstPM << ")" << endl; Chris@86: Chris@43: // if ((m_firstPM && (first[m_frameCount - 1] == index)) || Chris@43: // (!m_firstPM && (m_last[index-1] < m_frameCount))) Chris@86: if (m_first[m_frameCount - 1] == index) { Chris@86: Chris@86: updateValue(m_frameCount, index, AdvanceThis, Chris@86: min2, distance); Chris@86: Chris@86: } else { Chris@86: Chris@72: double min1 = getPathCost(m_frameCount - 1, index - 1); Chris@89: if (min1 + diagDistance <= min2 + distance) { Chris@86: updateValue(m_frameCount, index, AdvanceBoth, Chris@86: min1, distance); Chris@86: } else { Chris@86: updateValue(m_frameCount, index, AdvanceThis, Chris@86: min2, distance); Chris@86: } cannam@0: } Chris@86: cannam@0: } else { Chris@86: Chris@86: double min1 = getPathCost(m_frameCount, index - 1); Chris@72: double min2 = getPathCost(m_frameCount - 1, index); Chris@86: double min3 = getPathCost(m_frameCount - 1, index - 1); Chris@87: Chris@89: double cost1 = min1 + distance; Chris@89: double cost2 = min2 + distance; Chris@89: double cost3 = min3 + diagDistance; Chris@93: Chris@93: // Choosing is easy if there is a strict cheapest of the Chris@93: // three. If two or more share the lowest cost, we choose Chris@93: // in order of preference: cost3 (AdvanceBoth), cost2 Chris@94: // (AdvanceThis), cost1 (AdvanceOther) if we are the first Chris@94: // matcher; and cost3 (AdvanceBoth), cost1 (AdvanceOther), Chris@94: // cost2 (AdvanceThis) if we are the second matcher. That Chris@94: // is, we always prioritise the diagonal followed by the Chris@94: // first matcher. Chris@94: Chris@94: if (( m_firstPM && (cost1 < cost2)) || Chris@94: (!m_firstPM && (cost1 <= cost2))) { Chris@89: if (cost3 <= cost1) { Chris@86: updateValue(m_frameCount, index, AdvanceBoth, Chris@86: min3, distance); Chris@86: } else { Chris@86: updateValue(m_frameCount, index, AdvanceOther, Chris@86: min1, distance); Chris@86: } cannam@0: } else { Chris@89: if (cost3 <= cost2) { Chris@86: updateValue(m_frameCount, index, AdvanceBoth, Chris@86: min3, distance); Chris@86: } else { Chris@86: updateValue(m_frameCount, index, AdvanceThis, Chris@86: min2, distance); Chris@86: } cannam@0: } cannam@0: } Chris@86: Chris@43: m_otherMatcher->m_last[index]++; cannam@0: } // loop for row (resp. column) cannam@0: Chris@43: m_frameCount++; Chris@43: m_runCount++; cannam@0: Chris@43: m_otherMatcher->m_runCount = 0; Chris@21: } cannam@0: cannam@0: void Chris@96: Matcher::updateValue(int i, int j, Advance dir, double value, float distance) cannam@0: { Chris@96: float weighted = distance; Chris@83: if (dir == AdvanceBoth) { Chris@83: weighted *= m_params.diagonalWeight; Chris@83: } Chris@83: Chris@43: if (m_firstPM) { Chris@45: Chris@96: setDistance(i, j, distance); Chris@83: setPathCost(i, j, dir, value + weighted); Chris@45: cannam@0: } else { Chris@45: Chris@86: if (dir == AdvanceThis) dir = AdvanceOther; Chris@86: else if (dir == AdvanceOther) dir = AdvanceThis; Chris@84: Chris@43: int idx = i - m_otherMatcher->m_first[j]; Chris@45: Chris@69: if (idx == (int)m_otherMatcher->m_distance[j].size()) { cannam@0: // This should never happen, but if we allow arbitrary cannam@0: // pauses in either direction, and arbitrary lengths at cannam@0: // end, it is better than a segmentation fault. Chris@72: cerr << "Emergency resize: " << idx << " -> " << idx * 2 << endl; Chris@71: m_otherMatcher->m_bestPathCost[j].resize(idx * 2, -1); Chris@71: m_otherMatcher->m_distance[j].resize(idx * 2, -1); Chris@46: m_otherMatcher->m_advance[j].resize(idx * 2, AdvanceNone); cannam@0: } Chris@45: Chris@96: m_otherMatcher->setDistance(j, i, distance); Chris@83: m_otherMatcher->setPathCost(j, i, dir, value + weighted); cannam@0: } Chris@71: } cannam@0: Chris@72: Matcher::Advance Chris@72: Matcher::getAdvance(int i, int j) Chris@72: { Chris@72: if (m_firstPM) { Chris@72: if (!isInRange(i, j)) { Chris@72: cerr << "ERROR: Matcher::getAdvance(" << i << ", " << j << "): " Chris@72: << "Location is not in range" << endl; Chris@72: throw "Advance not available"; Chris@72: } Chris@72: return m_advance[i][j - m_first[i]]; Chris@72: } else { Chris@72: return m_otherMatcher->getAdvance(j, i); Chris@72: } Chris@72: }