Chris@158: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@158: Chris@158: /* Chris@158: NNLS-Chroma / Chordino Chris@158: Chris@158: Audio feature extraction plugins for chromagram and chord Chris@158: estimation. Chris@158: Chris@158: Centre for Digital Music, Queen Mary University of London. Chris@158: This file copyright 2014 QMUL. Chris@158: Chris@158: This program is free software; you can redistribute it and/or Chris@158: modify it under the terms of the GNU General Public License as Chris@158: published by the Free Software Foundation; either version 2 of the Chris@158: License, or (at your option) any later version. See the file Chris@158: COPYING included with this distribution for more information. Chris@158: */ Chris@158: Chris@158: /* Chris@158: Extract chords from an audio file, read using libsndfile. Works by Chris@158: constructing the plugin as a C++ class directly, and using plugin Chris@158: adapters from the Vamp Host SDK to provide input. Chris@158: Chris@158: You can compile this with e.g. the following (Linux example): Chris@158: Chris@158: $ g++ -D_VAMP_PLUGIN_IN_HOST_NAMESPACE -O2 -ffast-math chordextract.cpp Chordino.cpp NNLSBase.cpp chromamethods.cpp viterbi.cpp nnls.c -o chordextract -lsndfile -lvamp-hostsdk -ldl Chris@158: Chris@158: But the same idea should work on any platform, so long as the Boost Chris@158: Tokenizer headers and the Vamp Host SDK library are available and Chris@158: the _VAMP_PLUGIN_IN_HOST_NAMESPACE preprocessor symbol is defined Chris@158: throughout. Chris@158: */ Chris@158: Chris@158: #define _VAMP_PLUGIN_IN_HOST_NAMESPACE 1 Chris@158: Chris@158: #include Chris@158: #include Chris@158: Chris@158: #include "Chordino.h" Chris@158: Chris@158: #include Chris@158: Chris@158: #include Chris@158: #include Chris@158: Chris@158: using namespace std; Chris@158: using namespace Vamp; Chris@158: using namespace Vamp::HostExt; Chris@158: Chris@158: int main(int argc, char **argv) Chris@158: { Chris@158: const char *myname = argv[0]; Chris@158: Chris@158: if (argc != 2) { Chris@158: cerr << "usage: " << myname << " file.wav" << endl; Chris@158: return 2; Chris@158: } Chris@158: Chris@158: const char *infile = argv[1]; Chris@158: Chris@158: SF_INFO sfinfo; Chris@158: SNDFILE *sndfile = sf_open(infile, SFM_READ, &sfinfo); Chris@158: Chris@158: if (!sndfile) { Chris@158: cerr << myname << ": Failed to open input file " << infile Chris@158: << ": " << sf_strerror(sndfile) << endl; Chris@158: return 1; Chris@158: } Chris@158: Chris@158: Chordino *chordino = new Chordino(sfinfo.samplerate); Chris@158: PluginInputDomainAdapter *ia = new PluginInputDomainAdapter(chordino); Chris@158: ia->setProcessTimestampMethod(PluginInputDomainAdapter::ShiftData); Chris@158: PluginBufferingAdapter *adapter = new PluginBufferingAdapter(ia); Chris@158: Chris@158: int blocksize = adapter->getPreferredBlockSize(); Chris@158: Chris@158: // Plugin requires 1 channel (we will mix down) Chris@158: if (!adapter->initialise(1, blocksize, blocksize)) { Chris@158: cerr << myname << ": Failed to initialise Chordino adapter!" << endl; Chris@158: return 1; Chris@158: } Chris@158: Chris@158: float *filebuf = new float[sfinfo.channels * blocksize]; Chris@158: float *mixbuf = new float[blocksize]; Chris@158: Chris@158: Plugin::FeatureList chordFeatures; Chris@158: Plugin::FeatureSet fs; Chris@158: Chris@158: int chordFeatureNo = -1; Chris@158: Plugin::OutputList outputs = adapter->getOutputDescriptors(); Chris@158: for (int i = 0; i < int(outputs.size()); ++i) { Chris@158: if (outputs[i].identifier == "simplechord") { Chris@158: chordFeatureNo = i; Chris@158: } Chris@158: } Chris@158: if (chordFeatureNo < 0) { Chris@158: cerr << myname << ": Failed to identify chords output!" << endl; Chris@158: return 1; Chris@158: } Chris@158: Chris@158: int frame = 0; Chris@158: while (frame < sfinfo.frames) { Chris@158: Chris@158: int count = -1; Chris@158: if ((count = sf_readf_float(sndfile, filebuf, blocksize)) <= 0) break; Chris@158: Chris@158: // mix down Chris@158: for (int i = 0; i < blocksize; ++i) { Chris@158: mixbuf[i] = 0.f; Chris@158: if (i < count) { Chris@158: for (int c = 0; c < sfinfo.channels; ++c) { Chris@158: mixbuf[i] += filebuf[i * sfinfo.channels + c] / sfinfo.channels; Chris@158: } Chris@158: } Chris@158: } Chris@158: Chris@158: RealTime timestamp = RealTime::frame2RealTime(frame, sfinfo.samplerate); Chris@158: Chris@158: // feed to plugin: can just take address of buffer, as only one channel Chris@158: fs = adapter->process(&mixbuf, timestamp); Chris@158: Chris@158: chordFeatures.insert(chordFeatures.end(), Chris@158: fs[chordFeatureNo].begin(), Chris@158: fs[chordFeatureNo].end()); Chris@158: Chris@158: frame += count; Chris@158: } Chris@158: Chris@158: sf_close(sndfile); Chris@158: Chris@158: // features at end of processing (actually Chordino does all its work here) Chris@158: fs = adapter->getRemainingFeatures(); Chris@158: Chris@158: // chord output is output index 0 Chris@158: chordFeatures.insert(chordFeatures.end(), Chris@158: fs[chordFeatureNo].begin(), Chris@158: fs[chordFeatureNo].end()); Chris@158: Chris@158: for (int i = 0; i < (int)chordFeatures.size(); ++i) { Chris@160: cout << chordFeatures[i].timestamp.toString() << ": " Chris@158: << chordFeatures[i].label << endl; Chris@158: } Chris@158: Chris@162: delete[] filebuf; Chris@162: delete[] mixbuf; Chris@162: Chris@158: delete adapter; Chris@158: } Chris@158: