Chris@23: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ matthiasm@0: Chris@35: /* Chris@35: NNLS-Chroma / Chordino Chris@35: Chris@35: Audio feature extraction plugins for chromagram and chord Chris@35: estimation. Chris@35: Chris@35: Centre for Digital Music, Queen Mary University of London. Chris@35: This file copyright 2008-2010 Matthias Mauch and QMUL. Chris@35: Chris@35: This program is free software; you can redistribute it and/or Chris@35: modify it under the terms of the GNU General Public License as Chris@35: published by the Free Software Foundation; either version 2 of the Chris@35: License, or (at your option) any later version. See the file Chris@35: COPYING included with this distribution for more information. Chris@35: */ Chris@35: Chris@35: #include "Chordino.h" Chris@27: Chris@27: #include "chromamethods.h" Chris@27: Chris@27: #include Chris@27: #include matthiasm@0: #include matthiasm@9: Chris@27: #include matthiasm@0: matthiasm@0: const bool debug_on = false; matthiasm@0: Chris@27: const vector hw(hammingwind, hammingwind+19); matthiasm@0: Chris@35: Chordino::Chordino(float inputSampleRate) : Chris@35: NNLSBase(inputSampleRate) matthiasm@0: { Chris@35: if (debug_on) cerr << "--> Chordino" << endl; matthiasm@0: } matthiasm@0: Chris@35: Chordino::~Chordino() matthiasm@0: { Chris@35: if (debug_on) cerr << "--> ~Chordino" << endl; matthiasm@0: } matthiasm@0: matthiasm@0: string Chris@35: Chordino::getIdentifier() const matthiasm@0: { Chris@23: if (debug_on) cerr << "--> getIdentifier" << endl; Chris@35: return "chordino"; matthiasm@0: } matthiasm@0: matthiasm@0: string Chris@35: Chordino::getName() const matthiasm@0: { Chris@23: if (debug_on) cerr << "--> getName" << endl; Chris@35: return "Chordino"; matthiasm@0: } matthiasm@0: matthiasm@0: string Chris@35: Chordino::getDescription() const matthiasm@0: { Chris@23: if (debug_on) cerr << "--> getDescription" << endl; matthiasm@13: return "This plugin provides a number of features derived from a log-frequency amplitude spectrum of the DFT: some variants of the log-frequency spectrum, including a semitone spectrum derived from approximate transcription using the NNLS algorithm; based on this semitone spectrum, chroma features and a simple chord estimate."; matthiasm@0: } matthiasm@0: Chris@35: Chordino::OutputList Chris@35: Chordino::getOutputDescriptors() const matthiasm@0: { Chris@23: if (debug_on) cerr << "--> getOutputDescriptors" << endl; matthiasm@0: OutputList list; matthiasm@0: Chris@35: int index = 0; matthiasm@0: matthiasm@0: OutputDescriptor d7; matthiasm@0: d7.identifier = "simplechord"; matthiasm@0: d7.name = "Simple Chord Estimate"; matthiasm@0: d7.description = "A simple chord estimate based on the inner product of chord templates with the smoothed chroma."; matthiasm@0: d7.unit = ""; matthiasm@0: d7.hasFixedBinCount = true; matthiasm@0: d7.binCount = 0; matthiasm@0: d7.hasKnownExtents = false; matthiasm@0: d7.isQuantized = false; matthiasm@0: d7.sampleType = OutputDescriptor::VariableSampleRate; matthiasm@0: d7.hasDuration = false; matthiasm@0: d7.sampleRate = (m_stepSize == 0) ? m_inputSampleRate/2048 : m_inputSampleRate/m_stepSize; matthiasm@0: list.push_back(d7); Chris@35: m_outputChords = index++; matthiasm@0: Chris@23: OutputDescriptor d8; matthiasm@17: d8.identifier = "harmonicchange"; matthiasm@17: d8.name = "Harmonic change value"; matthiasm@17: d8.description = "Harmonic change."; matthiasm@17: d8.unit = ""; matthiasm@17: d8.hasFixedBinCount = true; matthiasm@17: d8.binCount = 1; matthiasm@17: d8.hasKnownExtents = true; Chris@23: d8.minValue = 0.0; Chris@23: d8.maxValue = 0.999; matthiasm@17: d8.isQuantized = false; matthiasm@17: d8.sampleType = OutputDescriptor::FixedSampleRate; matthiasm@17: d8.hasDuration = false; matthiasm@17: // d8.sampleRate = (m_stepSize == 0) ? m_inputSampleRate/2048 : m_inputSampleRate/m_stepSize; matthiasm@17: list.push_back(d8); Chris@35: m_outputHarmonicChange = index++; matthiasm@1: matthiasm@0: return list; matthiasm@0: } matthiasm@0: matthiasm@0: bool Chris@35: Chordino::initialise(size_t channels, size_t stepSize, size_t blockSize) matthiasm@0: { Chris@23: if (debug_on) { Chris@23: cerr << "--> initialise"; Chris@23: } matthiasm@1: Chris@35: if (!NNLSBase::initialise(channels, stepSize, blockSize)) { Chris@35: return false; Chris@35: } matthiasm@1: matthiasm@0: return true; matthiasm@0: } matthiasm@0: matthiasm@0: void Chris@35: Chordino::reset() matthiasm@0: { Chris@23: if (debug_on) cerr << "--> reset"; Chris@35: NNLSBase::reset(); matthiasm@0: } matthiasm@0: Chris@35: Chordino::FeatureSet Chris@35: Chordino::process(const float *const *inputBuffers, Vamp::RealTime timestamp) matthiasm@0: { Chris@23: if (debug_on) cerr << "--> process" << endl; matthiasm@0: Chris@35: NNLSBase::baseProcess(inputBuffers, timestamp); matthiasm@0: Chris@35: return FeatureSet(); matthiasm@0: } matthiasm@0: Chris@35: Chordino::FeatureSet Chris@35: Chordino::getRemainingFeatures() matthiasm@0: { Chris@23: if (debug_on) cerr << "--> getRemainingFeatures" << endl; Chris@23: FeatureSet fsOut; Chris@35: if (m_logSpectrum.size() == 0) return fsOut; Chris@23: int nChord = m_chordnames.size(); Chris@23: // Chris@23: /** Calculate Tuning Chris@23: calculate tuning from (using the angle of the complex number defined by the Chris@23: cumulative mean real and imag values) Chris@23: **/ Chris@23: float meanTuningImag = sinvalue * m_meanTuning1 - sinvalue * m_meanTuning2; Chris@23: float meanTuningReal = m_meanTuning0 + cosvalue * m_meanTuning1 + cosvalue * m_meanTuning2; Chris@23: float cumulativetuning = 440 * pow(2,atan2(meanTuningImag, meanTuningReal)/(24*M_PI)); Chris@23: float normalisedtuning = atan2(meanTuningImag, meanTuningReal)/(2*M_PI); Chris@23: int intShift = floor(normalisedtuning * 3); Chris@23: float intFactor = normalisedtuning * 3 - intShift; // intFactor is a really bad name for this matthiasm@1: Chris@23: char buffer0 [50]; matthiasm@1: Chris@23: sprintf(buffer0, "estimated tuning: %0.1f Hz", cumulativetuning); matthiasm@1: matthiasm@1: Chris@23: /** Tune Log-Frequency Spectrogram Chris@23: calculate a tuned log-frequency spectrogram (f2): use the tuning estimated above (kinda f0) to Chris@23: perform linear interpolation on the existing log-frequency spectrogram (kinda f1). Chris@23: **/ Chris@35: cerr << endl << "[Chordino Plugin] Tuning Log-Frequency Spectrogram ... "; matthiasm@13: Chris@23: float tempValue = 0; Chris@23: float dbThreshold = 0; // relative to the background spectrum Chris@23: float thresh = pow(10,dbThreshold/20); Chris@23: // cerr << "tune local ? " << m_tuneLocal << endl; Chris@23: int count = 0; matthiasm@1: Chris@35: FeatureList tunedSpec; Chris@35: Chris@35: for (FeatureList::iterator i = m_logSpectrum.begin(); i != m_logSpectrum.end(); ++i) { Chris@23: Feature f1 = *i; Chris@23: Feature f2; // tuned log-frequency spectrum Chris@23: f2.hasTimestamp = true; Chris@23: f2.timestamp = f1.timestamp; Chris@23: f2.values.push_back(0.0); f2.values.push_back(0.0); // set lower edge to zero matthiasm@1: Chris@23: if (m_tuneLocal) { Chris@23: intShift = floor(m_localTuning[count] * 3); Chris@23: intFactor = m_localTuning[count] * 3 - intShift; // intFactor is a really bad name for this Chris@23: } matthiasm@1: Chris@23: // cerr << intShift << " " << intFactor << endl; matthiasm@1: Chris@23: for (unsigned k = 2; k < f1.values.size() - 3; ++k) { // interpolate all inner bins Chris@23: tempValue = f1.values[k + intShift] * (1-intFactor) + f1.values[k+intShift+1] * intFactor; Chris@23: f2.values.push_back(tempValue); Chris@23: } matthiasm@1: Chris@23: f2.values.push_back(0.0); f2.values.push_back(0.0); f2.values.push_back(0.0); // upper edge Chris@23: vector runningmean = SpecialConvolution(f2.values,hw); Chris@23: vector runningstd; Chris@23: for (int i = 0; i < 256; i++) { // first step: squared values into vector (variance) Chris@23: runningstd.push_back((f2.values[i] - runningmean[i]) * (f2.values[i] - runningmean[i])); Chris@23: } Chris@23: runningstd = SpecialConvolution(runningstd,hw); // second step convolve Chris@23: for (int i = 0; i < 256; i++) { Chris@23: runningstd[i] = sqrt(runningstd[i]); // square root to finally have running std Chris@23: if (runningstd[i] > 0) { Chris@23: // f2.values[i] = (f2.values[i] / runningmean[i]) > thresh ? Chris@23: // (f2.values[i] - runningmean[i]) / pow(runningstd[i],m_paling) : 0; Chris@23: f2.values[i] = (f2.values[i] - runningmean[i]) > 0 ? Chris@23: (f2.values[i] - runningmean[i]) / pow(runningstd[i],m_paling) : 0; Chris@23: } Chris@23: if (f2.values[i] < 0) { Chris@23: cerr << "ERROR: negative value in logfreq spectrum" << endl; Chris@23: } Chris@23: } Chris@35: tunedSpec.push_back(f2); Chris@23: count++; Chris@23: } Chris@23: cerr << "done." << endl; matthiasm@1: Chris@23: /** Semitone spectrum and chromagrams Chris@23: Semitone-spaced log-frequency spectrum derived from the tuned log-freq spectrum above. the spectrum Chris@23: is inferred using a non-negative least squares algorithm. Chris@23: Three different kinds of chromagram are calculated, "treble", "bass", and "both" (which means Chris@23: bass and treble stacked onto each other). Chris@23: **/ Chris@23: if (m_dictID == 1) { Chris@35: cerr << "[Chordino Plugin] Mapping to semitone spectrum and chroma ... "; Chris@23: } else { Chris@35: cerr << "[Chordino Plugin] Performing NNLS and mapping to chroma ... "; Chris@23: } matthiasm@13: matthiasm@1: Chris@23: vector > chordogram; Chris@23: vector > scoreChordogram; Chris@35: vector chordchange = vector(tunedSpec.size(),0); Chris@23: count = 0; matthiasm@9: Chris@35: FeatureList chromaList; Chris@35: Chris@35: for (FeatureList::iterator it = tunedSpec.begin(); it != tunedSpec.end(); ++it) { Chris@23: Feature f2 = *it; // logfreq spectrum Chris@23: Feature f6; // treble and bass chromagram Chris@35: Chris@23: f6.hasTimestamp = true; Chris@23: f6.timestamp = f2.timestamp; Chris@35: Chris@35: float b[256]; matthiasm@1: Chris@23: bool some_b_greater_zero = false; Chris@23: float sumb = 0; Chris@23: for (int i = 0; i < 256; i++) { Chris@23: // b[i] = m_dict[(256 * count + i) % (256 * 84)]; Chris@23: b[i] = f2.values[i]; Chris@23: sumb += b[i]; Chris@23: if (b[i] > 0) { Chris@23: some_b_greater_zero = true; Chris@23: } Chris@23: } matthiasm@1: Chris@23: // here's where the non-negative least squares algorithm calculates the note activation x matthiasm@1: Chris@23: vector chroma = vector(12, 0); Chris@23: vector basschroma = vector(12, 0); Chris@23: float currval; Chris@23: unsigned iSemitone = 0; matthiasm@1: Chris@23: if (some_b_greater_zero) { Chris@23: if (m_dictID == 1) { Chris@23: for (unsigned iNote = 2; iNote < nNote - 2; iNote += 3) { Chris@23: currval = 0; Chris@35: currval += b[iNote + 1 + -1] * 0.5; Chris@35: currval += b[iNote + 1 + 0] * 1.0; Chris@35: currval += b[iNote + 1 + 1] * 0.5; Chris@23: chroma[iSemitone % 12] += currval * treblewindow[iSemitone]; Chris@23: basschroma[iSemitone % 12] += currval * basswindow[iSemitone]; Chris@23: iSemitone++; Chris@23: } matthiasm@1: Chris@23: } else { Chris@35: float x[84+1000]; Chris@23: for (int i = 1; i < 1084; ++i) x[i] = 1.0; Chris@23: vector signifIndex; Chris@23: int index=0; Chris@23: sumb /= 84.0; Chris@23: for (unsigned iNote = 2; iNote < nNote - 2; iNote += 3) { Chris@23: float currval = 0; Chris@23: currval += b[iNote + 1 + -1]; Chris@23: currval += b[iNote + 1 + 0]; Chris@23: currval += b[iNote + 1 + 1]; Chris@23: if (currval > 0) signifIndex.push_back(index); Chris@23: index++; Chris@23: } Chris@35: float rnorm; Chris@35: float w[84+1000]; Chris@35: float zz[84+1000]; Chris@23: int indx[84+1000]; Chris@23: int mode; Chris@23: int dictsize = 256*signifIndex.size(); Chris@35: float *curr_dict = new float[dictsize]; Chris@23: for (unsigned iNote = 0; iNote < signifIndex.size(); ++iNote) { Chris@23: for (unsigned iBin = 0; iBin < 256; iBin++) { Chris@23: curr_dict[iNote * 256 + iBin] = 1.0 * m_dict[signifIndex[iNote] * 256 + iBin]; Chris@23: } Chris@23: } Chris@35: nnls(curr_dict, nNote, nNote, signifIndex.size(), b, x, &rnorm, w, zz, indx, &mode); Chris@23: delete [] curr_dict; Chris@23: for (unsigned iNote = 0; iNote < signifIndex.size(); ++iNote) { Chris@23: // cerr << mode << endl; Chris@23: chroma[signifIndex[iNote] % 12] += x[iNote] * treblewindow[signifIndex[iNote]]; Chris@23: basschroma[signifIndex[iNote] % 12] += x[iNote] * basswindow[signifIndex[iNote]]; Chris@23: } Chris@23: } Chris@23: } Chris@35: Chris@35: vector origchroma = chroma; Chris@23: chroma.insert(chroma.begin(), basschroma.begin(), basschroma.end()); // just stack the both chromas Chris@35: f6.values = chroma; Chris@35: Chris@23: if (m_doNormalizeChroma > 0) { Chris@23: vector chromanorm = vector(3,0); Chris@23: switch (int(m_doNormalizeChroma)) { Chris@23: case 0: // should never end up here Chris@23: break; Chris@23: case 1: Chris@35: chromanorm[0] = *max_element(origchroma.begin(), origchroma.end()); Chris@35: chromanorm[1] = *max_element(basschroma.begin(), basschroma.end()); Chris@23: chromanorm[2] = max(chromanorm[0], chromanorm[1]); Chris@23: break; Chris@23: case 2: Chris@35: for (vector::iterator it = chroma.begin(); it != chroma.end(); ++it) { Chris@23: chromanorm[2] += *it; Chris@23: } Chris@23: break; Chris@23: case 3: Chris@35: for (vector::iterator it = chroma.begin(); it != chroma.end(); ++it) { Chris@23: chromanorm[2] += pow(*it,2); Chris@23: } Chris@23: chromanorm[2] = sqrt(chromanorm[2]); Chris@23: break; Chris@23: } Chris@23: if (chromanorm[2] > 0) { Chris@35: for (int i = 0; i < chroma.size(); i++) { Chris@23: f6.values[i] /= chromanorm[2]; Chris@23: } Chris@23: } Chris@23: } Chris@35: Chris@35: chromaList.push_back(f6); Chris@35: Chris@23: // local chord estimation Chris@23: vector currentChordSalience; Chris@23: float tempchordvalue = 0; Chris@23: float sumchordvalue = 0; matthiasm@9: Chris@23: for (int iChord = 0; iChord < nChord; iChord++) { Chris@23: tempchordvalue = 0; Chris@23: for (int iBin = 0; iBin < 12; iBin++) { Chris@23: tempchordvalue += m_chorddict[24 * iChord + iBin] * chroma[iBin]; Chris@23: } Chris@23: for (int iBin = 12; iBin < 24; iBin++) { Chris@23: tempchordvalue += m_chorddict[24 * iChord + iBin] * chroma[iBin]; Chris@23: } Chris@23: sumchordvalue+=tempchordvalue; Chris@23: currentChordSalience.push_back(tempchordvalue); Chris@23: } Chris@23: if (sumchordvalue > 0) { Chris@23: for (int iChord = 0; iChord < nChord; iChord++) { Chris@23: currentChordSalience[iChord] /= sumchordvalue; Chris@23: } Chris@23: } else { Chris@23: currentChordSalience[nChord-1] = 1.0; Chris@23: } Chris@23: chordogram.push_back(currentChordSalience); matthiasm@1: Chris@23: count++; Chris@23: } Chris@23: cerr << "done." << endl; matthiasm@13: matthiasm@10: Chris@23: /* Simple chord estimation Chris@23: I just take the local chord estimates ("currentChordSalience") and average them over time, then Chris@23: take the maximum. Very simple, don't do this at home... Chris@23: */ Chris@35: cerr << "[Chordino Plugin] Chord Estimation ... "; Chris@23: count = 0; Chris@23: int halfwindowlength = m_inputSampleRate / m_stepSize; Chris@23: vector chordSequence; Chris@35: Chris@35: for (FeatureList::iterator it = chromaList.begin(); it != chromaList.end(); ++it) { // initialise the score chordogram Chris@23: vector temp = vector(nChord,0); Chris@23: scoreChordogram.push_back(temp); Chris@23: } Chris@35: Chris@35: for (FeatureList::iterator it = chromaList.begin(); it < chromaList.end()-2*halfwindowlength-1; ++it) { Chris@23: int startIndex = count + 1; Chris@23: int endIndex = count + 2 * halfwindowlength; matthiasm@10: Chris@23: float chordThreshold = 2.5/nChord;//*(2*halfwindowlength+1); matthiasm@10: Chris@23: vector chordCandidates; Chris@23: for (unsigned iChord = 0; iChord < nChord-1; iChord++) { Chris@23: // float currsum = 0; Chris@23: // for (unsigned iFrame = startIndex; iFrame < endIndex; ++iFrame) { Chris@23: // currsum += chordogram[iFrame][iChord]; Chris@23: // } Chris@23: // if (currsum > chordThreshold) chordCandidates.push_back(iChord); Chris@23: for (unsigned iFrame = startIndex; iFrame < endIndex; ++iFrame) { Chris@23: if (chordogram[iFrame][iChord] > chordThreshold) { Chris@23: chordCandidates.push_back(iChord); Chris@23: break; Chris@23: } Chris@23: } Chris@23: } Chris@23: chordCandidates.push_back(nChord-1); Chris@35: // cerr << chordCandidates.size() << endl; Chris@23: Chris@23: float maxval = 0; // will be the value of the most salient *chord change* in this frame Chris@23: float maxindex = 0; //... and the index thereof Chris@23: unsigned bestchordL = nChord-1; // index of the best "left" chord Chris@23: unsigned bestchordR = nChord-1; // index of the best "right" chord Chris@23: Chris@23: for (int iWF = 1; iWF < 2*halfwindowlength; ++iWF) { Chris@23: // now find the max values on both sides of iWF Chris@23: // left side: Chris@23: float maxL = 0; Chris@23: unsigned maxindL = nChord-1; Chris@23: for (unsigned kChord = 0; kChord < chordCandidates.size(); kChord++) { Chris@23: unsigned iChord = chordCandidates[kChord]; Chris@23: float currsum = 0; Chris@23: for (unsigned iFrame = 0; iFrame < iWF-1; ++iFrame) { Chris@23: currsum += chordogram[count+iFrame][iChord]; matthiasm@10: } Chris@23: if (iChord == nChord-1) currsum *= 0.8; Chris@23: if (currsum > maxL) { Chris@23: maxL = currsum; Chris@23: maxindL = iChord; Chris@23: } Chris@23: } Chris@23: // right side: Chris@23: float maxR = 0; Chris@23: unsigned maxindR = nChord-1; Chris@23: for (unsigned kChord = 0; kChord < chordCandidates.size(); kChord++) { Chris@23: unsigned iChord = chordCandidates[kChord]; Chris@23: float currsum = 0; Chris@23: for (unsigned iFrame = iWF-1; iFrame < 2*halfwindowlength; ++iFrame) { Chris@23: currsum += chordogram[count+iFrame][iChord]; Chris@23: } Chris@23: if (iChord == nChord-1) currsum *= 0.8; Chris@23: if (currsum > maxR) { Chris@23: maxR = currsum; Chris@23: maxindR = iChord; Chris@23: } Chris@23: } Chris@23: if (maxL+maxR > maxval) { Chris@23: maxval = maxL+maxR; Chris@23: maxindex = iWF; Chris@23: bestchordL = maxindL; Chris@23: bestchordR = maxindR; Chris@23: } matthiasm@3: Chris@23: } Chris@35: // cerr << "maxindex: " << maxindex << ", bestchordR is " << bestchordR << ", of frame " << count << endl; Chris@23: // add a score to every chord-frame-point that was part of a maximum Chris@23: for (unsigned iFrame = 0; iFrame < maxindex-1; ++iFrame) { Chris@23: scoreChordogram[iFrame+count][bestchordL]++; Chris@23: } Chris@23: for (unsigned iFrame = maxindex-1; iFrame < 2*halfwindowlength; ++iFrame) { Chris@23: scoreChordogram[iFrame+count][bestchordR]++; Chris@23: } Chris@23: if (bestchordL != bestchordR) chordchange[maxindex+count] += (halfwindowlength - abs(maxindex-halfwindowlength)) * 2.0 / halfwindowlength; Chris@23: count++; Chris@23: } Chris@35: // cerr << "******* agent finished *******" << endl; Chris@23: count = 0; Chris@35: for (FeatureList::iterator it = chromaList.begin(); it != chromaList.end(); ++it) { Chris@23: float maxval = 0; // will be the value of the most salient chord in this frame Chris@23: float maxindex = 0; //... and the index thereof Chris@23: for (unsigned iChord = 0; iChord < nChord; iChord++) { Chris@23: if (scoreChordogram[count][iChord] > maxval) { Chris@23: maxval = scoreChordogram[count][iChord]; Chris@23: maxindex = iChord; Chris@23: // cerr << iChord << endl; Chris@23: } Chris@23: } Chris@23: chordSequence.push_back(maxindex); Chris@23: // cerr << "before modefilter, maxindex: " << maxindex << endl; Chris@23: count++; Chris@23: } Chris@35: // cerr << "******* mode filter done *******" << endl; matthiasm@10: matthiasm@3: Chris@23: // mode filter on chordSequence Chris@23: count = 0; Chris@23: string oldChord = ""; Chris@35: for (FeatureList::iterator it = chromaList.begin(); it != chromaList.end(); ++it) { Chris@23: Feature f6 = *it; Chris@23: Feature f7; // chord estimate Chris@23: f7.hasTimestamp = true; Chris@23: f7.timestamp = f6.timestamp; Chris@23: Feature f8; // chord estimate Chris@23: f8.hasTimestamp = true; Chris@23: f8.timestamp = f6.timestamp; matthiasm@17: Chris@23: vector chordCount = vector(nChord,0); Chris@23: int maxChordCount = 0; Chris@23: int maxChordIndex = nChord-1; Chris@23: string maxChord; Chris@23: int startIndex = max(count - halfwindowlength/2,0); Chris@23: int endIndex = min(int(chordogram.size()), count + halfwindowlength/2); Chris@23: for (int i = startIndex; i < endIndex; i++) { Chris@23: chordCount[chordSequence[i]]++; Chris@23: if (chordCount[chordSequence[i]] > maxChordCount) { Chris@23: // cerr << "start index " << startIndex << endl; Chris@23: maxChordCount++; Chris@23: maxChordIndex = chordSequence[i]; Chris@23: maxChord = m_chordnames[maxChordIndex]; Chris@23: } Chris@23: } Chris@23: // chordSequence[count] = maxChordIndex; Chris@23: // cerr << maxChordIndex << endl; Chris@23: f8.values.push_back(chordchange[count]/(halfwindowlength*2)); Chris@23: // cerr << chordchange[count] << endl; Chris@35: fsOut[m_outputHarmonicChange].push_back(f8); Chris@23: if (oldChord != maxChord) { Chris@23: oldChord = maxChord; Chris@23: f7.label = m_chordnames[maxChordIndex]; Chris@35: fsOut[m_outputChords].push_back(f7); Chris@23: } Chris@23: count++; Chris@23: } Chris@23: Feature f7; // last chord estimate Chris@23: f7.hasTimestamp = true; Chris@35: f7.timestamp = chromaList[chromaList.size()-1].timestamp; Chris@23: f7.label = "N"; Chris@35: fsOut[m_outputChords].push_back(f7); Chris@23: cerr << "done." << endl; Chris@35: Chris@23: return fsOut; matthiasm@0: matthiasm@0: } matthiasm@0: