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@10: //#define DEBUG_MATCHER 1 Chris@10: Chris@38: Matcher::Matcher(Parameters parameters, Chris@38: FeatureExtractor::Parameters feParams, Chris@38: Matcher *p) : Chris@43: m_params(parameters), Chris@43: m_featureExtractor(feParams), Chris@43: m_metric(parameters.distanceNorm) cannam@0: { Chris@10: #ifdef DEBUG_MATCHER Chris@43: cerr << "Matcher::Matcher(" << m_params.sampleRate << ", " << p << ")" << endl; Chris@10: #endif cannam@0: 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_featureSize = m_featureExtractor.getFeatureSize(); Chris@43: m_blockSize = 0; Chris@23: Chris@43: m_blockSize = lrint(m_params.blockTime / m_params.hopTime); Chris@23: #ifdef DEBUG_MATCHER Chris@43: cerr << "Matcher: m_blockSize = " << m_blockSize << endl; Chris@23: #endif Chris@23: Chris@43: m_initialised = false; Chris@23: } Chris@23: Chris@43: Matcher::Matcher(Parameters parameters, Matcher *p, int m_featureSize_) : Chris@43: m_params(parameters), Chris@43: m_featureSize(m_featureSize_), Chris@43: m_featureExtractor(FeatureExtractor::Parameters(m_params.sampleRate, m_params.fftSize)), // unused default config Chris@43: m_metric(parameters.distanceNorm) Chris@23: { Chris@23: #ifdef DEBUG_MATCHER Chris@43: cerr << "Matcher::Matcher(" << m_params.sampleRate << ", " << p << ", " << m_featureSize << ")" << endl; Chris@23: #endif Chris@23: 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@43: m_frames = vector > Chris@69: (m_blockSize, vector(m_featureSize, -1.0)); 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: 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@14: vector Chris@21: Matcher::consumeFrame(double *reBuffer, double *imBuffer) cannam@0: { Chris@43: if (!m_initialised) init(); cannam@0: Chris@43: vector real(reBuffer, reBuffer + m_params.fftSize/2 + 1); Chris@43: vector imag(imBuffer, imBuffer + m_params.fftSize/2 + 1); Chris@43: vector feature = m_featureExtractor.process(real, imag); Chris@43: int frameIndex = m_frameCount % m_blockSize; Chris@43: m_frames[frameIndex] = feature; Chris@21: calcAdvance(); Chris@21: Chris@38: return feature; Chris@23: } Chris@21: Chris@23: void Chris@23: Matcher::consumeFeatureVector(std::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; cannam@0: if (index < 0) cannam@0: index = 0; Chris@43: m_first[m_frameCount] = index; Chris@43: m_last[m_frameCount] = stop; cannam@0: Chris@46: float mn= -1; Chris@46: float mx= -1; cannam@0: for ( ; index < stop; index++) { Chris@26: Chris@52: float dMN = (float) m_metric.calcDistance Chris@43: (m_frames[frameIndex], Chris@45: m_otherMatcher->m_frames[index % m_blockSize]); Chris@26: cannam@0: if (mx<0) cannam@0: mx = mn = dMN; cannam@0: else if (dMN > mx) cannam@0: mx = dMN; cannam@0: else if (dMN < mn) cannam@0: mn = dMN; Chris@26: Chris@43: if ((m_frameCount == 0) && (index == 0)) // first element Chris@71: updateValue(0, 0, AdvanceNone, 0, dMN); Chris@43: else if (m_frameCount == 0) // first row Chris@71: updateValue(0, index, AdvanceOther, Chris@71: getValue(0, index-1), dMN); cannam@0: else if (index == 0) // first column Chris@71: updateValue(m_frameCount, index, AdvanceThis, Chris@71: getValue(m_frameCount - 1, 0), dMN); Chris@43: else if (index == m_otherMatcher->m_frameCount - m_blockSize) { 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@71: double min2 = getValue(m_frameCount - 1, index); Chris@43: // if ((m_firstPM && (first[m_frameCount - 1] == index)) || Chris@43: // (!m_firstPM && (m_last[index-1] < m_frameCount))) Chris@43: if (m_first[m_frameCount - 1] == index) Chris@71: updateValue(m_frameCount, index, AdvanceThis, min2, dMN); cannam@0: else { Chris@71: double min1 = getValue(m_frameCount - 1, index - 1); cannam@0: if (min1 + dMN <= min2) Chris@71: updateValue(m_frameCount, index, AdvanceBoth, min1,dMN); cannam@0: else Chris@71: updateValue(m_frameCount, index, AdvanceThis, min2,dMN); cannam@0: } cannam@0: } else { Chris@71: double min1 = getValue(m_frameCount, index-1); Chris@71: double min2 = getValue(m_frameCount - 1, index); Chris@71: double min3 = getValue(m_frameCount - 1, index-1); cannam@0: if (min1 <= min2) { cannam@0: if (min3 + dMN <= min1) Chris@71: updateValue(m_frameCount, index, AdvanceBoth, min3,dMN); cannam@0: else Chris@71: updateValue(m_frameCount, index, AdvanceOther,min1,dMN); cannam@0: } else { cannam@0: if (min3 + dMN <= min2) Chris@71: updateValue(m_frameCount, index, AdvanceBoth, min3,dMN); cannam@0: else Chris@71: updateValue(m_frameCount, index, AdvanceThis, min2,dMN); cannam@0: } cannam@0: } 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: Chris@71: bool Chris@71: Matcher::isInRange(int i, int j) Chris@71: { Chris@71: if (m_firstPM) { Chris@71: return ((i >= 0) && Chris@71: (i < int(m_first.size())) && Chris@71: (j >= m_first[i]) && Chris@71: (j < int(m_first[i] + m_bestPathCost[i].size()))); Chris@71: } else { Chris@71: return m_otherMatcher->isInRange(j, i); Chris@71: } Chris@71: } Chris@71: Chris@71: bool Chris@71: Matcher::isAvailable(int i, int j) Chris@71: { Chris@71: if (m_firstPM) { Chris@71: if (isInRange(i, j)) { Chris@71: return (m_bestPathCost[i][j - m_first[i]] >= 0); Chris@71: } else { Chris@71: return false; Chris@71: } Chris@71: } else { Chris@71: return m_otherMatcher->isAvailable(j, i); Chris@71: } Chris@71: } Chris@71: Chris@53: double Chris@71: Matcher::getValue(int i, int j) cannam@0: { Chris@71: if (m_firstPM) { Chris@71: if (!isAvailable(i, j)) { Chris@71: if (!isInRange(i, j)) { Chris@71: cerr << "ERROR: Matcher::getValue(" << i << ", " << j << "): " Chris@71: << "Location is not in range" << endl; Chris@71: } else { Chris@71: cerr << "ERROR: Matcher::getValue(" << i << ", " << j << "): " Chris@71: << "Location is in range, but value (" Chris@71: << m_bestPathCost[i][j - m_first[i]] Chris@71: << ") is invalid or has not been set" << endl; Chris@71: } Chris@71: throw "Value not available"; Chris@71: } Chris@43: return m_bestPathCost[i][j - m_first[i]]; Chris@71: } else { Chris@71: return m_otherMatcher->getValue(j, i); Chris@71: } Chris@71: } Chris@71: Chris@71: void Chris@71: Matcher::setValue(int i, int j, double value) Chris@71: { Chris@71: if (m_firstPM) { Chris@71: if (!isInRange(i, j)) { Chris@71: cerr << "ERROR: Matcher::setValue(" << i << ", " << j << ", " Chris@71: << value << "): Location is out of range" << endl; Chris@71: throw "Indices out of range"; Chris@71: } Chris@71: m_bestPathCost[i][j - m_first[i]] = value; Chris@71: } else { Chris@71: m_otherMatcher->setValue(j, i, value); Chris@71: } Chris@71: } cannam@0: cannam@0: void Chris@71: Matcher::updateValue(int i, int j, Advance dir, double value, float dMN) cannam@0: { Chris@43: if (m_firstPM) { Chris@45: Chris@45: int jdx = j - m_first[i]; Chris@45: m_distance[i][jdx] = dMN; Chris@45: m_advance[i][jdx] = dir; Chris@71: setValue(i, j, value + (dir == AdvanceBoth ? dMN*2: dMN)); Chris@45: cannam@0: } else { Chris@45: Chris@45: if (dir == AdvanceThis) { Chris@45: dir = AdvanceOther; Chris@45: } else if (dir == AdvanceOther) { Chris@45: dir = AdvanceThis; Chris@45: } Chris@45: 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. cannam@0: std::cerr << "Emergency resize: " << idx << " -> " << idx * 2 << std::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@45: m_otherMatcher->m_distance[j][idx] = dMN; Chris@45: m_otherMatcher->m_advance[j][idx] = dir; Chris@71: m_otherMatcher->setValue(j, i, value + (dir == AdvanceBoth ? dMN*2: dMN)); cannam@0: } Chris@71: } cannam@0: