cannam@475: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ cannam@475: /* cannam@475: QM DSP Library cannam@475: cannam@475: Centre for Digital Music, Queen Mary, University of London. cannam@475: This file 2005-2006 Christian Landone and Katy Noland. cannam@475: cannam@475: Fixes to correct chroma offsets and for thread safety contributed cannam@475: by Daniel Schürmann. cannam@475: cannam@475: This program is free software; you can redistribute it and/or cannam@475: modify it under the terms of the GNU General Public License as cannam@475: published by the Free Software Foundation; either version 2 of the cannam@475: License, or (at your option) any later version. See the file cannam@475: COPYING included with this distribution for more information. cannam@475: */ cannam@475: cannam@475: #include "GetKeyMode.h" cannam@509: cannam@509: #include "dsp/rateconversion/Decimator.h" cannam@509: #include "dsp/chromagram/Chromagram.h" cannam@509: cannam@475: #include "maths/MathUtilities.h" cannam@475: #include "base/Pitch.h" cannam@475: cannam@475: #include cannam@475: cannam@475: #include cannam@475: #include cannam@475: cannam@475: static const int kBinsPerOctave = 36; cannam@475: cannam@475: // Chords profile cannam@475: static double MajProfile[kBinsPerOctave] = { cannam@475: 0.0384, 0.0629, 0.0258, 0.0121, 0.0146, 0.0106, 0.0364, 0.0610, 0.0267, cannam@475: 0.0126, 0.0121, 0.0086, 0.0364, 0.0623, 0.0279, 0.0275, 0.0414, 0.0186, cannam@475: 0.0173, 0.0248, 0.0145, 0.0364, 0.0631, 0.0262, 0.0129, 0.0150, 0.0098, cannam@499: 0.0312, 0.0521, 0.0235, 0.0129, 0.0142, 0.0095, 0.0289, 0.0478, 0.0239 cannam@499: }; cannam@475: cannam@475: static double MinProfile[kBinsPerOctave] = { cannam@475: 0.0375, 0.0682, 0.0299, 0.0119, 0.0138, 0.0093, 0.0296, 0.0543, 0.0257, cannam@475: 0.0292, 0.0519, 0.0246, 0.0159, 0.0234, 0.0135, 0.0291, 0.0544, 0.0248, cannam@475: 0.0137, 0.0176, 0.0104, 0.0352, 0.0670, 0.0302, 0.0222, 0.0349, 0.0164, cannam@499: 0.0174, 0.0297, 0.0166, 0.0222, 0.0401, 0.0202, 0.0175, 0.0270, 0.0146 cannam@499: }; cannam@475: // cannam@475: cannam@475: cannam@475: ////////////////////////////////////////////////////////////////////// cannam@475: // Construction/Destruction cannam@475: ////////////////////////////////////////////////////////////////////// cannam@475: cannam@509: GetKeyMode::GetKeyMode(Config config) : cannam@509: m_hpcpAverage(config.hpcpAverage), cannam@509: m_medianAverage(config.medianAverage), cannam@509: m_decimationFactor(config.decimationFactor), cannam@499: m_chrPointer(0), cannam@499: m_decimatedBuffer(0), cannam@499: m_chromaBuffer(0), cannam@499: m_meanHPCP(0), cannam@499: m_majCorr(0), cannam@499: m_minCorr(0), cannam@499: m_medianFilterBuffer(0), cannam@499: m_sortedBuffer(0), cannam@475: m_keyStrengths(0) cannam@475: { cannam@509: ChromaConfig chromaConfig; cannam@509: cannam@475: // Chromagram configuration parameters cannam@509: chromaConfig.normalise = MathUtilities::NormaliseUnitMax; cannam@509: chromaConfig.FS = config.sampleRate / (double)m_decimationFactor; cannam@509: if (chromaConfig.FS < 1) { cannam@509: chromaConfig.FS = 1; cannam@475: } cannam@475: cannam@475: // Set C3 (= MIDI #48) as our base: cannam@475: // This implies that key = 1 => Cmaj, key = 12 => Bmaj, key = 13 => Cmin, etc. cannam@509: chromaConfig.min = cannam@509: Pitch::getFrequencyForPitch( 48, 0, config.tuningFrequency ); cannam@509: chromaConfig.max = cannam@509: Pitch::getFrequencyForPitch( 96, 0, config.tuningFrequency ); cannam@475: cannam@509: chromaConfig.BPO = kBinsPerOctave; cannam@509: chromaConfig.CQThresh = 0.0054; cannam@475: cannam@475: // Chromagram inst. cannam@509: m_chroma = new Chromagram(chromaConfig); cannam@475: cannam@475: // Get calculated parameters from chroma object cannam@499: m_chromaFrameSize = m_chroma->getFrameSize(); cannam@509: cannam@475: // override hopsize for this application cannam@509: m_chromaHopSize = m_chromaFrameSize / config.frameOverlapFactor; cannam@475: cannam@475: // std::cerr << "chroma frame size = " << m_ChromaFrameSize << ", decimation factor = " << m_DecimationFactor << " therefore block size = " << getBlockSize() << std::endl; cannam@475: cannam@475: // Chromagram average and estimated key median filter lengths cannam@499: m_chromaBufferSize = (int)ceil cannam@509: (m_hpcpAverage * chromaConfig.FS / m_chromaFrameSize); cannam@499: m_medianWinSize = (int)ceil cannam@509: (m_medianAverage * chromaConfig.FS / m_chromaFrameSize); cannam@475: cannam@475: // Reset counters cannam@499: m_bufferIndex = 0; cannam@499: m_chromaBufferFilling = 0; cannam@499: m_medianBufferFilling = 0; cannam@475: cannam@475: // Spawn objectc/arrays cannam@499: m_decimatedBuffer = new double[m_chromaFrameSize]; cannam@499: m_chromaBuffer = new double[kBinsPerOctave * m_chromaBufferSize]; cannam@499: cannam@499: memset(m_chromaBuffer, 0, cannam@499: sizeof(double) * kBinsPerOctave * m_chromaBufferSize); cannam@475: cannam@499: m_meanHPCP = new double[kBinsPerOctave]; cannam@475: cannam@499: m_majCorr = new double[kBinsPerOctave]; cannam@499: m_minCorr = new double[kBinsPerOctave]; cannam@475: cannam@499: m_majProfileNorm = new double[kBinsPerOctave]; cannam@499: m_minProfileNorm = new double[kBinsPerOctave]; cannam@475: cannam@475: double mMaj = MathUtilities::mean( MajProfile, kBinsPerOctave ); cannam@475: double mMin = MathUtilities::mean( MinProfile, kBinsPerOctave ); cannam@475: cannam@499: for (int i = 0; i < kBinsPerOctave; i++) { cannam@499: m_majProfileNorm[i] = MajProfile[i] - mMaj; cannam@499: m_minProfileNorm[i] = MinProfile[i] - mMin; cannam@475: } cannam@475: cannam@499: m_medianFilterBuffer = new int[ m_medianWinSize ]; cannam@499: memset( m_medianFilterBuffer, 0, sizeof(int)*m_medianWinSize); cannam@475: cannam@499: m_sortedBuffer = new int[ m_medianWinSize ]; cannam@499: memset( m_sortedBuffer, 0, sizeof(int)*m_medianWinSize); cannam@475: cannam@499: m_decimator = new Decimator( m_chromaFrameSize * m_decimationFactor, cannam@499: m_decimationFactor ); cannam@475: cannam@475: m_keyStrengths = new double[24]; cannam@475: } cannam@475: cannam@475: GetKeyMode::~GetKeyMode() cannam@475: { cannam@499: delete m_chroma; cannam@499: delete m_decimator; cannam@475: cannam@499: delete [] m_decimatedBuffer; cannam@499: delete [] m_chromaBuffer; cannam@499: delete [] m_meanHPCP; cannam@499: delete [] m_majCorr; cannam@499: delete [] m_minCorr; cannam@499: delete [] m_majProfileNorm; cannam@499: delete [] m_minProfileNorm; cannam@499: delete [] m_medianFilterBuffer; cannam@499: delete [] m_sortedBuffer; cannam@475: delete [] m_keyStrengths; cannam@475: } cannam@475: cannam@475: double GetKeyMode::krumCorr( const double *pDataNorm, const double *pProfileNorm, cannam@499: int shiftProfile, int length) cannam@475: { cannam@475: double retVal= 0.0; cannam@475: cannam@475: double num = 0; cannam@475: double den = 0; cannam@475: double sum1 = 0; cannam@475: double sum2 = 0; cannam@475: cannam@499: for (int i = 0; i < length; i++) { cannam@478: cannam@475: int k = (i - shiftProfile + length) % length; cannam@475: cannam@475: num += pDataNorm[i] * pProfileNorm[k]; cannam@475: cannam@499: sum1 += (pDataNorm[i] * pDataNorm[i]); cannam@499: sum2 += (pProfileNorm[k] * pProfileNorm[k]); cannam@475: } cannam@478: cannam@475: den = sqrt(sum1 * sum2); cannam@475: cannam@499: if (den > 0) { cannam@475: retVal = num/den; cannam@475: } else { cannam@475: retVal = 0; cannam@475: } cannam@475: cannam@475: return retVal; cannam@475: } cannam@475: cannam@499: int GetKeyMode::process(double *pcmData) cannam@475: { cannam@475: int key; cannam@499: int j, k; cannam@475: cannam@499: m_decimator->process(pcmData, m_decimatedBuffer); cannam@475: cannam@499: m_chrPointer = m_chroma->process(m_decimatedBuffer); cannam@475: cannam@499: // populate hpcp values cannam@475: int cbidx; cannam@499: for (j = 0;j < kBinsPerOctave;j++ ) { cannam@499: cbidx = (m_bufferIndex * kBinsPerOctave) + j; cannam@499: m_chromaBuffer[ cbidx ] = m_chrPointer[j]; cannam@475: } cannam@475: cannam@499: // keep track of input buffers cannam@499: if (m_bufferIndex++ >= m_chromaBufferSize - 1) { cannam@499: m_bufferIndex = 0; cannam@475: } cannam@475: cannam@475: // track filling of chroma matrix cannam@499: if (m_chromaBufferFilling++ >= m_chromaBufferSize) { cannam@499: m_chromaBufferFilling = m_chromaBufferSize; cannam@475: } cannam@475: cannam@499: // calculate mean cannam@499: for (k = 0; k < kBinsPerOctave; k++) { cannam@475: double mnVal = 0.0; cannam@499: for (j = 0; j < m_chromaBufferFilling; j++) { cannam@499: mnVal += m_chromaBuffer[ k + (j * kBinsPerOctave) ]; cannam@475: } cannam@475: cannam@499: m_meanHPCP[k] = mnVal / (double)m_chromaBufferFilling; cannam@475: } cannam@475: cannam@475: // Normalize for zero average cannam@499: double mHPCP = MathUtilities::mean(m_meanHPCP, kBinsPerOctave); cannam@499: for (k = 0; k < kBinsPerOctave; k++) { cannam@499: m_meanHPCP[k] -= mHPCP; cannam@475: } cannam@475: cannam@499: for (k = 0; k < kBinsPerOctave; k++) { cannam@478: // The Chromagram has the center of C at bin 0, while the major cannam@475: // and minor profiles have the center of C at 1. We want to have cannam@475: // the correlation for C result also at 1. cannam@475: // To achieve this we have to shift two times: cannam@499: m_majCorr[k] = krumCorr cannam@499: (m_meanHPCP, m_majProfileNorm, k - 2, kBinsPerOctave); cannam@499: m_minCorr[k] = krumCorr cannam@499: (m_meanHPCP, m_minProfileNorm, k - 2, kBinsPerOctave); cannam@475: } cannam@475: cannam@475: // m_MajCorr[1] is C center 1 / 3 + 1 = 1 cannam@475: // m_MajCorr[4] is D center 4 / 3 + 1 = 2 cannam@475: // '+ 1' because we number keys 1-24, not 0-23. cannam@475: double maxMaj; cannam@499: int maxMajBin = MathUtilities::getMax(m_majCorr, kBinsPerOctave, &maxMaj); cannam@475: double maxMin; cannam@499: int maxMinBin = MathUtilities::getMax(m_minCorr, kBinsPerOctave, &maxMin); cannam@475: int maxBin = (maxMaj > maxMin) ? maxMajBin : (maxMinBin + kBinsPerOctave); cannam@475: key = maxBin / 3 + 1; cannam@475: cannam@499: // Median filtering cannam@475: cannam@475: // track Median buffer initial filling cannam@499: if (m_medianBufferFilling++ >= m_medianWinSize) { cannam@499: m_medianBufferFilling = m_medianWinSize; cannam@475: } cannam@475: cannam@499: // shift median buffer cannam@499: for (k = 1; k < m_medianWinSize; k++ ) { cannam@499: m_medianFilterBuffer[ k - 1 ] = m_medianFilterBuffer[ k ]; cannam@475: } cannam@475: cannam@499: // write new key value into median buffer cannam@499: m_medianFilterBuffer[ m_medianWinSize - 1 ] = key; cannam@475: cannam@499: // copy median into sorting buffer, reversed cannam@499: int ijx = 0; cannam@499: for (k = 0; k < m_medianWinSize; k++) { cannam@499: m_sortedBuffer[k] = m_medianFilterBuffer[m_medianWinSize - 1 - ijx]; cannam@475: ijx++; cannam@475: } cannam@475: cannam@499: qsort(m_sortedBuffer, m_medianBufferFilling, sizeof(int), cannam@475: MathUtilities::compareInt); cannam@478: cannam@499: int sortlength = m_medianBufferFilling; cannam@499: int midpoint = (int)ceil((double)sortlength / 2); cannam@475: cannam@499: if (midpoint <= 0) { cannam@475: midpoint = 1; cannam@475: } cannam@475: cannam@499: key = m_sortedBuffer[midpoint-1]; cannam@475: cannam@475: return key; cannam@475: } cannam@475: cannam@475: double* GetKeyMode::getKeyStrengths() { cannam@499: int k; cannam@475: cannam@475: for (k = 0; k < 24; ++k) { cannam@475: m_keyStrengths[k] = 0; cannam@475: } cannam@475: cannam@499: for (k = 0; k < kBinsPerOctave; k++) { cannam@475: int idx = k / (kBinsPerOctave/12); cannam@475: int rem = k % (kBinsPerOctave/12); cannam@499: if (rem == 0 || m_majCorr[k] > m_keyStrengths[idx]) { cannam@499: m_keyStrengths[idx] = m_majCorr[k]; cannam@475: } cannam@475: } cannam@475: cannam@499: for (k = 0; k < kBinsPerOctave; k++) { cannam@475: int idx = (k + kBinsPerOctave) / (kBinsPerOctave/12); cannam@475: int rem = k % (kBinsPerOctave/12); cannam@499: if (rem == 0 || m_minCorr[k] > m_keyStrengths[idx]) { cannam@499: m_keyStrengths[idx] = m_minCorr[k]; cannam@475: } cannam@475: } cannam@475: cannam@475: return m_keyStrengths; cannam@475: }