cannam@1: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ cannam@1: cannam@1: /* cannam@1: Vamp cannam@1: cannam@1: An API for audio analysis and feature extraction plugins. cannam@1: cannam@1: Centre for Digital Music, Queen Mary, University of London. cannam@1: Copyright 2006 Chris Cannam. cannam@16: FFT code from Don Cross's public domain FFT implementation. cannam@1: cannam@1: Permission is hereby granted, free of charge, to any person cannam@1: obtaining a copy of this software and associated documentation cannam@1: files (the "Software"), to deal in the Software without cannam@1: restriction, including without limitation the rights to use, copy, cannam@1: modify, merge, publish, distribute, sublicense, and/or sell copies cannam@1: of the Software, and to permit persons to whom the Software is cannam@1: furnished to do so, subject to the following conditions: cannam@1: cannam@1: The above copyright notice and this permission notice shall be cannam@1: included in all copies or substantial portions of the Software. cannam@1: cannam@1: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, cannam@1: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF cannam@1: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND cannam@6: NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR cannam@1: ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF cannam@1: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION cannam@1: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cannam@1: cannam@1: Except as contained in this notice, the names of the Centre for cannam@1: Digital Music; Queen Mary, University of London; and Chris Cannam cannam@1: shall not be used in advertising or otherwise to promote the sale, cannam@1: use or other dealings in this Software without prior written cannam@1: authorization. cannam@1: */ cannam@1: cannam@64: #include "vamp-sdk/PluginHostAdapter.h" cannam@64: #include "vamp-sdk/hostext/PluginChannelAdapter.h" cannam@64: #include "vamp-sdk/hostext/PluginInputDomainAdapter.h" cannam@64: #include "vamp-sdk/hostext/PluginLoader.h" cannam@64: #include "vamp/vamp.h" cannam@1: cannam@16: #include cannam@88: #include cannam@99: #include cannam@16: #include cannam@1: cannam@1: #include "system.h" cannam@1: cannam@19: #include cannam@19: cannam@99: using namespace std; cannam@16: cannam@99: using Vamp::Plugin; cannam@99: using Vamp::PluginHostAdapter; cannam@99: using Vamp::RealTime; cannam@64: using Vamp::HostExt::PluginLoader; cannam@64: cannam@64: #define HOST_VERSION "1.1" cannam@40: cannam@95: enum Verbosity { cannam@95: PluginIds, cannam@95: PluginOutputIds, cannam@95: PluginInformation cannam@95: }; cannam@95: cannam@99: void printFeatures(int, int, int, Plugin::FeatureSet, ofstream *); cannam@16: void transformInput(float *, size_t); cannam@16: void fft(unsigned int, bool, double *, double *, double *, double *); cannam@64: void printPluginPath(bool verbose); cannam@99: void printPluginCategoryList(); cannam@95: void enumeratePlugins(Verbosity); cannam@64: void listPluginsInLibrary(string soname); cannam@64: int runPlugin(string myname, string soname, string id, string output, cannam@88: int outputNo, string inputFile, string outfilename); cannam@40: cannam@64: void usage(const char *name) cannam@64: { cannam@64: cerr << "\n" cannam@64: << name << ": A simple Vamp plugin host.\n\n" cannam@64: "Centre for Digital Music, Queen Mary, University of London.\n" cannam@64: "Copyright 2006-2007 Chris Cannam and QMUL.\n" cannam@64: "Freely redistributable; published under a BSD-style license.\n\n" cannam@64: "Usage:\n\n" cannam@95: " " << name << " pluginlibrary[." << PLUGIN_SUFFIX << "]:plugin[:output] file.wav [-o out.txt]\n" cannam@95: " " << name << " pluginlibrary[." << PLUGIN_SUFFIX << "]:plugin file.wav [outputno] [-o out.txt]\n\n" cannam@64: " -- Load plugin id \"plugin\" from \"pluginlibrary\" and run it on the\n" cannam@73: " audio data in \"file.wav\", retrieving the named \"output\", or output\n" cannam@73: " number \"outputno\" (the first output by default) and dumping it to\n" cannam@95: " standard output, or to \"out.txt\" if the -o option is given.\n\n" cannam@73: " \"pluginlibrary\" should be a library name, not a file path; the\n" cannam@73: " standard Vamp library search path will be used to locate it. If\n" cannam@73: " a file path is supplied, the directory part(s) will be ignored.\n\n" cannam@64: " " << name << " -l\n\n" cannam@95: " -- List the plugin libraries and Vamp plugins in the library search path\n" cannam@95: " in a verbose human-readable format.\n\n" cannam@95: " " << name << " --list-ids\n\n" cannam@95: " -- List the plugins in the search path in a terse machine-readable format,\n" cannam@95: " in the form vamp:soname:identifier.\n\n" cannam@95: " " << name << " --list-outputs\n\n" cannam@95: " -- List the outputs for plugins in the search path in a machine-readable\n" cannam@95: " format, in the form vamp:soname:identifier:output.\n\n" cannam@99: " " << name << " --list-by-category\n\n" cannam@99: " -- List the plugins as a plugin index by category, in a machine-readable\n" cannam@99: " format. The format may change in future releases.\n\n" cannam@64: " " << name << " -p\n\n" cannam@73: " -- Print out the Vamp library search path.\n\n" cannam@64: " " << name << " -v\n\n" cannam@95: " -- Display version information only.\n" cannam@64: << endl; cannam@64: exit(2); cannam@64: } cannam@1: cannam@1: int main(int argc, char **argv) cannam@1: { cannam@64: char *scooter = argv[0]; cannam@64: char *name = 0; cannam@64: while (scooter && *scooter) { cannam@64: if (*scooter == '/' || *scooter == '\\') name = ++scooter; cannam@64: else ++scooter; cannam@1: } cannam@64: if (!name || !*name) name = argv[0]; cannam@43: cannam@88: if (argc < 2) usage(name); cannam@64: cannam@88: if (argc == 2) { cannam@88: cannam@88: if (!strcmp(argv[1], "-v")) { cannam@88: cannam@88: cout << "Simple Vamp plugin host version: " << HOST_VERSION << endl cannam@88: << "Vamp API version: " << VAMP_API_VERSION << endl cannam@88: << "Vamp SDK version: " << VAMP_SDK_VERSION << endl; cannam@88: return 0; cannam@88: cannam@88: } else if (!strcmp(argv[1], "-l")) { cannam@88: cannam@88: printPluginPath(true); cannam@95: enumeratePlugins(PluginInformation); cannam@88: return 0; cannam@88: cannam@88: } else if (!strcmp(argv[1], "-p")) { cannam@88: cannam@88: printPluginPath(false); cannam@88: return 0; cannam@88: cannam@95: } else if (!strcmp(argv[1], "--list-ids")) { cannam@95: cannam@95: enumeratePlugins(PluginIds); cannam@95: return 0; cannam@95: cannam@95: } else if (!strcmp(argv[1], "--list-outputs")) { cannam@95: cannam@95: enumeratePlugins(PluginOutputIds); cannam@95: return 0; cannam@95: cannam@99: } else if (!strcmp(argv[1], "--list-by-category")) { cannam@99: cannam@99: printPluginCategoryList(); cannam@99: return 0; cannam@99: cannam@88: } else usage(name); cannam@64: } cannam@64: cannam@88: if (argc < 3) usage(name); cannam@88: cannam@88: string soname = argv[1]; cannam@88: string wavname = argv[2]; cannam@88: string plugid = ""; cannam@88: string output = ""; cannam@88: int outputNo = -1; cannam@88: string outfilename; cannam@88: cannam@88: if (argc >= 4) { cannam@88: cannam@88: int idx = 3; cannam@88: cannam@88: if (isdigit(*argv[idx])) { cannam@88: outputNo = atoi(argv[idx++]); cannam@88: } cannam@88: cannam@88: if (argc == idx + 2) { cannam@88: if (!strcmp(argv[idx], "-o")) { cannam@88: outfilename = argv[idx+1]; cannam@88: } else usage(name); cannam@88: } else if (argc != idx) { cannam@88: (usage(name)); cannam@88: } cannam@40: } cannam@40: cannam@64: cerr << endl << name << ": Running..." << endl; cannam@1: cannam@88: cerr << "Reading file: \"" << wavname << "\", writing to "; cannam@88: if (outfilename == "") { cannam@88: cerr << "standard output" << endl; cannam@88: } else { cannam@88: cerr << "\"" << outfilename << "\"" << endl; cannam@88: } cannam@16: cannam@64: string::size_type sep = soname.find(':'); cannam@64: cannam@64: if (sep != string::npos) { cannam@49: plugid = soname.substr(sep + 1); cannam@20: soname = soname.substr(0, sep); cannam@1: cannam@64: sep = plugid.find(':'); cannam@64: if (sep != string::npos) { cannam@64: output = plugid.substr(sep + 1); cannam@64: plugid = plugid.substr(0, sep); cannam@16: } cannam@16: } cannam@16: cannam@64: if (plugid == "") { cannam@64: usage(name); cannam@16: } cannam@64: cannam@64: if (output != "" && outputNo != -1) { cannam@64: usage(name); cannam@64: } cannam@64: cannam@84: if (output == "" && outputNo == -1) { cannam@84: outputNo = 0; cannam@84: } cannam@84: cannam@88: return runPlugin(name, soname, plugid, output, outputNo, cannam@88: wavname, outfilename); cannam@64: } cannam@64: cannam@64: cannam@64: int runPlugin(string myname, string soname, string id, cannam@88: string output, int outputNo, string wavname, cannam@88: string outfilename) cannam@64: { cannam@64: PluginLoader *loader = PluginLoader::getInstance(); cannam@64: cannam@64: PluginLoader::PluginKey key = loader->composePluginKey(soname, id); cannam@16: cannam@16: SNDFILE *sndfile; cannam@16: SF_INFO sfinfo; cannam@16: memset(&sfinfo, 0, sizeof(SF_INFO)); cannam@16: cannam@16: sndfile = sf_open(wavname.c_str(), SFM_READ, &sfinfo); cannam@16: if (!sndfile) { cannam@64: cerr << myname << ": ERROR: Failed to open input file \"" cannam@64: << wavname << "\": " << sf_strerror(sndfile) << endl; cannam@16: return 1; cannam@16: } cannam@16: cannam@88: ofstream *out = 0; cannam@88: if (outfilename != "") { cannam@88: out = new ofstream(outfilename.c_str(), ios::out); cannam@88: if (!*out) { cannam@88: cerr << myname << ": ERROR: Failed to open output file \"" cannam@88: << outfilename << "\" for writing" << endl; cannam@88: delete out; cannam@88: return 1; cannam@88: } cannam@88: } cannam@88: cannam@99: Plugin *plugin = loader->loadPlugin cannam@92: (key, sfinfo.samplerate, PluginLoader::ADAPT_ALL_SAFE); cannam@64: if (!plugin) { cannam@64: cerr << myname << ": ERROR: Failed to load plugin \"" << id cannam@64: << "\" from library \"" << soname << "\"" << endl; cannam@64: sf_close(sndfile); cannam@88: if (out) { cannam@88: out->close(); cannam@88: delete out; cannam@88: } cannam@64: return 1; cannam@64: } cannam@16: cannam@64: cerr << "Running plugin: \"" << plugin->getIdentifier() << "\"..." << endl; cannam@16: cannam@16: int blockSize = plugin->getPreferredBlockSize(); cannam@16: int stepSize = plugin->getPreferredStepSize(); cannam@16: cannam@91: if (blockSize == 0) { cannam@91: blockSize = 1024; cannam@91: } cannam@83: if (stepSize == 0) { cannam@99: if (plugin->getInputDomain() == Plugin::FrequencyDomain) { cannam@83: stepSize = blockSize/2; cannam@83: } else { cannam@83: stepSize = blockSize; cannam@83: } cannam@91: } else if (stepSize > blockSize) { cannam@91: cerr << "WARNING: stepSize " << stepSize << " > blockSize " << blockSize << ", resetting blockSize to "; cannam@99: if (plugin->getInputDomain() == Plugin::FrequencyDomain) { cannam@91: blockSize = stepSize * 2; cannam@91: } else { cannam@91: blockSize = stepSize; cannam@91: } cannam@91: cerr << blockSize << endl; cannam@83: } cannam@83: cannam@16: int channels = sfinfo.channels; cannam@16: cannam@16: float *filebuf = new float[blockSize * channels]; cannam@16: float **plugbuf = new float*[channels]; cannam@47: for (int c = 0; c < channels; ++c) plugbuf[c] = new float[blockSize + 2]; cannam@16: cannam@16: cerr << "Using block size = " << blockSize << ", step size = " cannam@16: << stepSize << endl; cannam@16: cannam@16: int minch = plugin->getMinChannelCount(); cannam@16: int maxch = plugin->getMaxChannelCount(); cannam@16: cerr << "Plugin accepts " << minch << " -> " << maxch << " channel(s)" << endl; cannam@64: cerr << "Sound file has " << channels << " (will mix/augment if necessary)" << endl; cannam@16: cannam@99: Plugin::OutputList outputs = plugin->getOutputDescriptors(); cannam@99: Plugin::OutputDescriptor od; cannam@16: cannam@29: int returnValue = 1; cannam@88: int progress = 0; cannam@29: cannam@16: if (outputs.empty()) { cannam@64: cerr << "ERROR: Plugin has no outputs!" << endl; cannam@16: goto done; cannam@16: } cannam@16: cannam@64: if (outputNo < 0) { cannam@16: cannam@64: for (size_t oi = 0; oi < outputs.size(); ++oi) { cannam@64: if (outputs[oi].identifier == output) { cannam@64: outputNo = oi; cannam@64: break; cannam@64: } cannam@64: } cannam@64: cannam@64: if (outputNo < 0) { cannam@64: cerr << "ERROR: Non-existent output \"" << output << "\" requested" << endl; cannam@64: goto done; cannam@64: } cannam@64: cannam@64: } else { cannam@64: cannam@64: if (int(outputs.size()) <= outputNo) { cannam@64: cerr << "ERROR: Output " << outputNo << " requested, but plugin has only " << outputs.size() << " output(s)" << endl; cannam@64: goto done; cannam@64: } cannam@64: } cannam@64: cannam@64: od = outputs[outputNo]; cannam@64: cerr << "Output is: \"" << od.identifier << "\"" << endl; cannam@16: cannam@29: if (!plugin->initialise(channels, stepSize, blockSize)) { cannam@29: cerr << "ERROR: Plugin initialise (channels = " << channels cannam@29: << ", stepSize = " << stepSize << ", blockSize = " cannam@29: << blockSize << ") failed." << endl; cannam@29: goto done; cannam@29: } cannam@16: cannam@16: for (size_t i = 0; i < sfinfo.frames; i += stepSize) { cannam@16: cannam@16: int count; cannam@16: cannam@16: if (sf_seek(sndfile, i, SEEK_SET) < 0) { cannam@16: cerr << "ERROR: sf_seek failed: " << sf_strerror(sndfile) << endl; cannam@16: break; cannam@16: } cannam@16: cannam@16: if ((count = sf_readf_float(sndfile, filebuf, blockSize)) < 0) { cannam@16: cerr << "ERROR: sf_readf_float failed: " << sf_strerror(sndfile) << endl; cannam@16: break; cannam@16: } cannam@16: cannam@16: for (int c = 0; c < channels; ++c) { cannam@64: int j = 0; cannam@64: while (j < count) { cannam@64: plugbuf[c][j] = filebuf[j * sfinfo.channels + c]; cannam@64: ++j; cannam@64: } cannam@64: while (j < blockSize) { cannam@16: plugbuf[c][j] = 0.0f; cannam@64: ++j; cannam@16: } cannam@16: } cannam@16: cannam@16: printFeatures cannam@64: (i, sfinfo.samplerate, outputNo, plugin->process cannam@99: (plugbuf, RealTime::frame2RealTime(i, sfinfo.samplerate)), cannam@88: out); cannam@88: cannam@88: int pp = progress; cannam@88: progress = lrintf((float(i) / sfinfo.frames) * 100.f); cannam@88: if (progress != pp && out) { cannam@88: cerr << "\r" << progress << "%"; cannam@88: } cannam@16: } cannam@88: if (out) cerr << "\rDone" << endl; cannam@16: cannam@64: printFeatures(sfinfo.frames, sfinfo.samplerate, outputNo, cannam@88: plugin->getRemainingFeatures(), out); cannam@16: cannam@29: returnValue = 0; cannam@29: cannam@16: done: cannam@16: delete plugin; cannam@88: if (out) { cannam@88: out->close(); cannam@88: delete out; cannam@88: } cannam@16: sf_close(sndfile); cannam@29: return returnValue; cannam@1: } cannam@1: cannam@16: void cannam@99: printFeatures(int frame, int sr, int output, cannam@99: Plugin::FeatureSet features, ofstream *out) cannam@99: { cannam@99: for (unsigned int i = 0; i < features[output].size(); ++i) { cannam@99: cannam@99: RealTime rt = RealTime::frame2RealTime(frame, sr); cannam@99: cannam@99: if (features[output][i].hasTimestamp) { cannam@99: rt = features[output][i].timestamp; cannam@99: } cannam@99: cannam@99: (out ? *out : cout) << rt.toString() << ":"; cannam@99: cannam@99: for (unsigned int j = 0; j < features[output][i].values.size(); ++j) { cannam@99: (out ? *out : cout) << " " << features[output][i].values[j]; cannam@99: } cannam@99: cannam@99: (out ? *out : cout) << endl; cannam@99: } cannam@99: } cannam@99: cannam@99: void cannam@64: printPluginPath(bool verbose) cannam@40: { cannam@64: if (verbose) { cannam@64: cout << "\nVamp plugin search path: "; cannam@64: } cannam@64: cannam@99: vector path = PluginHostAdapter::getPluginPath(); cannam@40: for (size_t i = 0; i < path.size(); ++i) { cannam@64: if (verbose) { cannam@64: cout << "[" << path[i] << "]"; cannam@64: } else { cannam@64: cout << path[i] << endl; cannam@64: } cannam@40: } cannam@64: cannam@64: if (verbose) cout << endl; cannam@40: } cannam@40: cannam@40: void cannam@95: enumeratePlugins(Verbosity verbosity) cannam@40: { cannam@64: PluginLoader *loader = PluginLoader::getInstance(); cannam@64: cannam@95: if (verbosity == PluginInformation) { cannam@95: cout << "\nVamp plugin libraries found in search path:" << endl; cannam@95: } cannam@64: cannam@99: vector plugins = loader->listPlugins(); cannam@99: typedef multimap cannam@64: LibraryMap; cannam@64: LibraryMap libraryMap; cannam@64: cannam@64: for (size_t i = 0; i < plugins.size(); ++i) { cannam@99: string path = loader->getLibraryPathForPlugin(plugins[i]); cannam@64: libraryMap.insert(LibraryMap::value_type(path, plugins[i])); cannam@64: } cannam@64: cannam@99: string prevPath = ""; cannam@64: int index = 0; cannam@64: cannam@64: for (LibraryMap::iterator i = libraryMap.begin(); cannam@64: i != libraryMap.end(); ++i) { cannam@64: cannam@99: string path = i->first; cannam@64: PluginLoader::PluginKey key = i->second; cannam@64: cannam@64: if (path != prevPath) { cannam@64: prevPath = path; cannam@64: index = 0; cannam@95: if (verbosity == PluginInformation) { cannam@95: cout << "\n " << path << ":" << endl; cannam@95: } cannam@40: } cannam@64: cannam@99: Plugin *plugin = loader->loadPlugin(key, 48000); cannam@64: if (plugin) { cannam@64: cannam@64: char c = char('A' + index); cannam@64: if (c > 'Z') c = char('a' + (index - 26)); cannam@64: cannam@95: if (verbosity == PluginInformation) { cannam@64: cannam@95: cout << " [" << c << "] [v" cannam@95: << plugin->getVampApiVersion() << "] " cannam@95: << plugin->getName() << ", \"" cannam@95: << plugin->getIdentifier() << "\"" << " [" cannam@95: << plugin->getMaker() << "]" << endl; cannam@95: cannam@95: PluginLoader::PluginCategoryHierarchy category = cannam@95: loader->getPluginCategory(key); cannam@95: cannam@95: if (!category.empty()) { cannam@95: cout << " "; cannam@95: for (size_t ci = 0; ci < category.size(); ++ci) { cannam@95: cout << " > " << category[ci]; cannam@95: } cannam@95: cout << endl; cannam@64: } cannam@95: cannam@95: if (plugin->getDescription() != "") { cannam@95: cout << " - " << plugin->getDescription() << endl; cannam@95: } cannam@95: cannam@95: } else if (verbosity == PluginIds) { cannam@95: cout << "vamp:" << key << endl; cannam@47: } cannam@95: cannam@99: Plugin::OutputList outputs = cannam@64: plugin->getOutputDescriptors(); cannam@64: cannam@95: if (outputs.size() > 1 || verbosity == PluginOutputIds) { cannam@64: for (size_t j = 0; j < outputs.size(); ++j) { cannam@95: if (verbosity == PluginInformation) { cannam@95: cout << " (" << j << ") " cannam@95: << outputs[j].name << ", \"" cannam@95: << outputs[j].identifier << "\"" << endl; cannam@95: if (outputs[j].description != "") { cannam@95: cout << " - " cannam@95: << outputs[j].description << endl; cannam@95: } cannam@95: } else if (verbosity == PluginOutputIds) { cannam@95: cout << "vamp:" << key << ":" << outputs[j].identifier << endl; cannam@40: } cannam@40: } cannam@64: } cannam@64: cannam@64: ++index; cannam@64: cannam@64: delete plugin; cannam@40: } cannam@40: } cannam@64: cannam@95: if (verbosity == PluginInformation) { cannam@95: cout << endl; cannam@95: } cannam@40: } cannam@40: cannam@40: void cannam@99: printPluginCategoryList() cannam@16: { cannam@99: PluginLoader *loader = PluginLoader::getInstance(); cannam@88: cannam@99: vector plugins = loader->listPlugins(); cannam@88: cannam@99: set printedcats; cannam@99: cannam@99: for (size_t i = 0; i < plugins.size(); ++i) { cannam@99: cannam@99: PluginLoader::PluginKey key = plugins[i]; cannam@99: cannam@99: PluginLoader::PluginCategoryHierarchy category = cannam@99: loader->getPluginCategory(key); cannam@99: cannam@99: Plugin *plugin = loader->loadPlugin(key, 48000); cannam@99: if (!plugin) continue; cannam@99: cannam@99: string catstr = ""; cannam@99: cannam@99: if (category.empty()) catstr = '|'; cannam@99: else { cannam@99: for (size_t j = 0; j < category.size(); ++j) { cannam@99: catstr += category[j]; cannam@99: catstr += '|'; cannam@99: if (printedcats.find(catstr) == printedcats.end()) { cannam@99: std::cout << catstr << std::endl; cannam@99: printedcats.insert(catstr); cannam@99: } cannam@99: } cannam@16: } cannam@88: cannam@99: std::cout << catstr << key << ":::" << plugin->getName() << ":::" << plugin->getMaker() << ":::" << plugin->getDescription() << std::endl; cannam@16: } cannam@16: } cannam@16: