commit 768471896a52ae4c65efd43fc15843370de8a5a3 Author: chatlanin Date: Wed Jul 24 10:01:27 2024 +0300 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..80fbc34 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build +.cache +subprojects/* +!subprojects/hack.wrap diff --git a/README.md b/README.md new file mode 100644 index 0000000..31e460d --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +Это полностью переписаный на С++ vamp-plugin-sdk (https://vamp-plugins.org/code-doc/main.html) +Оптимизирован только для linux. Он не будет работать (только если чудом) на других системах. + +В идеале хотелось бы привести это к единому заголовочному файлу и возможно это случится... +Если вы считаете нужным что-то добавить, убрать, поправить, переписать делайте это сами в своих форках и не спрашивайте меня ни о чем! diff --git a/host/system.h b/host/system.h new file mode 100644 index 0000000..170b6dd --- /dev/null +++ b/host/system.h @@ -0,0 +1,50 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006 Chris Cannam. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef _SYSTEM_H_ +#define _SYSTEM_H_ + +#include + +#define DLOPEN(a,b) dlopen((a).c_str(),(b)) +#define DLSYM(a,b) dlsym((a),(b)) +#define DLCLOSE(a) dlclose((a)) +#define DLERROR() dlerror() +#define PLUGIN_SUFFIX "so" +#define HAVE_OPENDIR 1 + +#endif + diff --git a/host/test-c.c b/host/test-c.c new file mode 100644 index 0000000..196ad51 --- /dev/null +++ b/host/test-c.c @@ -0,0 +1,40 @@ + +#include + +#include + +int main(int argc, char **argv) +{ + int i; + int libcount = vhGetLibraryCount(); + + printf("Vamp plugin libraries found:\n"); + for (i = 0; i < libcount; ++i) { + printf("%d: %s\n", i, vhGetLibraryName(i)); + } + + printf("Going to try loading qm-vamp-plugins...\n"); + int libindex = vhGetLibraryIndex("qm-vamp-plugins"); + vhLibrary lib = vhLoadLibrary(libindex); + if (!lib) { + printf("Failure!\n"); + return 1; + } + + int plugincount = vhGetPluginCount(lib); + printf("Success: it contains %d plugins; they are:\n", plugincount); + + for (i = 0; i < plugincount; ++i) { + const VampPluginDescriptor *descriptor = vhGetPluginDescriptor(lib, i); + if (!descriptor) { + printf("\n"); + } else { + printf("%s\n", descriptor->identifier); + } + } + + vhUnloadLibrary(lib); + + return 0; +} + diff --git a/host/vamp-simple-host.cpp b/host/vamp-simple-host.cpp new file mode 100644 index 0000000..e47ac52 --- /dev/null +++ b/host/vamp-simple-host.cpp @@ -0,0 +1,863 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006 Chris Cannam, copyright 2007-2008 QMUL. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + + +/* + * This "simple" Vamp plugin host is no longer as simple as it was; it + * now has a lot of options and includes a lot of code to handle the + * various useful listing modes it supports. + * + * However, the runPlugin function still contains a reasonable + * implementation of a fairly generic Vamp plugin host capable of + * evaluating a given output on a given plugin for a sound file read + * via libsndfile. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "system.h" + +#include + +using namespace std; + +using Vamp::Plugin; +using Vamp::PluginHostAdapter; +using Vamp::RealTime; +using Vamp::HostExt::PluginLoader; +using Vamp::HostExt::PluginWrapper; +using Vamp::HostExt::PluginInputDomainAdapter; + +#define HOST_VERSION "1.5" + +enum Verbosity { + PluginIds, + PluginOutputIds, + PluginInformation, + PluginInformationDetailed +}; + +void printFeatures(int, int, + const Plugin::OutputDescriptor &, int, + const Plugin::FeatureSet &, ofstream *, bool frames); +void transformInput(float *, size_t); +void fft(unsigned int, bool, double *, double *, double *, double *); +void printPluginPath(bool verbose); +void printPluginCategoryList(); +void enumeratePlugins(Verbosity); +void listPluginsInLibrary(string soname); +int runPlugin(string myname, string soname, string id, string output, + int outputNo, string inputFile, string outfilename, bool frames); + +void usage(const char *name) +{ + cerr << "\n" + << name << ": A command-line host for Vamp audio analysis plugins.\n\n" + "Centre for Digital Music, Queen Mary, University of London.\n" + "Copyright 2006-2009 Chris Cannam and QMUL.\n" + "Freely redistributable; published under a BSD-style license.\n\n" + "Usage:\n\n" + " " << name << " [-s] pluginlibrary[." << PLUGIN_SUFFIX << "]:plugin[:output] file.wav [-o out.txt]\n" + " " << name << " [-s] pluginlibrary[." << PLUGIN_SUFFIX << "]:plugin file.wav [outputno] [-o out.txt]\n\n" + " -- Load plugin id \"plugin\" from \"pluginlibrary\" and run it on the\n" + " audio data in \"file.wav\", retrieving the named \"output\", or output\n" + " number \"outputno\" (the first output by default) and dumping it to\n" + " standard output, or to \"out.txt\" if the -o option is given.\n\n" + " \"pluginlibrary\" should be a library name, not a file path; the\n" + " standard Vamp library search path will be used to locate it. If\n" + " a file path is supplied, the directory part(s) will be ignored.\n\n" + " If the -s option is given, results will be labelled with the audio\n" + " sample frame at which they occur. Otherwise, they will be labelled\n" + " with time in seconds.\n\n" + " " << name << " -l\n" + " " << name << " --list\n\n" + " -- List the plugin libraries and Vamp plugins in the library search path\n" + " in a verbose human-readable format.\n\n" + " " << name << " -L\n" + " " << name << " --list-full\n\n" + " -- List all data reported by all the Vamp plugins in the library search\n" + " path in a very verbose human-readable format.\n\n" + " " << name << " --list-ids\n\n" + " -- List the plugins in the search path in a terse machine-readable format,\n" + " in the form vamp:soname:identifier.\n\n" + " " << name << " --list-outputs\n\n" + " -- List the outputs for plugins in the search path in a machine-readable\n" + " format, in the form vamp:soname:identifier:output.\n\n" + " " << name << " --list-by-category\n\n" + " -- List the plugins as a plugin index by category, in a machine-readable\n" + " format. The format may change in future releases.\n\n" + " " << name << " -p\n\n" + " -- Print out the Vamp library search path.\n\n" + " " << name << " -v\n\n" + " -- Display version information only.\n" + << endl; + exit(2); +} + +int main(int argc, char **argv) +{ + char *scooter = argv[0]; + char *name = 0; + while (scooter && *scooter) { + if (*scooter == '/' || *scooter == '\\') name = ++scooter; + else ++scooter; + } + if (!name || !*name) name = argv[0]; + + if (argc < 2) usage(name); + + if (argc == 2) { + + if (!strcmp(argv[1], "-v")) { + + cout << "Simple Vamp plugin host version: " << HOST_VERSION << endl + << "Vamp API version: " << VAMP_API_VERSION << endl + << "Vamp SDK version: " << VAMP_SDK_VERSION << endl; + return 0; + + } else if (!strcmp(argv[1], "-l") || !strcmp(argv[1], "--list")) { + + printPluginPath(true); + enumeratePlugins(PluginInformation); + return 0; + + } else if (!strcmp(argv[1], "-L") || !strcmp(argv[1], "--list-full")) { + + enumeratePlugins(PluginInformationDetailed); + return 0; + + } else if (!strcmp(argv[1], "-p")) { + + printPluginPath(false); + return 0; + + } else if (!strcmp(argv[1], "--list-ids")) { + + enumeratePlugins(PluginIds); + return 0; + + } else if (!strcmp(argv[1], "--list-outputs")) { + + enumeratePlugins(PluginOutputIds); + return 0; + + } else if (!strcmp(argv[1], "--list-by-category")) { + + printPluginCategoryList(); + return 0; + + } else usage(name); + } + + if (argc < 3) usage(name); + + bool useFrames = false; + + int base = 1; + if (!strcmp(argv[1], "-s")) { + useFrames = true; + base = 2; + } + + string soname = argv[base]; + string wavname = argv[base+1]; + string plugid = ""; + string output = ""; + int outputNo = -1; + string outfilename; + + if (argc >= base+3) { + + int idx = base+2; + + if (isdigit(*argv[idx])) { + outputNo = atoi(argv[idx++]); + } + + if (argc == idx + 2) { + if (!strcmp(argv[idx], "-o")) { + outfilename = argv[idx+1]; + } else usage(name); + } else if (argc != idx) { + (usage(name)); + } + } + + cerr << endl << name << ": Running..." << endl; + + cerr << "Reading file: \"" << wavname << "\", writing to "; + if (outfilename == "") { + cerr << "standard output" << endl; + } else { + cerr << "\"" << outfilename << "\"" << endl; + } + + string::size_type sep = soname.find(':'); + + if (sep != string::npos) { + plugid = soname.substr(sep + 1); + soname = soname.substr(0, sep); + + sep = plugid.find(':'); + if (sep != string::npos) { + output = plugid.substr(sep + 1); + plugid = plugid.substr(0, sep); + } + } + + if (plugid == "") { + usage(name); + } + + if (output != "" && outputNo != -1) { + usage(name); + } + + if (output == "" && outputNo == -1) { + outputNo = 0; + } + + return runPlugin(name, soname, plugid, output, outputNo, + wavname, outfilename, useFrames); +} + + +int runPlugin(string myname, string soname, string id, + string output, int outputNo, string wavname, + string outfilename, bool useFrames) +{ + PluginLoader *loader = PluginLoader::getInstance(); + + PluginLoader::PluginKey key = loader->composePluginKey(soname, id); + + SNDFILE *sndfile; + SF_INFO sfinfo; + memset(&sfinfo, 0, sizeof(SF_INFO)); + + sndfile = sf_open(wavname.c_str(), SFM_READ, &sfinfo); + if (!sndfile) { + cerr << myname << ": ERROR: Failed to open input file \"" + << wavname << "\": " << sf_strerror(sndfile) << endl; + return 1; + } + + ofstream *out = 0; + if (outfilename != "") { + out = new ofstream(outfilename.c_str(), ios::out); + if (!*out) { + cerr << myname << ": ERROR: Failed to open output file \"" + << outfilename << "\" for writing" << endl; + delete out; + return 1; + } + } + + Plugin *plugin = loader->loadPlugin + (key, sfinfo.samplerate, PluginLoader::ADAPT_ALL_SAFE); + if (!plugin) { + cerr << myname << ": ERROR: Failed to load plugin \"" << id + << "\" from library \"" << soname << "\"" << endl; + sf_close(sndfile); + if (out) { + out->close(); + delete out; + } + return 1; + } + + cerr << "Running plugin: \"" << plugin->getIdentifier() << "\"..." << endl; + + // Note that the following would be much simpler if we used a + // PluginBufferingAdapter as well -- i.e. if we had passed + // PluginLoader::ADAPT_ALL to loader->loadPlugin() above, instead + // of ADAPT_ALL_SAFE. Then we could simply specify our own block + // size, keep the step size equal to the block size, and ignore + // the plugin's bleatings. However, there are some issues with + // using a PluginBufferingAdapter that make the results sometimes + // technically different from (if effectively the same as) the + // un-adapted plugin, so we aren't doing that here. See the + // PluginBufferingAdapter documentation for details. + + int blockSize = plugin->getPreferredBlockSize(); + int stepSize = plugin->getPreferredStepSize(); + + if (blockSize == 0) { + blockSize = 1024; + } + if (stepSize == 0) { + if (plugin->getInputDomain() == Plugin::FrequencyDomain) { + stepSize = blockSize/2; + } else { + stepSize = blockSize; + } + } else if (stepSize > blockSize) { + cerr << "WARNING: stepSize " << stepSize << " > blockSize " << blockSize << ", resetting blockSize to "; + if (plugin->getInputDomain() == Plugin::FrequencyDomain) { + blockSize = stepSize * 2; + } else { + blockSize = stepSize; + } + cerr << blockSize << endl; + } + int overlapSize = blockSize - stepSize; + sf_count_t currentStep = 0; + int finalStepsRemaining = max(1, (blockSize / stepSize) - 1); // at end of file, this many part-silent frames needed after we hit EOF + + int channels = sfinfo.channels; + + float *filebuf = new float[blockSize * channels]; + float **plugbuf = new float*[channels]; + for (int c = 0; c < channels; ++c) plugbuf[c] = new float[blockSize + 2]; + + cerr << "Using block size = " << blockSize << ", step size = " + << stepSize << endl; + + // The channel queries here are for informational purposes only -- + // a PluginChannelAdapter is being used automatically behind the + // scenes, and it will take case of any channel mismatch + + int minch = plugin->getMinChannelCount(); + int maxch = plugin->getMaxChannelCount(); + cerr << "Plugin accepts " << minch << " -> " << maxch << " channel(s)" << endl; + cerr << "Sound file has " << channels << " (will mix/augment if necessary)" << endl; + + Plugin::OutputList outputs = plugin->getOutputDescriptors(); + Plugin::OutputDescriptor od; + Plugin::FeatureSet features; + + int returnValue = 1; + int progress = 0; + + RealTime rt; + PluginWrapper *wrapper = 0; + RealTime adjustment = RealTime::zeroTime; + + if (outputs.empty()) { + cerr << "ERROR: Plugin has no outputs!" << endl; + goto done; + } + + if (outputNo < 0) { + + for (size_t oi = 0; oi < outputs.size(); ++oi) { + if (outputs[oi].identifier == output) { + outputNo = oi; + break; + } + } + + if (outputNo < 0) { + cerr << "ERROR: Non-existent output \"" << output << "\" requested" << endl; + goto done; + } + + } else { + + if (int(outputs.size()) <= outputNo) { + cerr << "ERROR: Output " << outputNo << " requested, but plugin has only " << outputs.size() << " output(s)" << endl; + goto done; + } + } + + od = outputs[outputNo]; + cerr << "Output is: \"" << od.identifier << "\"" << endl; + + if (!plugin->initialise(channels, stepSize, blockSize)) { + cerr << "ERROR: Plugin initialise (channels = " << channels + << ", stepSize = " << stepSize << ", blockSize = " + << blockSize << ") failed." << endl; + goto done; + } + + wrapper = dynamic_cast(plugin); + if (wrapper) { + // See documentation for + // PluginInputDomainAdapter::getTimestampAdjustment + PluginInputDomainAdapter *ida = + wrapper->getWrapper(); + if (ida) adjustment = ida->getTimestampAdjustment(); + } + + // Here we iterate over the frames, avoiding asking the numframes in case it's streaming input. + do { + + int count; + + if ((blockSize==stepSize) || (currentStep==0)) { + // read a full fresh block + if ((count = sf_readf_float(sndfile, filebuf, blockSize)) < 0) { + cerr << "ERROR: sf_readf_float failed: " << sf_strerror(sndfile) << endl; + break; + } + if (count != blockSize) --finalStepsRemaining; + } else { + // otherwise shunt the existing data down and read the remainder. + memmove(filebuf, filebuf + (stepSize * channels), overlapSize * channels * sizeof(float)); + if ((count = sf_readf_float(sndfile, filebuf + (overlapSize * channels), stepSize)) < 0) { + cerr << "ERROR: sf_readf_float failed: " << sf_strerror(sndfile) << endl; + break; + } + if (count != stepSize) --finalStepsRemaining; + count += overlapSize; + } + + for (int c = 0; c < channels; ++c) { + int j = 0; + while (j < count) { + plugbuf[c][j] = filebuf[j * sfinfo.channels + c]; + ++j; + } + while (j < blockSize) { + plugbuf[c][j] = 0.0f; + ++j; + } + } + + rt = RealTime::frame2RealTime(currentStep * stepSize, sfinfo.samplerate); + + features = plugin->process(plugbuf, rt); + + printFeatures + (RealTime::realTime2Frame(rt + adjustment, sfinfo.samplerate), + sfinfo.samplerate, od, outputNo, features, out, useFrames); + + if (sfinfo.frames > 0){ + int pp = progress; + progress = (int)((float(currentStep * stepSize) / sfinfo.frames) * 100.f + 0.5f); + if (progress != pp && out) { + cerr << "\r" << progress << "%"; + } + } + + ++currentStep; + + } while (finalStepsRemaining > 0); + + if (out) cerr << "\rDone" << endl; + + rt = RealTime::frame2RealTime(currentStep * stepSize, sfinfo.samplerate); + + features = plugin->getRemainingFeatures(); + + printFeatures(RealTime::realTime2Frame(rt + adjustment, sfinfo.samplerate), + sfinfo.samplerate, od, outputNo, features, out, useFrames); + + returnValue = 0; + +done: + delete plugin; + if (out) { + out->close(); + delete out; + } + sf_close(sndfile); + return returnValue; +} + +static double +toSeconds(const RealTime &time) +{ + return time.sec + double(time.nsec + 1) / 1000000000.0; +} + +void +printFeatures(int frame, int sr, + const Plugin::OutputDescriptor &output, int outputNo, + const Plugin::FeatureSet &features, ofstream *out, bool useFrames) +{ + static int featureCount = -1; + + if (features.find(outputNo) == features.end()) return; + + for (size_t i = 0; i < features.at(outputNo).size(); ++i) { + + const Plugin::Feature &f = features.at(outputNo).at(i); + + bool haveRt = false; + RealTime rt; + + if (output.sampleType == Plugin::OutputDescriptor::VariableSampleRate) { + rt = f.timestamp; + haveRt = true; + } else if (output.sampleType == Plugin::OutputDescriptor::FixedSampleRate) { + int n = featureCount + 1; + if (f.hasTimestamp) { + n = int(round(toSeconds(f.timestamp) * output.sampleRate)); + } + rt = RealTime::fromSeconds(double(n) / output.sampleRate); + haveRt = true; + featureCount = n; + } + + if (useFrames) { + + int displayFrame = frame; + + if (haveRt) { + displayFrame = RealTime::realTime2Frame(rt, sr); + } + + (out ? *out : cout) << displayFrame; + + if (f.hasDuration) { + displayFrame = RealTime::realTime2Frame(f.duration, sr); + (out ? *out : cout) << "," << displayFrame; + } + + (out ? *out : cout) << ":"; + + } else { + + if (!haveRt) { + rt = RealTime::frame2RealTime(frame, sr); + } + + (out ? *out : cout) << rt.toString(); + + if (f.hasDuration) { + rt = f.duration; + (out ? *out : cout) << "," << rt.toString(); + } + + (out ? *out : cout) << ":"; + } + + for (unsigned int j = 0; j < f.values.size(); ++j) { + (out ? *out : cout) << " " << f.values[j]; + } + (out ? *out : cout) << " " << f.label; + + (out ? *out : cout) << endl; + } +} + +void +printPluginPath(bool verbose) +{ + if (verbose) { + cout << "\nVamp plugin search path: "; + } + + vector path = PluginHostAdapter::getPluginPath(); + for (size_t i = 0; i < path.size(); ++i) { + if (verbose) { + cout << "[" << path[i] << "]"; + } else { + cout << path[i] << endl; + } + } + + if (verbose) cout << endl; +} + +static +string +header(string text, int level) +{ + string out = '\n' + text + '\n'; + for (size_t i = 0; i < text.length(); ++i) { + out += (level == 1 ? '=' : level == 2 ? '-' : '~'); + } + out += '\n'; + return out; +} + +void +enumeratePlugins(Verbosity verbosity) +{ + PluginLoader *loader = PluginLoader::getInstance(); + + if (verbosity == PluginInformation) { + cout << "\nVamp plugin libraries found in search path:" << endl; + } + + vector plugins = loader->listPlugins(); + typedef multimap + LibraryMap; + LibraryMap libraryMap; + + for (size_t i = 0; i < plugins.size(); ++i) { + string path = loader->getLibraryPathForPlugin(plugins[i]); + libraryMap.insert(LibraryMap::value_type(path, plugins[i])); + } + + string prevPath = ""; + int index = 0; + + for (LibraryMap::iterator i = libraryMap.begin(); + i != libraryMap.end(); ++i) { + + string path = i->first; + PluginLoader::PluginKey key = i->second; + + if (path != prevPath) { + prevPath = path; + index = 0; + if (verbosity == PluginInformation) { + cout << "\n " << path << ":" << endl; + } else if (verbosity == PluginInformationDetailed) { + string::size_type ki = i->second.find(':'); + string text = "Library \"" + i->second.substr(0, ki) + "\""; + cout << "\n" << header(text, 1); + } + } + + Plugin *plugin = loader->loadPlugin(key, 48000); + if (plugin) { + + char c = char('A' + index); + if (c > 'Z') c = char('a' + (index - 26)); + + PluginLoader::PluginCategoryHierarchy category = + loader->getPluginCategory(key); + string catstr; + if (!category.empty()) { + for (size_t ci = 0; ci < category.size(); ++ci) { + if (ci > 0) catstr += " > "; + catstr += category[ci]; + } + } + + if (verbosity == PluginInformation) { + + cout << " [" << c << "] [v" + << plugin->getVampApiVersion() << "] " + << plugin->getName() << ", \"" + << plugin->getIdentifier() << "\"" << " [" + << plugin->getMaker() << "]" << endl; + + if (catstr != "") { + cout << " > " << catstr << endl; + } + + if (plugin->getDescription() != "") { + cout << " - " << plugin->getDescription() << endl; + } + + } else if (verbosity == PluginInformationDetailed) { + + cout << header(plugin->getName(), 2); + cout << " - Identifier: " + << key << endl; + cout << " - Plugin Version: " + << plugin->getPluginVersion() << endl; + cout << " - Vamp API Version: " + << plugin->getVampApiVersion() << endl; + cout << " - Maker: \"" + << plugin->getMaker() << "\"" << endl; + cout << " - Copyright: \"" + << plugin->getCopyright() << "\"" << endl; + cout << " - Description: \"" + << plugin->getDescription() << "\"" << endl; + cout << " - Input Domain: " + << (plugin->getInputDomain() == Vamp::Plugin::TimeDomain ? + "Time Domain" : "Frequency Domain") << endl; + cout << " - Default Step Size: " + << plugin->getPreferredStepSize() << endl; + cout << " - Default Block Size: " + << plugin->getPreferredBlockSize() << endl; + cout << " - Minimum Channels: " + << plugin->getMinChannelCount() << endl; + cout << " - Maximum Channels: " + << plugin->getMaxChannelCount() << endl; + + } else if (verbosity == PluginIds) { + cout << "vamp:" << key << endl; + } + + Plugin::OutputList outputs = + plugin->getOutputDescriptors(); + + if (verbosity == PluginInformationDetailed) { + + Plugin::ParameterList params = plugin->getParameterDescriptors(); + for (size_t j = 0; j < params.size(); ++j) { + Plugin::ParameterDescriptor &pd(params[j]); + cout << "\nParameter " << j+1 << ": \"" << pd.name << "\"" << endl; + cout << " - Identifier: " << pd.identifier << endl; + cout << " - Description: \"" << pd.description << "\"" << endl; + if (pd.unit != "") { + cout << " - Unit: " << pd.unit << endl; + } + cout << " - Range: "; + cout << pd.minValue << " -> " << pd.maxValue << endl; + cout << " - Default: "; + cout << pd.defaultValue << endl; + if (pd.isQuantized) { + cout << " - Quantize Step: " + << pd.quantizeStep << endl; + } + if (!pd.valueNames.empty()) { + cout << " - Value Names: "; + for (size_t k = 0; k < pd.valueNames.size(); ++k) { + if (k > 0) cout << ", "; + cout << "\"" << pd.valueNames[k] << "\""; + } + cout << endl; + } + } + + if (outputs.empty()) { + cout << "\n** Note: This plugin reports no outputs!" << endl; + } + for (size_t j = 0; j < outputs.size(); ++j) { + Plugin::OutputDescriptor &od(outputs[j]); + cout << "\nOutput " << j+1 << ": \"" << od.name << "\"" << endl; + cout << " - Identifier: " << od.identifier << endl; + cout << " - Description: \"" << od.description << "\"" << endl; + if (od.unit != "") { + cout << " - Unit: " << od.unit << endl; + } + if (od.hasFixedBinCount) { + cout << " - Default Bin Count: " << od.binCount << endl; + } + if (!od.binNames.empty()) { + bool have = false; + for (size_t k = 0; k < od.binNames.size(); ++k) { + if (od.binNames[k] != "") { + have = true; break; + } + } + if (have) { + cout << " - Bin Names: "; + for (size_t k = 0; k < od.binNames.size(); ++k) { + if (k > 0) cout << ", "; + cout << "\"" << od.binNames[k] << "\""; + } + cout << endl; + } + } + if (od.hasKnownExtents) { + cout << " - Default Extents: "; + cout << od.minValue << " -> " << od.maxValue << endl; + } + if (od.isQuantized) { + cout << " - Quantize Step: " + << od.quantizeStep << endl; + } + cout << " - Sample Type: " + << (od.sampleType == + Plugin::OutputDescriptor::OneSamplePerStep ? + "One Sample Per Step" : + od.sampleType == + Plugin::OutputDescriptor::FixedSampleRate ? + "Fixed Sample Rate" : + "Variable Sample Rate") << endl; + if (od.sampleType != + Plugin::OutputDescriptor::OneSamplePerStep) { + cout << " - Default Rate: " + << od.sampleRate << endl; + } + cout << " - Has Duration: " + << (od.hasDuration ? "Yes" : "No") << endl; + } + } + + if (outputs.size() > 1 || verbosity == PluginOutputIds) { + for (size_t j = 0; j < outputs.size(); ++j) { + if (verbosity == PluginInformation) { + cout << " (" << j << ") " + << outputs[j].name << ", \"" + << outputs[j].identifier << "\"" << endl; + if (outputs[j].description != "") { + cout << " - " + << outputs[j].description << endl; + } + } else if (verbosity == PluginOutputIds) { + cout << "vamp:" << key << ":" << outputs[j].identifier << endl; + } + } + } + + ++index; + + delete plugin; + } + } + + if (verbosity == PluginInformation || + verbosity == PluginInformationDetailed) { + cout << endl; + } +} + +void +printPluginCategoryList() +{ + PluginLoader *loader = PluginLoader::getInstance(); + + vector plugins = loader->listPlugins(); + + set printedcats; + + for (size_t i = 0; i < plugins.size(); ++i) { + + PluginLoader::PluginKey key = plugins[i]; + + PluginLoader::PluginCategoryHierarchy category = + loader->getPluginCategory(key); + + Plugin *plugin = loader->loadPlugin(key, 48000); + if (!plugin) continue; + + string catstr = ""; + + if (category.empty()) catstr = '|'; + else { + for (size_t j = 0; j < category.size(); ++j) { + catstr += category[j]; + catstr += '|'; + if (printedcats.find(catstr) == printedcats.end()) { + std::cout << catstr << std::endl; + printedcats.insert(catstr); + } + } + } + + std::cout << catstr << key << ":::" << plugin->getName() << ":::" << plugin->getMaker() << ":::" << plugin->getDescription() << std::endl; + } +} + diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..9eaaae9 --- /dev/null +++ b/meson.build @@ -0,0 +1,39 @@ +project( + meson.current_source_dir().split('/').get(-1), + 'cpp', + version : run_command('git', 'rev-parse', '--short', 'HEAD', check: false).stdout().strip(), + default_options : [ + 'warning_level=1', + 'optimization=3', + 'cpp_std=c++20' +]) + +add_project_arguments ( + '-Wpedantic', + '-Wno-shadow', + '-Wno-unused-but-set-variable', + '-Wno-comment', + '-Wno-unused-parameter', + '-Wno-unused-value', + '-Wno-missing-field-initializers', + '-Wno-narrowing', + '-Wno-deprecated-enum-enum-conversion', + '-Wno-volatile', + '-Wno-format-security', + '-Wno-switch', + '-Wno-ignored-attributes', + '-Wno-unused-variable', + '-Wno-deprecated-enum-enum-conversion', + language: 'cpp' +) + +#args = ['-lglfw', '-ldl', '-lGL', '-lpthread', '-lX11', '-lXxf86vm', '-lXrandr', '-lXi'] +args = [] +inc = [] +deps = [ + subproject('hack').get_variable('hack_dep'), +] + +subdir('src') +subdir('test') + diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..8e09d57 --- /dev/null +++ b/run.sh @@ -0,0 +1,23 @@ +#!/bin/zsh + +PROJECT_NAME=$(basename $PWD) + +run() { + command meson compile -C build + if [[ -z "$1" ]]; then + cd build + ./test/$PROJECT_NAME + cd .. + else + meson test $1 -C build + fi +} + +if [ -d "build" ]; then + run +else + command meson setup build + run +fi + + diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..080d148 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,59 @@ +inc += include_directories('.') + +headers = [ + 'vamp.h', + 'vamp-hostsdk/Files.h', + 'vamp-hostsdk/host-c.h', + 'vamp-hostsdk/PluginBufferingAdapter.h', + 'vamp-hostsdk/PluginChannelAdapter.h', + 'vamp-hostsdk/PluginHostAdapter.h', + 'vamp-hostsdk/PluginInputDomainAdapter.h', + 'vamp-hostsdk/PluginLoader.h', + 'vamp-hostsdk/PluginSummarisingAdapter.h', + 'vamp-hostsdk/PluginWrapper.h', + 'vamp-hostsdk/vamp-hostsdk.h', + 'vamp-hostsdk/Window.h', + + 'vamp-sdk/ext/vamp_kiss_fft.h', + 'vamp-sdk/ext/vamp_kiss_fft_guts.h', + 'vamp-sdk/ext/vamp_kiss_fftr.h', + + 'vamp-sdk/FFT.h', + 'vamp-sdk/Plugin.h', + 'vamp-sdk/PluginAdapter.h', + 'vamp-sdk/PluginBase.h', + 'vamp-sdk/RealTime.h', + 'vamp-sdk/vamp-sdk.h' +] + +sources = [ + 'vamp-hostsdk/acsymbols.cpp', + 'vamp-hostsdk/Files.cpp', + 'vamp-hostsdk/host-c.cpp', + 'vamp-hostsdk/PluginBufferingAdapter.cpp', + 'vamp-hostsdk/PluginChannelAdapter.cpp', + 'vamp-hostsdk/PluginHostAdapter.cpp', + 'vamp-hostsdk/PluginInputDomainAdapter.cpp', + 'vamp-hostsdk/PluginLoader.cpp', + 'vamp-hostsdk/PluginSummarisingAdapter.cpp', + 'vamp-hostsdk/PluginWrapper.cpp', + + 'vamp-sdk/acsymbols.cpp', + 'vamp-sdk/FFT.cpp', + 'vamp-sdk/PluginAdapter.cpp', + 'vamp-sdk/RealTime.cpp' +] + +lib = library( + meson.project_name(), + include_directories : inc, + sources: [headers, sources], + dependencies : deps, + cpp_args: args +) + +deps += declare_dependency( + include_directories: inc, + link_with: lib, +) + diff --git a/src/vamp-hostsdk/Files.cpp b/src/vamp-hostsdk/Files.cpp new file mode 100644 index 0000000..5049eb5 --- /dev/null +++ b/src/vamp-hostsdk/Files.cpp @@ -0,0 +1,195 @@ +#include "vamp-hostsdk/PluginHostAdapter.h" + +#include "vamp-hostsdk/Files.h" + +#include // tolower +#include +#include +#include +#include + +#define PLUGIN_SUFFIX "so" + + +std::vector Files::listLibraryFiles() +{ + return listLibraryFilesMatching(Filter()); +} + +std::vector Files::listLibraryFilesMatching(Filter filter) +{ + std::vector path = Vamp::PluginHostAdapter::getPluginPath(); + std::vector libraryFiles; + + // we match case-insensitively, but only with ascii range + // characters (input strings are expected to be utf-8) + std::vector libraryNames; + for (int j = 0; j < int(filter.libraryNames.size()); ++j) { + std::string n = filter.libraryNames[j]; + for (size_t i = 0; i < n.length(); ++i) { + if (!(n[i] & 0x80)) { + n[i] = char(tolower(n[i])); + } + } + libraryNames.push_back(n); + } + + for (size_t i = 0; i < path.size(); ++i) { + + std::vector files = listFiles(path[i], PLUGIN_SUFFIX); + + for (std::vector::iterator fi = files.begin(); + fi != files.end(); ++fi) { + + // we match case-insensitively, but only with ascii range + // characters (this string is expected to be utf-8) + std::string cleaned = *fi; + for (size_t j = 0; j < cleaned.length(); ++j) { + if (!(cleaned[j] & 0x80)) { + cleaned[j] = char(tolower(cleaned[j])); + } + } + + // libraryName should be lacking an extension, as it is + // supposed to have come from the plugin key + std::string::size_type pi = cleaned.find('.'); + if (pi != std::string::npos) { + cleaned = cleaned.substr(0, pi); + } + + bool matched = false; + + switch (filter.type) { + + case Filter::All: + matched = true; + break; + + case Filter::Matching: + for (int j = 0; j < int(libraryNames.size()); ++j) { + if (cleaned == libraryNames[j]) { + matched = true; + break; + } + } + break; + + case Filter::NotMatching: + matched = true; + for (int j = 0; j < int(libraryNames.size()); ++j) { + if (cleaned == libraryNames[j]) { + matched = false; + break; + } + } + break; + } + + if (!matched) continue; + + std::string fullPath = path[i]; + fullPath = splicePath(fullPath, *fi); + libraryFiles.push_back(fullPath); + } + } + + return libraryFiles; +} + +void* Files::loadLibrary(std::string path) +{ + void *handle = 0; + handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL); + if (!handle) { + std::cerr << "Vamp::HostExt: Unable to load library \"" + << path << "\": " << dlerror() << std::endl; + } + return handle; +} + +void Files::unloadLibrary(void *handle) +{ + dlclose(handle); +} + +void* Files::lookupInLibrary(void *handle, const char *symbol) +{ + return (void *)dlsym(handle, symbol); +} + +std::string Files::lcBasename(std::string path) +{ + std::string basename(path); + + std::string::size_type li = basename.rfind('/'); + if (li != std::string::npos) basename = basename.substr(li + 1); + + li = basename.find('.'); + if (li != std::string::npos) basename = basename.substr(0, li); + + // case-insensitive, but only with ascii range characters (this + // string is expected to be utf-8) + for (size_t i = 0; i < basename.length(); ++i) { + if (!(basename[i] & 0x80)) { + basename[i] = char(tolower(basename[i])); + } + } + + return basename; +} + +std::string Files::splicePath(std::string a, std::string b) +{ + return a + "/" + b; +} + +std::vector Files::listFiles(std::string dir, std::string extension) +{ + std::vector files; + + size_t extlen = extension.length(); + DIR* d = opendir(dir.c_str()); + if (!d) return files; + + struct dirent* e = 0; + + while ((e = readdir(d))) + { + + size_t len = strlen(e->d_name); + if (len < extlen + 2 || e->d_name + len - extlen - 1 != "." + extension) + continue; + + files.push_back(e->d_name); + } + + closedir(d); + + return files; +} + +bool Files::isNonNative32Bit() +{ + // Return true if we are running on a system for which we should + // use the VAMP_PATH_32 variable instead of VAMP_PATH. This will + // be the case if we are a 32-bit executable but the OS is + // natively 64-bit. + // + // This currently works only on Windows; other operating systems + // will use VAMP_PATH always. + if (sizeof(void *) == 8) + return false; + return false; +} + +bool Files::getEnvUtf8(std::string variable, std::string &value) +{ + value = ""; + char *val = getenv(variable.c_str()); + if (!val) { + return false; + } + + value = val; + return true; +} diff --git a/src/vamp-hostsdk/Files.h b/src/vamp-hostsdk/Files.h new file mode 100644 index 0000000..8b42b73 --- /dev/null +++ b/src/vamp-hostsdk/Files.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +/** + * This is a private implementation class for the Vamp Host SDK. + */ +class Files +{ + public: + static std::vector listLibraryFiles(); + + struct Filter { + enum { All, Matching, NotMatching } type; + std::vector libraryNames; + Filter() : type(All) { } + }; + static std::vector listLibraryFilesMatching(Filter); + + static void *loadLibrary(std::string filename); + static void unloadLibrary(void *); + static void *lookupInLibrary(void *, const char *symbol); + + static std::string lcBasename(std::string path); + static std::string splicePath(std::string a, std::string b); + static std::vector listFiles(std::string dir, std::string ext); + + static bool isNonNative32Bit(); + static bool getEnvUtf8(std::string variable, std::string &value); +}; diff --git a/src/vamp-hostsdk/PluginBufferingAdapter.cpp b/src/vamp-hostsdk/PluginBufferingAdapter.cpp new file mode 100644 index 0000000..cb17d07 --- /dev/null +++ b/src/vamp-hostsdk/PluginBufferingAdapter.cpp @@ -0,0 +1,722 @@ +#include +#include + +#include "vamp-hostsdk/PluginBufferingAdapter.h" +#include "vamp-hostsdk/PluginInputDomainAdapter.h" + +#include +using std::cerr; +using std::endl; + +using std::vector; +using std::map; + +namespace Vamp { + +namespace HostExt { + +class PluginBufferingAdapter::Impl +{ +public: + Impl(Plugin *plugin, float inputSampleRate); + ~Impl(); + + void setPluginStepSize(size_t stepSize); + void setPluginBlockSize(size_t blockSize); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + + void getActualStepAndBlockSizes(size_t &stepSize, size_t &blockSize); + + OutputList getOutputDescriptors() const; + + void setParameter(std::string, float); + void selectProgram(std::string); + + void reset(); + + FeatureSet process(const float *const *inputBuffers, RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + class RingBuffer + { + public: + RingBuffer(int n) : + m_buffer(new float[n+1]), m_writer(0), m_reader(0), m_size(n+1) { } + virtual ~RingBuffer() { delete[] m_buffer; } + + int getSize() const { return m_size-1; } + void reset() { m_writer = 0; m_reader = 0; } + + int getReadSpace() const { + int writer = m_writer, reader = m_reader, space; + if (writer > reader) space = writer - reader; + else if (writer < reader) space = (writer + m_size) - reader; + else space = 0; + return space; + } + + int getWriteSpace() const { + int writer = m_writer; + int reader = m_reader; + int space = (reader + m_size - writer - 1); + if (space >= m_size) space -= m_size; + return space; + } + + int peek(float *destination, int n) const { + + int available = getReadSpace(); + + if (n > available) { + for (int i = available; i < n; ++i) { + destination[i] = 0.f; + } + n = available; + } + if (n == 0) return n; + + int reader = m_reader; + int here = m_size - reader; + const float *const bufbase = m_buffer + reader; + + if (here >= n) { + for (int i = 0; i < n; ++i) { + destination[i] = bufbase[i]; + } + } else { + for (int i = 0; i < here; ++i) { + destination[i] = bufbase[i]; + } + float *const destbase = destination + here; + const int nh = n - here; + for (int i = 0; i < nh; ++i) { + destbase[i] = m_buffer[i]; + } + } + + return n; + } + + int skip(int n) { + + int available = getReadSpace(); + if (n > available) { + n = available; + } + if (n == 0) return n; + + int reader = m_reader; + reader += n; + while (reader >= m_size) reader -= m_size; + m_reader = reader; + return n; + } + + int write(const float *source, int n) { + + int available = getWriteSpace(); + if (n > available) { + n = available; + } + if (n == 0) return n; + + int writer = m_writer; + int here = m_size - writer; + float *const bufbase = m_buffer + writer; + + if (here >= n) { + for (int i = 0; i < n; ++i) { + bufbase[i] = source[i]; + } + } else { + for (int i = 0; i < here; ++i) { + bufbase[i] = source[i]; + } + const int nh = n - here; + const float *const srcbase = source + here; + float *const buf = m_buffer; + for (int i = 0; i < nh; ++i) { + buf[i] = srcbase[i]; + } + } + + writer += n; + while (writer >= m_size) writer -= m_size; + m_writer = writer; + + return n; + } + + int zero(int n) { + + int available = getWriteSpace(); + if (n > available) { + n = available; + } + if (n == 0) return n; + + int writer = m_writer; + int here = m_size - writer; + float *const bufbase = m_buffer + writer; + + if (here >= n) { + for (int i = 0; i < n; ++i) { + bufbase[i] = 0.f; + } + } else { + for (int i = 0; i < here; ++i) { + bufbase[i] = 0.f; + } + const int nh = n - here; + for (int i = 0; i < nh; ++i) { + m_buffer[i] = 0.f; + } + } + + writer += n; + while (writer >= m_size) writer -= m_size; + m_writer = writer; + + return n; + } + + protected: + float *m_buffer; + int m_writer; + int m_reader; + int m_size; + + private: + RingBuffer(const RingBuffer &); // not provided + RingBuffer &operator=(const RingBuffer &); // not provided + }; + + Plugin *m_plugin; + size_t m_inputStepSize; // value passed to wrapper initialise() + size_t m_inputBlockSize; // value passed to wrapper initialise() + size_t m_setStepSize; // value passed to setPluginStepSize() + size_t m_setBlockSize; // value passed to setPluginBlockSize() + size_t m_stepSize; // value actually used to initialise plugin + size_t m_blockSize; // value actually used to initialise plugin + size_t m_channels; + vector m_queue; + float **m_buffers; + float m_inputSampleRate; + long m_frame; + bool m_unrun; + mutable OutputList m_outputs; + mutable std::map m_rewriteOutputTimes; + std::map m_fixedRateFeatureNos; // output no -> feature no + + void processBlock(FeatureSet& allFeatureSets); + void adjustFixedRateFeatureTime(int outputNo, Feature &); +}; + +PluginBufferingAdapter::PluginBufferingAdapter(Plugin *plugin) : + PluginWrapper(plugin) +{ + m_impl = new Impl(plugin, m_inputSampleRate); +} + +PluginBufferingAdapter::~PluginBufferingAdapter() +{ + delete m_impl; +} + +size_t +PluginBufferingAdapter::getPreferredStepSize() const +{ + return getPreferredBlockSize(); +} + +size_t +PluginBufferingAdapter::getPreferredBlockSize() const +{ + return PluginWrapper::getPreferredBlockSize(); +} + +size_t +PluginBufferingAdapter::getPluginPreferredStepSize() const +{ + return PluginWrapper::getPreferredStepSize(); +} + +size_t +PluginBufferingAdapter::getPluginPreferredBlockSize() const +{ + return PluginWrapper::getPreferredBlockSize(); +} + +void +PluginBufferingAdapter::setPluginStepSize(size_t stepSize) +{ + m_impl->setPluginStepSize(stepSize); +} + +void +PluginBufferingAdapter::setPluginBlockSize(size_t blockSize) +{ + m_impl->setPluginBlockSize(blockSize); +} + +void +PluginBufferingAdapter::getActualStepAndBlockSizes(size_t &stepSize, + size_t &blockSize) +{ + m_impl->getActualStepAndBlockSizes(stepSize, blockSize); +} + +bool +PluginBufferingAdapter::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + return m_impl->initialise(channels, stepSize, blockSize); +} + +PluginBufferingAdapter::OutputList +PluginBufferingAdapter::getOutputDescriptors() const +{ + return m_impl->getOutputDescriptors(); +} + +void +PluginBufferingAdapter::setParameter(std::string name, float value) +{ + m_impl->setParameter(name, value); +} + +void +PluginBufferingAdapter::selectProgram(std::string name) +{ + m_impl->selectProgram(name); +} + +void +PluginBufferingAdapter::reset() +{ + m_impl->reset(); +} + +PluginBufferingAdapter::FeatureSet +PluginBufferingAdapter::process(const float *const *inputBuffers, + RealTime timestamp) +{ + return m_impl->process(inputBuffers, timestamp); +} + +PluginBufferingAdapter::FeatureSet +PluginBufferingAdapter::getRemainingFeatures() +{ + return m_impl->getRemainingFeatures(); +} + +PluginBufferingAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) : + m_plugin(plugin), + m_inputStepSize(0), + m_inputBlockSize(0), + m_setStepSize(0), + m_setBlockSize(0), + m_stepSize(0), + m_blockSize(0), + m_channels(0), + m_queue(0), + m_buffers(0), + m_inputSampleRate(inputSampleRate), + m_frame(0), + m_unrun(true) +{ + (void)getOutputDescriptors(); // set up m_outputs and m_rewriteOutputTimes +} + +PluginBufferingAdapter::Impl::~Impl() +{ + // the adapter will delete the plugin + + for (size_t i = 0; i < m_channels; ++i) { + delete m_queue[i]; + delete[] m_buffers[i]; + } + delete[] m_buffers; +} + +void +PluginBufferingAdapter::Impl::setPluginStepSize(size_t stepSize) +{ + if (m_inputStepSize != 0) { + std::cerr << "PluginBufferingAdapter::setPluginStepSize: ERROR: Cannot be called after initialise()" << std::endl; + return; + } + m_setStepSize = stepSize; +} + +void +PluginBufferingAdapter::Impl::setPluginBlockSize(size_t blockSize) +{ + if (m_inputBlockSize != 0) { + std::cerr << "PluginBufferingAdapter::setPluginBlockSize: ERROR: Cannot be called after initialise()" << std::endl; + return; + } + m_setBlockSize = blockSize; +} + +void +PluginBufferingAdapter::Impl::getActualStepAndBlockSizes(size_t &stepSize, + size_t &blockSize) +{ + stepSize = m_stepSize; + blockSize = m_blockSize; +} + +bool +PluginBufferingAdapter::Impl::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (stepSize != blockSize) { + std::cerr << "PluginBufferingAdapter::initialise: input stepSize must be equal to blockSize for this adapter (stepSize = " << stepSize << ", blockSize = " << blockSize << ")" << std::endl; + return false; + } + + m_channels = channels; + m_inputStepSize = stepSize; + m_inputBlockSize = blockSize; + + // if the user has requested particular step or block sizes, use + // those; otherwise use the step and block sizes which the plugin + // prefers + + m_stepSize = 0; + m_blockSize = 0; + + if (m_setStepSize > 0) { + m_stepSize = m_setStepSize; + } + if (m_setBlockSize > 0) { + m_blockSize = m_setBlockSize; + } + + if (m_stepSize == 0 && m_blockSize == 0) { + m_stepSize = m_plugin->getPreferredStepSize(); + m_blockSize = m_plugin->getPreferredBlockSize(); + } + + bool freq = (m_plugin->getInputDomain() == Vamp::Plugin::FrequencyDomain); + + // or sensible defaults if it has no preference + if (m_blockSize == 0) { + if (m_stepSize == 0) { + m_blockSize = 1024; + if (freq) { + m_stepSize = m_blockSize / 2; + } else { + m_stepSize = m_blockSize; + } + } else if (freq) { + m_blockSize = m_stepSize * 2; + } else { + m_blockSize = m_stepSize; + } + } else if (m_stepSize == 0) { // m_blockSize != 0 (that was handled above) + if (freq) { + m_stepSize = m_blockSize/2; + } else { + m_stepSize = m_blockSize; + } + } + + // current implementation breaks if step is greater than block + if (m_stepSize > m_blockSize) { + size_t newBlockSize; + if (freq) { + newBlockSize = m_stepSize * 2; + } else { + newBlockSize = m_stepSize; + } + std::cerr << "PluginBufferingAdapter::initialise: WARNING: step size " << m_stepSize << " is greater than block size " << m_blockSize << ": cannot handle this in adapter; adjusting block size to " << newBlockSize << std::endl; + m_blockSize = newBlockSize; + } + +// std::cerr << "PluginBufferingAdapter::initialise: NOTE: stepSize " << m_inputStepSize << " -> " << m_stepSize +// << ", blockSize " << m_inputBlockSize << " -> " << m_blockSize << std::endl; + + m_buffers = new float *[m_channels]; + + for (size_t i = 0; i < m_channels; ++i) { + m_queue.push_back(new RingBuffer(int(m_blockSize + m_inputBlockSize))); + m_buffers[i] = new float[m_blockSize]; + } + + bool success = m_plugin->initialise(m_channels, m_stepSize, m_blockSize); + +// std::cerr << "PluginBufferingAdapter::initialise: success = " << success << std::endl; + + if (success) { + // Re-query outputs; properties such as bin count may have + // changed on initialise + m_outputs.clear(); + (void)getOutputDescriptors(); + } + + return success; +} + +PluginBufferingAdapter::OutputList +PluginBufferingAdapter::Impl::getOutputDescriptors() const +{ + if (m_outputs.empty()) { +// std::cerr << "PluginBufferingAdapter::getOutputDescriptors: querying anew" << std::endl; + + m_outputs = m_plugin->getOutputDescriptors(); + } + + PluginBufferingAdapter::OutputList outs = m_outputs; + + for (int i = 0; i < int(outs.size()); ++i) { + + switch (outs[i].sampleType) { + + case OutputDescriptor::OneSamplePerStep: + outs[i].sampleType = OutputDescriptor::FixedSampleRate; + outs[i].sampleRate = m_inputSampleRate / float(m_stepSize); + m_rewriteOutputTimes[i] = true; + break; + + case OutputDescriptor::FixedSampleRate: + if (outs[i].sampleRate == 0.f) { + outs[i].sampleRate = m_inputSampleRate / float(m_stepSize); + } + // We actually only need to rewrite output times for + // features that don't have timestamps already, but we + // can't tell from here whether our features will have + // timestamps or not + m_rewriteOutputTimes[i] = true; + break; + + case OutputDescriptor::VariableSampleRate: + m_rewriteOutputTimes[i] = false; + break; + } + } + + return outs; +} + +void +PluginBufferingAdapter::Impl::setParameter(std::string name, float value) +{ + m_plugin->setParameter(name, value); + + // Re-query outputs; properties such as bin count may have changed + m_outputs.clear(); + (void)getOutputDescriptors(); +} + +void +PluginBufferingAdapter::Impl::selectProgram(std::string name) +{ + m_plugin->selectProgram(name); + + // Re-query outputs; properties such as bin count may have changed + m_outputs.clear(); + (void)getOutputDescriptors(); +} + +void +PluginBufferingAdapter::Impl::reset() +{ + m_frame = 0; + m_unrun = true; + + for (size_t i = 0; i < m_queue.size(); ++i) { + m_queue[i]->reset(); + } + + m_fixedRateFeatureNos.clear(); + + m_plugin->reset(); +} + +PluginBufferingAdapter::FeatureSet +PluginBufferingAdapter::Impl::process(const float *const *inputBuffers, + RealTime timestamp) +{ + if (m_inputStepSize == 0) { + std::cerr << "PluginBufferingAdapter::process: ERROR: Plugin has not been initialised" << std::endl; + return FeatureSet(); + } + + FeatureSet allFeatureSets; + + if (m_unrun) { + m_frame = RealTime::realTime2Frame(timestamp, + int(m_inputSampleRate + 0.5)); + m_unrun = false; + } + + // queue the new input + + for (size_t i = 0; i < m_channels; ++i) { + int written = m_queue[i]->write(inputBuffers[i], int(m_inputBlockSize)); + if (written < int(m_inputBlockSize) && i == 0) { + std::cerr << "WARNING: PluginBufferingAdapter::Impl::process: " + << "Buffer overflow: wrote " << written + << " of " << m_inputBlockSize + << " input samples (for plugin step size " + << m_stepSize << ", block size " << m_blockSize << ")" + << std::endl; + } + } + + // process as much as we can + + while (m_queue[0]->getReadSpace() >= int(m_blockSize)) { + processBlock(allFeatureSets); + } + + return allFeatureSets; +} + +void +PluginBufferingAdapter::Impl::adjustFixedRateFeatureTime(int outputNo, + Feature &feature) +{ +// cerr << "adjustFixedRateFeatureTime: from " << feature.timestamp; + + double rate = m_outputs[outputNo].sampleRate; + if (rate == 0.0) { + rate = m_inputSampleRate / float(m_stepSize); + } + + if (feature.hasTimestamp) { + double secs = feature.timestamp.sec; + secs += feature.timestamp.nsec / 1e9; + m_fixedRateFeatureNos[outputNo] = int(secs * rate + 0.5); +// cerr << " [secs = " << secs << ", no = " << m_fixedRateFeatureNos[outputNo] << "]"; + } + + feature.timestamp = RealTime::fromSeconds + (m_fixedRateFeatureNos[outputNo] / rate); + +// cerr << " to " << feature.timestamp << " (rate = " << rate << ", hasTimestamp = " << feature.hasTimestamp << ")" << endl; + + feature.hasTimestamp = true; + + m_fixedRateFeatureNos[outputNo] = m_fixedRateFeatureNos[outputNo] + 1; +} + +PluginBufferingAdapter::FeatureSet +PluginBufferingAdapter::Impl::getRemainingFeatures() +{ + FeatureSet allFeatureSets; + + // process remaining samples in queue + while (m_queue[0]->getReadSpace() >= int(m_blockSize)) { + processBlock(allFeatureSets); + } + + // pad any last samples remaining and process + if (m_queue[0]->getReadSpace() > 0) { + for (size_t i = 0; i < m_channels; ++i) { + m_queue[i]->zero(int(m_blockSize) - m_queue[i]->getReadSpace()); + } + processBlock(allFeatureSets); + } + + // get remaining features + + FeatureSet featureSet = m_plugin->getRemainingFeatures(); + + for (map::iterator iter = featureSet.begin(); + iter != featureSet.end(); ++iter) { + + int outputNo = iter->first; + FeatureList featureList = iter->second; + + for (size_t i = 0; i < featureList.size(); ++i) { + + if (m_outputs[outputNo].sampleType == + OutputDescriptor::FixedSampleRate) { + adjustFixedRateFeatureTime(outputNo, featureList[i]); + } + + allFeatureSets[outputNo].push_back(featureList[i]); + } + } + + return allFeatureSets; +} + +void +PluginBufferingAdapter::Impl::processBlock(FeatureSet& allFeatureSets) +{ + for (size_t i = 0; i < m_channels; ++i) { + m_queue[i]->peek(m_buffers[i], int(m_blockSize)); + } + + long frame = m_frame; + RealTime timestamp = RealTime::frame2RealTime + (frame, int(m_inputSampleRate + 0.5)); + + FeatureSet featureSet = m_plugin->process(m_buffers, timestamp); + + PluginWrapper *wrapper = dynamic_cast(m_plugin); + RealTime adjustment; + if (wrapper) { + PluginInputDomainAdapter *ida = + wrapper->getWrapper(); + if (ida) adjustment = ida->getTimestampAdjustment(); + } + + for (FeatureSet::iterator iter = featureSet.begin(); + iter != featureSet.end(); ++iter) { + + int outputNo = iter->first; + + if (m_rewriteOutputTimes[outputNo]) { + + FeatureList featureList = iter->second; + + for (size_t i = 0; i < featureList.size(); ++i) { + + switch (m_outputs[outputNo].sampleType) { + + case OutputDescriptor::OneSamplePerStep: + // use our internal timestamp, always + featureList[i].timestamp = timestamp + adjustment; + featureList[i].hasTimestamp = true; + break; + + case OutputDescriptor::FixedSampleRate: + adjustFixedRateFeatureTime(outputNo, featureList[i]); + break; + + case OutputDescriptor::VariableSampleRate: + // plugin must set timestamp + break; + + default: + break; + } + + allFeatureSets[outputNo].push_back(featureList[i]); + } + } else { + for (size_t i = 0; i < iter->second.size(); ++i) { + allFeatureSets[outputNo].push_back(iter->second[i]); + } + } + } + + // step forward + + for (size_t i = 0; i < m_channels; ++i) { + m_queue[i]->skip(int(m_stepSize)); + } + + // increment internal frame counter each time we step forward + m_frame += m_stepSize; +} + +} + +} diff --git a/src/vamp-hostsdk/PluginBufferingAdapter.h b/src/vamp-hostsdk/PluginBufferingAdapter.h new file mode 100644 index 0000000..feb1c64 --- /dev/null +++ b/src/vamp-hostsdk/PluginBufferingAdapter.h @@ -0,0 +1,149 @@ +#pragma once + +#include "vamp-hostsdk/PluginWrapper.h" + +namespace Vamp { + +namespace HostExt { + +/** + * \class PluginBufferingAdapter PluginBufferingAdapter.h + * + * PluginBufferingAdapter is a Vamp plugin adapter that allows plugins + * to be used by a host supplying an audio stream in non-overlapping + * buffers of arbitrary size. + * + * A host using PluginBufferingAdapter may ignore the preferred step + * and block size reported by the plugin, and still expect the plugin + * to run. The value of blockSize and stepSize passed to initialise + * should be the size of the buffer which the host will supply; the + * stepSize should be equal to the blockSize. + * + * If the internal step size used for the plugin differs from that + * supplied by the host, the adapter will modify the sample type and + * rate specifications for the plugin outputs appropriately, and set + * timestamps on the output features for outputs that formerly used a + * different sample rate specification. This is necessary in order to + * obtain correct time stamping. + * + * In other respects, the PluginBufferingAdapter behaves identically + * to the plugin that it wraps. The wrapped plugin will be deleted + * when the wrapper is deleted. + */ + +class PluginBufferingAdapter : public PluginWrapper +{ +public: + /** + * Construct a PluginBufferingAdapter wrapping the given plugin. + * The adapter takes ownership of the plugin, which will be + * deleted when the adapter is deleted. + */ + PluginBufferingAdapter(Plugin *plugin); + virtual ~PluginBufferingAdapter(); + + /** + * Return the preferred step size for this adapter. + * + * Because of the way this adapter works, its preferred step size + * will always be the same as its preferred block size. This may + * or may not be the same as the preferred step size of the + * underlying plugin, which may be obtained by calling + * getPluginPreferredStepSize(). + */ + size_t getPreferredStepSize() const; + + /** + * Return the preferred block size for this adapter. + * + * This may or may not be the same as the preferred block size of + * the underlying plugin, which may be obtained by calling + * getPluginPreferredBlockSize(). + * + * Note that this adapter may be initialised with any block size, + * not just its supposedly preferred one. + */ + size_t getPreferredBlockSize() const; + + /** + * Initialise the adapter (and therefore the plugin) for the given + * number of channels. Initialise the adapter for the given step + * and block size, which must be equal. + * + * The step and block size used for the underlying plugin will + * depend on its preferences, or any values previously passed to + * setPluginStepSize and setPluginBlockSize. + */ + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + + /** + * Return the preferred step size of the plugin wrapped by this + * adapter. + * + * This is included mainly for informational purposes. This value + * is not likely to be a valid step size for the adapter itself, + * and it is not usually of any use in interpreting the results + * (because the adapter re-writes OneSamplePerStep outputs to + * FixedSampleRate so that the hop size no longer needs to be + * known beforehand in order to interpret them). + */ + size_t getPluginPreferredStepSize() const; + + /** + * Return the preferred block size of the plugin wrapped by this + * adapter. + * + * This is included mainly for informational purposes. + */ + size_t getPluginPreferredBlockSize() const; + + /** + * Set the step size that will be used for the underlying plugin + * when initialise() is called. If this is not set, the plugin's + * own preferred step size will be used. You will not usually + * need to call this function. If you do call it, it must be + * before the first call to initialise(). + */ + void setPluginStepSize(size_t stepSize); + + /** + * Set the block size that will be used for the underlying plugin + * when initialise() is called. If this is not set, the plugin's + * own preferred block size will be used. You will not usually + * need to call this function. If you do call it, it must be + * before the first call to initialise(). + */ + void setPluginBlockSize(size_t blockSize); + + /** + * Return the step and block sizes that were actually used when + * initialising the underlying plugin. + * + * This is included mainly for informational purposes. You will + * not usually need to call this function. If this is called + * before initialise(), it will return 0 for both values. If it + * is called after a failed call to initialise(), it will return + * the values that were used in the failed call to the plugin's + * initialise() function. + */ + void getActualStepAndBlockSizes(size_t &stepSize, size_t &blockSize); + + void setParameter(std::string, float); + void selectProgram(std::string); + + OutputList getOutputDescriptors() const; + + void reset(); + + FeatureSet process(const float *const *inputBuffers, RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + class Impl; + Impl *m_impl; +}; + +} + +} diff --git a/src/vamp-hostsdk/PluginChannelAdapter.cpp b/src/vamp-hostsdk/PluginChannelAdapter.cpp new file mode 100644 index 0000000..4f04afa --- /dev/null +++ b/src/vamp-hostsdk/PluginChannelAdapter.cpp @@ -0,0 +1,228 @@ +#include "vamp-hostsdk/PluginChannelAdapter.h" + +namespace Vamp { + +namespace HostExt { + +class PluginChannelAdapter::Impl +{ +public: + Impl(Plugin *plugin); + ~Impl(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + + FeatureSet process(const float *const *inputBuffers, RealTime timestamp); + FeatureSet processInterleaved(const float *inputBuffers, RealTime timestamp); + +protected: + Plugin *m_plugin; + size_t m_blockSize; + size_t m_inputChannels; + size_t m_pluginChannels; + float **m_buffer; + float **m_deinterleave; + const float **m_forwardPtrs; +}; + +PluginChannelAdapter::PluginChannelAdapter(Plugin *plugin) : + PluginWrapper(plugin) +{ + m_impl = new Impl(plugin); +} + +PluginChannelAdapter::~PluginChannelAdapter() +{ + delete m_impl; +} + +bool +PluginChannelAdapter::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + return m_impl->initialise(channels, stepSize, blockSize); +} + +PluginChannelAdapter::FeatureSet +PluginChannelAdapter::process(const float *const *inputBuffers, + RealTime timestamp) +{ + return m_impl->process(inputBuffers, timestamp); +} + +PluginChannelAdapter::FeatureSet +PluginChannelAdapter::processInterleaved(const float *inputBuffers, + RealTime timestamp) +{ + return m_impl->processInterleaved(inputBuffers, timestamp); +} + +PluginChannelAdapter::Impl::Impl(Plugin *plugin) : + m_plugin(plugin), + m_blockSize(0), + m_inputChannels(0), + m_pluginChannels(0), + m_buffer(0), + m_deinterleave(0), + m_forwardPtrs(0) +{ +} + +PluginChannelAdapter::Impl::~Impl() +{ + // the adapter will delete the plugin + + if (m_buffer) { + if (m_inputChannels > m_pluginChannels) { + delete[] m_buffer[0]; + } else { + for (size_t i = 0; i < m_pluginChannels - m_inputChannels; ++i) { + delete[] m_buffer[i]; + } + } + delete[] m_buffer; + m_buffer = 0; + } + + if (m_deinterleave) { + for (size_t i = 0; i < m_inputChannels; ++i) { + delete[] m_deinterleave[i]; + } + delete[] m_deinterleave; + m_deinterleave = 0; + } + + if (m_forwardPtrs) { + delete[] m_forwardPtrs; + m_forwardPtrs = 0; + } +} + +bool +PluginChannelAdapter::Impl::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + m_blockSize = blockSize; + + size_t minch = m_plugin->getMinChannelCount(); + size_t maxch = m_plugin->getMaxChannelCount(); + + m_inputChannels = channels; + + if (m_inputChannels < minch) { + + m_forwardPtrs = new const float *[minch]; + + if (m_inputChannels > 1) { + // We need a set of zero-valued buffers to add to the + // forwarded pointers + m_buffer = new float*[minch - channels]; + for (size_t i = 0; i < minch; ++i) { + m_buffer[i] = new float[blockSize]; + for (size_t j = 0; j < blockSize; ++j) { + m_buffer[i][j] = 0.f; + } + } + } + + m_pluginChannels = minch; + +// std::cerr << "PluginChannelAdapter::initialise: expanding " << m_inputChannels << " to " << m_pluginChannels << " for plugin" << std::endl; + + } else if (m_inputChannels > maxch) { + + // We only need m_buffer if we are mixing down to a single + // channel -- otherwise we can just forward the same float* as + // passed in to process(), expecting the excess to be ignored + + if (maxch == 1) { + m_buffer = new float *[1]; + m_buffer[0] = new float[blockSize]; + +// std::cerr << "PluginChannelAdapter::initialise: mixing " << m_inputChannels << " to mono for plugin" << std::endl; + + } else { + +// std::cerr << "PluginChannelAdapter::initialise: reducing " << m_inputChannels << " to " << m_pluginChannels << " for plugin" << std::endl; + } + + m_pluginChannels = maxch; + + } else { + +// std::cerr << "PluginChannelAdapter::initialise: accepting given number of channels (" << m_inputChannels << ")" << std::endl; + m_pluginChannels = m_inputChannels; + } + + return m_plugin->initialise(m_pluginChannels, stepSize, blockSize); +} + +PluginChannelAdapter::FeatureSet +PluginChannelAdapter::Impl::processInterleaved(const float *inputBuffers, + RealTime timestamp) +{ + if (!m_deinterleave) { + m_deinterleave = new float *[m_inputChannels]; + for (size_t i = 0; i < m_inputChannels; ++i) { + m_deinterleave[i] = new float[m_blockSize]; + } + } + + for (size_t i = 0; i < m_inputChannels; ++i) { + for (size_t j = 0; j < m_blockSize; ++j) { + m_deinterleave[i][j] = inputBuffers[j * m_inputChannels + i]; + } + } + + return process(m_deinterleave, timestamp); +} + +PluginChannelAdapter::FeatureSet +PluginChannelAdapter::Impl::process(const float *const *inputBuffers, + RealTime timestamp) +{ +// std::cerr << "PluginChannelAdapter::process: " << m_inputChannels << " -> " << m_pluginChannels << " channels" << std::endl; + + if (m_inputChannels < m_pluginChannels) { + + if (m_inputChannels == 1) { + for (size_t i = 0; i < m_pluginChannels; ++i) { + m_forwardPtrs[i] = inputBuffers[0]; + } + } else { + for (size_t i = 0; i < m_inputChannels; ++i) { + m_forwardPtrs[i] = inputBuffers[i]; + } + for (size_t i = m_inputChannels; i < m_pluginChannels; ++i) { + m_forwardPtrs[i] = m_buffer[i - m_inputChannels]; + } + } + + return m_plugin->process(m_forwardPtrs, timestamp); + + } else if (m_inputChannels > m_pluginChannels) { + + if (m_pluginChannels == 1) { + for (size_t j = 0; j < m_blockSize; ++j) { + m_buffer[0][j] = inputBuffers[0][j]; + } + for (size_t i = 1; i < m_inputChannels; ++i) { + for (size_t j = 0; j < m_blockSize; ++j) { + m_buffer[0][j] += inputBuffers[i][j]; + } + } + for (size_t j = 0; j < m_blockSize; ++j) { + m_buffer[0][j] /= float(m_inputChannels); + } + return m_plugin->process(m_buffer, timestamp); + } else { + return m_plugin->process(inputBuffers, timestamp); + } + + } else { + + return m_plugin->process(inputBuffers, timestamp); + } +} + +} + +} diff --git a/src/vamp-hostsdk/PluginChannelAdapter.h b/src/vamp-hostsdk/PluginChannelAdapter.h new file mode 100644 index 0000000..507eac0 --- /dev/null +++ b/src/vamp-hostsdk/PluginChannelAdapter.h @@ -0,0 +1,105 @@ +#pragma once + +#include "vamp-hostsdk/PluginWrapper.h" + +namespace Vamp { + +namespace HostExt { + +/** + * \class PluginChannelAdapter PluginChannelAdapter.h + * + * PluginChannelAdapter is a Vamp plugin adapter that implements a + * policy for management of plugins that expect a different number of + * input channels from the number actually available in the source + * audio data. + * + * A host using PluginChannelAdapter may ignore the getMinChannelCount + * and getMaxChannelCount reported by the plugin, and still expect the + * plugin to run. + * + * PluginChannelAdapter implements the following policy: + * + * - If the plugin supports the provided number of channels directly, + * PluginChannelAdapter will just run the plugin as normal. + * + * - If the plugin only supports exactly one channel but more than + * one channel is provided, PluginChannelAdapter will use the mean of + * the channels. This ensures that the resulting values remain + * within the same magnitude range as expected for mono data. + * + * - If the plugin requires more than one channel but exactly one is + * provided, the provided channel will be duplicated across all the + * plugin input channels. + * + * If none of the above apply: + * + * - If the plugin requires more channels than are provided, the + * minimum acceptable number of channels will be produced by adding + * empty (zero valued) channels to those provided. + * + * - If the plugin requires fewer channels than are provided, the + * maximum acceptable number of channels will be produced by + * discarding the excess channels. + * + * Hosts requiring a different channel policy from the above will need + * to implement it themselves, instead of using PluginChannelAdapter. + * + * Note that PluginChannelAdapter does not override the minimum and + * maximum channel counts returned by the wrapped plugin. The host + * will need to be aware that it is using a PluginChannelAdapter, and + * be prepared to ignore these counts as necessary. (This contrasts + * with the approach used in PluginInputDomainAdapter, which aims to + * make the host completely unaware of which underlying input domain + * is in fact in use.) + * + * (The rationale for this is that a host may wish to use the + * PluginChannelAdapter but still discriminate in some way on the + * basis of the number of channels actually supported. For example, a + * simple stereo audio host may prefer to reject plugins that require + * more than two channels on the grounds that doesn't actually + * understand what they are for, rather than allow the channel adapter + * to make a potentially meaningless channel conversion for them.) + * + * In every respect other than its management of channels, the + * PluginChannelAdapter behaves identically to the plugin that it + * wraps. The wrapped plugin will be deleted when the wrapper is + * deleted. + * + * \note This class was introduced in version 1.1 of the Vamp plugin SDK. + */ + +class PluginChannelAdapter : public PluginWrapper +{ +public: + /** + * Construct a PluginChannelAdapter wrapping the given plugin. + * The adapter takes ownership of the plugin, which will be + * deleted when the adapter is deleted. + */ + PluginChannelAdapter(Plugin *plugin); + virtual ~PluginChannelAdapter(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + + FeatureSet process(const float *const *inputBuffers, RealTime timestamp); + + /** + * Call process(), providing interleaved audio data with the + * number of channels passed to initialise(). The adapter will + * de-interleave into temporary buffers as appropriate before + * calling process(). + * + * \note This function was introduced in version 1.4 of the Vamp + * plugin SDK. + */ + FeatureSet processInterleaved(const float *inputBuffer, RealTime timestamp); + +protected: + class Impl; + Impl *m_impl; +}; + +} + +} diff --git a/src/vamp-hostsdk/PluginHostAdapter.cpp b/src/vamp-hostsdk/PluginHostAdapter.cpp new file mode 100644 index 0000000..77659f3 --- /dev/null +++ b/src/vamp-hostsdk/PluginHostAdapter.cpp @@ -0,0 +1,424 @@ +#include "vamp-hostsdk/PluginHostAdapter.h" +#include + +#include "vamp-hostsdk/Files.h" + +namespace Vamp +{ + +PluginHostAdapter::PluginHostAdapter(const VampPluginDescriptor *descriptor, + float inputSampleRate) : + Plugin(inputSampleRate), + m_descriptor(descriptor) +{ +// std::cerr << "PluginHostAdapter::PluginHostAdapter (plugin = " << descriptor->name << ")" << std::endl; + m_handle = m_descriptor->instantiate(m_descriptor, inputSampleRate); + if (!m_handle) { +// std::cerr << "WARNING: PluginHostAdapter: Plugin instantiation failed for plugin " << m_descriptor->name << std::endl; + } +} + +PluginHostAdapter::~PluginHostAdapter() +{ +// std::cerr << "PluginHostAdapter::~PluginHostAdapter (plugin = " << m_descriptor->name << ")" << std::endl; + if (m_handle) m_descriptor->cleanup(m_handle); +} + +std::vector +PluginHostAdapter::getPluginPath() +{ + std::vector path; + std::string envPath; + + if (Files::isNonNative32Bit()) { + (void)Files::getEnvUtf8("VAMP_PATH_32", envPath); + } else { + (void)Files::getEnvUtf8("VAMP_PATH", envPath); + } + +#ifdef _WIN32 +#define PATH_SEPARATOR ';' +#define DEFAULT_VAMP_PATH "%ProgramFiles%\\Vamp Plugins" +#else +#define PATH_SEPARATOR ':' +#ifdef __APPLE__ +#define DEFAULT_VAMP_PATH "$HOME/Library/Audio/Plug-Ins/Vamp:/Library/Audio/Plug-Ins/Vamp" +#else +#define DEFAULT_VAMP_PATH "$HOME/vamp:$HOME/.vamp:/usr/local/lib/vamp:/usr/lib/vamp" +#endif +#endif + + if (envPath == "") { + envPath = DEFAULT_VAMP_PATH; + std::string home; + if (Files::getEnvUtf8("HOME", home)) { + std::string::size_type f; + while ((f = envPath.find("$HOME")) != std::string::npos && + f < envPath.length()) { + envPath.replace(f, 5, home); + } + } +#ifdef _WIN32 + std::string pfiles; + if (!Files::getEnvUtf8("ProgramFiles", pfiles)) { + pfiles = "C:\\Program Files"; + } + std::string::size_type f; + while ((f = envPath.find("%ProgramFiles%")) != std::string::npos && + f < envPath.length()) { + envPath.replace(f, 14, pfiles); + } +#endif + } + + std::string::size_type index = 0, newindex = 0; + + while ((newindex = envPath.find(PATH_SEPARATOR, index)) < envPath.size()) { + path.push_back(envPath.substr(index, newindex - index)); + index = newindex + 1; + } + + path.push_back(envPath.substr(index)); + + return path; +} + +bool +PluginHostAdapter::initialise(size_t channels, + size_t stepSize, + size_t blockSize) +{ + if (!m_handle) return false; + return m_descriptor->initialise + (m_handle, + (unsigned int)channels, + (unsigned int)stepSize, + (unsigned int)blockSize) ? + true : false; +} + +void +PluginHostAdapter::reset() +{ + if (!m_handle) { +// std::cerr << "PluginHostAdapter::reset: no handle" << std::endl; + return; + } +// std::cerr << "PluginHostAdapter::reset(" << m_handle << ")" << std::endl; + m_descriptor->reset(m_handle); +} + +PluginHostAdapter::InputDomain +PluginHostAdapter::getInputDomain() const +{ + if (m_descriptor->inputDomain == vampFrequencyDomain) { + return FrequencyDomain; + } else { + return TimeDomain; + } +} + +unsigned int +PluginHostAdapter::getVampApiVersion() const +{ + return m_descriptor->vampApiVersion; +} + +std::string +PluginHostAdapter::getIdentifier() const +{ + return m_descriptor->identifier; +} + +std::string +PluginHostAdapter::getName() const +{ + return m_descriptor->name; +} + +std::string +PluginHostAdapter::getDescription() const +{ + return m_descriptor->description; +} + +std::string +PluginHostAdapter::getMaker() const +{ + return m_descriptor->maker; +} + +int +PluginHostAdapter::getPluginVersion() const +{ + return m_descriptor->pluginVersion; +} + +std::string +PluginHostAdapter::getCopyright() const +{ + return m_descriptor->copyright; +} + +PluginHostAdapter::ParameterList +PluginHostAdapter::getParameterDescriptors() const +{ + ParameterList list; + for (unsigned int i = 0; i < m_descriptor->parameterCount; ++i) { + const VampParameterDescriptor *spd = m_descriptor->parameters[i]; + ParameterDescriptor pd; + pd.identifier = spd->identifier; + pd.name = spd->name; + pd.description = spd->description; + pd.unit = spd->unit; + pd.minValue = spd->minValue; + pd.maxValue = spd->maxValue; + pd.defaultValue = spd->defaultValue; + pd.isQuantized = spd->isQuantized; + pd.quantizeStep = spd->quantizeStep; + if (pd.isQuantized && spd->valueNames) { + for (unsigned int j = 0; spd->valueNames[j]; ++j) { + pd.valueNames.push_back(spd->valueNames[j]); + } + } + list.push_back(pd); + } + return list; +} + +float +PluginHostAdapter::getParameter(std::string param) const +{ + if (!m_handle) return 0.0; + + for (unsigned int i = 0; i < m_descriptor->parameterCount; ++i) { + if (param == m_descriptor->parameters[i]->identifier) { + return m_descriptor->getParameter(m_handle, i); + } + } + + return 0.0; +} + +void +PluginHostAdapter::setParameter(std::string param, + float value) +{ + if (!m_handle) return; + + for (unsigned int i = 0; i < m_descriptor->parameterCount; ++i) { + if (param == m_descriptor->parameters[i]->identifier) { + m_descriptor->setParameter(m_handle, i, value); + return; + } + } +} + +PluginHostAdapter::ProgramList +PluginHostAdapter::getPrograms() const +{ + ProgramList list; + + for (unsigned int i = 0; i < m_descriptor->programCount; ++i) { + list.push_back(m_descriptor->programs[i]); + } + + return list; +} + +std::string +PluginHostAdapter::getCurrentProgram() const +{ + if (!m_handle) return ""; + + int pn = m_descriptor->getCurrentProgram(m_handle); + if (pn < (int)m_descriptor->programCount) { + return m_descriptor->programs[pn]; + } else { + return ""; + } +} + +void +PluginHostAdapter::selectProgram(std::string program) +{ + if (!m_handle) return; + + for (unsigned int i = 0; i < m_descriptor->programCount; ++i) { + if (program == m_descriptor->programs[i]) { + m_descriptor->selectProgram(m_handle, i); + return; + } + } +} + +size_t +PluginHostAdapter::getPreferredStepSize() const +{ + if (!m_handle) return 0; + return m_descriptor->getPreferredStepSize(m_handle); +} + +size_t +PluginHostAdapter::getPreferredBlockSize() const +{ + if (!m_handle) return 0; + return m_descriptor->getPreferredBlockSize(m_handle); +} + +size_t +PluginHostAdapter::getMinChannelCount() const +{ + if (!m_handle) return 0; + return m_descriptor->getMinChannelCount(m_handle); +} + +size_t +PluginHostAdapter::getMaxChannelCount() const +{ + if (!m_handle) return 0; + return m_descriptor->getMaxChannelCount(m_handle); +} + +PluginHostAdapter::OutputList +PluginHostAdapter::getOutputDescriptors() const +{ + OutputList list; + if (!m_handle) { +// std::cerr << "PluginHostAdapter::getOutputDescriptors: no handle " << std::endl; + return list; + } + + unsigned int count = m_descriptor->getOutputCount(m_handle); + + for (unsigned int i = 0; i < count; ++i) { + VampOutputDescriptor *sd = m_descriptor->getOutputDescriptor(m_handle, i); + OutputDescriptor d; + d.identifier = sd->identifier; + d.name = sd->name; + d.description = sd->description; + d.unit = sd->unit; + d.hasFixedBinCount = sd->hasFixedBinCount; + d.binCount = sd->binCount; + if (d.hasFixedBinCount && sd->binNames) { + for (unsigned int j = 0; j < sd->binCount; ++j) { + d.binNames.push_back(sd->binNames[j] ? sd->binNames[j] : ""); + } + } + d.hasKnownExtents = sd->hasKnownExtents; + d.minValue = sd->minValue; + d.maxValue = sd->maxValue; + d.isQuantized = sd->isQuantized; + d.quantizeStep = sd->quantizeStep; + + switch (sd->sampleType) { + case vampOneSamplePerStep: + d.sampleType = OutputDescriptor::OneSamplePerStep; break; + case vampFixedSampleRate: + d.sampleType = OutputDescriptor::FixedSampleRate; break; + case vampVariableSampleRate: + d.sampleType = OutputDescriptor::VariableSampleRate; break; + } + + d.sampleRate = sd->sampleRate; + + if (m_descriptor->vampApiVersion >= 2) { + d.hasDuration = sd->hasDuration; + } else { + d.hasDuration = false; + } + + list.push_back(d); + + m_descriptor->releaseOutputDescriptor(sd); + } + + return list; +} + +PluginHostAdapter::FeatureSet +PluginHostAdapter::process(const float *const *inputBuffers, + RealTime timestamp) +{ + FeatureSet fs; + if (!m_handle) return fs; + + int sec = timestamp.sec; + int nsec = timestamp.nsec; + + VampFeatureList *features = m_descriptor->process(m_handle, + inputBuffers, + sec, nsec); + + convertFeatures(features, fs); + m_descriptor->releaseFeatureSet(features); + return fs; +} + +PluginHostAdapter::FeatureSet +PluginHostAdapter::getRemainingFeatures() +{ + FeatureSet fs; + if (!m_handle) return fs; + + VampFeatureList *features = m_descriptor->getRemainingFeatures(m_handle); + + convertFeatures(features, fs); + m_descriptor->releaseFeatureSet(features); + return fs; +} + +void +PluginHostAdapter::convertFeatures(VampFeatureList *features, + FeatureSet &fs) +{ + if (!features) return; + + unsigned int outputs = m_descriptor->getOutputCount(m_handle); + + for (unsigned int i = 0; i < outputs; ++i) { + + VampFeatureList &list = features[i]; + + if (list.featureCount > 0) { + + Feature feature; + feature.values.reserve(list.features[0].v1.valueCount); + + for (unsigned int j = 0; j < list.featureCount; ++j) { + + feature.hasTimestamp = list.features[j].v1.hasTimestamp; + feature.timestamp = RealTime(list.features[j].v1.sec, + list.features[j].v1.nsec); + feature.hasDuration = false; + + if (m_descriptor->vampApiVersion >= 2) { + unsigned int j2 = j + list.featureCount; + feature.hasDuration = list.features[j2].v2.hasDuration; + feature.duration = RealTime(list.features[j2].v2.durationSec, + list.features[j2].v2.durationNsec); + } + + for (unsigned int k = 0; k < list.features[j].v1.valueCount; ++k) { + feature.values.push_back(list.features[j].v1.values[k]); + } + + if (list.features[j].v1.label) { + feature.label = list.features[j].v1.label; + } + + fs[i].push_back(feature); + + if (list.features[j].v1.valueCount > 0) { + feature.values.clear(); + } + + if (list.features[j].v1.label) { + feature.label = ""; + } + } + } + } +} + +} diff --git a/src/vamp-hostsdk/PluginHostAdapter.h b/src/vamp-hostsdk/PluginHostAdapter.h new file mode 100644 index 0000000..bf4b943 --- /dev/null +++ b/src/vamp-hostsdk/PluginHostAdapter.h @@ -0,0 +1,75 @@ +#pragma once + +#include "vamp-sdk/Plugin.h" +#include "vamp.h" +#include + +namespace Vamp { + +/** + * \class PluginHostAdapter PluginHostAdapter.h + * + * PluginHostAdapter is a wrapper class that a Vamp host can use to + * make the C-language VampPluginDescriptor object appear as a C++ + * Vamp::Plugin object. + * + * The Vamp API is defined in vamp/vamp.h as a C API. The C++ objects + * used for convenience by plugins and hosts actually communicate + * using the C low-level API, but the details of this communication + * are handled seamlessly by the Vamp SDK implementation provided the + * plugin and host use the proper C++ wrapper objects. + * + * See also PluginAdapter, the plugin-side wrapper that makes a C++ + * plugin object available using the C query API. + */ + +class PluginHostAdapter : public Plugin +{ +public: + PluginHostAdapter(const VampPluginDescriptor *descriptor, + float inputSampleRate); + virtual ~PluginHostAdapter(); + + static std::vector getPluginPath(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + void reset(); + + InputDomain getInputDomain() const; + + unsigned int getVampApiVersion() const; + std::string getIdentifier() const; + std::string getName() const; + std::string getDescription() const; + std::string getMaker() const; + int getPluginVersion() const; + std::string getCopyright() const; + + ParameterList getParameterDescriptors() const; + float getParameter(std::string) const; + void setParameter(std::string, float); + + ProgramList getPrograms() const; + std::string getCurrentProgram() const; + void selectProgram(std::string); + + size_t getPreferredStepSize() const; + size_t getPreferredBlockSize() const; + + size_t getMinChannelCount() const; + size_t getMaxChannelCount() const; + + OutputList getOutputDescriptors() const; + + FeatureSet process(const float *const *inputBuffers, RealTime timestamp); + + FeatureSet getRemainingFeatures(); + +protected: + void convertFeatures(VampFeatureList *, FeatureSet &); + + const VampPluginDescriptor *m_descriptor; + VampPluginHandle m_handle; +}; + +} diff --git a/src/vamp-hostsdk/PluginInputDomainAdapter.cpp b/src/vamp-hostsdk/PluginInputDomainAdapter.cpp new file mode 100644 index 0000000..2bcf614 --- /dev/null +++ b/src/vamp-hostsdk/PluginInputDomainAdapter.cpp @@ -0,0 +1,482 @@ +#include "vamp-hostsdk/PluginInputDomainAdapter.h" +#include "vamp-sdk/ext/vamp_kiss_fft.h" +#include "vamp-sdk/ext/vamp_kiss_fftr.h" + +#include +#include "Window.h" +#include +#include +#include +#include +#include + + +namespace Vamp { + +namespace HostExt { + +class PluginInputDomainAdapter::Impl +{ +public: + Impl(Plugin *plugin, float inputSampleRate); + ~Impl(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + void reset(); + + size_t getPreferredStepSize() const; + size_t getPreferredBlockSize() const; + + FeatureSet process(const float *const *inputBuffers, RealTime timestamp); + + void setProcessTimestampMethod(ProcessTimestampMethod m); + ProcessTimestampMethod getProcessTimestampMethod() const; + + RealTime getTimestampAdjustment() const; + + WindowType getWindowType() const; + void setWindowType(WindowType type); + +protected: + Plugin *m_plugin; + float m_inputSampleRate; + int m_channels; + int m_stepSize; + int m_blockSize; + float **m_freqbuf; + vamp_kiss_fft_scalar *m_ri; + + WindowType m_windowType; + typedef Window W; + W *m_window; + + ProcessTimestampMethod m_method; + int m_processCount; + float **m_shiftBuffers; + + vamp_kiss_fftr_cfg m_cfg; + vamp_kiss_fft_cpx *m_cbuf; + + FeatureSet processShiftingTimestamp(const float *const *inputBuffers, RealTime timestamp); + FeatureSet processShiftingData(const float *const *inputBuffers, RealTime timestamp); + + size_t makeBlockSizeAcceptable(size_t) const; + + W::WindowType convertType(WindowType t) const; +}; + +PluginInputDomainAdapter::PluginInputDomainAdapter(Plugin *plugin) : + PluginWrapper(plugin) +{ + m_impl = new Impl(plugin, m_inputSampleRate); +} + +PluginInputDomainAdapter::~PluginInputDomainAdapter() +{ + delete m_impl; +} + +bool +PluginInputDomainAdapter::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + return m_impl->initialise(channels, stepSize, blockSize); +} + +void +PluginInputDomainAdapter::reset() +{ + m_impl->reset(); +} + +Plugin::InputDomain +PluginInputDomainAdapter::getInputDomain() const +{ + return TimeDomain; +} + +size_t +PluginInputDomainAdapter::getPreferredStepSize() const +{ + return m_impl->getPreferredStepSize(); +} + +size_t +PluginInputDomainAdapter::getPreferredBlockSize() const +{ + return m_impl->getPreferredBlockSize(); +} + +Plugin::FeatureSet +PluginInputDomainAdapter::process(const float *const *inputBuffers, RealTime timestamp) +{ + return m_impl->process(inputBuffers, timestamp); +} + +void +PluginInputDomainAdapter::setProcessTimestampMethod(ProcessTimestampMethod m) +{ + m_impl->setProcessTimestampMethod(m); +} + +PluginInputDomainAdapter::ProcessTimestampMethod +PluginInputDomainAdapter::getProcessTimestampMethod() const +{ + return m_impl->getProcessTimestampMethod(); +} + +RealTime +PluginInputDomainAdapter::getTimestampAdjustment() const +{ + return m_impl->getTimestampAdjustment(); +} + +PluginInputDomainAdapter::WindowType +PluginInputDomainAdapter::getWindowType() const +{ + return m_impl->getWindowType(); +} + +void +PluginInputDomainAdapter::setWindowType(WindowType w) +{ + m_impl->setWindowType(w); +} + + +PluginInputDomainAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) : + m_plugin(plugin), + m_inputSampleRate(inputSampleRate), + m_channels(0), + m_stepSize(0), + m_blockSize(0), + m_freqbuf(0), + m_ri(0), + m_windowType(HanningWindow), + m_window(0), + m_method(ShiftTimestamp), + m_processCount(0), + m_shiftBuffers(0), + m_cfg(0), + m_cbuf(0) +{ +} + +PluginInputDomainAdapter::Impl::~Impl() +{ + // the adapter will delete the plugin + + if (m_shiftBuffers) { + for (int c = 0; c < m_channels; ++c) { + delete[] m_shiftBuffers[c]; + } + delete[] m_shiftBuffers; + } + + if (m_channels > 0) { + for (int c = 0; c < m_channels; ++c) { + delete[] m_freqbuf[c]; + } + delete[] m_freqbuf; + delete[] m_ri; + if (m_cfg) { + vamp_kiss_fftr_free(m_cfg); + m_cfg = 0; + delete[] m_cbuf; + m_cbuf = 0; + } + delete m_window; + } +} + +// for some visual studii apparently +#ifndef M_PI +#define M_PI 3.14159265358979232846 +#endif + +bool +PluginInputDomainAdapter::Impl::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (m_plugin->getInputDomain() == TimeDomain) { + + m_stepSize = int(stepSize); + m_blockSize = int(blockSize); + m_channels = int(channels); + + return m_plugin->initialise(channels, stepSize, blockSize); + } + + if (blockSize < 2) { + std::cerr << "ERROR: PluginInputDomainAdapter::initialise: blocksize < 2 not supported" << std::endl; + return false; + } + + if (blockSize % 2) { + std::cerr << "ERROR: PluginInputDomainAdapter::initialise: odd blocksize " << blockSize << " not supported" << std::endl; + return false; + } + + if (m_channels > 0) { + for (int c = 0; c < m_channels; ++c) { + delete[] m_freqbuf[c]; + } + delete[] m_freqbuf; + delete[] m_ri; + if (m_cfg) { + vamp_kiss_fftr_free(m_cfg); + m_cfg = 0; + delete[] m_cbuf; + m_cbuf = 0; + } + delete m_window; + } + + m_stepSize = int(stepSize); + m_blockSize = int(blockSize); + m_channels = int(channels); + + m_freqbuf = new float *[m_channels]; + for (int c = 0; c < m_channels; ++c) { + m_freqbuf[c] = new float[m_blockSize + 2]; + } + m_ri = new vamp_kiss_fft_scalar[m_blockSize]; + + m_window = new W(convertType(m_windowType), m_blockSize); + + m_cfg = vamp_kiss_fftr_alloc(m_blockSize, false, 0, 0); + m_cbuf = new vamp_kiss_fft_cpx[m_blockSize/2+1]; + + m_processCount = 0; + + return m_plugin->initialise(channels, stepSize, m_blockSize); +} + +void +PluginInputDomainAdapter::Impl::reset() +{ + m_processCount = 0; + m_plugin->reset(); +} + +size_t +PluginInputDomainAdapter::Impl::getPreferredStepSize() const +{ + size_t step = m_plugin->getPreferredStepSize(); + + if (step == 0 && (m_plugin->getInputDomain() == FrequencyDomain)) { + step = getPreferredBlockSize() / 2; + } + + return step; +} + +size_t +PluginInputDomainAdapter::Impl::getPreferredBlockSize() const +{ + size_t block = m_plugin->getPreferredBlockSize(); + + if (m_plugin->getInputDomain() == FrequencyDomain) { + if (block == 0) { + block = 1024; + } else { + block = makeBlockSizeAcceptable(block); + } + } + + return block; +} + +size_t +PluginInputDomainAdapter::Impl::makeBlockSizeAcceptable(size_t blockSize) const +{ + if (blockSize < 2) { + + std::cerr << "WARNING: PluginInputDomainAdapter::initialise: blocksize < 2 not" << std::endl + << "supported, increasing from " << blockSize << " to 2" << std::endl; + blockSize = 2; + + } else if (blockSize % 2) { + + std::cerr << "WARNING: PluginInputDomainAdapter::initialise: odd blocksize not" << std::endl + << "supported, increasing from " << blockSize << " to " << (blockSize+1) << std::endl; + blockSize = blockSize+1; + } + + return blockSize; +} + +RealTime +PluginInputDomainAdapter::Impl::getTimestampAdjustment() const +{ + if (m_plugin->getInputDomain() == TimeDomain) { + return RealTime::zeroTime; + } else if (m_method == ShiftData || m_method == NoShift) { + return RealTime::zeroTime; + } else { + return RealTime::frame2RealTime + (m_blockSize/2, int(m_inputSampleRate + 0.5)); + } +} + +void +PluginInputDomainAdapter::Impl::setProcessTimestampMethod(ProcessTimestampMethod m) +{ + m_method = m; +} + +PluginInputDomainAdapter::ProcessTimestampMethod +PluginInputDomainAdapter::Impl::getProcessTimestampMethod() const +{ + return m_method; +} + +void +PluginInputDomainAdapter::Impl::setWindowType(WindowType t) +{ + if (m_windowType == t) return; + m_windowType = t; + if (m_window) { + delete m_window; + m_window = new W(convertType(m_windowType), m_blockSize); + } +} + +PluginInputDomainAdapter::WindowType +PluginInputDomainAdapter::Impl::getWindowType() const +{ + return m_windowType; +} + +PluginInputDomainAdapter::Impl::W::WindowType +PluginInputDomainAdapter::Impl::convertType(WindowType t) const +{ + switch (t) { + case RectangularWindow: + return W::RectangularWindow; + case BartlettWindow: + return W::BartlettWindow; + case HammingWindow: + return W::HammingWindow; + case HanningWindow: + return W::HanningWindow; + case BlackmanWindow: + return W::BlackmanWindow; + case NuttallWindow: + return W::NuttallWindow; + case BlackmanHarrisWindow: + return W::BlackmanHarrisWindow; + default: + return W::HanningWindow; + } +} + +Plugin::FeatureSet +PluginInputDomainAdapter::Impl::process(const float *const *inputBuffers, + RealTime timestamp) +{ + if (m_plugin->getInputDomain() == TimeDomain) { + return m_plugin->process(inputBuffers, timestamp); + } + + if (m_method == ShiftTimestamp || m_method == NoShift) { + return processShiftingTimestamp(inputBuffers, timestamp); + } else { + return processShiftingData(inputBuffers, timestamp); + } +} + +Plugin::FeatureSet +PluginInputDomainAdapter::Impl::processShiftingTimestamp(const float *const *inputBuffers, + RealTime timestamp) +{ + unsigned int roundedRate = 1; + if (m_inputSampleRate > 0.f) { + roundedRate = (unsigned int)round(m_inputSampleRate); + } + + if (m_method == ShiftTimestamp) { + // we may need to add one nsec if timestamp + + // getTimestampAdjustment() rounds down + timestamp = timestamp + getTimestampAdjustment(); + RealTime nsec(0, 1); + if (RealTime::realTime2Frame(timestamp, roundedRate) < + RealTime::realTime2Frame(timestamp + nsec, roundedRate)) { + timestamp = timestamp + nsec; + } + } + + for (int c = 0; c < m_channels; ++c) { + + m_window->cut(inputBuffers[c], m_ri); + + for (int i = 0; i < m_blockSize/2; ++i) { + // FFT shift + vamp_kiss_fft_scalar value = m_ri[i]; + m_ri[i] = m_ri[i + m_blockSize/2]; + m_ri[i + m_blockSize/2] = value; + } + + vamp_kiss_fftr(m_cfg, m_ri, m_cbuf); + + for (int i = 0; i <= m_blockSize/2; ++i) { + m_freqbuf[c][i * 2] = float(m_cbuf[i].r); + m_freqbuf[c][i * 2 + 1] = float(m_cbuf[i].i); + } + } + + return m_plugin->process(m_freqbuf, timestamp); +} + +Plugin::FeatureSet +PluginInputDomainAdapter::Impl::processShiftingData(const float *const *inputBuffers, + RealTime timestamp) +{ + if (m_processCount == 0) { + if (!m_shiftBuffers) { + m_shiftBuffers = new float *[m_channels]; + for (int c = 0; c < m_channels; ++c) { + m_shiftBuffers[c] = new float[m_blockSize + m_blockSize/2]; + } + } + for (int c = 0; c < m_channels; ++c) { + for (int i = 0; i < m_blockSize + m_blockSize/2; ++i) { + m_shiftBuffers[c][i] = 0.f; + } + } + } + + for (int c = 0; c < m_channels; ++c) { + for (int i = m_stepSize; i < m_blockSize + m_blockSize/2; ++i) { + m_shiftBuffers[c][i - m_stepSize] = m_shiftBuffers[c][i]; + } + for (int i = 0; i < m_blockSize; ++i) { + m_shiftBuffers[c][i + m_blockSize/2] = inputBuffers[c][i]; + } + } + + for (int c = 0; c < m_channels; ++c) { + + m_window->cut(m_shiftBuffers[c], m_ri); + + for (int i = 0; i < m_blockSize/2; ++i) { + // FFT shift + vamp_kiss_fft_scalar value = m_ri[i]; + m_ri[i] = m_ri[i + m_blockSize/2]; + m_ri[i + m_blockSize/2] = value; + } + + vamp_kiss_fftr(m_cfg, m_ri, m_cbuf); + + for (int i = 0; i <= m_blockSize/2; ++i) { + m_freqbuf[c][i * 2] = float(m_cbuf[i].r); + m_freqbuf[c][i * 2 + 1] = float(m_cbuf[i].i); + } + } + + ++m_processCount; + + return m_plugin->process(m_freqbuf, timestamp); +} + +} + +} diff --git a/src/vamp-hostsdk/PluginInputDomainAdapter.h b/src/vamp-hostsdk/PluginInputDomainAdapter.h new file mode 100644 index 0000000..e87679a --- /dev/null +++ b/src/vamp-hostsdk/PluginInputDomainAdapter.h @@ -0,0 +1,191 @@ +#pragma once + +#include "vamp-hostsdk/PluginWrapper.h" + +namespace Vamp { + +namespace HostExt { + +/** + * \class PluginInputDomainAdapter PluginInputDomainAdapter.h + * + * PluginInputDomainAdapter is a Vamp plugin adapter that converts + * time-domain input into frequency-domain input for plugins that need + * it. This permits a host to use time- and frequency-domain plugins + * interchangeably without needing to handle the conversion itself. + * + * This adapter uses a basic windowed FFT (using Hann window by + * default) that supports power-of-two block sizes only. If a + * frequency domain plugin requests a non-power-of-two blocksize, the + * adapter will adjust it to a nearby power of two instead. Thus, + * getPreferredBlockSize() will always return a power of two if the + * wrapped plugin is a frequency domain one. If the plugin doesn't + * accept the adjusted power of two block size, initialise() will + * fail. + * + * The adapter provides no way for the host to discover whether the + * underlying plugin is actually a time or frequency domain plugin + * (except that if the preferred block size is not a power of two, it + * must be a time domain plugin). + * + * The FFT implementation is simple and self-contained, but unlikely + * to be the fastest available: a host can usually do better if it + * cares enough. + * + * The window shape for the FFT frame can be set using setWindowType + * and the current shape retrieved using getWindowType. (This was + * added in v2.3 of the SDK.) + * + * In every respect other than its input domain handling, the + * PluginInputDomainAdapter behaves identically to the plugin that it + * wraps. The wrapped plugin will be deleted when the wrapper is + * deleted. + * + * \note This class was introduced in version 1.1 of the Vamp plugin SDK. + */ + +class PluginInputDomainAdapter : public PluginWrapper +{ +public: + /** + * Construct a PluginInputDomainAdapter wrapping the given plugin. + * The adapter takes ownership of the plugin, which will be + * deleted when the adapter is deleted. + */ + PluginInputDomainAdapter(Plugin *plugin); + virtual ~PluginInputDomainAdapter(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + void reset(); + + InputDomain getInputDomain() const; + + size_t getPreferredStepSize() const; + size_t getPreferredBlockSize() const; + + FeatureSet process(const float *const *inputBuffers, RealTime timestamp); + + /** + * ProcessTimestampMethod determines how the + * PluginInputDomainAdapter handles timestamps for the data passed + * to the process() function of the plugin it wraps, in the case + * where the plugin is expecting frequency-domain data. + * + * The Vamp specification requires that the timestamp passed to + * the plugin for frequency-domain input should be that of the + * centre of the processing block, rather than the start as is the + * case for time-domain input. + * + * Since PluginInputDomainAdapter aims to be transparent in use, + * it needs to handle this timestamp adjustment itself. However, + * some control is available over the method used for adjustment, + * by means of the ProcessTimestampMethod setting. + * + * If ProcessTimestampMethod is set to ShiftTimestamp (the + * default), then the data passed to the wrapped plugin will be + * calculated from the same input data block as passed to the + * wrapper, but the timestamp passed to the plugin will be + * advanced by half of the window size. + * + * If ProcessTimestampMethod is set to ShiftData, then the + * timestamp passed to the wrapped plugin will be the same as that + * passed to the process call of the wrapper, but the data block + * used to calculate the input will be shifted back (earlier) by + * half of the window size, with half a block of zero padding at + * the start of the first process call. This has the advantage of + * preserving the first half block of audio without any + * deterioration from window shaping. + * + * If ProcessTimestampMethod is set to NoShift, then no adjustment + * will be made and the timestamps will be incorrect. + */ + enum ProcessTimestampMethod { + ShiftTimestamp, + ShiftData, + NoShift + }; + + /** + * Set the method used for timestamp adjustment in plugins taking + * frequency-domain input. See the ProcessTimestampMethod + * documentation for details. + * + * This function must be called before the first call to + * process(). + */ + void setProcessTimestampMethod(ProcessTimestampMethod); + + /** + * Retrieve the method used for timestamp adjustment in plugins + * taking frequency-domain input. See the ProcessTimestampMethod + * documentation for details. + */ + ProcessTimestampMethod getProcessTimestampMethod() const; + + /** + * Return the amount by which the timestamps supplied to process() + * are being incremented when they are passed to the plugin's own + * process() implementation. + * + * The Vamp API mandates that the timestamp passed to the plugin + * for time-domain input should be the time of the first sample in + * the block, but the timestamp passed for frequency-domain input + * should be the timestamp of the centre of the block. + * + * The PluginInputDomainAdapter adjusts its timestamps properly so + * that the plugin receives correct times, but in some + * circumstances (such as for establishing the correct timing of + * implicitly-timed features, i.e. features without their own + * timestamps) the host may need to be aware that this adjustment + * is taking place. + * + * If the plugin requires time-domain input or the + * PluginInputDomainAdapter is configured with its + * ProcessTimestampMethod set to ShiftData instead of + * ShiftTimestamp, then this function will return zero. + * + * The result of calling this function before initialise() has + * been called is undefined. + */ + RealTime getTimestampAdjustment() const; + + /** + * The set of supported window shapes. + */ + enum WindowType { + + RectangularWindow = 0, + + BartlettWindow = 1, /// synonym for RectangularWindow + TriangularWindow = 1, /// synonym for BartlettWindow + + HammingWindow = 2, + + HanningWindow = 3, /// synonym for HannWindow + HannWindow = 3, /// synonym for HanningWindow + + BlackmanWindow = 4, + + NuttallWindow = 7, + + BlackmanHarrisWindow = 8 + }; + + /** + * Return the current window shape. The default is HanningWindow. + */ + WindowType getWindowType() const; + + /** + * Set the current window shape. + */ + void setWindowType(WindowType type); + + +protected: + class Impl; + Impl *m_impl; +}; + +} +} diff --git a/src/vamp-hostsdk/PluginLoader.cpp b/src/vamp-hostsdk/PluginLoader.cpp new file mode 100644 index 0000000..40c416f --- /dev/null +++ b/src/vamp-hostsdk/PluginLoader.cpp @@ -0,0 +1,567 @@ +#include "vamp-hostsdk/PluginLoader.h" +#include "vamp-hostsdk/PluginInputDomainAdapter.h" +#include "vamp-hostsdk/PluginChannelAdapter.h" +#include "vamp-hostsdk/PluginBufferingAdapter.h" +#include "vamp-hostsdk/PluginHostAdapter.h" + +#include "vamp.h" + +#include "vamp-hostsdk/Files.h" + +#include + +namespace Vamp { + +namespace HostExt { + +class PluginLoader::Impl +{ +public: + Impl(); + virtual ~Impl(); + + PluginKeyList listPlugins(); + PluginKeyList listPluginsIn(std::vector); + PluginKeyList listPluginsNotIn(std::vector); + + Plugin *loadPlugin(PluginKey key, + float inputSampleRate, + int adapterFlags); + + PluginKey composePluginKey(std::string libraryName, std::string identifier); + + PluginCategoryHierarchy getPluginCategory(PluginKey key); + + std::string getLibraryPathForPlugin(PluginKey key); + + static void setInstanceToClean(PluginLoader *instance); + +protected: + class PluginDeletionNotifyAdapter : public PluginWrapper { + public: + PluginDeletionNotifyAdapter(Plugin *plugin, Impl *loader); + virtual ~PluginDeletionNotifyAdapter(); + protected: + Impl *m_loader; + }; + + class InstanceCleaner { + public: + InstanceCleaner() : m_instance(0) { } + ~InstanceCleaner() { delete m_instance; } + void setInstance(PluginLoader *instance) { m_instance = instance; } + protected: + PluginLoader *m_instance; + }; + + virtual void pluginDeleted(PluginDeletionNotifyAdapter *adapter); + + std::map m_pluginLibraryNameMap; + bool m_allPluginsEnumerated; + + struct Enumeration { + enum { All, SinglePlugin, InLibraries, NotInLibraries } type; + PluginKey key; + std::vector libraryNames; + Enumeration() : type(All) { } + }; + std::vector listLibraryFilesFor(Enumeration); + + /// Populate m_pluginLibraryNameMap and return a list of the keys + /// that were added to it + std::vector enumeratePlugins(Enumeration); + + std::map m_taxonomy; + void generateTaxonomy(); + + std::map m_pluginLibraryHandleMap; + + bool decomposePluginKey(PluginKey key, + std::string &libraryName, std::string &identifier); + + static InstanceCleaner m_cleaner; +}; + +PluginLoader * +PluginLoader::m_instance = 0; + +PluginLoader::Impl::InstanceCleaner +PluginLoader::Impl::m_cleaner; + +PluginLoader::PluginLoader() +{ + m_impl = new Impl(); +} + +PluginLoader::~PluginLoader() +{ + delete m_impl; +} + +PluginLoader * +PluginLoader::getInstance() +{ + if (!m_instance) { + // The cleaner doesn't own the instance, because we leave the + // instance pointer in the base class for binary backwards + // compatibility reasons and to avoid waste + m_instance = new PluginLoader(); + Impl::setInstanceToClean(m_instance); + } + return m_instance; +} + +PluginLoader::PluginKeyList +PluginLoader::listPlugins() +{ + return m_impl->listPlugins(); +} + +PluginLoader::PluginKeyList +PluginLoader::listPluginsIn(std::vector libs) +{ + return m_impl->listPluginsIn(libs); +} + +PluginLoader::PluginKeyList +PluginLoader::listPluginsNotIn(std::vector libs) +{ + return m_impl->listPluginsNotIn(libs); +} + +Plugin * +PluginLoader::loadPlugin(PluginKey key, + float inputSampleRate, + int adapterFlags) +{ + return m_impl->loadPlugin(key, inputSampleRate, adapterFlags); +} + +PluginLoader::PluginKey +PluginLoader::composePluginKey(std::string libraryName, std::string identifier) +{ + return m_impl->composePluginKey(libraryName, identifier); +} + +PluginLoader::PluginCategoryHierarchy +PluginLoader::getPluginCategory(PluginKey key) +{ + return m_impl->getPluginCategory(key); +} + + std::string +PluginLoader::getLibraryPathForPlugin(PluginKey key) +{ + return m_impl->getLibraryPathForPlugin(key); +} + +PluginLoader::Impl::Impl() : + m_allPluginsEnumerated(false) +{ +} + +PluginLoader::Impl::~Impl() +{ +} + +void +PluginLoader::Impl::setInstanceToClean(PluginLoader *instance) +{ + m_cleaner.setInstance(instance); +} + +PluginLoader::PluginKeyList +PluginLoader::Impl::listPlugins() +{ + if (!m_allPluginsEnumerated) enumeratePlugins(Enumeration()); + + std::vector plugins; + for (std::map::const_iterator i = + m_pluginLibraryNameMap.begin(); + i != m_pluginLibraryNameMap.end(); ++i) { + plugins.push_back(i->first); + } + + return plugins; +} + +PluginLoader::PluginKeyList +PluginLoader::Impl::listPluginsIn(std::vector libs) +{ + Enumeration enumeration; + enumeration.type = Enumeration::InLibraries; + enumeration.libraryNames = libs; + return enumeratePlugins(enumeration); +} + +PluginLoader::PluginKeyList +PluginLoader::Impl::listPluginsNotIn(std::vector libs) +{ + Enumeration enumeration; + enumeration.type = Enumeration::NotInLibraries; + enumeration.libraryNames = libs; + return enumeratePlugins(enumeration); +} + + std::vector +PluginLoader::Impl::listLibraryFilesFor(Enumeration enumeration) +{ + Files::Filter filter; + + switch (enumeration.type) { + + case Enumeration::All: + filter.type = Files::Filter::All; + break; + + case Enumeration::SinglePlugin: + { + std::string libraryName, identifier; + if (!decomposePluginKey(enumeration.key, libraryName, identifier)) { + std::cerr << "WARNING: Vamp::HostExt::PluginLoader: " + << "Invalid plugin key \"" << enumeration.key + << "\" in enumerate" << std::endl; + return std::vector(); + } + filter.type = Files::Filter::Matching; + filter.libraryNames.clear(); + filter.libraryNames.push_back(libraryName); + break; + } + + case Enumeration::InLibraries: + filter.type = Files::Filter::Matching; + filter.libraryNames = enumeration.libraryNames; + break; + + case Enumeration::NotInLibraries: + filter.type = Files::Filter::NotMatching; + filter.libraryNames = enumeration.libraryNames; + break; + } + + return Files::listLibraryFilesMatching(filter); +} + + std::vector +PluginLoader::Impl::enumeratePlugins(Enumeration enumeration) +{ + std::string libraryName, identifier; + if (enumeration.type == Enumeration::SinglePlugin) { + decomposePluginKey(enumeration.key, libraryName, identifier); + } + + std::vector fullPaths = listLibraryFilesFor(enumeration); + + // For these we should warn if a plugin can be loaded from a library + bool specific = (enumeration.type == Enumeration::SinglePlugin || + enumeration.type == Enumeration::InLibraries); + + std::vector added; + + for (size_t i = 0; i < fullPaths.size(); ++i) { + + std::string fullPath = fullPaths[i]; + void *handle = Files::loadLibrary(fullPath); + if (!handle) continue; + + VampGetPluginDescriptorFunction fn = + (VampGetPluginDescriptorFunction)Files::lookupInLibrary + (handle, "vampGetPluginDescriptor"); + + if (!fn) { + if (specific) { + std::cerr << "Vamp::HostExt::PluginLoader: " + << "No vampGetPluginDescriptor function found in library \"" + << fullPath << "\"" << std::endl; + } + Files::unloadLibrary(handle); + continue; + } + + int index = 0; + const VampPluginDescriptor *descriptor = 0; + bool found = false; + + while ((descriptor = fn(VAMP_API_VERSION, index))) { + ++index; + if (identifier != "") { + if (descriptor->identifier != identifier) { + continue; + } + } + found = true; + PluginKey key = composePluginKey(fullPath, descriptor->identifier); + if (m_pluginLibraryNameMap.find(key) == + m_pluginLibraryNameMap.end()) { + m_pluginLibraryNameMap[key] = fullPath; + } + added.push_back(key); + } + + if (!found && specific) { + std::cerr << "Vamp::HostExt::PluginLoader: Plugin \"" + << identifier << "\" not found in library \"" + << fullPath << "\"" << std::endl; + } + + Files::unloadLibrary(handle); + } + + if (enumeration.type == Enumeration::All) { + m_allPluginsEnumerated = true; + } + + return added; +} + +PluginLoader::PluginKey +PluginLoader::Impl::composePluginKey(std::string libraryName, std::string identifier) +{ + std::string basename = Files::lcBasename(libraryName); + return basename + ":" + identifier; +} + +bool +PluginLoader::Impl::decomposePluginKey(PluginKey key, + std::string &libraryName, + std::string &identifier) +{ + std::string::size_type ki = key.find(':'); + if (ki == std::string::npos) { + return false; + } + + libraryName = key.substr(0, ki); + identifier = key.substr(ki + 1); + return true; +} + +PluginLoader::PluginCategoryHierarchy +PluginLoader::Impl::getPluginCategory(PluginKey plugin) +{ + if (m_taxonomy.empty()) generateTaxonomy(); + if (m_taxonomy.find(plugin) == m_taxonomy.end()) { + return PluginCategoryHierarchy(); + } + return m_taxonomy[plugin]; +} + + std::string +PluginLoader::Impl::getLibraryPathForPlugin(PluginKey plugin) +{ + if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) { + if (m_allPluginsEnumerated) return ""; + Enumeration enumeration; + enumeration.type = Enumeration::SinglePlugin; + enumeration.key = plugin; + enumeratePlugins(enumeration); + } + if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) { + return ""; + } + return m_pluginLibraryNameMap[plugin]; +} + +Plugin * +PluginLoader::Impl::loadPlugin(PluginKey key, + float inputSampleRate, int adapterFlags) +{ + std::string libname, identifier; + if (!decomposePluginKey(key, libname, identifier)) { + std::cerr << "Vamp::HostExt::PluginLoader: Invalid plugin key \"" + << key << "\" in loadPlugin" << std::endl; + return 0; + } + + std::string fullPath = getLibraryPathForPlugin(key); + if (fullPath == "") { + std::cerr << "Vamp::HostExt::PluginLoader: No library found in Vamp path for plugin \"" << key << "\"" << std::endl; + return 0; + } + + void *handle = Files::loadLibrary(fullPath); + if (!handle) return 0; + + VampGetPluginDescriptorFunction fn = + (VampGetPluginDescriptorFunction)Files::lookupInLibrary + (handle, "vampGetPluginDescriptor"); + + if (!fn) { + std::cerr << "Vamp::HostExt::PluginLoader: No vampGetPluginDescriptor function found in library \"" + << fullPath << "\"" << std::endl; + Files::unloadLibrary(handle); + return 0; + } + + int index = 0; + const VampPluginDescriptor *descriptor = 0; + + while ((descriptor = fn(VAMP_API_VERSION, index))) { + + if (std::string(descriptor->identifier) == identifier) { + + Vamp::PluginHostAdapter *plugin = + new Vamp::PluginHostAdapter(descriptor, inputSampleRate); + + Plugin *adapter = new PluginDeletionNotifyAdapter(plugin, this); + + m_pluginLibraryHandleMap[adapter] = handle; + + if (adapterFlags & ADAPT_INPUT_DOMAIN) { + if (adapter->getInputDomain() == Plugin::FrequencyDomain) { + adapter = new PluginInputDomainAdapter(adapter); + } + } + + if (adapterFlags & ADAPT_BUFFER_SIZE) { + adapter = new PluginBufferingAdapter(adapter); + } + + if (adapterFlags & ADAPT_CHANNEL_COUNT) { + adapter = new PluginChannelAdapter(adapter); + } + + return adapter; + } + + ++index; + } + + std::cerr << "Vamp::HostExt::PluginLoader: Plugin \"" + << identifier << "\" not found in library \"" + << fullPath << "\"" << std::endl; + + return 0; +} + +void +PluginLoader::Impl::generateTaxonomy() +{ +// cerr << "PluginLoader::Impl::generateTaxonomy" << endl; + + std::vector path = PluginHostAdapter::getPluginPath(); + std::string libfragment = "/lib/"; + std::vector catpath; + + std::string suffix = "cat"; + + for (std::vector::iterator i = path.begin(); + i != path.end(); ++i) { + + // It doesn't matter that we're using literal forward-slash in + // this bit, as it's only relevant if the path contains + // "/lib/", which is only meaningful and only plausible on + // systems with forward-slash delimiters + + std::string dir = *i; + std::string::size_type li = dir.find(libfragment); + + if (li != std::string::npos) { + catpath.push_back + (dir.substr(0, li) + + "/share/" + + dir.substr(li + libfragment.length())); + } + + catpath.push_back(dir); + } + + char buffer[1024]; + + for (std::vector::iterator i = catpath.begin(); + i != catpath.end(); ++i) { + + std::vector files = Files::listFiles(*i, suffix); + + for (std::vector::iterator fi = files.begin(); + fi != files.end(); ++fi) { + + std::string filepath = Files::splicePath(*i, *fi); + std::ifstream is(filepath.c_str(), std::ifstream::in | std::ifstream::binary); + + if (is.fail()) { +// cerr << "failed to open: " << filepath << endl; + continue; + } + +// cerr << "opened: " << filepath << endl; + + while (!!is.getline(buffer, 1024)) { + + std::string line(buffer); + +// cerr << "line = " << line << endl; + + std::string::size_type di = line.find("::"); + if (di == std::string::npos) continue; + + std::string id = line.substr(0, di); + std::string encodedCat = line.substr(di + 2); + + if (id.substr(0, 5) != "vamp:") continue; + id = id.substr(5); + + while (encodedCat.length() >= 1 && + encodedCat[encodedCat.length()-1] == '\r') { + encodedCat = encodedCat.substr(0, encodedCat.length()-1); + } + +// cerr << "id = " << id << ", cat = " << encodedCat << endl; + + PluginCategoryHierarchy category; + std::string::size_type ai; + while ((ai = encodedCat.find(" > ")) != std::string::npos) { + category.push_back(encodedCat.substr(0, ai)); + encodedCat = encodedCat.substr(ai + 3); + } + if (encodedCat != "") category.push_back(encodedCat); + + m_taxonomy[id] = category; + } + } + } +} + +void +PluginLoader::Impl::pluginDeleted(PluginDeletionNotifyAdapter *adapter) +{ + void *handle = m_pluginLibraryHandleMap[adapter]; + if (!handle) return; + + m_pluginLibraryHandleMap.erase(adapter); + + for (auto h: m_pluginLibraryHandleMap) { + if (h.second == handle) { + // still in use + return; + } + } + + Files::unloadLibrary(handle); +} + +PluginLoader::Impl::PluginDeletionNotifyAdapter::PluginDeletionNotifyAdapter(Plugin *plugin, + Impl *loader) : + PluginWrapper(plugin), + m_loader(loader) +{ +} + +PluginLoader::Impl::PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter() +{ + // We need to delete the plugin before calling pluginDeleted, as + // the delete call may require calling through to the descriptor + // (for e.g. cleanup) but pluginDeleted may unload the required + // library for the call. To prevent a double deletion when our + // parent's destructor runs (after this one), be sure to set + // m_plugin to 0 after deletion. + delete m_plugin; + m_plugin = 0; + + if (m_loader) m_loader->pluginDeleted(this); +} + +} + +} diff --git a/src/vamp-hostsdk/PluginLoader.h b/src/vamp-hostsdk/PluginLoader.h new file mode 100644 index 0000000..c37657e --- /dev/null +++ b/src/vamp-hostsdk/PluginLoader.h @@ -0,0 +1,235 @@ +#pragma once + +#include +#include +#include + +#include "vamp-hostsdk/PluginWrapper.h" + +namespace Vamp { + +class Plugin; + +namespace HostExt { + +/** + * \class PluginLoader PluginLoader.h + * + * Vamp::HostExt::PluginLoader is a convenience class for discovering + * and loading Vamp plugins using the typical plugin-path, library + * naming, and categorisation conventions described in the Vamp SDK + * documentation. This class is intended to greatly simplify the task + * of becoming a Vamp plugin host for any C++ application. + * + * Hosts are not required by the Vamp specification to use the same + * plugin search path and naming conventions as implemented by this + * class, and are certainly not required to use this actual class. + * But we do strongly recommend it. + * + * This class is not thread-safe; use it from a single application + * thread, or guard access to it with a mutex. + * + * \note This class was introduced in version 1.1 of the Vamp plugin SDK. + */ + +class PluginLoader +{ +public: + /** + * Obtain a pointer to the singleton instance of PluginLoader. + * Use this to obtain your loader object. + */ + static PluginLoader *getInstance(); + + /** + * PluginKey is a string type that is used to identify a plugin + * uniquely within the scope of "the current system". It consists + * of the lower-cased base name of the plugin library, a colon + * separator, and the identifier string for the plugin. It is + * only meaningful in the context of a given plugin path (the one + * returned by PluginHostAdapter::getPluginPath()). + * + * Use composePluginKey() to construct a plugin key from a known + * plugin library name and identifier. + * + * Note: the fact that the library component of the key is + * lower-cased implies that library names are matched + * case-insensitively by the PluginLoader class, regardless of the + * case sensitivity of the underlying filesystem. (Plugin + * identifiers _are_ case sensitive, however.) Also, it is not + * possible to portably extract a working library name from a + * plugin key, as the result may fail on case-sensitive + * filesystems. Use getLibraryPathForPlugin() instead. + */ + typedef std::string PluginKey; + + /** + * PluginKeyList is a sequence of plugin keys, such as returned by + * listPlugins(). + */ + typedef std::vector PluginKeyList; + + /** + * PluginCategoryHierarchy is a sequence of general->specific + * category names, as may be associated with a single plugin. + * This sequence describes the location of a plugin within a + * category forest, containing the human-readable names of the + * plugin's category tree root, followed by each of the nodes down + * to the leaf containing the plugin. + * + * \see getPluginCategory() + */ + typedef std::vector PluginCategoryHierarchy; + + /** + * Search for all available Vamp plugins, and return a list of + * them in the order in which they were found. + */ + PluginKeyList listPlugins(); + + /** + * Search for available Vamp plugins in libraries with the given + * library names, and return a list of them in the order in which + * they were found. Do not attempt to load any plugin libraries + * other than those named. + * + * The library names should be supplied without path or + * suffix. For example, use "vamp-example-plugins" to find plugins + * in /install/path/of/vamp-example-plugins.dll (or .so etc). This + * is the same concept of "library name" as appears in the plugin + * key: \see composePluginKey(). + */ + PluginKeyList listPluginsIn(std::vector libraryNames); + + /** + * Search for available Vamp plugins in libraries other than those + * with the given library names, and return a list of them in the + * order in which they were found. Do not attempt to load any of + * the libraries named. + * + * The library names should be supplied without path or + * suffix. For example, use "vamp-example-plugins" to find plugins + * not appearing in /install/path/of/vamp-example-plugins.dll (or + * .so etc). This is the same concept of "library name" as appears + * in the plugin key: \see composePluginKey(). + */ + PluginKeyList listPluginsNotIn(std::vector libraryNames); + + /** + * AdapterFlags contains a set of values that may be OR'd together + * to indicate in which circumstances PluginLoader should use a + * plugin adapter to make a plugin easier to use for a host that + * does not want to cater for complex features. + * + * The available flags are: + * + * ADAPT_INPUT_DOMAIN - If the plugin expects frequency domain + * input, wrap it in a PluginInputDomainAdapter that automatically + * converts the plugin to one that expects time-domain input. + * This enables a host to accommodate time- and frequency-domain + * plugins without needing to do any conversion itself. + * + * ADAPT_CHANNEL_COUNT - Wrap the plugin in a PluginChannelAdapter + * to handle any mismatch between the number of channels of audio + * the plugin can handle and the number available in the host. + * This enables a host to use plugins that may require the input + * to be mixed down to mono, etc., without having to worry about + * doing that itself. + * + * ADAPT_BUFFER_SIZE - Wrap the plugin in a PluginBufferingAdapter + * permitting the host to provide audio input using any block + * size, with no overlap, regardless of the plugin's preferred + * block size (suitable for hosts that read from non-seekable + * streaming media, for example). This adapter introduces some + * run-time overhead and also changes the semantics of the plugin + * slightly (see the PluginBufferingAdapter header documentation + * for details). + * + * ADAPT_ALL_SAFE - Perform all available adaptations that are + * meaningful for the plugin and "safe". Currently this means to + * ADAPT_INPUT_DOMAIN if the plugin wants FrequencyDomain input; + * ADAPT_CHANNEL_COUNT always; and ADAPT_BUFFER_SIZE never. + * + * ADAPT_ALL - Perform all available adaptations that are + * meaningful for the plugin. + * + * See PluginInputDomainAdapter, PluginChannelAdapter and + * PluginBufferingAdapter for more details of the classes that the + * loader may use if these flags are set. + */ + enum AdapterFlags { + + ADAPT_INPUT_DOMAIN = 0x01, + ADAPT_CHANNEL_COUNT = 0x02, + ADAPT_BUFFER_SIZE = 0x04, + + ADAPT_ALL_SAFE = 0x03, + + ADAPT_ALL = 0xff + }; + + /** + * Load a Vamp plugin, given its identifying key. If the plugin + * could not be loaded, returns 0. + * + * The returned plugin should be deleted (using the standard C++ + * delete keyword) after use. + * + * \param adapterFlags a bitwise OR of the values in the AdapterFlags + * enumeration, indicating under which circumstances an adapter should be + * used to wrap the original plugin. If adapterFlags is 0, no + * optional adapters will be used. Otherwise, the returned plugin + * may be of an adapter class type which will behave identically + * to the original plugin, apart from any particular features + * implemented by the adapter itself. + * + * \see AdapterFlags, PluginInputDomainAdapter, PluginChannelAdapter + */ + Plugin *loadPlugin(PluginKey key, + float inputSampleRate, + int adapterFlags = 0); + + /** + * Given a Vamp plugin library name and plugin identifier, return + * the corresponding plugin key in a form suitable for passing in to + * loadPlugin(). + * + * (Note that the reverse of this is not well-defined and is not + * offered in this API - consider using getLibraryPathForPlugin + * instead. See documentation for the PluginKey type for details.) + * + * \see PluginKey, getLibraryPathForPlugin, loadPlugin + */ + PluginKey composePluginKey(std::string libraryName, + std::string identifier); + + /** + * Return the category hierarchy for a Vamp plugin, given its + * identifying key. + * + * If the plugin has no category information, return an empty + * hierarchy. + * + * \see PluginCategoryHierarchy + */ + PluginCategoryHierarchy getPluginCategory(PluginKey plugin); + + /** + * Return the file path of the dynamic library from which the + * given plugin will be loaded (if available). + */ + std::string getLibraryPathForPlugin(PluginKey plugin); + +protected: + PluginLoader(); + virtual ~PluginLoader(); + + class Impl; + Impl *m_impl; + + static PluginLoader *m_instance; +}; + +} + +} diff --git a/src/vamp-hostsdk/PluginSummarisingAdapter.cpp b/src/vamp-hostsdk/PluginSummarisingAdapter.cpp new file mode 100644 index 0000000..495bd6c --- /dev/null +++ b/src/vamp-hostsdk/PluginSummarisingAdapter.cpp @@ -0,0 +1,948 @@ +#include "vamp-hostsdk/PluginSummarisingAdapter.h" + +#include +#include +#include +#include + +using namespace std; + +//#define DEBUG_PLUGIN_SUMMARISING_ADAPTER 1 +//#define DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT 1 + +namespace Vamp { + +namespace HostExt { + +class PluginSummarisingAdapter::Impl +{ +public: + Impl(Plugin *plugin, float inputSampleRate); + ~Impl(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + + void reset(); + + FeatureSet process(const float *const *inputBuffers, RealTime timestamp); + FeatureSet getRemainingFeatures(); + + void setSummarySegmentBoundaries(const SegmentBoundaries &); + + FeatureList getSummaryForOutput(int output, + SummaryType type, + AveragingMethod avg); + + FeatureSet getSummaryForAllOutputs(SummaryType type, + AveragingMethod avg); + +protected: + Plugin *m_plugin; + float m_inputSampleRate; + size_t m_stepSize; + size_t m_blockSize; + + SegmentBoundaries m_boundaries; + + typedef vector ValueList; + + struct Result { // smaller than Feature + RealTime time; + RealTime duration; + ValueList values; // bin number -> value + }; + + typedef vector ResultList; + + struct OutputAccumulator { + int bins; + ResultList results; + OutputAccumulator() : bins(0) { } + }; + + typedef map OutputAccumulatorMap; + OutputAccumulatorMap m_accumulators; // output number -> accumulator + + typedef map SegmentAccumulatorMap; + typedef map OutputSegmentAccumulatorMap; + OutputSegmentAccumulatorMap m_segmentedAccumulators; // output -> segmented + + typedef map OutputTimestampMap; + OutputTimestampMap m_prevTimestamps; // output number -> timestamp + OutputTimestampMap m_prevDurations; // output number -> durations + + struct OutputBinSummary { + + int count; + + // extents + double minimum; + double maximum; + double sum; + + // sample-average results + double median; + double mode; + double variance; + + // continuous-time average results + double median_c; + double mode_c; + double mean_c; + double variance_c; + }; + + typedef map OutputSummary; + typedef map SummarySegmentMap; + typedef map OutputSummarySegmentMap; + + OutputSummarySegmentMap m_summaries; + + bool m_reduced; + RealTime m_endTime; + + void accumulate(const FeatureSet &fs, RealTime, bool final); + void accumulate(int output, const Feature &f, RealTime, bool final); + void accumulateFinalDurations(); + void findSegmentBounds(RealTime t, RealTime &start, RealTime &end); + void segment(); + void reduce(); + + string getSummaryLabel(SummaryType type, AveragingMethod avg); +}; + +static RealTime INVALID_DURATION(INT_MIN, INT_MIN); + +PluginSummarisingAdapter::PluginSummarisingAdapter(Plugin *plugin) : + PluginWrapper(plugin) +{ + m_impl = new Impl(plugin, m_inputSampleRate); +} + +PluginSummarisingAdapter::~PluginSummarisingAdapter() +{ + delete m_impl; +} + +bool +PluginSummarisingAdapter::initialise(size_t channels, + size_t stepSize, size_t blockSize) +{ + return + PluginWrapper::initialise(channels, stepSize, blockSize) && + m_impl->initialise(channels, stepSize, blockSize); +} + +void +PluginSummarisingAdapter::reset() +{ + m_impl->reset(); +} + +Plugin::FeatureSet +PluginSummarisingAdapter::process(const float *const *inputBuffers, RealTime timestamp) +{ + return m_impl->process(inputBuffers, timestamp); +} + +Plugin::FeatureSet +PluginSummarisingAdapter::getRemainingFeatures() +{ + return m_impl->getRemainingFeatures(); +} + +void +PluginSummarisingAdapter::setSummarySegmentBoundaries(const SegmentBoundaries &b) +{ + m_impl->setSummarySegmentBoundaries(b); +} + +Plugin::FeatureList +PluginSummarisingAdapter::getSummaryForOutput(int output, + SummaryType type, + AveragingMethod avg) +{ + return m_impl->getSummaryForOutput(output, type, avg); +} + +Plugin::FeatureSet +PluginSummarisingAdapter::getSummaryForAllOutputs(SummaryType type, + AveragingMethod avg) +{ + return m_impl->getSummaryForAllOutputs(type, avg); +} + +PluginSummarisingAdapter::Impl::Impl(Plugin *plugin, float inputSampleRate) : + m_plugin(plugin), + m_inputSampleRate(inputSampleRate), + m_reduced(false) +{ +} + +PluginSummarisingAdapter::Impl::~Impl() +{ +} + +bool +PluginSummarisingAdapter::Impl::initialise(size_t, size_t stepSize, size_t blockSize) +{ + m_stepSize = stepSize; + m_blockSize = blockSize; + return true; +} + +void +PluginSummarisingAdapter::Impl::reset() +{ + m_accumulators.clear(); + m_segmentedAccumulators.clear(); + m_prevTimestamps.clear(); + m_prevDurations.clear(); + m_summaries.clear(); + m_reduced = false; + m_endTime = RealTime(); + m_plugin->reset(); +} + +Plugin::FeatureSet +PluginSummarisingAdapter::Impl::process(const float *const *inputBuffers, + RealTime timestamp) +{ + if (m_reduced) { + cerr << "WARNING: Cannot call PluginSummarisingAdapter::process() or getRemainingFeatures() after one of the getSummary methods" << endl; + } + FeatureSet fs = m_plugin->process(inputBuffers, timestamp); + accumulate(fs, timestamp, false); + m_endTime = timestamp + + RealTime::frame2RealTime(m_stepSize, int(m_inputSampleRate + 0.5)); +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "timestamp = " << timestamp << ", end time becomes " << m_endTime + << endl; +#endif + return fs; +} + +Plugin::FeatureSet +PluginSummarisingAdapter::Impl::getRemainingFeatures() +{ + if (m_reduced) { + cerr << "WARNING: Cannot call PluginSummarisingAdapter::process() or getRemainingFeatures() after one of the getSummary methods" << endl; + } + FeatureSet fs = m_plugin->getRemainingFeatures(); + accumulate(fs, m_endTime, true); + return fs; +} + +void +PluginSummarisingAdapter::Impl::setSummarySegmentBoundaries(const SegmentBoundaries &b) +{ + m_boundaries = b; +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "PluginSummarisingAdapter::setSummarySegmentBoundaries: boundaries are:" << endl; + for (SegmentBoundaries::const_iterator i = m_boundaries.begin(); + i != m_boundaries.end(); ++i) { + cerr << *i << " "; + } + cerr << endl; +#endif +} + +Plugin::FeatureList +PluginSummarisingAdapter::Impl::getSummaryForOutput(int output, + SummaryType type, + AveragingMethod avg) +{ + if (!m_reduced) { + accumulateFinalDurations(); + segment(); + reduce(); + m_reduced = true; + } + + bool continuous = (avg == ContinuousTimeAverage); + + FeatureList fl; + for (SummarySegmentMap::const_iterator i = m_summaries[output].begin(); + i != m_summaries[output].end(); ++i) { + + Feature f; + + f.hasTimestamp = true; + f.timestamp = i->first; + + f.hasDuration = true; + SummarySegmentMap::const_iterator ii = i; + if (++ii == m_summaries[output].end()) { + f.duration = m_endTime - f.timestamp; + } else { + f.duration = ii->first - f.timestamp; + } + + f.label = getSummaryLabel(type, avg); + + for (OutputSummary::const_iterator j = i->second.begin(); + j != i->second.end(); ++j) { + + // these will be ordered by bin number, and no bin numbers + // will be missing except at the end (because of the way + // the accumulators were initially filled in accumulate()) + + const OutputBinSummary &summary = j->second; + double result = 0.f; + + switch (type) { + + case Minimum: + result = summary.minimum; + break; + + case Maximum: + result = summary.maximum; + break; + + case Mean: + if (continuous) { + result = summary.mean_c; + } else if (summary.count) { + result = summary.sum / summary.count; + } + break; + + case Median: + if (continuous) result = summary.median_c; + else result = summary.median; + break; + + case Mode: + if (continuous) result = summary.mode_c; + else result = summary.mode; + break; + + case Sum: + result = summary.sum; + break; + + case Variance: + if (continuous) result = summary.variance_c; + else result = summary.variance; + break; + + case StandardDeviation: + if (continuous) result = sqrt(summary.variance_c); + else result = sqrt(summary.variance); + break; + + case Count: + result = summary.count; + break; + + case UnknownSummaryType: + break; + + default: + break; + } + + f.values.push_back(float(result)); + } + + fl.push_back(f); + } + return fl; +} + +Plugin::FeatureSet +PluginSummarisingAdapter::Impl::getSummaryForAllOutputs(SummaryType type, + AveragingMethod avg) +{ + if (!m_reduced) { + accumulateFinalDurations(); + segment(); + reduce(); + m_reduced = true; + } + + FeatureSet fs; + for (OutputSummarySegmentMap::const_iterator i = m_summaries.begin(); + i != m_summaries.end(); ++i) { + fs[i->first] = getSummaryForOutput(i->first, type, avg); + } + return fs; +} + +void +PluginSummarisingAdapter::Impl::accumulate(const FeatureSet &fs, + RealTime timestamp, + bool final) +{ + for (FeatureSet::const_iterator i = fs.begin(); i != fs.end(); ++i) { + for (FeatureList::const_iterator j = i->second.begin(); + j != i->second.end(); ++j) { + if (j->hasTimestamp) { + accumulate(i->first, *j, j->timestamp, final); + } else { + //!!! is this correct? + accumulate(i->first, *j, timestamp, final); + } + } + } +} + +string +PluginSummarisingAdapter::Impl::getSummaryLabel(SummaryType type, + AveragingMethod avg) +{ + string label; + string avglabel; + + if (avg == SampleAverage) avglabel = ", sample average"; + else avglabel = ", continuous-time average"; + + switch (type) { + case Minimum: label = "(minimum value)"; break; + case Maximum: label = "(maximum value)"; break; + case Mean: label = "(mean value" + avglabel + ")"; break; + case Median: label = "(median value" + avglabel + ")"; break; + case Mode: label = "(modal value" + avglabel + ")"; break; + case Sum: label = "(sum)"; break; + case Variance: label = "(variance" + avglabel + ")"; break; + case StandardDeviation: label = "(standard deviation" + avglabel + ")"; break; + case Count: label = "(count)"; break; + case UnknownSummaryType: label = "(unknown summary)"; break; + } + + return label; +} + +void +PluginSummarisingAdapter::Impl::accumulate(int output, + const Feature &f, + RealTime timestamp, + bool +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + final +#endif + ) +{ + // What should happen if a feature's duration spans a segment + // boundary? I think we probably want to chop it, and pretend + // that it appears in both. A very long feature (e.g. key, if the + // whole audio is in a single key) might span many or all + // segments, and we want that to be reflected in the results + // (e.g. it is the modal key in all of those segments, not just + // the first). This is actually quite complicated to do. + + // If features spanning a boundary should be chopped, then we need + // to have per-segment accumulators (and the feature value goes + // into both -- with a separate phase to split the accumulator up + // into segments). + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "output " << output << ": timestamp " << timestamp << ", prev timestamp " << m_prevTimestamps[output] << ", final " << final << endl; +#endif + + // At each process step, accumulate() is called once for each + // feature on each output within that process's returned feature + // list, and with the timestamp passed in being that of the start + // of the process block. + + // At the end (in getRemainingFeatures), accumulate() is called + // once for each feature on each output within the feature list + // returned by getRemainingFeatures, and with the timestamp being + // the same as the last process block and final set to true. + + // (What if getRemainingFeatures doesn't return any features? We + // still need to ensure that the final duration is written. Need + // a separate function to close the durations.) + + // At each call, we pull out the value for the feature and stuff + // it into the accumulator's appropriate values array; and we + // calculate the duration for the _previous_ feature, or pull it + // from the prevDurations array if the previous feature had a + // duration in its structure, and stuff that into the + // accumulator's appropriate durations array. + + if (m_prevDurations.find(output) != m_prevDurations.end()) { + + // Not the first time accumulate has been called for this + // output -- there has been a previous feature + + RealTime prevDuration; + + // Note that m_prevDurations[output] only contains the + // duration field that was contained in the previous feature. + // If it didn't have an explicit duration, + // m_prevDurations[output] should be INVALID_DURATION and we + // will have to calculate the duration from the previous and + // current timestamps. + + if (m_prevDurations[output] != INVALID_DURATION) { + prevDuration = m_prevDurations[output]; +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "Previous duration from previous feature: " << prevDuration << endl; +#endif + } else { + prevDuration = timestamp - m_prevTimestamps[output]; +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "Previous duration from diff: " << timestamp << " - " + << m_prevTimestamps[output] << endl; +#endif + } + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "output " << output << ": "; + cerr << "Pushing previous duration as " << prevDuration << endl; +#endif + + m_accumulators[output].results + [m_accumulators[output].results.size() - 1] + .duration = prevDuration; + } + + if (f.hasDuration) m_prevDurations[output] = f.duration; + else m_prevDurations[output] = INVALID_DURATION; + + m_prevTimestamps[output] = timestamp; + + if (f.hasDuration) { + RealTime et = timestamp; + et = et + f.duration; + if (et > m_endTime) m_endTime = et; +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "feature has duration, updating end time to " << m_endTime << endl; +#endif + } + + Result result; + result.time = timestamp; + result.duration = INVALID_DURATION; + + if (int(f.values.size()) > m_accumulators[output].bins) { + m_accumulators[output].bins = int(f.values.size()); + } + + for (int i = 0; i < int(f.values.size()); ++i) { + result.values.push_back(f.values[i]); + } + + m_accumulators[output].results.push_back(result); +} + +void +PluginSummarisingAdapter::Impl::accumulateFinalDurations() +{ + for (OutputTimestampMap::iterator i = m_prevTimestamps.begin(); + i != m_prevTimestamps.end(); ++i) { + + int output = i->first; + + int acount = int(m_accumulators[output].results.size()); + + if (acount == 0) continue; + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "output " << output << ": "; +#endif + + if (m_prevDurations.find(output) != m_prevDurations.end() && + m_prevDurations[output] != INVALID_DURATION) { + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "Pushing final duration from feature as " << m_prevDurations[output] << endl; +#endif + + m_accumulators[output].results[acount - 1].duration = + m_prevDurations[output]; + + } else { + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "Pushing final duration from diff as " << m_endTime << " - " << m_prevTimestamps[output] << endl; +#endif + + m_accumulators[output].results[acount - 1].duration = + m_endTime - m_prevTimestamps[output]; + } + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "so duration for result no " << acount-1 << " is " + << m_accumulators[output].results[acount-1].duration + << endl; +#endif + } +} + +void +PluginSummarisingAdapter::Impl::findSegmentBounds(RealTime t, + RealTime &start, + RealTime &end) +{ +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT + cerr << "findSegmentBounds: t = " << t << endl; +#endif + + SegmentBoundaries::const_iterator i = upper_bound + (m_boundaries.begin(), m_boundaries.end(), t); + + start = RealTime::zeroTime; + end = m_endTime; + + if (i != m_boundaries.end()) { + end = *i; + } + + if (i != m_boundaries.begin()) { + start = *--i; + } + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT + cerr << "findSegmentBounds: " << t << " is in segment " << start << " -> " << end << endl; +#endif +} + +void +PluginSummarisingAdapter::Impl::segment() +{ +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT + cerr << "segment: starting" << endl; +#endif + + for (OutputAccumulatorMap::iterator i = m_accumulators.begin(); + i != m_accumulators.end(); ++i) { + + int output = i->first; + OutputAccumulator &source = i->second; + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT + cerr << "segment: total results for output " << output << " = " + << source.results.size() << endl; +#endif + + // This is basically nonsense if the results have no values + // (i.e. their times and counts are the only things of + // interest)... but perhaps it's the user's problem if they + // ask for segmentation (or any summary at all) in that case + + for (int n = 0; n < int(source.results.size()); ++n) { + + // This result spans source.results[n].time to + // source.results[n].time + source.results[n].duration. + // We need to dispose it into segments appropriately + + RealTime resultStart = source.results[n].time; + RealTime resultEnd = resultStart + source.results[n].duration; + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT + cerr << "output: " << output << ", result start = " << resultStart << ", end = " << resultEnd << endl; +#endif + + RealTime segmentStart = RealTime::zeroTime; + RealTime segmentEnd = resultEnd - RealTime(1, 0); + + RealTime prevSegmentStart = segmentStart - RealTime(1, 0); + + while (segmentEnd < resultEnd) { + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT + cerr << "segment end " << segmentEnd << " < result end " + << resultEnd << " (with result start " << resultStart << ")" << endl; +#endif + + findSegmentBounds(resultStart, segmentStart, segmentEnd); + + if (segmentStart == prevSegmentStart) { + // This can happen when we reach the end of the + // input, if a feature's end time overruns the + // input audio end time + break; + } + prevSegmentStart = segmentStart; + + RealTime chunkStart = resultStart; + if (chunkStart < segmentStart) chunkStart = segmentStart; + + RealTime chunkEnd = resultEnd; + if (chunkEnd > segmentEnd) chunkEnd = segmentEnd; + + m_segmentedAccumulators[output][segmentStart].bins = source.bins; + + Result chunk; + chunk.time = chunkStart; + chunk.duration = chunkEnd - chunkStart; + chunk.values = source.results[n].values; + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER_SEGMENT + cerr << "chunk for segment " << segmentStart << ": from " << chunk.time << ", duration " << chunk.duration << endl; +#endif + + m_segmentedAccumulators[output][segmentStart].results + .push_back(chunk); + + resultStart = chunkEnd; + } + } + } +} + +struct ValueDurationFloatPair +{ + float value; + float duration; + + ValueDurationFloatPair() : value(0), duration(0) { } + ValueDurationFloatPair(float v, float d) : value(v), duration(d) { } + ValueDurationFloatPair &operator=(const ValueDurationFloatPair &p) { + value = p.value; + duration = p.duration; + return *this; + } + bool operator<(const ValueDurationFloatPair &p) const { + return value < p.value; + } +}; + +static double toSec(const RealTime &r) +{ + return r.sec + double(r.nsec) / 1000000000.0; +} + +void +PluginSummarisingAdapter::Impl::reduce() +{ + for (OutputSegmentAccumulatorMap::iterator i = + m_segmentedAccumulators.begin(); + i != m_segmentedAccumulators.end(); ++i) { + + int output = i->first; + SegmentAccumulatorMap &segments = i->second; + + for (SegmentAccumulatorMap::iterator j = segments.begin(); + j != segments.end(); ++j) { + + RealTime segmentStart = j->first; + OutputAccumulator &accumulator = j->second; + + int sz = int(accumulator.results.size()); + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "reduce: segment starting at " << segmentStart + << " on output " << output << " has " << sz << " result(s)" << endl; +#endif + + double totalDuration = 0.0; + //!!! is this right? + if (sz > 0) { +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "last time = " << accumulator.results[sz-1].time + << ", duration = " << accumulator.results[sz-1].duration + << " (step = " << m_stepSize << ", block = " << m_blockSize << ")" + << endl; +#endif + totalDuration = toSec((accumulator.results[sz-1].time + + accumulator.results[sz-1].duration) - + segmentStart); + } + + for (int bin = 0; bin < accumulator.bins; ++bin) { + + // work on all values over time for a single bin + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "bin " << bin << ":" << endl; +#endif + + OutputBinSummary summary; + + summary.count = sz; + + summary.minimum = 0.f; + summary.maximum = 0.f; + + summary.median = 0.f; + summary.mode = 0.f; + summary.sum = 0.f; + summary.variance = 0.f; + + summary.median_c = 0.f; + summary.mode_c = 0.f; + summary.mean_c = 0.f; + summary.variance_c = 0.f; + + if (sz == 0) continue; + + vector valvec; + + for (int k = 0; k < sz; ++k) { + while (int(accumulator.results[k].values.size()) < + accumulator.bins) { + accumulator.results[k].values.push_back(0.f); + } + } + + for (int k = 0; k < sz; ++k) { + float value = accumulator.results[k].values[bin]; + valvec.push_back + (ValueDurationFloatPair + (value, + float(toSec(accumulator.results[k].duration)))); + } + + sort(valvec.begin(), valvec.end()); + + summary.minimum = valvec[0].value; + summary.maximum = valvec[sz-1].value; + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "total duration = " << totalDuration << endl; +#endif + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER +/* + cerr << "value vector for medians:" << endl; + for (int k = 0; k < sz; ++k) { + cerr << "(" << valvec[k].value << "," << valvec[k].duration << ") "; + } + cerr << endl; +*/ +#endif + + if (sz % 2 == 1) { + summary.median = valvec[sz/2].value; + } else { + summary.median = (valvec[sz/2].value + valvec[sz/2 + 1].value) / 2; + } + + double duracc = 0.0; + summary.median_c = valvec[sz-1].value; + + for (int k = 0; k < sz; ++k) { + duracc += valvec[k].duration; + if (duracc > totalDuration/2) { + summary.median_c = valvec[k].value; + break; + } + } + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "median_c = " << summary.median_c << endl; + cerr << "median = " << summary.median << endl; +#endif + + map distribution; + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "summing (discrete): "; +#endif + for (int k = 0; k < sz; ++k) { +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << accumulator.results[k].values[bin] << " "; +#endif + summary.sum += accumulator.results[k].values[bin]; + distribution[accumulator.results[k].values[bin]] += 1; + } +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << endl; +#endif + + int md = 0; + + for (map::iterator di = distribution.begin(); + di != distribution.end(); ++di) { + if (di->second > md) { + md = di->second; + summary.mode = di->first; + } + } + + distribution.clear(); + + map distribution_c; + + for (int k = 0; k < sz; ++k) { + distribution_c[accumulator.results[k].values[bin]] + += toSec(accumulator.results[k].duration); + } + + double mrd = 0.0; + + for (map::iterator di = distribution_c.begin(); + di != distribution_c.end(); ++di) { + if (di->second > mrd) { +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "element " << di->first << " spans time " + << di->second << " and so is an improved mode " + << "candidate over element " << summary.mode_c + << " which spanned " << mrd << endl; +#endif + mrd = di->second; + summary.mode_c = di->first; + } + } + + distribution_c.clear(); + + if (totalDuration > 0.0) { + + double sum_c = 0.0; + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "summing (continuous): "; +#endif + for (int k = 0; k < sz; ++k) { +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << accumulator.results[k].values[bin] << "*" + << toSec(accumulator.results[k].duration) << " "; +#endif + double value = accumulator.results[k].values[bin] + * toSec(accumulator.results[k].duration); + sum_c += value; + } +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << endl; +#endif + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "mean_c = " << sum_c << " / " << totalDuration << " = " + << sum_c / totalDuration << " (sz = " << sz << ")" << endl; +#endif + + summary.mean_c = sum_c / totalDuration; + + for (int k = 0; k < sz; ++k) { + double value = accumulator.results[k].values[bin]; +// * toSec(accumulator.results[k].duration); + summary.variance_c += + (value - summary.mean_c) * (value - summary.mean_c) + * toSec(accumulator.results[k].duration); + } + +// summary.variance_c /= summary.count; + summary.variance_c /= totalDuration; + } + + double mean = summary.sum / summary.count; + +#ifdef DEBUG_PLUGIN_SUMMARISING_ADAPTER + cerr << "mean = " << summary.sum << " / " << summary.count << " = " + << summary.sum / summary.count << endl; +#endif + + for (int k = 0; k < sz; ++k) { + float value = accumulator.results[k].values[bin]; + summary.variance += (value - mean) * (value - mean); + } + summary.variance /= summary.count; + + m_summaries[output][segmentStart][bin] = summary; + } + } + } + + m_segmentedAccumulators.clear(); + m_accumulators.clear(); +} + + +} + +} diff --git a/src/vamp-hostsdk/PluginSummarisingAdapter.h b/src/vamp-hostsdk/PluginSummarisingAdapter.h new file mode 100644 index 0000000..28b804f --- /dev/null +++ b/src/vamp-hostsdk/PluginSummarisingAdapter.h @@ -0,0 +1,152 @@ +#pragma once + +#include "vamp-hostsdk/PluginWrapper.h" + +#include + +namespace Vamp { +namespace HostExt { + +/** + * \class PluginSummarisingAdapter PluginSummarisingAdapter.h + * + * PluginSummarisingAdapter is a Vamp plugin adapter that provides + * summarisation methods such as mean and median averages of output + * features, for use in any context where an available plugin produces + * individual values but the result that is actually needed is some + * sort of aggregate. + * + * To make use of PluginSummarisingAdapter, the host should configure, + * initialise and run the plugin through the adapter interface just as + * normal. Then, after the process and getRemainingFeatures methods + * have been properly called and processing is complete, the host may + * call getSummaryForOutput or getSummaryForAllOutputs to obtain + * summarised features: averages, maximum values, etc, depending on + * the SummaryType passed to the function. + * + * By default PluginSummarisingAdapter calculates a single summary of + * each output's feature across the whole duration of processed audio. + * A host needing summaries of sub-segments of the whole audio may + * call setSummarySegmentBoundaries before retrieving the summaries, + * providing a list of times such that one summary will be provided + * for each segment between two consecutive times. + * + * PluginSummarisingAdapter is straightforward rather than fast. It + * calculates all of the summary types for all outputs always, and + * then returns only the ones that are requested. It is designed on + * the basis that, for most features, summarising and storing + * summarised results is far cheaper than calculating the results in + * the first place. If this is not true for your particular feature, + * PluginSummarisingAdapter may not be the best approach for you. + * + * \note This class was introduced in version 2.0 of the Vamp plugin SDK. + */ + +class PluginSummarisingAdapter : public PluginWrapper +{ +public: + /** + * Construct a PluginSummarisingAdapter wrapping the given plugin. + * The adapter takes ownership of the plugin, which will be + * deleted when the adapter is deleted. + */ + PluginSummarisingAdapter(Plugin *plugin); + virtual ~PluginSummarisingAdapter(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + + void reset(); + + FeatureSet process(const float *const *inputBuffers, RealTime timestamp); + FeatureSet getRemainingFeatures(); + + typedef std::set SegmentBoundaries; + + /** + * Specify a series of segment boundaries, such that one summary + * will be returned for each of the contiguous intra-boundary + * segments. This function must be called before + * getSummaryForOutput or getSummaryForAllOutputs. + * + * Note that you cannot retrieve results with multiple different + * segmentations by repeatedly calling this function followed by + * one of the getSummary functions. The summaries are all + * calculated at the first call to any getSummary function, and + * once the summaries have been calculated, they remain + * calculated. + */ + void setSummarySegmentBoundaries(const SegmentBoundaries &); + + enum SummaryType { + Minimum = 0, + Maximum = 1, + Mean = 2, + Median = 3, + Mode = 4, + Sum = 5, + Variance = 6, + StandardDeviation = 7, + Count = 8, + + UnknownSummaryType = 999 + }; + + /** + * AveragingMethod indicates how the adapter should handle + * average-based summaries of features whose results are not + * equally spaced in time. + * + * If SampleAverage is specified, summary types based on averages + * will be calculated by treating each result individually without + * regard to its time: for example, the mean will be the sum of + * all values divided by the number of values. + * + * If ContinuousTimeAverage is specified, each feature will be + * considered to have a duration, either as specified in the + * feature's duration field, or until the following feature: thus, + * for example, the mean will be the sum of the products of values + * and durations, divided by the total duration. + * + * Although SampleAverage is useful for many types of feature, + * ContinuousTimeAverage is essential for some situations, for + * example finding the result that spans the largest proportion of + * the input given a feature that emits a new result only when the + * value changes (the modal value integrated over time). + */ + enum AveragingMethod { + SampleAverage = 0, + ContinuousTimeAverage = 1 + }; + + /** + * Return summaries of the features that were returned on the + * given output, using the given SummaryType and AveragingMethod. + * + * The plugin must have been fully run (process() and + * getRemainingFeatures() calls all made as appropriate) before + * this function is called. + */ + FeatureList getSummaryForOutput(int output, + SummaryType type, + AveragingMethod method = SampleAverage); + + /** + * Return summaries of the features that were returned on all of + * the plugin's outputs, using the given SummaryType and + * AveragingMethod. + * + * The plugin must have been fully run (process() and + * getRemainingFeatures() calls all made as appropriate) before + * this function is called. + */ + FeatureSet getSummaryForAllOutputs(SummaryType type, + AveragingMethod method = SampleAverage); + +protected: + class Impl; + Impl *m_impl; +}; + +} + +} diff --git a/src/vamp-hostsdk/PluginWrapper.cpp b/src/vamp-hostsdk/PluginWrapper.cpp new file mode 100644 index 0000000..44972ab --- /dev/null +++ b/src/vamp-hostsdk/PluginWrapper.cpp @@ -0,0 +1,157 @@ +#include "vamp-hostsdk/PluginWrapper.h" + +namespace Vamp { + +namespace HostExt { + +PluginWrapper::PluginWrapper(Plugin *plugin) : + Plugin(plugin->getInputSampleRate()), + m_plugin(plugin) +{ +} + +PluginWrapper::~PluginWrapper() +{ + delete m_plugin; +} + +bool +PluginWrapper::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + return m_plugin->initialise(channels, stepSize, blockSize); +} + +void +PluginWrapper::reset() +{ + m_plugin->reset(); +} + +Plugin::InputDomain +PluginWrapper::getInputDomain() const +{ + return m_plugin->getInputDomain(); +} + +unsigned int +PluginWrapper::getVampApiVersion() const +{ + return m_plugin->getVampApiVersion(); +} + +std::string +PluginWrapper::getIdentifier() const +{ + return m_plugin->getIdentifier(); +} + +std::string +PluginWrapper::getName() const +{ + return m_plugin->getName(); +} + +std::string +PluginWrapper::getDescription() const +{ + return m_plugin->getDescription(); +} + +std::string +PluginWrapper::getMaker() const +{ + return m_plugin->getMaker(); +} + +int +PluginWrapper::getPluginVersion() const +{ + return m_plugin->getPluginVersion(); +} + +std::string +PluginWrapper::getCopyright() const +{ + return m_plugin->getCopyright(); +} + +PluginBase::ParameterList +PluginWrapper::getParameterDescriptors() const +{ + return m_plugin->getParameterDescriptors(); +} + +float +PluginWrapper::getParameter(std::string parameter) const +{ + return m_plugin->getParameter(parameter); +} + +void +PluginWrapper::setParameter(std::string parameter, float value) +{ + m_plugin->setParameter(parameter, value); +} + +PluginBase::ProgramList +PluginWrapper::getPrograms() const +{ + return m_plugin->getPrograms(); +} + +std::string +PluginWrapper::getCurrentProgram() const +{ + return m_plugin->getCurrentProgram(); +} + +void +PluginWrapper::selectProgram(std::string program) +{ + m_plugin->selectProgram(program); +} + +size_t +PluginWrapper::getPreferredStepSize() const +{ + return m_plugin->getPreferredStepSize(); +} + +size_t +PluginWrapper::getPreferredBlockSize() const +{ + return m_plugin->getPreferredBlockSize(); +} + +size_t +PluginWrapper::getMinChannelCount() const +{ + return m_plugin->getMinChannelCount(); +} + +size_t PluginWrapper::getMaxChannelCount() const +{ + return m_plugin->getMaxChannelCount(); +} + +Plugin::OutputList +PluginWrapper::getOutputDescriptors() const +{ + return m_plugin->getOutputDescriptors(); +} + +Plugin::FeatureSet +PluginWrapper::process(const float *const *inputBuffers, RealTime timestamp) +{ + return m_plugin->process(inputBuffers, timestamp); +} + +Plugin::FeatureSet +PluginWrapper::getRemainingFeatures() +{ + return m_plugin->getRemainingFeatures(); +} + +} + +} diff --git a/src/vamp-hostsdk/PluginWrapper.h b/src/vamp-hostsdk/PluginWrapper.h new file mode 100644 index 0000000..b28a3f8 --- /dev/null +++ b/src/vamp-hostsdk/PluginWrapper.h @@ -0,0 +1,88 @@ +#pragma once + +#include "vamp-sdk/Plugin.h" + +namespace Vamp::HostExt { + +/** + * \class PluginWrapper PluginWrapper.h + * + * PluginWrapper is a simple base class for adapter plugins. It takes + * a pointer to a "to be wrapped" Vamp plugin on construction, and + * provides implementations of all the Vamp plugin methods that simply + * delegate through to the wrapped plugin. A subclass can therefore + * override only the methods that are meaningful for the particular + * adapter. + * + * \note This class was introduced in version 1.1 of the Vamp plugin SDK. + */ + +class PluginWrapper : public Plugin +{ +public: + virtual ~PluginWrapper(); + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + void reset(); + + InputDomain getInputDomain() const; + + unsigned int getVampApiVersion() const; + std::string getIdentifier() const; + std::string getName() const; + std::string getDescription() const; + std::string getMaker() const; + int getPluginVersion() const; + std::string getCopyright() const; + + ParameterList getParameterDescriptors() const; + float getParameter(std::string) const; + void setParameter(std::string, float); + + ProgramList getPrograms() const; + std::string getCurrentProgram() const; + void selectProgram(std::string); + + size_t getPreferredStepSize() const; + size_t getPreferredBlockSize() const; + + size_t getMinChannelCount() const; + size_t getMaxChannelCount() const; + + OutputList getOutputDescriptors() const; + + FeatureSet process(const float *const *inputBuffers, RealTime timestamp); + + FeatureSet getRemainingFeatures(); + + /** + * Return a pointer to the plugin wrapper of type WrapperType + * surrounding this wrapper's plugin, if present. + * + * This is useful in situations where a plugin is wrapped by + * multiple different wrappers (one inside another) and the host + * wants to call some wrapper-specific function on one of the + * layers without having to care about the order in which they are + * wrapped. For example, the plugin returned by + * PluginLoader::loadPlugin may have more than one wrapper; if the + * host wanted to query or fine-tune some property of one of them, + * it would be hard to do so without knowing the order of the + * wrappers. This function therefore gives direct access to the + * wrapper of a particular type. + */ + template + WrapperType *getWrapper() { + WrapperType *w = dynamic_cast(this); + if (w) return w; + PluginWrapper *pw = dynamic_cast(m_plugin); + if (pw) return pw->getWrapper(); + return 0; + } + +protected: + PluginWrapper(Plugin *plugin); // I take ownership of plugin + Plugin *m_plugin; +}; + +} + diff --git a/src/vamp-hostsdk/Window.h b/src/vamp-hostsdk/Window.h new file mode 100644 index 0000000..2f86211 --- /dev/null +++ b/src/vamp-hostsdk/Window.h @@ -0,0 +1,128 @@ +#pragma once + +#include "vamp-hostsdk/hostguard.h" + +#include +#include + +_VAMP_SDK_HOSTSPACE_BEGIN(Window.h) + +template +class Window +{ +public: + enum WindowType { + RectangularWindow, + BartlettWindow, + HammingWindow, + HanningWindow, + BlackmanWindow, + NuttallWindow, + BlackmanHarrisWindow + }; + + /** + * Construct a windower of the given type. + */ + Window(WindowType type, size_t size) : m_type(type), m_size(size) { encache(); } + Window(const Window &w) : m_type(w.m_type), m_size(w.m_size) { encache(); } + Window &operator=(const Window &w) { + if (&w == this) return *this; + m_type = w.m_type; + m_size = w.m_size; + encache(); + return *this; + } + virtual ~Window() { delete[] m_cache; } + + void cut(T *src) const { cut(src, src); } + void cut(T *src, T *dst) const { + for (size_t i = 0; i < m_size; ++i) dst[i] = src[i] * m_cache[i]; + } + template + void cut(T0 *src, T1 *dst) const { + for (size_t i = 0; i < m_size; ++i) dst[i] = src[i] * m_cache[i]; + } + + T getArea() { return m_area; } + T getValue(size_t i) { return m_cache[i]; } + + WindowType getType() const { return m_type; } + size_t getSize() const { return m_size; } + +protected: + WindowType m_type; + size_t m_size; + T *m_cache; + T m_area; + + void encache(); + void cosinewin(T *, T, T, T, T); +}; + +template +void Window::encache() +{ + int n = int(m_size); + T *mult = new T[n]; + int i; + for (i = 0; i < n; ++i) mult[i] = 1.0; + + switch (m_type) { + + case RectangularWindow: + for (i = 0; i < n; ++i) { + mult[i] *= 0.5; + } + break; + + case BartlettWindow: + for (i = 0; i < n/2; ++i) { + mult[i] *= (i / T(n/2)); + mult[i + n/2] *= (1.0 - (i / T(n/2))); + } + break; + + case HammingWindow: + cosinewin(mult, 0.54, 0.46, 0.0, 0.0); + break; + + case HanningWindow: + cosinewin(mult, 0.50, 0.50, 0.0, 0.0); + break; + + case BlackmanWindow: + cosinewin(mult, 0.42, 0.50, 0.08, 0.0); + break; + + case NuttallWindow: + cosinewin(mult, 0.3635819, 0.4891775, 0.1365995, 0.0106411); + break; + + case BlackmanHarrisWindow: + cosinewin(mult, 0.35875, 0.48829, 0.14128, 0.01168); + break; + } + + m_cache = mult; + + m_area = 0; + for (int i = 0; i < n; ++i) { + m_area += m_cache[i]; + } + m_area /= n; +} + +template +void Window::cosinewin(T *mult, T a0, T a1, T a2, T a3) +{ + int n = int(m_size); + for (int i = 0; i < n; ++i) { + mult[i] *= (a0 + - a1 * cos((2 * M_PI * i) / n) + + a2 * cos((4 * M_PI * i) / n) + - a3 * cos((6 * M_PI * i) / n)); + } +} + +_VAMP_SDK_HOSTSPACE_END(Window.h) diff --git a/src/vamp-hostsdk/acsymbols.cpp b/src/vamp-hostsdk/acsymbols.cpp new file mode 100644 index 0000000..36963bd --- /dev/null +++ b/src/vamp-hostsdk/acsymbols.cpp @@ -0,0 +1,16 @@ +/* These stubs are provided so that autoconf can check library + * versions using C symbols only */ + +extern void libvamphostsdk_v_2_9_present(void) { } +extern void libvamphostsdk_v_2_8_present(void) { } +extern void libvamphostsdk_v_2_7_1_present(void) { } +extern void libvamphostsdk_v_2_7_present(void) { } +extern void libvamphostsdk_v_2_6_present(void) { } +extern void libvamphostsdk_v_2_5_present(void) { } +extern void libvamphostsdk_v_2_4_present(void) { } +extern void libvamphostsdk_v_2_3_1_present(void) { } +extern void libvamphostsdk_v_2_3_present(void) { } +extern void libvamphostsdk_v_2_2_1_present(void) { } +extern void libvamphostsdk_v_2_2_present(void) { } +extern void libvamphostsdk_v_2_1_present(void) { } +extern void libvamphostsdk_v_2_0_present(void) { } diff --git a/src/vamp-hostsdk/host-c.cpp b/src/vamp-hostsdk/host-c.cpp new file mode 100644 index 0000000..0ec11a5 --- /dev/null +++ b/src/vamp-hostsdk/host-c.cpp @@ -0,0 +1,114 @@ +#include "vamp-hostsdk/host-c.h" +#include "vamp-hostsdk/Files.h" + +#include +#include + +#include + +using namespace std; + +static vector files; +static map cnames; +static bool haveFiles = false; + +struct vhLibrary_t { + vhLibrary_t(void *h, VampGetPluginDescriptorFunction f) + : handle(h), func(f), nplugins(0) { } + void *handle; + VampGetPluginDescriptorFunction func; + int nplugins; +}; + +static void initFilenames() +{ + if (!haveFiles) { + files = Files::listLibraryFiles(); + for (size_t i = 0; i < files.size(); ++i) { + cnames[files[i]] = strdup(Files::lcBasename(files[i]).c_str()); + } + haveFiles = true; + } +} + +int vhGetLibraryCount() +{ + initFilenames(); + return int(files.size()); +} + +const char *vhGetLibraryName(int index) +{ + initFilenames(); + if (index >= 0 && index < int(files.size())) { + return cnames[files[index]]; + } + else return 0; +} + +int vhGetLibraryIndex(const char *name) +{ + for (size_t i = 0; i < files.size(); ++i) { + if (Files::lcBasename(name) == Files::lcBasename(files[i])) { + return i; + } + } + return -1; +} + +vhLibrary vhLoadLibrary(int index) +{ + initFilenames(); + if (index < 0 || index >= int(files.size())) { + return 0; + } + + string fullPath = files[index]; + void *lib = Files::loadLibrary(fullPath); + + if (!lib) return 0; + + VampGetPluginDescriptorFunction func = + (VampGetPluginDescriptorFunction)Files::lookupInLibrary + (lib, "vampGetPluginDescriptor"); + if (!func) { + cerr << "vhLoadLibrary: No vampGetPluginDescriptor function found in library \"" + << fullPath << "\"" << endl; + Files::unloadLibrary(lib); + return 0; + } + + vhLibrary_t *vhl = new vhLibrary_t(lib, func); + while (vhl->func(VAMP_API_VERSION, vhl->nplugins)) { + ++vhl->nplugins; + } + return vhl; +} + +int vhGetPluginCount(vhLibrary library) +{ + vhLibrary_t *vhl = static_cast(library); + if (vhl) return vhl->nplugins; + else return 0; +} + +const VampPluginDescriptor *vhGetPluginDescriptor(vhLibrary library, + int plugin) +{ + vhLibrary_t *vhl = static_cast(library); + if (vhl && plugin >= 0 && plugin < vhl->nplugins) { + return vhl->func(VAMP_API_VERSION, plugin); + } else { + return 0; + } +} + +void vhUnloadLibrary(vhLibrary library) +{ + vhLibrary_t *vhl = static_cast(library); + if (vhl && vhl->handle) { + Files::unloadLibrary(vhl->handle); + } + delete vhl; +} + diff --git a/src/vamp-hostsdk/host-c.h b/src/vamp-hostsdk/host-c.h new file mode 100644 index 0000000..13e2bcb --- /dev/null +++ b/src/vamp-hostsdk/host-c.h @@ -0,0 +1,60 @@ +#pragma once + +#include "vamp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct vhLibrary_t *vhLibrary; + +/** + * Return the number of Vamp plugin libraries discovered in the + * installation path. This number will remain fixed after the first + * call -- plugins are only discovered once, the first time this + * function is called. + */ +extern int vhGetLibraryCount(); + +/** + * Return the library name (base soname) of the library with the given + * index, in the range 0..(vhGetLibraryCount()-1). + */ +extern const char *vhGetLibraryName(int library); + +/** + * Return the library index for the given library name, or -1 if the + * name is not known. + */ +extern int vhGetLibraryIndex(const char *name); + +/** + * Load the library with the given index. If the library cannot be + * loaded for any reason, the return value is 0; otherwise it is an + * opaque pointer suitable for passing to other functions in this API. + */ +extern vhLibrary vhLoadLibrary(int library); + +/** + * Return the number of Vamp plugins in the given library. + */ +extern int vhGetPluginCount(vhLibrary library); + +/** + * Return a Vamp plugin descriptor for a plugin in a given + * library. This simply calls the vampGetPluginDescriptor function in + * that library with the given plugin index and returns the + * result. See vamp/vamp.h for details about the plugin descriptor. + */ +extern const VampPluginDescriptor *vhGetPluginDescriptor(vhLibrary library, + int plugin); + +/** + * Unload a plugin library. Do not do this while any of its plugins + * are still in use. + */ +extern void vhUnloadLibrary(vhLibrary); + +#ifdef __cplusplus +} +#endif diff --git a/src/vamp-hostsdk/vamp-hostsdk.h b/src/vamp-hostsdk/vamp-hostsdk.h new file mode 100644 index 0000000..f32583d --- /dev/null +++ b/src/vamp-hostsdk/vamp-hostsdk.h @@ -0,0 +1,53 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006 Chris Cannam. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef _VAMP_HOSTSDK_SINGLE_INCLUDE_H_ +#define _VAMP_HOSTSDK_SINGLE_INCLUDE_H_ + +#include "PluginBase.h" +#include "PluginBufferingAdapter.h" +#include "PluginChannelAdapter.h" +#include "Plugin.h" +#include "PluginHostAdapter.h" +#include "PluginInputDomainAdapter.h" +#include "PluginLoader.h" +#include "PluginSummarisingAdapter.h" +#include "PluginWrapper.h" +#include "RealTime.h" + +#endif + + diff --git a/src/vamp-sdk/FFT.cpp b/src/vamp-sdk/FFT.cpp new file mode 100644 index 0000000..734c341 --- /dev/null +++ b/src/vamp-sdk/FFT.cpp @@ -0,0 +1,213 @@ +#include "vamp-sdk/FFT.h" + +#include +#include + +#include "vamp-sdk/ext/vamp_kiss_fft.h" +#include "vamp-sdk/ext/vamp_kiss_fftr.h" + +namespace Vamp { + +void FFT::forward(unsigned int un, const double* ri, const double* ii, double* ro, double* io) +{ + int n(un); + vamp_kiss_fft_cfg c = vamp_kiss_fft_alloc(n, false, 0, 0); + vamp_kiss_fft_cpx *in = new vamp_kiss_fft_cpx[n]; + vamp_kiss_fft_cpx *out = new vamp_kiss_fft_cpx[n]; + for (int i = 0; i < n; ++i) { + in[i].r = ri[i]; + in[i].i = 0; + } + if (ii) { + for (int i = 0; i < n; ++i) { + in[i].i = ii[i]; + } + } + vamp_kiss_fft(c, in, out); + for (int i = 0; i < n; ++i) { + ro[i] = out[i].r; + io[i] = out[i].i; + } + vamp_kiss_fft_free(c); + delete[] in; + delete[] out; +} + +void +FFT::inverse(unsigned int un, + const double *ri, const double *ii, + double *ro, double *io) +{ + int n(un); + vamp_kiss_fft_cfg c = vamp_kiss_fft_alloc(n, true, 0, 0); + vamp_kiss_fft_cpx *in = new vamp_kiss_fft_cpx[n]; + vamp_kiss_fft_cpx *out = new vamp_kiss_fft_cpx[n]; + for (int i = 0; i < n; ++i) { + in[i].r = ri[i]; + in[i].i = 0; + } + if (ii) { + for (int i = 0; i < n; ++i) { + in[i].i = ii[i]; + } + } + vamp_kiss_fft(c, in, out); + double scale = 1.0 / double(n); + for (int i = 0; i < n; ++i) { + ro[i] = out[i].r * scale; + io[i] = out[i].i * scale; + } + vamp_kiss_fft_free(c); + delete[] in; + delete[] out; +} + +class FFTComplex::D +{ +public: + D(int n) : + m_n(n), + m_fconf(vamp_kiss_fft_alloc(n, false, 0, 0)), + m_iconf(vamp_kiss_fft_alloc(n, true, 0, 0)), + m_ci(new vamp_kiss_fft_cpx[m_n]), + m_co(new vamp_kiss_fft_cpx[m_n]) { } + + ~D() { + vamp_kiss_fftr_free(m_fconf); + vamp_kiss_fftr_free(m_iconf); + delete[] m_ci; + delete[] m_co; + } + + void forward(const double *ci, double *co) { + for (int i = 0; i < m_n; ++i) { + m_ci[i].r = ci[i*2]; + m_ci[i].i = ci[i*2+1]; + } + vamp_kiss_fft(m_fconf, m_ci, m_co); + for (int i = 0; i < m_n; ++i) { + co[i*2] = m_co[i].r; + co[i*2+1] = m_co[i].i; + } + } + + void inverse(const double *ci, double *co) { + for (int i = 0; i < m_n; ++i) { + m_ci[i].r = ci[i*2]; + m_ci[i].i = ci[i*2+1]; + } + vamp_kiss_fft(m_iconf, m_ci, m_co); + double scale = 1.0 / double(m_n); + for (int i = 0; i < m_n; ++i) { + co[i*2] = m_co[i].r * scale; + co[i*2+1] = m_co[i].i * scale; + } + } + +private: + int m_n; + vamp_kiss_fft_cfg m_fconf; + vamp_kiss_fft_cfg m_iconf; + vamp_kiss_fft_cpx *m_ci; + vamp_kiss_fft_cpx *m_co; +}; + +FFTComplex::FFTComplex(unsigned int n) : + m_d(new D(n)) +{ +} + +FFTComplex::~FFTComplex() +{ + delete m_d; +} + +void +FFTComplex::forward(const double *ci, double *co) +{ + m_d->forward(ci, co); +} + +void +FFTComplex::inverse(const double *ci, double *co) +{ + m_d->inverse(ci, co); +} + +class FFTReal::D +{ +public: + D(int n) : + m_n(n), + m_fconf(vamp_kiss_fftr_alloc(n, false, 0, 0)), + m_iconf(vamp_kiss_fftr_alloc(n, true, 0, 0)), + m_ri(new vamp_kiss_fft_scalar[m_n]), + m_ro(new vamp_kiss_fft_scalar[m_n]), + m_freq(new vamp_kiss_fft_cpx[n/2+1]) { } + + ~D() { + vamp_kiss_fftr_free(m_fconf); + vamp_kiss_fftr_free(m_iconf); + delete[] m_ri; + delete[] m_ro; + delete[] m_freq; + } + + void forward(const double *ri, double *co) { + for (int i = 0; i < m_n; ++i) { + // in case vamp_kiss_fft_scalar is float + m_ri[i] = ri[i]; + } + vamp_kiss_fftr(m_fconf, m_ri, m_freq); + int hs = m_n/2 + 1; + for (int i = 0; i < hs; ++i) { + co[i*2] = m_freq[i].r; + co[i*2+1] = m_freq[i].i; + } + } + + void inverse(const double *ci, double *ro) { + int hs = m_n/2 + 1; + for (int i = 0; i < hs; ++i) { + m_freq[i].r = ci[i*2]; + m_freq[i].i = ci[i*2+1]; + } + vamp_kiss_fftri(m_iconf, m_freq, m_ro); + double scale = 1.0 / double(m_n); + for (int i = 0; i < m_n; ++i) { + ro[i] = m_ro[i] * scale; + } + } + +private: + int m_n; + vamp_kiss_fftr_cfg m_fconf; + vamp_kiss_fftr_cfg m_iconf; + vamp_kiss_fft_scalar *m_ri; + vamp_kiss_fft_scalar *m_ro; + vamp_kiss_fft_cpx *m_freq; +}; + +FFTReal::FFTReal(unsigned int n) : + m_d(new D(n)) +{ +} + +FFTReal::~FFTReal() +{ + delete m_d; +} + +void +FFTReal::forward(const double *ri, double *co) +{ + m_d->forward(ri, co); +} + +void +FFTReal::inverse(const double *ci, double *ro) +{ + m_d->inverse(ci, ro); +} + +} diff --git a/src/vamp-sdk/FFT.h b/src/vamp-sdk/FFT.h new file mode 100644 index 0000000..130d493 --- /dev/null +++ b/src/vamp-sdk/FFT.h @@ -0,0 +1,159 @@ +#pragma once + +namespace Vamp { + +/** + * A simple FFT implementation provided for convenience of plugin + * authors. This class provides one-shot (i.e. fixed table state is + * recalculated every time) double-precision complex-complex + * transforms. For repeated transforms from real time-domain data, use + * an FFTComplex or FFTReal object instead. + * + * Note: If the SDK has been compiled with the SINGLE_PRECISION_FFT + * flag, then all FFTs will use single precision internally. The + * default is double precision. The API uses doubles in either case. + * + * The forward transform is unscaled; the inverse transform is scaled + * by 1/n. + */ +class FFT +{ +public: + /** + * Calculate a one-shot forward transform of size n. + * n must be a multiple of 2. + * + * ri and ii must point to the real and imaginary component arrays + * of the input. For real input, ii may be NULL. + * + * ro and io must point to enough space to receive the real and + * imaginary component arrays of the output. + * + * All input and output arrays are of size n. + */ + static void forward(unsigned int n, + const double *ri, const double *ii, + double *ro, double *io); + + /** + * Calculate a one-shot inverse transform of size n. + * n must be a power of 2, greater than 1. + * + * ri and ii must point to the real and imaginary component arrays + * of the input. For real input, ii may be NULL. + * + * ro and io must point to enough space to receive the real and + * imaginary component arrays of the output. The output is scaled + * by 1/n. The output pointers may not be NULL, even if the output + * is expected to be real. + * + * All input and output arrays are of size n. + */ + static void inverse(unsigned int n, + const double *ri, const double *ii, + double *ro, double *io); +}; + +/** + * A simple FFT implementation provided for convenience of plugin + * authors. This class provides double-precision complex-complex + * transforms. + * + * Note: If the SDK has been compiled with the SINGLE_PRECISION_FFT + * flag, then all FFTs will use single precision internally. The + * default is double precision. The API uses doubles in either case. + * + * The forward transform is unscaled; the inverse transform is scaled + * by 1/n. + */ +class FFTComplex +{ +public: + /** + * Prepare to calculate transforms of size n. + * n must be a multiple of 2. + */ + FFTComplex(unsigned int n); + + ~FFTComplex(); + + /** + * Calculate a forward transform of size n. + * + * ci must point to the interleaved complex input data of size n + * (that is, 2n doubles in total). + * + * co must point to enough space to receive an interleaved complex + * output array of size n (that is, 2n doubles in total). + */ + void forward(const double *ci, double *co); + + /** + * Calculate an inverse transform of size n. + * + * ci must point to an interleaved complex input array of size n + * (that is, 2n doubles in total). + * + * co must point to enough space to receive the interleaved + * complex output data of size n (that is, 2n doubles in + * total). The output is scaled by 1/n. + */ + void inverse(const double *ci, double *co); + +private: + class D; + D *m_d; +}; + +/** + * A simple FFT implementation provided for convenience of plugin + * authors. This class provides transforms between double-precision + * real time-domain and double-precision complex frequency-domain + * data. + * + * Note: If the SDK has been compiled with the SINGLE_PRECISION_FFT + * flag, then all FFTs will use single precision internally. The + * default is double precision. The API uses doubles in either case. + * + * The forward transform is unscaled; the inverse transform is scaled + * by 1/n. + */ +class FFTReal +{ +public: + /** + * Prepare to calculate transforms of size n. + * n must be a multiple of 2. + */ + FFTReal(unsigned int n); + + ~FFTReal(); + + /** + * Calculate a forward transform of size n. + * + * ri must point to the real input data of size n. + * + * co must point to enough space to receive an interleaved complex + * output array of size n/2+1 (that is, n+2 doubles in total). + */ + void forward(const double *ri, double *co); + + /** + * Calculate an inverse transform of size n. + * + * ci must point to an interleaved complex input array of size + * n/2+1 (that is, n+2 doubles in total). + * + * ro must point to enough space to receive the real output data + * of size n. The output is scaled by 1/n and only the real part + * is returned. + */ + void inverse(const double *ci, double *ro); + +private: + class D; + D *m_d; +}; + +} diff --git a/src/vamp-sdk/Plugin.h b/src/vamp-sdk/Plugin.h new file mode 100644 index 0000000..9c4961c --- /dev/null +++ b/src/vamp-sdk/Plugin.h @@ -0,0 +1,412 @@ +#pragma once + +#include +#include +#include + +#include "PluginBase.h" +#include "RealTime.h" + +namespace Vamp { + +/** + * \class Plugin Plugin.h + * + * Vamp::Plugin is a base class for plugin instance classes + * that provide feature extraction from audio or related data. + * + * In most cases, the input will be audio and the output will be a + * stream of derived data at a lower sampling resolution than the + * input. + * + * Note that this class inherits several abstract methods from + * PluginBase. These must be implemented by the subclass. + * + * + * PLUGIN LIFECYCLE + * + * Feature extraction plugins are managed differently from real-time + * plugins (such as VST effects). The main difference is that the + * parameters for a feature extraction plugin are configured before + * the plugin is used, and do not change during use. + * + * 1. Host constructs the plugin, passing it the input sample rate. + * The plugin may do basic initialisation, but should not do anything + * computationally expensive at this point. You must make sure your + * plugin is cheap to construct, otherwise you'll seriously affect the + * startup performance of almost all hosts. If you have serious + * initialisation to do, the proper place is in initialise() (step 5). + * + * 2. Host may query the plugin's available outputs. + * + * 3. Host queries programs and parameter descriptors, and may set + * some or all of them. Parameters that are not explicitly set should + * take their default values as specified in the parameter descriptor. + * When a program is set, the parameter values may change and the host + * will re-query them to check. + * + * 4. Host queries the preferred step size, block size and number of + * channels. These may all vary depending on the parameter values. + * (Note however that you cannot make the number of distinct outputs + * dependent on parameter values.) + * + * 5. Plugin is properly initialised with a call to initialise. This + * fixes the step size, block size, and number of channels, as well as + * all of the parameter and program settings. If the values passed in + * to initialise do not match the plugin's advertised preferred values + * from step 4, the plugin may refuse to initialise and return false + * (although if possible it should accept the new values). Any + * computationally expensive setup code should take place here. + * + * 6. Host finally checks the number of values, resolution, extents + * etc per output (which may vary depending on the number of channels, + * step size and block size as well as the parameter values). + * + * 7. Host will repeatedly call the process method to pass in blocks + * of input data. This method may return features extracted from that + * data (if the plugin is causal). + * + * 8. Host will call getRemainingFeatures exactly once, after all the + * input data has been processed. This may return any non-causal or + * leftover features. + * + * 9. At any point after initialise was called, the host may + * optionally call the reset method and restart processing. (This + * does not mean it can change the parameters, which are fixed from + * initialise until destruction.) + * + * A plugin does not need to handle the case where setParameter or + * selectProgram is called after initialise has been called. It's the + * host's responsibility not to do that. Similarly, the plugin may + * safely assume that initialise is called no more than once. + */ + +class Plugin : public PluginBase +{ +public: + virtual ~Plugin() { } + + /** + * Initialise a plugin to prepare it for use with the given number + * of input channels, step size (window increment, in sample + * frames) and block size (window size, in sample frames). + * + * The input sample rate should have been already specified at + * construction time. + * + * Return true for successful initialisation, false if the number + * of input channels, step size and/or block size cannot be + * supported. + */ + virtual bool initialise(size_t inputChannels, + size_t stepSize, + size_t blockSize) = 0; + + /** + * Reset the plugin after use, to prepare it for another clean + * run. Not called for the first initialisation (i.e. initialise + * must also do a reset). + */ + virtual void reset() = 0; + + enum InputDomain { TimeDomain, FrequencyDomain }; + + /** + * Get the plugin's required input domain. + * + * If this is TimeDomain, the samples provided to the process() + * function (below) will be in the time domain, as for a + * traditional audio processing plugin. + * + * If this is FrequencyDomain, the host will carry out a windowed + * FFT of size equal to the negotiated block size on the data + * before passing the frequency bin data in to process(). The + * input data for the FFT will be rotated so as to place the + * origin in the centre of the block. + * The plugin does not get to choose the window type -- the host + * will either let the user do so, or will use a Hanning window. + */ + virtual InputDomain getInputDomain() const = 0; + + /** + * Get the preferred block size (window size -- the number of + * sample frames passed in each block to the process() function). + * This should be called before initialise(). + * + * A plugin that can handle any block size may return 0. The + * final block size will be set in the initialise() call. + */ + virtual size_t getPreferredBlockSize() const { return 0; } + + /** + * Get the preferred step size (window increment -- the distance + * in sample frames between the start frames of consecutive blocks + * passed to the process() function) for the plugin. This should + * be called before initialise(). + * + * A plugin may return 0 if it has no particular interest in the + * step size. In this case, the host should make the step size + * equal to the block size if the plugin is accepting input in the + * time domain. If the plugin is accepting input in the frequency + * domain, the host may use any step size. The final step size + * will be set in the initialise() call. + */ + virtual size_t getPreferredStepSize() const { return 0; } + + /** + * Get the minimum supported number of input channels. + */ + virtual size_t getMinChannelCount() const { return 1; } + + /** + * Get the maximum supported number of input channels. + */ + virtual size_t getMaxChannelCount() const { return 1; } + + struct OutputDescriptor + { + /** + * The name of the output, in computer-usable form. Should be + * reasonably short and without whitespace or punctuation, using + * the characters [a-zA-Z0-9_-] only. + * Example: "zero_crossing_count" + */ + std::string identifier; + + /** + * The human-readable name of the output. + * Example: "Zero Crossing Counts" + */ + std::string name; + + /** + * A human-readable short text describing the output. May be + * empty if the name has said it all already. + * Example: "The number of zero crossing points per processing block" + */ + std::string description; + + /** + * The unit of the output, in human-readable form. + */ + std::string unit; + + /** + * True if the output has the same number of values per sample + * for every output sample. Outputs for which this is false + * are unlikely to be very useful in a general-purpose host. + */ + bool hasFixedBinCount; + + /** + * The number of values per result of the output. Undefined + * if hasFixedBinCount is false. If this is zero, the output + * is point data (i.e. only the time of each output is of + * interest, the value list will be empty). + */ + size_t binCount; + + /** + * The (human-readable) names of each of the bins, if + * appropriate. This is always optional. + */ + std::vector binNames; + + /** + * True if the results in each output bin fall within a fixed + * numeric range (minimum and maximum values). Undefined if + * binCount is zero. + */ + bool hasKnownExtents; + + /** + * Minimum value of the results in the output. Undefined if + * hasKnownExtents is false or binCount is zero. + */ + float minValue; + + /** + * Maximum value of the results in the output. Undefined if + * hasKnownExtents is false or binCount is zero. + */ + float maxValue; + + /** + * True if the output values are quantized to a particular + * resolution. Undefined if binCount is zero. + */ + bool isQuantized; + + /** + * Quantization resolution of the output values (e.g. 1.0 if + * they are all integers). Undefined if isQuantized is false + * or binCount is zero. + */ + float quantizeStep; + + enum SampleType { + + /// Results from each process() align with that call's block start + OneSamplePerStep, + + /// Results are evenly spaced in time (sampleRate specified below) + FixedSampleRate, + + /// Results are unevenly spaced and have individual timestamps + VariableSampleRate + }; + + /** + * Positioning in time of the output results. + */ + SampleType sampleType; + + /** + * Sample rate of the output results, as samples per second. + * Undefined if sampleType is OneSamplePerStep. + * + * If sampleType is VariableSampleRate and this value is + * non-zero, then it may be used to calculate a resolution for + * the output (i.e. the "duration" of each sample, in time, + * will be 1/sampleRate seconds). It's recommended to set + * this to zero if that behaviour is not desired. + */ + float sampleRate; + + /** + * True if the returned results for this output are known to + * have a duration field. + */ + bool hasDuration; + + OutputDescriptor() : // defaults for mandatory non-class-type members + hasFixedBinCount(false), + binCount(0), + hasKnownExtents(false), + minValue(0), + maxValue(0), + isQuantized(false), + quantizeStep(0), + sampleType(OneSamplePerStep), + sampleRate(0), + hasDuration(false) { } + }; + + typedef std::vector OutputList; + + /** + * Get the outputs of this plugin. An output's index in this list + * is used as its numeric index when looking it up in the + * FeatureSet returned from the process() call. + */ + virtual OutputList getOutputDescriptors() const = 0; + + struct Feature + { + /** + * True if an output feature has its own timestamp. This is + * mandatory if the output has VariableSampleRate, optional if + * the output has FixedSampleRate, and unused if the output + * has OneSamplePerStep. + */ + bool hasTimestamp; + + /** + * Timestamp of the output feature. This is mandatory if the + * output has VariableSampleRate or if the output has + * FixedSampleRate and hasTimestamp is true, and unused + * otherwise. + */ + RealTime timestamp; + + /** + * True if an output feature has a specified duration. This + * is optional if the output has VariableSampleRate or + * FixedSampleRate, and and unused if the output has + * OneSamplePerStep. + */ + bool hasDuration; + + /** + * Duration of the output feature. This is mandatory if the + * output has VariableSampleRate or FixedSampleRate and + * hasDuration is true, and unused otherwise. + */ + RealTime duration; + + /** + * Results for a single sample of this feature. If the output + * hasFixedBinCount, there must be the same number of values + * as the output's binCount count. + */ + std::vector values; + + /** + * Label for the sample of this feature. + */ + std::string label; + + Feature() : // defaults for mandatory non-class-type members + hasTimestamp(false), hasDuration(false) { } + }; + + typedef std::vector FeatureList; + + typedef std::map FeatureSet; // key is output no + + /** + * Process a single block of input data. + * + * If the plugin's inputDomain is TimeDomain, inputBuffers will + * point to one array of floats per input channel, and each of + * these arrays will contain blockSize consecutive audio samples + * (the host will zero-pad as necessary). The timestamp in this + * case will be the real time in seconds of the start of the + * supplied block of samples. + * + * If the plugin's inputDomain is FrequencyDomain, inputBuffers + * will point to one array of floats per input channel, and each + * of these arrays will contain blockSize/2+1 consecutive pairs of + * real and imaginary component floats corresponding to bins + * 0..(blockSize/2) of the FFT output. That is, bin 0 (the first + * pair of floats) contains the DC output, up to bin blockSize/2 + * which contains the Nyquist-frequency output. There will + * therefore be blockSize+2 floats per channel in total. The + * timestamp will be the real time in seconds of the centre of the + * FFT input window (i.e. the very first block passed to process + * might contain the FFT of half a block of zero samples and the + * first half-block of the actual data, with a timestamp of zero). + * + * Return any features that have become available after this + * process call. (These do not necessarily have to fall within + * the process block, except for OneSamplePerStep outputs.) + */ + virtual FeatureSet process(const float *const *inputBuffers, + RealTime timestamp) = 0; + + /** + * After all blocks have been processed, calculate and return any + * remaining features derived from the complete input. + */ + virtual FeatureSet getRemainingFeatures() = 0; + + /** + * Used to distinguish between Vamp::Plugin and other potential + * sibling subclasses of PluginBase. Do not reimplement this + * function in your subclass. + */ + virtual std::string getType() const { return "Feature Extraction Plugin"; } + + /** + * Retrieve the input sample rate set on construction. + */ + float getInputSampleRate() const { return m_inputSampleRate; } + +protected: + Plugin(float inputSampleRate) : + m_inputSampleRate(inputSampleRate) { } + + float m_inputSampleRate; +}; + +} diff --git a/src/vamp-sdk/PluginAdapter.cpp b/src/vamp-sdk/PluginAdapter.cpp new file mode 100644 index 0000000..97f3d42 --- /dev/null +++ b/src/vamp-sdk/PluginAdapter.cpp @@ -0,0 +1,964 @@ +#include "vamp-sdk/PluginAdapter.h" + +#include +#include + +#include + +using std::map; +using std::vector; +using std::string; +using std::cerr; +using std::endl; +using std::mutex; +using std::lock_guard; + +namespace Vamp { + +class PluginAdapterBase::Impl +{ +public: + Impl(PluginAdapterBase *); + ~Impl(); + + const VampPluginDescriptor *getDescriptor(); + +protected: + PluginAdapterBase *m_base; + + static VampPluginHandle vampInstantiate(const VampPluginDescriptor *desc, + float inputSampleRate); + + static void vampCleanup(VampPluginHandle handle); + + static int vampInitialise(VampPluginHandle handle, unsigned int channels, + unsigned int stepSize, unsigned int blockSize); + + static void vampReset(VampPluginHandle handle); + + static float vampGetParameter(VampPluginHandle handle, int param); + static void vampSetParameter(VampPluginHandle handle, int param, float value); + + static unsigned int vampGetCurrentProgram(VampPluginHandle handle); + static void vampSelectProgram(VampPluginHandle handle, unsigned int program); + + static unsigned int vampGetPreferredStepSize(VampPluginHandle handle); + static unsigned int vampGetPreferredBlockSize(VampPluginHandle handle); + static unsigned int vampGetMinChannelCount(VampPluginHandle handle); + static unsigned int vampGetMaxChannelCount(VampPluginHandle handle); + + static unsigned int vampGetOutputCount(VampPluginHandle handle); + + static VampOutputDescriptor *vampGetOutputDescriptor(VampPluginHandle handle, + unsigned int i); + + static void vampReleaseOutputDescriptor(VampOutputDescriptor *desc); + + static VampFeatureList *vampProcess(VampPluginHandle handle, + const float *const *inputBuffers, + int sec, + int nsec); + + static VampFeatureList *vampGetRemainingFeatures(VampPluginHandle handle); + + static void vampReleaseFeatureSet(VampFeatureList *fs); + + void checkOutputMap(Plugin *plugin); + void markOutputsChanged(Plugin *plugin); + + void cleanup(Plugin *plugin); + unsigned int getOutputCount(Plugin *plugin); + VampOutputDescriptor *getOutputDescriptor(Plugin *plugin, unsigned int i); + VampFeatureList *process(Plugin *plugin, + const float *const *inputBuffers, + int sec, int nsec); + VampFeatureList *getRemainingFeatures(Plugin *plugin); + VampFeatureList *convertFeatures(Plugin *plugin, + const Plugin::FeatureSet &features); + + // maps both plugins and descriptors to adapters + typedef map AdapterMap; + + static AdapterMap *m_adapterMap; + + static mutex &adapterMapMutex() { + // If this mutex was a global static, then it might be + // destroyed before the last adapter, and we would end up + // trying to lock an invalid mutex when removing an adapter + // from the adapter map. To ensure it outlasts the adapters, + // we need to ensure it is constructed before the construction + // of any of them is complete, since destruction order is + // reverse of construction. So we have to make sure this is + // called from the PluginAdapterBase::Impl constructor below. + static mutex m; + return m; + } + + static Impl *lookupAdapter(VampPluginHandle); + + mutex m_mutex; // guards all of the below + + bool m_populated; + VampPluginDescriptor m_descriptor; + Plugin::ParameterList m_parameters; + Plugin::ProgramList m_programs; + + typedef map OutputMap; + OutputMap m_pluginOutputs; + + map m_fs; + map > m_fsizes; + map > > m_fvsizes; + void resizeFS(Plugin *plugin, int n); + void resizeFL(Plugin *plugin, int n, size_t sz); + void resizeFV(Plugin *plugin, int n, int j, size_t sz); +}; + +PluginAdapterBase::PluginAdapterBase() +{ + m_impl = new Impl(this); +} + +PluginAdapterBase::~PluginAdapterBase() +{ + delete m_impl; +} + +const VampPluginDescriptor * +PluginAdapterBase::getDescriptor() +{ + return m_impl->getDescriptor(); +} + +PluginAdapterBase::Impl::Impl(PluginAdapterBase *base) : + m_base(base), + m_populated(false) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl[" << this << "]::Impl" << endl; +#endif + + (void)adapterMapMutex(); // see comment in adapterMapMutex function above +} + +const VampPluginDescriptor * +PluginAdapterBase::Impl::getDescriptor() +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl[" << this << "]::getDescriptor" << endl; +#endif + + lock_guard guard(m_mutex); + + if (m_populated) return &m_descriptor; + + Plugin *plugin = m_base->createPlugin(48000); + + if (!plugin) { + cerr << "PluginAdapterBase::Impl::getDescriptor: Failed to create plugin" << endl; + return 0; + } + + if (plugin->getVampApiVersion() != VAMP_API_VERSION) { + cerr << "Vamp::PluginAdapterBase::Impl::getDescriptor: ERROR: " + << "API version " << plugin->getVampApiVersion() + << " for\nplugin \"" << plugin->getIdentifier() << "\" " + << "differs from version " + << VAMP_API_VERSION << " for adapter.\n" + << "This plugin is probably linked against a different version of the Vamp SDK\n" + << "from the version it was compiled with. It will need to be re-linked correctly\n" + << "before it can be used." << endl; + delete plugin; + return 0; + } + + m_parameters = plugin->getParameterDescriptors(); + m_programs = plugin->getPrograms(); + + m_descriptor.vampApiVersion = plugin->getVampApiVersion(); + m_descriptor.identifier = strdup(plugin->getIdentifier().c_str()); + m_descriptor.name = strdup(plugin->getName().c_str()); + m_descriptor.description = strdup(plugin->getDescription().c_str()); + m_descriptor.maker = strdup(plugin->getMaker().c_str()); + m_descriptor.pluginVersion = plugin->getPluginVersion(); + m_descriptor.copyright = strdup(plugin->getCopyright().c_str()); + + m_descriptor.parameterCount = m_parameters.size(); + m_descriptor.parameters = (const VampParameterDescriptor **) + malloc(m_parameters.size() * sizeof(VampParameterDescriptor)); + + unsigned int i; + + for (i = 0; i < m_parameters.size(); ++i) { + VampParameterDescriptor *desc = (VampParameterDescriptor *) + malloc(sizeof(VampParameterDescriptor)); + desc->identifier = strdup(m_parameters[i].identifier.c_str()); + desc->name = strdup(m_parameters[i].name.c_str()); + desc->description = strdup(m_parameters[i].description.c_str()); + desc->unit = strdup(m_parameters[i].unit.c_str()); + desc->minValue = m_parameters[i].minValue; + desc->maxValue = m_parameters[i].maxValue; + desc->defaultValue = m_parameters[i].defaultValue; + desc->isQuantized = m_parameters[i].isQuantized; + desc->quantizeStep = m_parameters[i].quantizeStep; + desc->valueNames = 0; + if (desc->isQuantized && !m_parameters[i].valueNames.empty()) { + desc->valueNames = (const char **) + malloc((m_parameters[i].valueNames.size()+1) * sizeof(char *)); + for (unsigned int j = 0; j < m_parameters[i].valueNames.size(); ++j) { + desc->valueNames[j] = strdup(m_parameters[i].valueNames[j].c_str()); + } + desc->valueNames[m_parameters[i].valueNames.size()] = 0; + } + m_descriptor.parameters[i] = desc; + } + + m_descriptor.programCount = m_programs.size(); + m_descriptor.programs = (const char **) + malloc(m_programs.size() * sizeof(const char *)); + + for (i = 0; i < m_programs.size(); ++i) { + m_descriptor.programs[i] = strdup(m_programs[i].c_str()); + } + + if (plugin->getInputDomain() == Plugin::FrequencyDomain) { + m_descriptor.inputDomain = vampFrequencyDomain; + } else { + m_descriptor.inputDomain = vampTimeDomain; + } + + m_descriptor.instantiate = vampInstantiate; + m_descriptor.cleanup = vampCleanup; + m_descriptor.initialise = vampInitialise; + m_descriptor.reset = vampReset; + m_descriptor.getParameter = vampGetParameter; + m_descriptor.setParameter = vampSetParameter; + m_descriptor.getCurrentProgram = vampGetCurrentProgram; + m_descriptor.selectProgram = vampSelectProgram; + m_descriptor.getPreferredStepSize = vampGetPreferredStepSize; + m_descriptor.getPreferredBlockSize = vampGetPreferredBlockSize; + m_descriptor.getMinChannelCount = vampGetMinChannelCount; + m_descriptor.getMaxChannelCount = vampGetMaxChannelCount; + m_descriptor.getOutputCount = vampGetOutputCount; + m_descriptor.getOutputDescriptor = vampGetOutputDescriptor; + m_descriptor.releaseOutputDescriptor = vampReleaseOutputDescriptor; + m_descriptor.process = vampProcess; + m_descriptor.getRemainingFeatures = vampGetRemainingFeatures; + m_descriptor.releaseFeatureSet = vampReleaseFeatureSet; + + lock_guard adapterMapGuard(adapterMapMutex()); + + if (!m_adapterMap) { + m_adapterMap = new AdapterMap; + } + (*m_adapterMap)[&m_descriptor] = this; + + delete plugin; + + m_populated = true; + return &m_descriptor; +} + +PluginAdapterBase::Impl::~Impl() +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl[" << this << "]::~Impl" << endl; +#endif + + lock_guard guard(m_mutex); + + if (!m_populated) return; + + free((void *)m_descriptor.identifier); + free((void *)m_descriptor.name); + free((void *)m_descriptor.description); + free((void *)m_descriptor.maker); + free((void *)m_descriptor.copyright); + + for (unsigned int i = 0; i < m_descriptor.parameterCount; ++i) { + const VampParameterDescriptor *desc = m_descriptor.parameters[i]; + free((void *)desc->identifier); + free((void *)desc->name); + free((void *)desc->description); + free((void *)desc->unit); + if (desc->valueNames) { + for (unsigned int j = 0; desc->valueNames[j]; ++j) { + free((void *)desc->valueNames[j]); + } + free((void *)desc->valueNames); + } + free((void *)desc); + } + free((void *)m_descriptor.parameters); + + for (unsigned int i = 0; i < m_descriptor.programCount; ++i) { + free((void *)m_descriptor.programs[i]); + } + free((void *)m_descriptor.programs); + + lock_guard adapterMapGuard(adapterMapMutex()); + + if (m_adapterMap) { + + m_adapterMap->erase(&m_descriptor); + + if (m_adapterMap->empty()) { + delete m_adapterMap; + m_adapterMap = 0; + } + } +} + +PluginAdapterBase::Impl * +PluginAdapterBase::Impl::lookupAdapter(VampPluginHandle handle) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::lookupAdapter(" << handle << ")" << endl; +#endif + + lock_guard adapterMapGuard(adapterMapMutex()); + + if (!m_adapterMap) return 0; + AdapterMap::const_iterator i = m_adapterMap->find(handle); + if (i == m_adapterMap->end()) return 0; + return i->second; +} + +VampPluginHandle +PluginAdapterBase::Impl::vampInstantiate(const VampPluginDescriptor *desc, + float inputSampleRate) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampInstantiate(" << desc << ")" << endl; +#endif + + lock_guard adapterMapGuard(adapterMapMutex()); + + if (!m_adapterMap) { + m_adapterMap = new AdapterMap(); + } + + if (m_adapterMap->find(desc) == m_adapterMap->end()) { + cerr << "WARNING: PluginAdapterBase::Impl::vampInstantiate: Descriptor " << desc << " not in adapter map" << endl; + return 0; + } + + Impl *adapter = (*m_adapterMap)[desc]; + if (desc != &adapter->m_descriptor) return 0; + + Plugin *plugin = adapter->m_base->createPlugin(inputSampleRate); + if (plugin) { + (*m_adapterMap)[plugin] = adapter; + } + +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampInstantiate(" << desc << "): returning handle " << plugin << endl; +#endif + + return plugin; +} + +void +PluginAdapterBase::Impl::vampCleanup(VampPluginHandle handle) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampCleanup(" << handle << ")" << endl; +#endif + + Impl *adapter = lookupAdapter(handle); + if (!adapter) { + delete ((Plugin *)handle); + return; + } + adapter->cleanup(((Plugin *)handle)); +} + +int +PluginAdapterBase::Impl::vampInitialise(VampPluginHandle handle, + unsigned int channels, + unsigned int stepSize, + unsigned int blockSize) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampInitialise(" << handle << ", " << channels << ", " << stepSize << ", " << blockSize << ")" << endl; +#endif + + Impl *adapter = lookupAdapter(handle); + if (!adapter) return 0; + bool result = ((Plugin *)handle)->initialise(channels, stepSize, blockSize); + adapter->markOutputsChanged((Plugin *)handle); + return result ? 1 : 0; +} + +void +PluginAdapterBase::Impl::vampReset(VampPluginHandle handle) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampReset(" << handle << ")" << endl; +#endif + + ((Plugin *)handle)->reset(); +} + +float +PluginAdapterBase::Impl::vampGetParameter(VampPluginHandle handle, + int param) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampGetParameter(" << handle << ", " << param << ")" << endl; +#endif + + Impl *adapter = lookupAdapter(handle); + if (!adapter) return 0.0; + Plugin::ParameterList &list = adapter->m_parameters; + return ((Plugin *)handle)->getParameter(list[param].identifier); +} + +void +PluginAdapterBase::Impl::vampSetParameter(VampPluginHandle handle, + int param, float value) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampSetParameter(" << handle << ", " << param << ", " << value << ")" << endl; +#endif + + Impl *adapter = lookupAdapter(handle); + if (!adapter) return; + Plugin::ParameterList &list = adapter->m_parameters; + ((Plugin *)handle)->setParameter(list[param].identifier, value); + adapter->markOutputsChanged((Plugin *)handle); +} + +unsigned int +PluginAdapterBase::Impl::vampGetCurrentProgram(VampPluginHandle handle) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampGetCurrentProgram(" << handle << ")" << endl; +#endif + + Impl *adapter = lookupAdapter(handle); + if (!adapter) return 0; + Plugin::ProgramList &list = adapter->m_programs; + string program = ((Plugin *)handle)->getCurrentProgram(); + for (unsigned int i = 0; i < list.size(); ++i) { + if (list[i] == program) return i; + } + return 0; +} + +void +PluginAdapterBase::Impl::vampSelectProgram(VampPluginHandle handle, + unsigned int program) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampSelectProgram(" << handle << ", " << program << ")" << endl; +#endif + + Impl *adapter = lookupAdapter(handle); + if (!adapter) return; + + Plugin::ProgramList &list = adapter->m_programs; + ((Plugin *)handle)->selectProgram(list[program]); + + adapter->markOutputsChanged((Plugin *)handle); +} + +unsigned int +PluginAdapterBase::Impl::vampGetPreferredStepSize(VampPluginHandle handle) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampGetPreferredStepSize(" << handle << ")" << endl; +#endif + + return ((Plugin *)handle)->getPreferredStepSize(); +} + +unsigned int +PluginAdapterBase::Impl::vampGetPreferredBlockSize(VampPluginHandle handle) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampGetPreferredBlockSize(" << handle << ")" << endl; +#endif + + return ((Plugin *)handle)->getPreferredBlockSize(); +} + +unsigned int +PluginAdapterBase::Impl::vampGetMinChannelCount(VampPluginHandle handle) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampGetMinChannelCount(" << handle << ")" << endl; +#endif + + return ((Plugin *)handle)->getMinChannelCount(); +} + +unsigned int +PluginAdapterBase::Impl::vampGetMaxChannelCount(VampPluginHandle handle) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampGetMaxChannelCount(" << handle << ")" << endl; +#endif + + return ((Plugin *)handle)->getMaxChannelCount(); +} + +unsigned int +PluginAdapterBase::Impl::vampGetOutputCount(VampPluginHandle handle) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampGetOutputCount(" << handle << ")" << endl; +#endif + + Impl *adapter = lookupAdapter(handle); + +// cerr << "vampGetOutputCount: handle " << handle << " -> adapter "<< adapter << endl; + + if (!adapter) return 0; + return adapter->getOutputCount((Plugin *)handle); +} + +VampOutputDescriptor * +PluginAdapterBase::Impl::vampGetOutputDescriptor(VampPluginHandle handle, + unsigned int i) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampGetOutputDescriptor(" << handle << ", " << i << ")" << endl; +#endif + + Impl *adapter = lookupAdapter(handle); + +// cerr << "vampGetOutputDescriptor: handle " << handle << " -> adapter "<< adapter << endl; + + if (!adapter) return 0; + return adapter->getOutputDescriptor((Plugin *)handle, i); +} + +void +PluginAdapterBase::Impl::vampReleaseOutputDescriptor(VampOutputDescriptor *desc) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampReleaseOutputDescriptor(" << desc << ")" << endl; +#endif + + if (desc->identifier) free((void *)desc->identifier); + if (desc->name) free((void *)desc->name); + if (desc->description) free((void *)desc->description); + if (desc->unit) free((void *)desc->unit); + if (desc->hasFixedBinCount && desc->binNames) { + for (unsigned int i = 0; i < desc->binCount; ++i) { + if (desc->binNames[i]) { + free((void *)desc->binNames[i]); + } + } + } + if (desc->binNames) free((void *)desc->binNames); + free((void *)desc); +} + +VampFeatureList * +PluginAdapterBase::Impl::vampProcess(VampPluginHandle handle, + const float *const *inputBuffers, + int sec, + int nsec) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampProcess(" << handle << ", " << sec << ", " << nsec << ")" << endl; +#endif + + Impl *adapter = lookupAdapter(handle); + if (!adapter) return 0; + return adapter->process((Plugin *)handle, inputBuffers, sec, nsec); +} + +VampFeatureList * +PluginAdapterBase::Impl::vampGetRemainingFeatures(VampPluginHandle handle) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampGetRemainingFeatures(" << handle << ")" << endl; +#endif + + Impl *adapter = lookupAdapter(handle); + if (!adapter) return 0; + return adapter->getRemainingFeatures((Plugin *)handle); +} + +void +PluginAdapterBase::Impl::vampReleaseFeatureSet(VampFeatureList *) +{ +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::vampReleaseFeatureSet" << endl; +#endif +} + +void +PluginAdapterBase::Impl::cleanup(Plugin *plugin) +{ + // at this point no mutex is held + + lock_guard adapterMapGuard(adapterMapMutex()); + lock_guard guard(m_mutex); + + if (m_fs.find(plugin) != m_fs.end()) { + size_t outputCount = 0; + if (m_pluginOutputs[plugin]) { + outputCount = m_pluginOutputs[plugin]->size(); + } +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::cleanup: " << outputCount << " output(s)" << endl; +#endif + VampFeatureList *list = m_fs[plugin]; + for (unsigned int i = 0; i < outputCount; ++i) { + for (unsigned int j = 0; j < m_fsizes[plugin][i]; ++j) { + if (list[i].features[j].v1.label) { + free(list[i].features[j].v1.label); + } + if (list[i].features[j].v1.values) { + free(list[i].features[j].v1.values); + } + } + if (list[i].features) free(list[i].features); + } + if (list) free((void *)list); + m_fs.erase(plugin); + m_fsizes.erase(plugin); + m_fvsizes.erase(plugin); + } + + if (m_pluginOutputs.find(plugin) != m_pluginOutputs.end()) { + delete m_pluginOutputs[plugin]; + m_pluginOutputs.erase(plugin); + } + + if (m_adapterMap) { + m_adapterMap->erase(plugin); + + if (m_adapterMap->empty()) { + delete m_adapterMap; + m_adapterMap = 0; + } + } + + delete ((Plugin *)plugin); +} + +void +PluginAdapterBase::Impl::checkOutputMap(Plugin *plugin) +{ + // must be called with m_mutex held + + OutputMap::iterator i = m_pluginOutputs.find(plugin); + + if (i == m_pluginOutputs.end() || !i->second) { + + m_pluginOutputs[plugin] = new Plugin::OutputList + (plugin->getOutputDescriptors()); + +// cerr << "PluginAdapterBase::Impl::checkOutputMap: Have " << m_pluginOutputs[plugin]->size() << " outputs for plugin " << plugin->getIdentifier() << endl; + } +} + +void +PluginAdapterBase::Impl::markOutputsChanged(Plugin *plugin) +{ + lock_guard guard(m_mutex); + + OutputMap::iterator i = m_pluginOutputs.find(plugin); + +// cerr << "PluginAdapterBase::Impl::markOutputsChanged" << endl; + + if (i != m_pluginOutputs.end()) { + + Plugin::OutputList *list = i->second; + m_pluginOutputs.erase(i); + delete list; + } +} + +unsigned int +PluginAdapterBase::Impl::getOutputCount(Plugin *plugin) +{ + lock_guard guard(m_mutex); + + checkOutputMap(plugin); + + return m_pluginOutputs[plugin]->size(); +} + +VampOutputDescriptor * +PluginAdapterBase::Impl::getOutputDescriptor(Plugin *plugin, + unsigned int i) +{ + lock_guard guard(m_mutex); + + checkOutputMap(plugin); + + Plugin::OutputDescriptor &od = + (*m_pluginOutputs[plugin])[i]; + + VampOutputDescriptor *desc = (VampOutputDescriptor *) + malloc(sizeof(VampOutputDescriptor)); + + desc->identifier = strdup(od.identifier.c_str()); + desc->name = strdup(od.name.c_str()); + desc->description = strdup(od.description.c_str()); + desc->unit = strdup(od.unit.c_str()); + desc->hasFixedBinCount = od.hasFixedBinCount; + desc->binCount = od.binCount; + + if (od.hasFixedBinCount && od.binCount > 0 + // We would like to do "&& !od.binNames.empty()" here -- but we + // can't, because it will crash older versions of the host adapter + // which try to copy the names across whenever the bin count is + // non-zero, regardless of whether they exist or not + ) { + desc->binNames = (const char **) + malloc(od.binCount * sizeof(const char *)); + + for (unsigned int i = 0; i < od.binCount; ++i) { + if (i < od.binNames.size()) { + desc->binNames[i] = strdup(od.binNames[i].c_str()); + } else { + desc->binNames[i] = 0; + } + } + } else { + desc->binNames = 0; + } + + desc->hasKnownExtents = od.hasKnownExtents; + desc->minValue = od.minValue; + desc->maxValue = od.maxValue; + desc->isQuantized = od.isQuantized; + desc->quantizeStep = od.quantizeStep; + + switch (od.sampleType) { + case Plugin::OutputDescriptor::OneSamplePerStep: + desc->sampleType = vampOneSamplePerStep; break; + case Plugin::OutputDescriptor::FixedSampleRate: + desc->sampleType = vampFixedSampleRate; break; + case Plugin::OutputDescriptor::VariableSampleRate: + desc->sampleType = vampVariableSampleRate; break; + } + + desc->sampleRate = od.sampleRate; + desc->hasDuration = od.hasDuration; + + return desc; +} + +VampFeatureList * +PluginAdapterBase::Impl::process(Plugin *plugin, + const float *const *inputBuffers, + int sec, int nsec) +{ +// cerr << "PluginAdapterBase::Impl::process" << endl; + + RealTime rt(sec, nsec); + + // We don't want to hold the mutex during the actual process call, + // only while looking up the associated metadata + { + lock_guard guard(m_mutex); + checkOutputMap(plugin); + } + + return convertFeatures(plugin, plugin->process(inputBuffers, rt)); +} + +VampFeatureList * +PluginAdapterBase::Impl::getRemainingFeatures(Plugin *plugin) +{ +// cerr << "PluginAdapterBase::Impl::getRemainingFeatures" << endl; + + // We don't want to hold the mutex during the actual call, only + // while looking up the associated metadata + { + lock_guard guard(m_mutex); + checkOutputMap(plugin); + } + + return convertFeatures(plugin, plugin->getRemainingFeatures()); +} + +VampFeatureList * +PluginAdapterBase::Impl::convertFeatures(Plugin *plugin, + const Plugin::FeatureSet &features) +{ + lock_guard guard(m_mutex); + + int lastN = -1; + + int outputCount = 0; + if (m_pluginOutputs[plugin]) outputCount = m_pluginOutputs[plugin]->size(); + + resizeFS(plugin, outputCount); + VampFeatureList *fs = m_fs[plugin]; + +// cerr << "PluginAdapter(v2)::convertFeatures: NOTE: sizeof(Feature) == " << sizeof(Plugin::Feature) << ", sizeof(VampFeature) == " << sizeof(VampFeature) << ", sizeof(VampFeatureList) == " << sizeof(VampFeatureList) << endl; + + for (Plugin::FeatureSet::const_iterator fi = features.begin(); + fi != features.end(); ++fi) { + + int n = fi->first; + +// cerr << "PluginAdapterBase::Impl::convertFeatures: n = " << n << endl; + + if (n >= int(outputCount)) { + cerr << "WARNING: PluginAdapterBase::Impl::convertFeatures: Too many outputs from plugin (" << n+1 << ", only should be " << outputCount << ")" << endl; + continue; + } + + if (n > lastN + 1) { + for (int i = lastN + 1; i < n; ++i) { + fs[i].featureCount = 0; + } + } + + const Plugin::FeatureList &fl = fi->second; + + size_t sz = fl.size(); + if (sz > m_fsizes[plugin][n]) resizeFL(plugin, n, sz); + fs[n].featureCount = sz; + + for (size_t j = 0; j < sz; ++j) { + +// cerr << "PluginAdapterBase::Impl::convertFeatures: j = " << j << endl; + + VampFeature *feature = &fs[n].features[j].v1; + + feature->hasTimestamp = fl[j].hasTimestamp; + feature->sec = fl[j].timestamp.sec; + feature->nsec = fl[j].timestamp.nsec; + feature->valueCount = fl[j].values.size(); + + VampFeatureV2 *v2 = &fs[n].features[j + sz].v2; + + v2->hasDuration = fl[j].hasDuration; + v2->durationSec = fl[j].duration.sec; + v2->durationNsec = fl[j].duration.nsec; + + if (feature->label) free(feature->label); + + if (fl[j].label.empty()) { + feature->label = 0; + } else { + feature->label = strdup(fl[j].label.c_str()); + } + + if (feature->valueCount > m_fvsizes[plugin][n][j]) { + resizeFV(plugin, n, j, feature->valueCount); + } + + for (unsigned int k = 0; k < feature->valueCount; ++k) { +// cerr << "PluginAdapterBase::Impl::convertFeatures: k = " << k << endl; + feature->values[k] = fl[j].values[k]; + } + } + + lastN = n; + } + + if (lastN == -1) return 0; + + if (int(outputCount) > lastN + 1) { + for (int i = lastN + 1; i < int(outputCount); ++i) { + fs[i].featureCount = 0; + } + } + +// cerr << "PluginAdapter(v2)::convertFeatures: NOTE: have " << outputCount << " outputs" << endl; +// for (int i = 0; i < outputCount; ++i) { +// cerr << "PluginAdapter(v2)::convertFeatures: NOTE: output " << i << " has " << fs[i].featureCount << " features" << endl; +// } + + + return fs; +} + +void +PluginAdapterBase::Impl::resizeFS(Plugin *plugin, int n) +{ + // called with m_mutex held + +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::resizeFS(" << plugin << ", " << n << ")" << endl; +#endif + + int i = m_fsizes[plugin].size(); + if (i >= n) return; + +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "resizing from " << i << endl; +#endif + + m_fs[plugin] = (VampFeatureList *)realloc + (m_fs[plugin], n * sizeof(VampFeatureList)); + + while (i < n) { + m_fs[plugin][i].featureCount = 0; + m_fs[plugin][i].features = 0; + m_fsizes[plugin].push_back(0); + m_fvsizes[plugin].push_back(vector()); + i++; + } +} + +void +PluginAdapterBase::Impl::resizeFL(Plugin *plugin, int n, size_t sz) +{ + // called with m_mutex held + +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::resizeFL(" << plugin << ", " << n << ", " + << sz << ")" << endl; +#endif + + size_t i = m_fsizes[plugin][n]; + if (i >= sz) return; + +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "resizing from " << i << endl; +#endif + + m_fs[plugin][n].features = (VampFeatureUnion *)realloc + (m_fs[plugin][n].features, 2 * sz * sizeof(VampFeatureUnion)); + + while (m_fsizes[plugin][n] < sz) { + m_fs[plugin][n].features[m_fsizes[plugin][n]].v1.hasTimestamp = 0; + m_fs[plugin][n].features[m_fsizes[plugin][n]].v1.valueCount = 0; + m_fs[plugin][n].features[m_fsizes[plugin][n]].v1.values = 0; + m_fs[plugin][n].features[m_fsizes[plugin][n]].v1.label = 0; + m_fs[plugin][n].features[m_fsizes[plugin][n] + sz].v2.hasDuration = 0; + m_fvsizes[plugin][n].push_back(0); + m_fsizes[plugin][n]++; + } +} + +void +PluginAdapterBase::Impl::resizeFV(Plugin *plugin, int n, int j, size_t sz) +{ + // called with m_mutex held + +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "PluginAdapterBase::Impl::resizeFV(" << plugin << ", " << n << ", " + << j << ", " << sz << ")" << endl; +#endif + + size_t i = m_fvsizes[plugin][n][j]; + if (i >= sz) return; + +#ifdef DEBUG_PLUGIN_ADAPTER + cerr << "resizing from " << i << endl; +#endif + + m_fs[plugin][n].features[j].v1.values = (float *)realloc + (m_fs[plugin][n].features[j].v1.values, sz * sizeof(float)); + + m_fvsizes[plugin][n][j] = sz; +} + +PluginAdapterBase::Impl::AdapterMap * +PluginAdapterBase::Impl::m_adapterMap = 0; + +} diff --git a/src/vamp-sdk/PluginAdapter.h b/src/vamp-sdk/PluginAdapter.h new file mode 100644 index 0000000..7593eea --- /dev/null +++ b/src/vamp-sdk/PluginAdapter.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include "vamp.h" + +#include "vamp-sdk/Plugin.h" + +namespace Vamp { + +/** + * \class PluginAdapterBase PluginAdapter.h + * + * PluginAdapter and PluginAdapterBase provide a wrapper class that a + * plugin library can use to make its C++ Vamp::Plugin objects + * available through the Vamp C API. + * + * Almost all Vamp plugin libraries will want to make use of this. To + * do so, all they need to do is declare a PluginAdapter for each + * plugin class T in their library. It's very simple, and you need to + * know absolutely nothing about how it works in order to use it. + * Just cut and paste from an existing plugin's discovery function. + * \see vampGetPluginDescriptor + */ + +class PluginAdapterBase +{ +public: + virtual ~PluginAdapterBase(); + + /** + * Return a VampPluginDescriptor describing the plugin that is + * wrapped by this adapter. + */ + const VampPluginDescriptor *getDescriptor(); + +protected: + PluginAdapterBase(); + + virtual Plugin *createPlugin(float inputSampleRate) = 0; + + class Impl; + Impl *m_impl; +}; + +/** + * \class PluginAdapter PluginAdapter.h + * + * PluginAdapter turns a PluginAdapterBase into a specific wrapper for + * a particular plugin implementation. + * + * See PluginAdapterBase. + */ + +template +class PluginAdapter : public PluginAdapterBase +{ +public: + PluginAdapter() : PluginAdapterBase() { } + virtual ~PluginAdapter() { } + +protected: + Plugin *createPlugin(float inputSampleRate) { + P *p = new P(inputSampleRate); + Plugin *plugin = dynamic_cast(p); + if (!plugin) { + std::cerr << "ERROR: PluginAdapter::createPlugin: " + << "Template type is not a plugin!" + << std::endl; + delete p; + return 0; + } + return plugin; + } +}; + +} diff --git a/src/vamp-sdk/PluginBase.h b/src/vamp-sdk/PluginBase.h new file mode 100644 index 0000000..8596cf9 --- /dev/null +++ b/src/vamp-sdk/PluginBase.h @@ -0,0 +1,218 @@ +#pragma once + +#include +#include + +namespace Vamp { + +/** + * A base class for plugins with optional configurable parameters, + * programs, etc. The Vamp::Plugin is derived from this, and + * individual Vamp plugins should derive from that. + * + * This class does not provide the necessary interfaces to instantiate + * or run a plugin. It only specifies an interface for retrieving + * those controls that the host may wish to show to the user for + * editing. It could meaningfully be subclassed by real-time plugins + * or other sorts of plugin as well as Vamp plugins. + */ + +class PluginBase +{ +public: + virtual ~PluginBase() { } + + /** + * Get the Vamp API compatibility level of the plugin. + */ + virtual unsigned int getVampApiVersion() const { return 2; } + + /** + * Get the computer-usable name of the plugin. This should be + * reasonably short and contain no whitespace or punctuation + * characters. It may only contain the characters [a-zA-Z0-9_-]. + * This is the authoritative way for a program to identify a + * plugin within a given library. + * + * This text may be visible to the user, but it should not be the + * main text used to identify a plugin to the user (that will be + * the name, below). + * + * Example: "zero_crossings" + */ + virtual std::string getIdentifier() const = 0; + + /** + * Get a human-readable name or title of the plugin. This + * should be brief and self-contained, as it may be used to + * identify the plugin to the user in isolation (i.e. without also + * showing the plugin's "identifier"). + * + * Example: "Zero Crossings" + */ + virtual std::string getName() const = 0; + + /** + * Get a human-readable description for the plugin, typically + * a line of text that may optionally be displayed in addition + * to the plugin's "name". May be empty if the name has said + * it all already. + * + * Example: "Detect and count zero crossing points" + */ + virtual std::string getDescription() const = 0; + + /** + * Get the name of the author or vendor of the plugin in + * human-readable form. This should be a short identifying text, + * as it may be used to label plugins from the same source in a + * menu or similar. + */ + virtual std::string getMaker() const = 0; + + /** + * Get the copyright statement or licensing summary for the + * plugin. This can be an informative text, without the same + * presentation constraints as mentioned for getMaker above. + */ + virtual std::string getCopyright() const = 0; + + /** + * Get the version number of the plugin. + */ + virtual int getPluginVersion() const = 0; + + + struct ParameterDescriptor + { + /** + * The name of the parameter, in computer-usable form. Should + * be reasonably short, and may only contain the characters + * [a-zA-Z0-9_-]. + */ + std::string identifier; + + /** + * The human-readable name of the parameter. + */ + std::string name; + + /** + * A human-readable short text describing the parameter. May be + * empty if the name has said it all already. + */ + std::string description; + + /** + * The unit of the parameter, in human-readable form. + */ + std::string unit; + + /** + * The minimum value of the parameter. + */ + float minValue; + + /** + * The maximum value of the parameter. + */ + float maxValue; + + /** + * The default value of the parameter. The plugin should + * ensure that parameters have this value on initialisation + * (i.e. the host is not required to explicitly set parameters + * if it wants to use their default values). + */ + float defaultValue; + + /** + * True if the parameter values are quantized to a particular + * resolution. + */ + bool isQuantized; + + /** + * Quantization resolution of the parameter values (e.g. 1.0 + * if they are all integers). Undefined if isQuantized is + * false. + */ + float quantizeStep; + + /** + * Names for the quantized values. If isQuantized is true, + * this may either be empty or contain one string for each of + * the quantize steps from minValue up to maxValue inclusive. + * Undefined if isQuantized is false. + * + * If these names are provided, they should be shown to the + * user in preference to the values themselves. The user may + * never see the actual numeric values unless they are also + * encoded in the names. + */ + std::vector valueNames; + + ParameterDescriptor() : // the defaults are invalid: you must set them + minValue(0), + maxValue(0), + defaultValue(0), + isQuantized(false), + quantizeStep(0) { } + }; + + typedef std::vector ParameterList; + + /** + * Get the controllable parameters of this plugin. + */ + virtual ParameterList getParameterDescriptors() const { + return ParameterList(); + } + + /** + * Get the value of a named parameter. The argument is the identifier + * field from that parameter's descriptor. + */ + virtual float getParameter(std::string) const { return 0.0; } + + /** + * Set a named parameter. The first argument is the identifier field + * from that parameter's descriptor. + */ + virtual void setParameter(std::string, float) { } + + + typedef std::vector ProgramList; + + /** + * Get the program settings available in this plugin. A program + * is a named shorthand for a set of parameter values; changing + * the program may cause the plugin to alter the values of its + * published parameters (and/or non-public internal processing + * parameters). The host should re-read the plugin's parameter + * values after setting a new program. + * + * The programs must have unique names. + */ + virtual ProgramList getPrograms() const { return ProgramList(); } + + /** + * Get the current program. + */ + virtual std::string getCurrentProgram() const { return ""; } + + /** + * Select a program. (If the given program name is not one of the + * available programs, do nothing.) + */ + virtual void selectProgram(std::string) { } + + /** + * Get the type of plugin. This is to be implemented by the + * immediate subclass, not by actual plugins. Do not attempt to + * implement this in plugin code. + */ + virtual std::string getType() const = 0; +}; + +} diff --git a/src/vamp-sdk/RealTime.cpp b/src/vamp-sdk/RealTime.cpp new file mode 100644 index 0000000..f59eb2e --- /dev/null +++ b/src/vamp-sdk/RealTime.cpp @@ -0,0 +1,199 @@ +#include +#include + +#if (defined(__GNUC__)) && (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + +using std::cerr; +using std::endl; + +#ifndef _WIN32 +#include +#endif + +#include "vamp-sdk/RealTime.h" + +namespace Vamp { + +// A RealTime consists of two ints that must be at least 32 bits each. +// A signed 32-bit int can store values exceeding +/- 2 billion. This +// means we can safely use our lower int for nanoseconds, as there are +// 1 billion nanoseconds in a second and we need to handle double that +// because of the implementations of addition etc that we use. +// +// The maximum valid RealTime on a 32-bit system is somewhere around +// 68 years: 999999999 nanoseconds longer than the classic Unix epoch. + +#define ONE_BILLION 1000000000 + +RealTime::RealTime(int s, int n) : + sec(s), nsec(n) +{ + while (nsec <= -ONE_BILLION && sec > INT_MIN) { nsec += ONE_BILLION; --sec; } + while (nsec >= ONE_BILLION && sec < INT_MAX) { nsec -= ONE_BILLION; ++sec; } + while (nsec > 0 && sec < 0) { nsec -= ONE_BILLION; ++sec; } + while (nsec < 0 && sec > 0) { nsec += ONE_BILLION; --sec; } +} + +RealTime +RealTime::fromSeconds(double sec) +{ + if (sec != sec) { // NaN + cerr << "ERROR: NaN/Inf passed to Vamp::RealTime::fromSeconds" << endl; + return RealTime::zeroTime; + } else if (sec >= 0) { + return RealTime(int(sec), int((sec - int(sec)) * ONE_BILLION + 0.5)); + } else { + return -fromSeconds(-sec); + } +} + +RealTime +RealTime::fromMilliseconds(int msec) +{ + return RealTime(msec / 1000, (msec % 1000) * 1000000); +} + +#ifndef _WIN32 +RealTime +RealTime::fromTimeval(const struct timeval &tv) +{ + return RealTime(int(tv.tv_sec), int(tv.tv_usec * 1000)); +} +#endif + +std::ostream &operator<<(std::ostream &out, const RealTime &rt) +{ + if (rt < RealTime::zeroTime) { + out << "-"; + } else { + out << " "; + } + + int s = (rt.sec < 0 ? -rt.sec : rt.sec); + int n = (rt.nsec < 0 ? -rt.nsec : rt.nsec); + + out << s << "."; + + int nn(n); + if (nn == 0) out << "00000000"; + else while (nn < (ONE_BILLION / 10)) { + out << "0"; + nn *= 10; + } + + out << n << "R"; + return out; +} + +std::string +RealTime::toString() const +{ + std::stringstream out; + out << *this; + + std::string s = out.str(); + + // remove trailing R + return s.substr(0, s.length() - 1); +} + +std::string +RealTime::toText(bool fixedDp) const +{ + if (*this < RealTime::zeroTime) return "-" + (-*this).toText(fixedDp); + + std::stringstream out; + + if (sec >= 3600) { + out << (sec / 3600) << ":"; + } + + if (sec >= 60) { + int minutes = (sec % 3600) / 60; + if (sec >= 3600 && minutes < 10) out << "0"; + out << minutes << ":"; + } + + if (sec >= 10) { + out << ((sec % 60) / 10); + } + + out << (sec % 10); + + int ms = msec(); + + if (ms != 0) { + out << "."; + out << (ms / 100); + ms = ms % 100; + if (ms != 0) { + out << (ms / 10); + ms = ms % 10; + } else if (fixedDp) { + out << "0"; + } + if (ms != 0) { + out << ms; + } else if (fixedDp) { + out << "0"; + } + } else if (fixedDp) { + out << ".000"; + } + + std::string s = out.str(); + + return s; +} + +RealTime +RealTime::operator/(int d) const +{ + int secdiv = sec / d; + int secrem = sec % d; + + double nsecdiv = (double(nsec) + ONE_BILLION * double(secrem)) / d; + + return RealTime(secdiv, int(nsecdiv + 0.5)); +} + +double +RealTime::operator/(const RealTime &r) const +{ + double lTotal = double(sec) * ONE_BILLION + double(nsec); + double rTotal = double(r.sec) * ONE_BILLION + double(r.nsec); + + if (rTotal == 0) return 0.0; + else return lTotal/rTotal; +} + +long +RealTime::realTime2Frame(const RealTime &time, unsigned int sampleRate) +{ + if (time < zeroTime) return -realTime2Frame(-time, sampleRate); + double s = time.sec + double(time.nsec) / ONE_BILLION; + return long(s * sampleRate + 0.5); +} + +RealTime +RealTime::frame2RealTime(long frame, unsigned int sampleRate) +{ + if (frame < 0) return -frame2RealTime(-frame, sampleRate); + + int sec = int(frame / long(sampleRate)); + frame -= sec * long(sampleRate); + int nsec = (int)((double(frame) / double(sampleRate)) * ONE_BILLION + 0.5); + // Use ctor here instead of setting data members directly to + // ensure nsec > ONE_BILLION is handled properly. It's extremely + // unlikely, but not impossible. + return RealTime(sec, nsec); +} + +const RealTime RealTime::zeroTime(0,0); + +} diff --git a/src/vamp-sdk/RealTime.h b/src/vamp-sdk/RealTime.h new file mode 100644 index 0000000..0a931d8 --- /dev/null +++ b/src/vamp-sdk/RealTime.h @@ -0,0 +1,114 @@ +#pragma once + +#include +#include + +struct timeval; + +namespace Vamp { + +/** + * \class RealTime RealTime.h + * + * RealTime represents time values to nanosecond precision + * with accurate arithmetic and frame-rate conversion functions. + */ + +struct RealTime +{ + int sec; + int nsec; + + int usec() const { return nsec / 1000; } + int msec() const { return nsec / 1000000; } + + RealTime(): sec(0), nsec(0) {} + RealTime(int s, int n); + + RealTime(const RealTime &r) : + sec(r.sec), nsec(r.nsec) { } + + static RealTime fromSeconds(double sec); + static RealTime fromMilliseconds(int msec); + +#ifndef _WIN32 + static RealTime fromTimeval(const struct timeval &); +#endif + + RealTime &operator=(const RealTime &r) { + sec = r.sec; nsec = r.nsec; return *this; + } + + RealTime operator+(const RealTime &r) const { + return RealTime(sec + r.sec, nsec + r.nsec); + } + RealTime operator-(const RealTime &r) const { + return RealTime(sec - r.sec, nsec - r.nsec); + } + RealTime operator-() const { + return RealTime(-sec, -nsec); + } + + bool operator <(const RealTime &r) const { + if (sec == r.sec) return nsec < r.nsec; + else return sec < r.sec; + } + + bool operator >(const RealTime &r) const { + if (sec == r.sec) return nsec > r.nsec; + else return sec > r.sec; + } + + bool operator==(const RealTime &r) const { + return (sec == r.sec && nsec == r.nsec); + } + + bool operator!=(const RealTime &r) const { + return !(r == *this); + } + + bool operator>=(const RealTime &r) const { + if (sec == r.sec) return nsec >= r.nsec; + else return sec >= r.sec; + } + + bool operator<=(const RealTime &r) const { + if (sec == r.sec) return nsec <= r.nsec; + else return sec <= r.sec; + } + + RealTime operator/(int d) const; + + /** + * Return the ratio of two times. + */ + double operator/(const RealTime &r) const; + + /** + * Return a human-readable debug-type string to full precision + * (probably not a format to show to a user directly) + */ + std::string toString() const; + + /** + * Return a user-readable string to the nearest millisecond + * in a form like HH:MM:SS.mmm + */ + std::string toText(bool fixedDp = false) const; + + /** + * Convert a RealTime into a sample frame at the given sample rate. + */ + static long realTime2Frame(const RealTime &r, unsigned int sampleRate); + + /** + * Convert a sample frame at the given sample rate into a RealTime. + */ + static RealTime frame2RealTime(long frame, unsigned int sampleRate); + + static const RealTime zeroTime; +}; + +std::ostream &operator<<(std::ostream &out, const RealTime &rt); + +} diff --git a/src/vamp-sdk/acsymbols.cpp b/src/vamp-sdk/acsymbols.cpp new file mode 100644 index 0000000..8449e51 --- /dev/null +++ b/src/vamp-sdk/acsymbols.cpp @@ -0,0 +1,16 @@ +/* These stubs are provided so that autoconf can check library + * versions using C symbols only */ + +extern void libvampsdk_v_2_9_present(void) { } +extern void libvampsdk_v_2_8_present(void) { } +extern void libvampsdk_v_2_7_1_present(void) { } +extern void libvampsdk_v_2_7_present(void) { } +extern void libvampsdk_v_2_6_present(void) { } +extern void libvampsdk_v_2_5_present(void) { } +extern void libvampsdk_v_2_4_present(void) { } +extern void libvampsdk_v_2_3_1_present(void) { } +extern void libvampsdk_v_2_3_present(void) { } +extern void libvampsdk_v_2_2_1_present(void) { } +extern void libvampsdk_v_2_2_present(void) { } +extern void libvampsdk_v_2_1_present(void) { } +extern void libvampsdk_v_2_0_present(void) { } diff --git a/src/vamp-sdk/ext/vamp_kiss_fft.h b/src/vamp-sdk/ext/vamp_kiss_fft.h new file mode 100644 index 0000000..3d4d866 --- /dev/null +++ b/src/vamp-sdk/ext/vamp_kiss_fft.h @@ -0,0 +1,443 @@ +#pragma once + +#include +#include +#include +#include + +#include "vamp-sdk/ext/vamp_kiss_fft_guts.h" + +// #ifndef _VAMP_IN_PLUGINSDK +// # ifndef _VAMP_IN_HOSTSDK +// # error "Do not use the Vamp SDK mangled KissFFT header except through the Vamp SDK API" +// # endif +// #endif + +#ifdef VAMP_KISSFFT_USE_CPP_LINKAGE +#define VAMP_KISS_FFT_MALLOC ::malloc +#define VAMP_KISS_FFT_FREE ::free +#else +#ifdef __cplusplus +extern "C" { +#define VAMP_KISS_FFT_MALLOC malloc +#define VAMP_KISS_FFT_FREE free +#endif +#endif + +#define vamp_kiss_fft_scalar double + +typedef struct { + vamp_kiss_fft_scalar r; + vamp_kiss_fft_scalar i; +} vamp_kiss_fft_cpx; + + +struct vamp_kiss_fft_state{ + int nfft; + int inverse; + int factors[2*MAXFACTORS]; + vamp_kiss_fft_cpx twiddles[1]; +}; + +typedef struct vamp_kiss_fft_state* vamp_kiss_fft_cfg; + +static void kf_bfly2(vamp_kiss_fft_cpx * Fout, const size_t fstride, const vamp_kiss_fft_cfg st, int m) +{ + vamp_kiss_fft_cpx* Fout2; + vamp_kiss_fft_cpx* tw1 = st->twiddles; + vamp_kiss_fft_cpx t; + Fout2 = Fout + m; + do + { + C_FIXDIV(*Fout,2); + C_FIXDIV(*Fout2,2); + C_MUL(t, *Fout2, *tw1); + tw1 += fstride; + C_SUB(*Fout2 , *Fout, t); + C_ADDTO(*Fout , t); + ++Fout2; + ++Fout; + } + while (--m); +} + +static void kf_bfly4(vamp_kiss_fft_cpx* Fout, const size_t fstride, const vamp_kiss_fft_cfg st, const size_t m) +{ + vamp_kiss_fft_cpx *tw1,*tw2,*tw3; + vamp_kiss_fft_cpx scratch[6]; + size_t k = m; + const size_t m2=2*m; + const size_t m3=3*m; + + tw3 = tw2 = tw1 = st->twiddles; + + do + { + C_FIXDIV(*Fout, 4); + C_FIXDIV(Fout[m], 4); + C_FIXDIV(Fout[m2], 4); + C_FIXDIV(Fout[m3], 4); + + C_MUL(scratch[0], Fout[m], *tw1 ); + C_MUL(scratch[1], Fout[m2], *tw2 ); + C_MUL(scratch[2], Fout[m3], *tw3 ); + + C_SUB(scratch[5], *Fout, scratch[1]); + C_ADDTO(*Fout, scratch[1]); + C_ADD(scratch[3], scratch[0], scratch[2]); + C_SUB(scratch[4], scratch[0], scratch[2]); + C_SUB(Fout[m2], *Fout, scratch[3]); + tw1 += fstride; + tw2 += fstride * 2; + tw3 += fstride * 3; + C_ADDTO(*Fout, scratch[3]); + + if (st->inverse) + { + Fout[m].r = scratch[5].r - scratch[4].i; + Fout[m].i = scratch[5].i + scratch[4].r; + Fout[m3].r = scratch[5].r + scratch[4].i; + Fout[m3].i = scratch[5].i - scratch[4].r; + } + else + { + Fout[m].r = scratch[5].r + scratch[4].i; + Fout[m].i = scratch[5].i - scratch[4].r; + Fout[m3].r = scratch[5].r - scratch[4].i; + Fout[m3].i = scratch[5].i + scratch[4].r; + } + ++Fout; + } + while (--k); +} + +static void kf_bfly3(vamp_kiss_fft_cpx* Fout, const size_t fstride, const vamp_kiss_fft_cfg st, size_t m) +{ + size_t k=m; + const size_t m2 = 2*m; + vamp_kiss_fft_cpx *tw1,*tw2; + vamp_kiss_fft_cpx scratch[5]; + vamp_kiss_fft_cpx epi3; + epi3 = st->twiddles[fstride*m]; + + tw1=tw2=st->twiddles; + + do + { + C_FIXDIV(*Fout, 3); + C_FIXDIV(Fout[m], 3); + C_FIXDIV(Fout[m2], 3); + + C_MUL(scratch[1], Fout[m], *tw1); + C_MUL(scratch[2], Fout[m2], *tw2); + + C_ADD(scratch[3], scratch[1], scratch[2]); + C_SUB(scratch[0], scratch[1], scratch[2]); + tw1 += fstride; + tw2 += fstride*2; + + Fout[m].r = Fout->r - HALF_OF(scratch[3].r); + Fout[m].i = Fout->i - HALF_OF(scratch[3].i); + + C_MULBYSCALAR(scratch[0], epi3.i); + C_ADDTO(*Fout,scratch[3]); + + Fout[m2].r = Fout[m].r + scratch[0].i; + Fout[m2].i = Fout[m].i - scratch[0].r; + + Fout[m].r -= scratch[0].i; + Fout[m].i += scratch[0].r; + + ++Fout; + } + while (--k); +} + +static void kf_bfly5(vamp_kiss_fft_cpx* Fout, const size_t fstride, const vamp_kiss_fft_cfg st, int m) +{ + vamp_kiss_fft_cpx* Fout0; + vamp_kiss_fft_cpx* Fout1; + vamp_kiss_fft_cpx* Fout2; + vamp_kiss_fft_cpx* Fout3; + vamp_kiss_fft_cpx* Fout4; + + int u; + vamp_kiss_fft_cpx scratch[13]; + vamp_kiss_fft_cpx* twiddles = st->twiddles; + vamp_kiss_fft_cpx* tw; + vamp_kiss_fft_cpx ya,yb; + ya = twiddles[fstride * m]; + yb = twiddles[fstride * 2 * m]; + + Fout0 = Fout; + Fout1 = Fout0+m; + Fout2 = Fout0+2*m; + Fout3 = Fout0+3*m; + Fout4 = Fout0+4*m; + + tw = st->twiddles; + for ( u=0; ur += scratch[7].r + scratch[8].r; + Fout0->i += scratch[7].i + scratch[8].i; + + scratch[5].r = scratch[0].r + S_MUL(scratch[7].r,ya.r) + S_MUL(scratch[8].r,yb.r); + scratch[5].i = scratch[0].i + S_MUL(scratch[7].i,ya.r) + S_MUL(scratch[8].i,yb.r); + + scratch[6].r = S_MUL(scratch[10].i,ya.i) + S_MUL(scratch[9].i,yb.i); + scratch[6].i = -S_MUL(scratch[10].r,ya.i) - S_MUL(scratch[9].r,yb.i); + + C_SUB(*Fout1,scratch[5],scratch[6]); + C_ADD(*Fout4,scratch[5],scratch[6]); + + scratch[11].r = scratch[0].r + S_MUL(scratch[7].r,yb.r) + S_MUL(scratch[8].r,ya.r); + scratch[11].i = scratch[0].i + S_MUL(scratch[7].i,yb.r) + S_MUL(scratch[8].i,ya.r); + scratch[12].r = - S_MUL(scratch[10].i,yb.i) + S_MUL(scratch[9].i,ya.i); + scratch[12].i = S_MUL(scratch[10].r,yb.i) - S_MUL(scratch[9].r,ya.i); + + C_ADD(*Fout2,scratch[11],scratch[12]); + C_SUB(*Fout3,scratch[11],scratch[12]); + + ++Fout0; + ++Fout1; + ++Fout2; + ++Fout3; + ++Fout4; + } +} + +/* perform the butterfly for one stage of a mixed radix FFT */ +static void kf_bfly_generic(vamp_kiss_fft_cpx* Fout, const size_t fstride, const vamp_kiss_fft_cfg st, int m, int p) +{ + int u,k,q1,q; + vamp_kiss_fft_cpx * twiddles = st->twiddles; + vamp_kiss_fft_cpx t; + int Norig = st->nfft; + + vamp_kiss_fft_cpx * scratch = (vamp_kiss_fft_cpx*)VAMP_KISS_FFT_TMP_ALLOC(sizeof(vamp_kiss_fft_cpx)*p); + + for ( u=0; u= Norig) twidx -= Norig; + C_MUL(t,scratch[q], twiddles[twidx]); + C_ADDTO(Fout[k], t); + } + k += m; + } + } + + VAMP_KISS_FFT_TMP_FREE(scratch); +} + +static +void kf_work(vamp_kiss_fft_cpx* Fout, const vamp_kiss_fft_cpx* f, const size_t fstride, int in_stride, int* factors, const vamp_kiss_fft_cfg st) +{ + vamp_kiss_fft_cpx * Fout_beg=Fout; + const int p=*factors++; /* the radix */ + const int m=*factors++; /* stage's fft length/p */ + const vamp_kiss_fft_cpx * Fout_end = Fout + p*m; + +#ifdef _OPENMP + // use openmp extensions at the + // top-level (not recursive) + if (fstride==1 && p<=5) + { + int k; + + // execute the p different work units in different threads +# pragma omp parallel for + for (k=0;kr = f->r; + Fout->i = f->i; + f += fstride*in_stride; + }while(++Fout != Fout_end ); + }else{ + do{ + // recursive call: + // DFT of size m*p performed by doing + // p instances of smaller DFTs of size m, + // each one takes a decimated version of the input + kf_work( Fout , f, fstride*p, in_stride, factors,st); + f += fstride*in_stride; + }while( (Fout += m) != Fout_end ); + } + + Fout=Fout_beg; + + // recombine the p smaller DFTs + switch (p) { + case 2: kf_bfly2(Fout,fstride,st,m); break; + case 3: kf_bfly3(Fout,fstride,st,m); break; + case 4: kf_bfly4(Fout,fstride,st,m); break; + case 5: kf_bfly5(Fout,fstride,st,m); break; + default: kf_bfly_generic(Fout,fstride,st,m,p); break; + } +} + +/* facbuf is populated by p1,m1,p2,m2, ... + where + p[i] * m[i] = m[i-1] + m0 = n */ +static +void kf_factor(int n,int * facbuf) +{ + int p=4; + double floor_sqrt; + floor_sqrt = floor( sqrt((double)n) ); + + /*factor out powers of 4, powers of 2, then any remaining primes */ + do { + while (n % p) { + switch (p) { + case 4: p = 2; break; + case 2: p = 3; break; + default: p += 2; break; + } + if (p > floor_sqrt) + p = n; /* no more factors, skip to end */ + } + n /= p; + *facbuf++ = p; + *facbuf++ = n; + } while (n > 1); +} + +/* + * + * User-callable function to allocate all necessary storage space for the fft. + * + * The return value is a contiguous block of memory, allocated with malloc. As such, + * It can be freed with free(), rather than a kiss_fft-specific function. + * */ +inline vamp_kiss_fft_cfg vamp_kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem ) +{ + vamp_kiss_fft_cfg st=NULL; + size_t memneeded = sizeof(struct vamp_kiss_fft_state) + + sizeof(vamp_kiss_fft_cpx)*(nfft-1); /* twiddle factors*/ + + if ( lenmem==NULL ) { + st = ( vamp_kiss_fft_cfg)VAMP_KISS_FFT_MALLOC( memneeded ); + }else{ + if (mem != NULL && *lenmem >= memneeded) + st = (vamp_kiss_fft_cfg)mem; + *lenmem = memneeded; + } + if (st) { + int i; + st->nfft=nfft; + st->inverse = inverse_fft; + + for (i=0;iinverse) + phase *= -1; + kf_cexp(st->twiddles+i, phase ); + } + + kf_factor(nfft,st->factors); + } + return st; +} + +inline void vamp_kiss_fft_free(void *mem) +{ + free(mem); +} + +inline void vamp_kiss_fft_stride(vamp_kiss_fft_cfg st,const vamp_kiss_fft_cpx *fin,vamp_kiss_fft_cpx *fout,int in_stride) +{ + if (fin == fout) { + //NOTE: this is not really an in-place FFT algorithm. + //It just performs an out-of-place FFT into a temp buffer + vamp_kiss_fft_cpx * tmpbuf = (vamp_kiss_fft_cpx*)VAMP_KISS_FFT_TMP_ALLOC( sizeof(vamp_kiss_fft_cpx)*st->nfft); + kf_work(tmpbuf,fin,1,in_stride, st->factors,st); + memcpy(fout,tmpbuf,sizeof(vamp_kiss_fft_cpx)*st->nfft); + VAMP_KISS_FFT_TMP_FREE(tmpbuf); + }else{ + kf_work( fout, fin, 1,in_stride, st->factors,st ); + } +} + +inline void vamp_kiss_fft(vamp_kiss_fft_cfg cfg,const vamp_kiss_fft_cpx *fin,vamp_kiss_fft_cpx *fout) +{ + vamp_kiss_fft_stride(cfg,fin,fout,1); +} + + +inline void vamp_kiss_fft_cleanup(void) +{ + // nothing needed any more +} + +inline int vamp_kiss_fft_next_fast_size(int n) +{ + while(1) { + int m=n; + while ( (m%2) == 0 ) m/=2; + while ( (m%3) == 0 ) m/=3; + while ( (m%5) == 0 ) m/=5; + if (m<=1) + break; /* n is completely factorable by twos, threes, and fives */ + n++; + } + return n; +} + +/* for real ffts, we need an even size */ +#define vamp_kiss_fftr_next_fast_size_real(n) (vamp_kiss_fft_next_fast_size( ((n)+1)>>1)<<1) + +#ifndef VAMP_KISSFFT_USE_CPP_LINKAGE +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/vamp-sdk/ext/vamp_kiss_fft_guts.h b/src/vamp-sdk/ext/vamp_kiss_fft_guts.h new file mode 100644 index 0000000..aeb9a0f --- /dev/null +++ b/src/vamp-sdk/ext/vamp_kiss_fft_guts.h @@ -0,0 +1,74 @@ +#pragma once + +#include + +#define MAXFACTORS 32 +/* e.g. an fft of length 128 has 4 factors + as far as kissfft is concerned + 4*4*4*2 + */ + +/* + Explanation of macros dealing with complex math: + + C_MUL(m,a,b) : m = a*b + C_FIXDIV( c , div ) : if a fixed point impl., c /= div. noop otherwise + C_SUB( res, a,b) : res = a - b + C_SUBFROM( res , a) : res -= a + C_ADDTO( res , a) : res += a + * */ + +#define S_MUL(a,b) ( (a)*(b) ) +#define C_MUL(m,a,b) \ + do{ (m).r = (a).r*(b).r - (a).i*(b).i;\ + (m).i = (a).r*(b).i + (a).i*(b).r; }while(0) +#define C_FIXDIV(c,div) /* NOOP */ +#define C_MULBYSCALAR( c, s ) \ + do{ (c).r *= (s);\ + (c).i *= (s); }while(0) + +#define CHECK_OVERFLOW_OP(a,op,b) /* noop */ + +#define C_ADD( res, a,b)\ + do { \ + CHECK_OVERFLOW_OP((a).r,+,(b).r)\ + CHECK_OVERFLOW_OP((a).i,+,(b).i)\ + (res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \ + }while(0) +#define C_SUB( res, a,b)\ + do { \ + CHECK_OVERFLOW_OP((a).r,-,(b).r)\ + CHECK_OVERFLOW_OP((a).i,-,(b).i)\ + (res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \ + }while(0) +#define C_ADDTO( res , a)\ + do { \ + CHECK_OVERFLOW_OP((res).r,+,(a).r)\ + CHECK_OVERFLOW_OP((res).i,+,(a).i)\ + (res).r += (a).r; (res).i += (a).i;\ + }while(0) + +#define C_SUBFROM( res , a)\ + do {\ + CHECK_OVERFLOW_OP((res).r,-,(a).r)\ + CHECK_OVERFLOW_OP((res).i,-,(a).i)\ + (res).r -= (a).r; (res).i -= (a).i; \ + }while(0) + + +#define VAMP_KISS_FFT_COS(phase) (vamp_kiss_fft_scalar) cos(phase) +#define VAMP_KISS_FFT_SIN(phase) (vamp_kiss_fft_scalar) sin(phase) +#define HALF_OF(x) ((x)*.5) + +#define kf_cexp(x,phase) \ + do{ \ + (x)->r = VAMP_KISS_FFT_COS(phase);\ + (x)->i = VAMP_KISS_FFT_SIN(phase);\ + }while(0) + +#define VAMP_KISS_FFT_TMP_ALLOC(nbytes) VAMP_KISS_FFT_MALLOC(nbytes) +#define VAMP_KISS_FFT_TMP_FREE(ptr) VAMP_KISS_FFT_FREE(ptr) + +/* a debugging function */ +#define pcpx(c)\ + fprintf(stderr,"%g + %gi\n",(double)((c)->r),(double)((c)->i) ) diff --git a/src/vamp-sdk/ext/vamp_kiss_fftr.h b/src/vamp-sdk/ext/vamp_kiss_fftr.h new file mode 100644 index 0000000..1745492 --- /dev/null +++ b/src/vamp-sdk/ext/vamp_kiss_fftr.h @@ -0,0 +1,161 @@ +#pragma once + +#include "vamp_kiss_fft.h" + +#ifndef VAMP_KISSFFT_USE_CPP_LINKAGE +#ifdef __cplusplus +extern "C" { +#endif +#endif + +/* Real optimized version can save about 45% cpu time vs. complex fft of a real seq. */ + +typedef struct vamp_kiss_fftr_state *vamp_kiss_fftr_cfg; + +struct vamp_kiss_fftr_state{ + vamp_kiss_fft_cfg substate; + vamp_kiss_fft_cpx * tmpbuf; + vamp_kiss_fft_cpx * super_twiddles; +}; + +inline vamp_kiss_fftr_cfg vamp_kiss_fftr_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem) +{ + int i; + vamp_kiss_fftr_cfg st = NULL; + size_t subsize, memneeded; + + if (nfft & 1) { + fprintf(stderr,"Real FFT optimization must be even.\n"); + return NULL; + } + nfft >>= 1; + + vamp_kiss_fft_alloc (nfft, inverse_fft, NULL, &subsize); + memneeded = sizeof(struct vamp_kiss_fftr_state) + subsize + sizeof(vamp_kiss_fft_cpx) * ( nfft * 3 / 2); + + if (lenmem == NULL) { + st = (vamp_kiss_fftr_cfg) VAMP_KISS_FFT_MALLOC (memneeded); + } else { + if (*lenmem >= memneeded) + st = (vamp_kiss_fftr_cfg) mem; + *lenmem = memneeded; + } + if (!st) + return NULL; + + st->substate = (vamp_kiss_fft_cfg) (st + 1); /*just beyond vamp_kiss_fftr_state struct */ + st->tmpbuf = (vamp_kiss_fft_cpx *) (((char *) st->substate) + subsize); + st->super_twiddles = st->tmpbuf + nfft; + vamp_kiss_fft_alloc(nfft, inverse_fft, st->substate, &subsize); + + for (i = 0; i < nfft/2; ++i) { + double phase = + -3.14159265358979323846264338327 * ((double) (i+1) / nfft + .5); + if (inverse_fft) + phase *= -1; + kf_cexp (st->super_twiddles+i,phase); + } + return st; +} + +inline void vamp_kiss_fftr_free(void *mem) +{ + free(mem); +} + +inline void vamp_kiss_fftr(vamp_kiss_fftr_cfg st,const vamp_kiss_fft_scalar *timedata,vamp_kiss_fft_cpx *freqdata) +{ + /* input buffer timedata is stored row-wise */ + int k,ncfft; + vamp_kiss_fft_cpx fpnk,fpk,f1k,f2k,tw,tdc; + + if ( st->substate->inverse) { + fprintf(stderr,"kiss fft usage error: improper alloc\n"); + exit(1); + } + + ncfft = st->substate->nfft; + + /*perform the parallel fft of two real signals packed in real,imag*/ + vamp_kiss_fft( st->substate , (const vamp_kiss_fft_cpx*)timedata, st->tmpbuf ); + /* The real part of the DC element of the frequency spectrum in st->tmpbuf + * contains the sum of the even-numbered elements of the input time sequence + * The imag part is the sum of the odd-numbered elements + * + * The sum of tdc.r and tdc.i is the sum of the input time sequence. + * yielding DC of input time sequence + * The difference of tdc.r - tdc.i is the sum of the input (dot product) [1,-1,1,-1... + * yielding Nyquist bin of input time sequence + */ + + tdc.r = st->tmpbuf[0].r; + tdc.i = st->tmpbuf[0].i; + C_FIXDIV(tdc,2); + CHECK_OVERFLOW_OP(tdc.r ,+, tdc.i); + CHECK_OVERFLOW_OP(tdc.r ,-, tdc.i); + freqdata[0].r = tdc.r + tdc.i; + freqdata[ncfft].r = tdc.r - tdc.i; + freqdata[ncfft].i = freqdata[0].i = 0; + + for ( k=1;k <= ncfft/2 ; ++k ) { + fpk = st->tmpbuf[k]; + fpnk.r = st->tmpbuf[ncfft-k].r; + fpnk.i = - st->tmpbuf[ncfft-k].i; + C_FIXDIV(fpk,2); + C_FIXDIV(fpnk,2); + + C_ADD( f1k, fpk , fpnk ); + C_SUB( f2k, fpk , fpnk ); + C_MUL( tw , f2k , st->super_twiddles[k-1]); + + freqdata[k].r = HALF_OF(f1k.r + tw.r); + freqdata[k].i = HALF_OF(f1k.i + tw.i); + freqdata[ncfft-k].r = HALF_OF(f1k.r - tw.r); + freqdata[ncfft-k].i = HALF_OF(tw.i - f1k.i); + } +} + +inline void vamp_kiss_fftri(vamp_kiss_fftr_cfg st,const vamp_kiss_fft_cpx *freqdata,vamp_kiss_fft_scalar *timedata) +{ + /* input buffer timedata is stored row-wise */ + int k, ncfft; + + if (st->substate->inverse == 0) { + fprintf (stderr, "kiss fft usage error: improper alloc\n"); + exit (1); + } + + ncfft = st->substate->nfft; + + st->tmpbuf[0].r = freqdata[0].r + freqdata[ncfft].r; + st->tmpbuf[0].i = freqdata[0].r - freqdata[ncfft].r; + C_FIXDIV(st->tmpbuf[0],2); + + for (k = 1; k <= ncfft / 2; ++k) { + vamp_kiss_fft_cpx fk, fnkc, fek, fok, tmp; + fk = freqdata[k]; + fnkc.r = freqdata[ncfft - k].r; + fnkc.i = -freqdata[ncfft - k].i; + C_FIXDIV( fk , 2 ); + C_FIXDIV( fnkc , 2 ); + + C_ADD (fek, fk, fnkc); + C_SUB (tmp, fk, fnkc); + C_MUL (fok, tmp, st->super_twiddles[k-1]); + C_ADD (st->tmpbuf[k], fek, fok); + C_SUB (st->tmpbuf[ncfft - k], fek, fok); + st->tmpbuf[ncfft - k].i *= -1; + } + vamp_kiss_fft (st->substate, st->tmpbuf, (vamp_kiss_fft_cpx *) timedata); +} + +#ifndef VAMP_KISSFFT_USE_CPP_LINKAGE +#ifdef __cplusplus +} + +#endif +#endif + +#ifdef VAMP_KISSFFT_USE_CPP_LINKAGE +#define VAMP_KISSFFT_USED_CPP_LINKAGE 1 +#endif diff --git a/src/vamp-sdk/vamp-sdk.h b/src/vamp-sdk/vamp-sdk.h new file mode 100644 index 0000000..57b8ee7 --- /dev/null +++ b/src/vamp-sdk/vamp-sdk.h @@ -0,0 +1,7 @@ +#pragma once + +#include "PluginBase.h" +#include "Plugin.h" +#include "RealTime.h" +#include "FFT.h" + diff --git a/src/vamp.h b/src/vamp.h new file mode 100644 index 0000000..a553e44 --- /dev/null +++ b/src/vamp.h @@ -0,0 +1,352 @@ +#ifndef VAMP_HEADER_INCLUDED +#define VAMP_HEADER_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Plugin API version. This is incremented when a change is made that + * changes the binary layout of the descriptor records. When this + * happens, there should be a mechanism for retaining compatibility + * with older hosts and/or plugins. + * + * See also the vampApiVersion field in the plugin descriptor, and the + * hostApiVersion argument to the vampGetPluginDescriptor function. + */ +#define VAMP_API_VERSION 2 + +/** + * C language API for Vamp plugins. + * + * This is the formal plugin API for Vamp. Plugin authors may prefer + * to use the C++ classes provided in the Vamp plugin SDK, instead of + * using this API directly. There is an adapter class provided that + * makes C++ plugins available using this C API with relatively little + * work, and the C++ headers are more thoroughly documented. + * + * IMPORTANT: The comments in this file summarise the purpose of each + * of the declared fields and functions, but do not provide a complete + * guide to their permitted values and expected usage. Please refer + * to the C++ headers in the Vamp plugin SDK for further details and + * plugin lifecycle documentation. + */ + +typedef struct _VampParameterDescriptor +{ + /** Computer-usable name of the parameter. Must not change. [a-zA-Z0-9_-] */ + const char *identifier; + + /** Human-readable name of the parameter. May be translatable. */ + const char *name; + + /** Human-readable short text about the parameter. May be translatable. */ + const char *description; + + /** Human-readable unit of the parameter. */ + const char *unit; + + /** Minimum value. */ + float minValue; + + /** Maximum value. */ + float maxValue; + + /** Default value. Plugin is responsible for setting this on initialise. */ + float defaultValue; + + /** 1 if parameter values are quantized to a particular resolution. */ + int isQuantized; + + /** Quantization resolution, if isQuantized. */ + float quantizeStep; + + /** Human-readable names of the values, if isQuantized. May be NULL. */ + const char **valueNames; + +} VampParameterDescriptor; + +typedef enum +{ + /** Each process call returns results aligned with call's block start. */ + vampOneSamplePerStep, + + /** Returned results are evenly spaced at samplerate specified below. */ + vampFixedSampleRate, + + /** Returned results have their own individual timestamps. */ + vampVariableSampleRate + +} VampSampleType; + +typedef struct _VampOutputDescriptor +{ + /** Computer-usable name of the output. Must not change. [a-zA-Z0-9_-] */ + const char *identifier; + + /** Human-readable name of the output. May be translatable. */ + const char *name; + + /** Human-readable short text about the output. May be translatable. */ + const char *description; + + /** Human-readable name of the unit of the output. */ + const char *unit; + + /** 1 if output has equal number of values for each returned result. */ + int hasFixedBinCount; + + /** Number of values per result, if hasFixedBinCount. */ + unsigned int binCount; + + /** Names of returned value bins, if hasFixedBinCount. May be NULL. */ + const char **binNames; + + /** 1 if each returned value falls within the same fixed min/max range. */ + int hasKnownExtents; + + /** Minimum value for a returned result in any bin, if hasKnownExtents. */ + float minValue; + + /** Maximum value for a returned result in any bin, if hasKnownExtents. */ + float maxValue; + + /** 1 if returned results are quantized to a particular resolution. */ + int isQuantized; + + /** Quantization resolution for returned results, if isQuantized. */ + float quantizeStep; + + /** Time positioning method for returned results (see VampSampleType). */ + VampSampleType sampleType; + + /** Sample rate of returned results, if sampleType is vampFixedSampleRate. + "Resolution" of result, if sampleType is vampVariableSampleRate. */ + float sampleRate; + + /** 1 if the returned results for this output are known to have a + duration field. + + This field is new in Vamp API version 2; it must not be tested + for plugins that report an older API version in their plugin + descriptor. + */ + int hasDuration; + +} VampOutputDescriptor; + +typedef struct _VampFeature +{ + /** 1 if the feature has a timestamp (i.e. if vampVariableSampleRate). */ + int hasTimestamp; + + /** Seconds component of timestamp. */ + int sec; + + /** Nanoseconds component of timestamp. */ + int nsec; + + /** Number of values. Must be binCount if hasFixedBinCount. */ + unsigned int valueCount; + + /** Values for this returned sample. */ + float *values; + + /** Label for this returned sample. May be NULL. */ + char *label; + +} VampFeature; + +typedef struct _VampFeatureV2 +{ + /** 1 if the feature has a duration. */ + int hasDuration; + + /** Seconds component of duratiion. */ + int durationSec; + + /** Nanoseconds component of duration. */ + int durationNsec; + +} VampFeatureV2; + +typedef union _VampFeatureUnion +{ + // sizeof(featureV1) >= sizeof(featureV2) for backward compatibility + VampFeature v1; + VampFeatureV2 v2; + +} VampFeatureUnion; + +typedef struct _VampFeatureList +{ + /** Number of features in this feature list. */ + unsigned int featureCount; + + /** Features in this feature list. May be NULL if featureCount is + zero. + + If present, this array must contain featureCount feature + structures for a Vamp API version 1 plugin, or 2*featureCount + feature unions for a Vamp API version 2 plugin. + + The features returned by an API version 2 plugin must consist + of the same feature structures as in API version 1 for the + first featureCount array elements, followed by featureCount + unions that contain VampFeatureV2 structures (or NULL pointers + if no V2 feature structures are present). + */ + VampFeatureUnion *features; + +} VampFeatureList; + +typedef enum +{ + vampTimeDomain, + vampFrequencyDomain + +} VampInputDomain; + +typedef void *VampPluginHandle; + +typedef struct _VampPluginDescriptor +{ + /** API version with which this descriptor is compatible. */ + unsigned int vampApiVersion; + + /** Computer-usable name of the plugin. Must not change. [a-zA-Z0-9_-] */ + const char *identifier; + + /** Human-readable name of the plugin. May be translatable. */ + const char *name; + + /** Human-readable short text about the plugin. May be translatable. */ + const char *description; + + /** Human-readable name of plugin's author or vendor. */ + const char *maker; + + /** Version number of the plugin. */ + int pluginVersion; + + /** Human-readable summary of copyright or licensing for plugin. */ + const char *copyright; + + /** Number of parameter inputs. */ + unsigned int parameterCount; + + /** Fixed descriptors for parameter inputs. */ + const VampParameterDescriptor **parameters; + + /** Number of programs. */ + unsigned int programCount; + + /** Fixed names for programs. */ + const char **programs; + + /** Preferred input domain for audio input (time or frequency). */ + VampInputDomain inputDomain; + + /** Create and return a new instance of this plugin. */ + VampPluginHandle (*instantiate)(const struct _VampPluginDescriptor *, + float inputSampleRate); + + /** Destroy an instance of this plugin. */ + void (*cleanup)(VampPluginHandle); + + /** Initialise an instance following parameter configuration. */ + int (*initialise)(VampPluginHandle, + unsigned int inputChannels, + unsigned int stepSize, + unsigned int blockSize); + + /** Reset an instance, ready to use again on new input data. */ + void (*reset)(VampPluginHandle); + + /** Get a parameter value. */ + float (*getParameter)(VampPluginHandle, int); + + /** Set a parameter value. May only be called before initialise. */ + void (*setParameter)(VampPluginHandle, int, float); + + /** Get the current program (if programCount > 0). */ + unsigned int (*getCurrentProgram)(VampPluginHandle); + + /** Set the current program. May only be called before initialise. */ + void (*selectProgram)(VampPluginHandle, unsigned int); + + /** Get the plugin's preferred processing window increment in samples. */ + unsigned int (*getPreferredStepSize)(VampPluginHandle); + + /** Get the plugin's preferred processing window size in samples. */ + unsigned int (*getPreferredBlockSize)(VampPluginHandle); + + /** Get the minimum number of input channels this plugin can handle. */ + unsigned int (*getMinChannelCount)(VampPluginHandle); + + /** Get the maximum number of input channels this plugin can handle. */ + unsigned int (*getMaxChannelCount)(VampPluginHandle); + + /** Get the number of feature outputs (distinct sets of results). */ + unsigned int (*getOutputCount)(VampPluginHandle); + + /** Get a descriptor for a given feature output. Returned pointer + is valid only until next call to getOutputDescriptor for this + handle, or releaseOutputDescriptor for this descriptor. Host + must call releaseOutputDescriptor after use. */ + VampOutputDescriptor *(*getOutputDescriptor)(VampPluginHandle, + unsigned int); + + /** Destroy a descriptor for a feature output. */ + void (*releaseOutputDescriptor)(VampOutputDescriptor *); + + /** Process an input block and return a set of features. Returned + pointer is valid only until next call to process, + getRemainingFeatures, or cleanup for this handle, or + releaseFeatureSet for this feature set. Host must call + releaseFeatureSet after use. */ + VampFeatureList *(*process)(VampPluginHandle, + const float *const *inputBuffers, + int sec, + int nsec); + + /** Return any remaining features at the end of processing. */ + VampFeatureList *(*getRemainingFeatures)(VampPluginHandle); + + /** Release a feature set returned from process or getRemainingFeatures. */ + void (*releaseFeatureSet)(VampFeatureList *); + +} VampPluginDescriptor; + + +/** Get the descriptor for a given plugin index in this library. + Return NULL if the index is outside the range of valid indices for + this plugin library. + + The hostApiVersion argument tells the library code the highest + Vamp API version supported by the host. The function should + return a plugin descriptor compatible with the highest API version + supported by the library that is no higher than that supported by + the host. Provided the descriptor has the correct vampApiVersion + field for its actual compatibility level, the host should be able + to do the right thing with it: use it if possible, discard it + otherwise. + + This is the only symbol that a Vamp plugin actually needs to + export from its shared object; all others can be hidden. See the + accompanying documentation for notes on how to achieve this with + certain compilers. +*/ +const VampPluginDescriptor *vampGetPluginDescriptor + (unsigned int hostApiVersion, unsigned int index); + + +/** Function pointer type for vampGetPluginDescriptor. */ +typedef const VampPluginDescriptor *(*VampGetPluginDescriptorFunction) + (unsigned int, unsigned int); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/subprojects/hack.wrap b/subprojects/hack.wrap new file mode 100644 index 0000000..23519f6 --- /dev/null +++ b/subprojects/hack.wrap @@ -0,0 +1,7 @@ +[wrap-git] +url = https://gitcast.ru/chatlanin/hack.git +revision = master + +[provide] +hack = hack_dep + diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..87efab7 --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,208 @@ +#include "hack/logger/logger.hpp" + +#include "vamp-hostsdk/PluginInputDomainAdapter.h" +#include "vamp-hostsdk/PluginBufferingAdapter.h" +#include "vamp-sdk/Plugin.h" + + +class MyPlugin : public Vamp::Plugin +{ +public: + MyPlugin(float inputSampleRate); + virtual ~MyPlugin(); + + std::string getIdentifier() const; + std::string getName() const; + std::string getDescription() const; + std::string getMaker() const; + int getPluginVersion() const; + std::string getCopyright() const; + + InputDomain getInputDomain() const; + size_t getPreferredBlockSize() const; + size_t getPreferredStepSize() const; + size_t getMinChannelCount() const; + size_t getMaxChannelCount() const; + + ParameterList getParameterDescriptors() const; + float getParameter(std::string identifier) const; + void setParameter(std::string identifier, float value); + + ProgramList getPrograms() const; + std::string getCurrentProgram() const; + void selectProgram(std::string name); + + OutputList getOutputDescriptors() const; + + bool initialise(size_t channels, size_t stepSize, size_t blockSize); + void reset(); + + FeatureSet process(const float *const *inputBuffers, + Vamp::RealTime timestamp); + + FeatureSet getRemainingFeatures(); +}; + +MyPlugin::MyPlugin(float inputSampleRate) : Plugin(inputSampleRate) +{ +} + +MyPlugin::~MyPlugin() +{ +} + +std::string MyPlugin::getIdentifier() const +{ + return "myplugin"; +} + +std::string MyPlugin::getName() const +{ + return "My Plugin"; +} + +std::string MyPlugin::getDescription() const +{ + return ""; +} + +std::string MyPlugin::getMaker() const +{ + return ""; +} + +int MyPlugin::getPluginVersion() const +{ + return 1; +} + +std::string MyPlugin::getCopyright() const +{ + return ""; +} + +MyPlugin::InputDomain MyPlugin::getInputDomain() const +{ + return TimeDomain; +} + +size_t MyPlugin::getPreferredBlockSize() const +{ + return 0; +} + +size_t MyPlugin::getPreferredStepSize() const +{ + return 0; +} + +size_t MyPlugin::getMinChannelCount() const +{ + return 1; +} + +size_t MyPlugin::getMaxChannelCount() const +{ + return 1; +} + +MyPlugin::ParameterList MyPlugin::getParameterDescriptors() const +{ + ParameterList list; + ParameterDescriptor d; + d.identifier = "parameter"; + d.name = "Some Parameter"; + d.description = ""; + d.unit = ""; + d.minValue = 0; + d.maxValue = 10; + d.defaultValue = 5; + d.isQuantized = false; + list.push_back(d); + + return list; +} + +float MyPlugin::getParameter(std::string identifier) const +{ + if (identifier == "parameter") + return 5; + return 0; +} + +void MyPlugin::setParameter(std::string identifier, float value) +{ + if (identifier == "parameter") { + } +} + +MyPlugin::ProgramList MyPlugin::getPrograms() const +{ + ProgramList list; + return list; +} + +std::string MyPlugin::getCurrentProgram() const +{ + return ""; +} + +void MyPlugin::selectProgram(std::string name) +{ +} + +MyPlugin::OutputList MyPlugin::getOutputDescriptors() const +{ + OutputList list; + + OutputDescriptor d; + d.identifier = "output"; + d.name = "My Output"; + d.description = ""; + d.unit = ""; + d.hasFixedBinCount = true; + d.binCount = 1; + d.hasKnownExtents = false; + d.isQuantized = false; + d.sampleType = OutputDescriptor::OneSamplePerStep; + d.hasDuration = false; + list.push_back(d); + + return list; +} + +bool MyPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (channels < getMinChannelCount() || channels > getMaxChannelCount()) return false; + return true; +} + +void MyPlugin::reset() +{ +} + +MyPlugin::FeatureSet MyPlugin::process(const float *const *inputBuffers, Vamp::RealTime timestamp) +{ + return FeatureSet(); +} + +MyPlugin::FeatureSet MyPlugin::getRemainingFeatures() +{ + return FeatureSet(); +} + +auto main() -> int +{ + float samplerate = 10.f; + MyPlugin* ch = new MyPlugin(samplerate); + Vamp::HostExt::PluginInputDomainAdapter* ia = new Vamp::HostExt::PluginInputDomainAdapter(ch); + ia->setProcessTimestampMethod(Vamp::HostExt::PluginInputDomainAdapter::ShiftData); + + Vamp::HostExt::PluginBufferingAdapter* adapter = new Vamp::HostExt::PluginBufferingAdapter(ia); + + int blocksize = adapter->getPreferredBlockSize(); + if (!adapter->initialise(1, blocksize, blocksize)) + hack::error()("Не инициализировался адаптер"); + + hack::log()("is ok"); +} diff --git a/test/meson.build b/test/meson.build new file mode 100644 index 0000000..f6ed8ee --- /dev/null +++ b/test/meson.build @@ -0,0 +1,8 @@ +executable( + meson.project_name(), + 'main.cpp', + dependencies : deps, + cpp_args: args, + include_directories : inc +) +