Chris@0: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@0: Chris@26: /* Chris@26: VampyHost Chris@26: Chris@26: Use Vamp audio analysis plugins in Python Chris@26: Chris@26: Gyorgy Fazekas and Chris Cannam Chris@26: Centre for Digital Music, Queen Mary, University of London Chris@117: Copyright 2008-2015 Queen Mary, University of London Chris@26: Chris@26: Permission is hereby granted, free of charge, to any person Chris@26: obtaining a copy of this software and associated documentation Chris@26: files (the "Software"), to deal in the Software without Chris@26: restriction, including without limitation the rights to use, copy, Chris@26: modify, merge, publish, distribute, sublicense, and/or sell copies Chris@26: of the Software, and to permit persons to whom the Software is Chris@26: furnished to do so, subject to the following conditions: Chris@26: Chris@26: The above copyright notice and this permission notice shall be Chris@26: included in all copies or substantial portions of the Software. Chris@26: Chris@26: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, Chris@26: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF Chris@26: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND Chris@26: NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR Chris@26: ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF Chris@26: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION Chris@26: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Chris@26: Chris@26: Except as contained in this notice, the names of the Centre for Chris@26: Digital Music; Queen Mary, University of London; and the authors Chris@26: shall not be used in advertising or otherwise to promote the sale, Chris@26: use or other dealings in this Software without prior written Chris@26: authorization. Chris@26: */ Chris@26: Chris@39: // include for python extension module: must be first Chris@0: #include Chris@14: Chris@14: // define a unique API pointer Chris@27: #define PY_ARRAY_UNIQUE_SYMBOL VAMPYHOST_ARRAY_API Chris@14: #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION Chris@14: #include "numpy/arrayobject.h" Chris@14: Chris@31: #include "PyRealTime.h" Chris@31: #include "PyPluginObject.h" Chris@12: Chris@1: #include "vamp-hostsdk/PluginHostAdapter.h" Chris@1: #include "vamp-hostsdk/PluginChannelAdapter.h" Chris@1: #include "vamp-hostsdk/PluginInputDomainAdapter.h" Chris@1: #include "vamp-hostsdk/PluginLoader.h" Chris@16: Chris@29: #include "VectorConversion.h" Chris@112: #include "StringConversion.h" Chris@16: #include "PyRealTime.h" Chris@0: Chris@0: #include Chris@0: #include Chris@0: Chris@0: #include Chris@0: Chris@120: #if (VAMP_SDK_MAJOR_VERSION != 2 || VAMP_SDK_MINOR_VERSION < 6) Chris@120: #error "Vamp plugin SDK v2, version 2.6 or newer required" Chris@120: #endif Chris@120: Chris@0: using namespace std; Chris@0: using namespace Vamp; Chris@31: using namespace Vamp::HostExt; Chris@21: Chris@0: static PyObject * Chris@79: list_plugins(PyObject *self, PyObject *) Chris@0: { Chris@0: PluginLoader *loader = PluginLoader::getInstance(); Chris@0: vector plugins = loader->listPlugins(); Chris@29: VectorConversion conv; Chris@15: return conv.PyValue_From_StringVector(plugins); Chris@0: } Chris@0: Chris@15: static PyObject * Chris@79: get_plugin_path(PyObject *self, PyObject *) Chris@15: { Chris@15: vector path = PluginHostAdapter::getPluginPath(); Chris@29: VectorConversion conv; Chris@15: return conv.PyValue_From_StringVector(path); Chris@15: } Chris@0: Chris@15: static string toPluginKey(PyObject *pyPluginKey) Chris@0: { Chris@36: // convert to stl string Chris@112: string pluginKey(StringConversion().py2string(pyPluginKey)); Chris@0: Chris@36: // check pluginKey validity Chris@0: string::size_type ki = pluginKey.find(':'); Chris@0: if (ki == string::npos) { Chris@39: PyErr_SetString(PyExc_TypeError, Chris@39: "Plugin key must be of the form library:identifier"); Chris@39: return ""; Chris@0: } Chris@0: Chris@15: return pluginKey; Chris@15: } Chris@15: Chris@15: static PyObject * Chris@79: get_library_for(PyObject *self, PyObject *args) Chris@15: { Chris@15: PyObject *pyPluginKey; Chris@15: Chris@112: if (!PyArg_ParseTuple(args, Chris@112: #if (PY_MAJOR_VERSION >= 3) Chris@112: "U", Chris@112: #else Chris@112: "S", Chris@112: #endif Chris@112: &pyPluginKey)) { Chris@39: PyErr_SetString(PyExc_TypeError, Chris@79: "get_library_for() takes plugin key (string) argument"); Chris@39: return 0; } Chris@15: Chris@15: string pluginKey = toPluginKey(pyPluginKey); Chris@16: if (pluginKey == "") return 0; Chris@15: Chris@0: PluginLoader *loader = PluginLoader::getInstance(); Chris@0: string path = loader->getLibraryPathForPlugin(pluginKey); Chris@112: PyObject *pyPath = StringConversion().string2py(path.c_str()); Chris@0: return pyPath; Chris@0: } Chris@0: Chris@0: static PyObject * Chris@79: get_category_of(PyObject *self, PyObject *args) Chris@0: { Chris@0: PyObject *pyPluginKey; Chris@0: Chris@112: if (!PyArg_ParseTuple(args, Chris@112: #if (PY_MAJOR_VERSION >= 3) Chris@112: "U", Chris@112: #else Chris@112: "S", Chris@112: #endif Chris@112: &pyPluginKey)) { Chris@39: PyErr_SetString(PyExc_TypeError, Chris@79: "get_category_of() takes plugin key (string) argument"); Chris@39: return 0; } Chris@0: Chris@15: string pluginKey = toPluginKey(pyPluginKey); Chris@16: if (pluginKey == "") return 0; Chris@0: Chris@0: PluginLoader *loader = PluginLoader::getInstance(); luis@7: PluginLoader::PluginCategoryHierarchy Chris@39: category = loader->getPluginCategory(pluginKey); Chris@0: Chris@29: VectorConversion conv; Chris@15: return conv.PyValue_From_StringVector(category); Chris@0: } Chris@0: Chris@0: static PyObject * Chris@79: get_outputs_of(PyObject *self, PyObject *args) Chris@0: { Chris@31: PyObject *pyPluginKey; Chris@31: Chris@112: if (!PyArg_ParseTuple(args, Chris@112: #if (PY_MAJOR_VERSION >= 3) Chris@112: "U", Chris@112: #else Chris@112: "S", Chris@112: #endif Chris@112: &pyPluginKey)) { Chris@39: PyErr_SetString(PyExc_TypeError, Chris@79: "get_outputs_of() takes plugin key (string) argument"); Chris@39: return 0; } Chris@31: Chris@15: Plugin::OutputList outputs; Chris@0: Chris@31: string pluginKey = toPluginKey(pyPluginKey); Chris@31: if (pluginKey == "") return 0; Chris@31: Chris@31: PluginLoader *loader = PluginLoader::getInstance(); Chris@31: Chris@47: Plugin *plugin = loader->loadPlugin(pluginKey, 48000, 0); Chris@31: if (!plugin) { Chris@31: string pyerr("Failed to load plugin: "); pyerr += pluginKey; Chris@134: PyErr_SetString(PyExc_TypeError, pyerr.c_str()); Chris@31: return 0; Chris@0: } Chris@0: Chris@31: outputs = plugin->getOutputDescriptors(); luis@7: Chris@0: PyObject *pyList = PyList_New(outputs.size()); Chris@0: Chris@0: for (size_t i = 0; i < outputs.size(); ++i) { Chris@39: PyObject *pyOutputId = Chris@112: StringConversion().string2py(outputs[i].identifier.c_str()); Chris@39: PyList_SET_ITEM(pyList, i, pyOutputId); Chris@0: } Chris@0: Chris@0: return pyList; Chris@0: } Chris@0: Chris@0: static PyObject * Chris@79: load_plugin(PyObject *self, PyObject *args) Chris@0: { Chris@0: PyObject *pyPluginKey; Chris@0: float inputSampleRate; Chris@112: ssize_t adapterFlags; Chris@0: Chris@112: if (!PyArg_ParseTuple(args, Chris@112: #if (PY_MAJOR_VERSION >= 3) Chris@112: "Ufn", Chris@112: #else Chris@112: "Sfn", Chris@112: #endif Chris@39: &pyPluginKey, Chris@47: &inputSampleRate, Chris@47: &adapterFlags)) { Chris@39: PyErr_SetString(PyExc_TypeError, Chris@79: "load_plugin() takes plugin key (string), sample rate (float), and adapter flags (int) arguments"); Chris@39: return 0; } Chris@0: Chris@15: string pluginKey = toPluginKey(pyPluginKey); Chris@16: if (pluginKey == "") return 0; Chris@0: Chris@0: PluginLoader *loader = PluginLoader::getInstance(); luis@7: Chris@47: Plugin *plugin = loader->loadPlugin(pluginKey, Chris@47: inputSampleRate, Chris@47: adapterFlags); luis@7: if (!plugin) { Chris@39: string pyerr("Failed to load plugin: "); pyerr += pluginKey; Chris@39: PyErr_SetString(PyExc_TypeError,pyerr.c_str()); Chris@39: return 0; luis@7: } Chris@15: Chris@31: return PyPluginObject_From_Plugin(plugin); Chris@0: } Chris@0: Chris@60: static PyObject * Chris@79: frame_to_realtime(PyObject *self, PyObject *args) Chris@60: { Chris@112: ssize_t frame; Chris@112: float rate; Chris@60: Chris@112: if (!PyArg_ParseTuple(args, "nf", Chris@60: &frame, Chris@60: &rate)) { Chris@60: PyErr_SetString(PyExc_TypeError, Chris@112: "frame_to_realtime() takes frame (int) and sample rate (float) arguments"); Chris@60: return 0; } Chris@60: Chris@60: RealTime rt = RealTime::frame2RealTime(frame, rate); Chris@60: return PyRealTime_FromRealTime(rt); Chris@60: } Chris@60: Chris@18: // module methods table Chris@0: static PyMethodDef vampyhost_methods[] = { Chris@60: Chris@79: {"list_plugins", list_plugins, METH_NOARGS, Chris@79: "list_plugins() -> Return a list of the plugin keys of all installed Vamp plugins." }, Chris@0: Chris@79: {"get_plugin_path", get_plugin_path, METH_NOARGS, Chris@79: "get_plugin_path() -> Return a list of directories which will be searched for Vamp plugins. This may be changed by setting the VAMP_PATH environment variable."}, Chris@15: Chris@79: {"get_category_of", get_category_of, METH_VARARGS, Chris@139: "get_category_of(plugin_key) -> Return the category of a Vamp plugin given its key, if known. The category is expressed as a list of nested types from least to most specific."}, Chris@0: Chris@79: {"get_library_for", get_library_for, METH_VARARGS, Chris@139: "get_library_for(plugin_key) -> Return the file path of the Vamp plugin library in which the given plugin key is found, or an empty string if the plugin is not installed."}, Chris@0: Chris@79: {"get_outputs_of", get_outputs_of, METH_VARARGS, Chris@139: "get_outputs_of(plugin_key) -> Return a list of the output identifiers of the plugin with the given key, if installed."}, Chris@0: Chris@79: {"load_plugin", load_plugin, METH_VARARGS, Chris@139: "load_plugin(plugin_key, sample_rate, adapter_flags) -> Load the plugin that has the given key, if installed, and return the plugin object. The adapter_flags may be ADAPT_NONE, any additive combination of ADAPT_INPUT_DOMAIN, ADAPT_CHANNEL_COUNT, ADAPT_BUFFER_SIZE, or one of the special flags ADAPT_ALL_SAFE or ADAPT_ALL. If in doubt, pass ADAPT_ALL_SAFE. See the Vamp SDK documentation for the PluginLoader class for more details."}, Chris@0: Chris@79: {"frame_to_realtime", frame_to_realtime, METH_VARARGS, Chris@79: "frame_to_realtime() -> Convert sample frame number and sample rate to a RealTime object." }, Chris@60: Chris@39: {0, 0} /* sentinel */ Chris@0: }; Chris@0: Chris@25: static int Chris@25: setint(PyObject *d, const char *name, int value) Chris@25: { Chris@25: PyObject *v; Chris@25: int err; Chris@112: #if (PY_MAJOR_VERSION >= 3) Chris@112: v = PyLong_FromLong((long)value); Chris@112: #else Chris@25: v = PyInt_FromLong((long)value); Chris@112: #endif Chris@25: err = PyDict_SetItemString(d, name, v); Chris@25: Py_XDECREF(v); Chris@25: return err; Chris@25: } Chris@14: Chris@0: /* Initialization function for the module (*must* be called initxx) */ Chris@0: Chris@112: #if (PY_MAJOR_VERSION >= 3) Chris@112: static struct PyModuleDef vampyhostdef = { Chris@112: PyModuleDef_HEAD_INIT, Chris@112: "vampyhost", Chris@112: "Load and run Vamp audio analysis plugins.", Chris@112: -1, Chris@112: vampyhost_methods, Chris@112: 0, 0, 0, 0 Chris@112: }; Chris@112: #else Chris@112: PyDoc_STRVAR(module_doc, "Load and run Vamp audio analysis plugins."); Chris@112: #endif Chris@112: Chris@25: // module initialization (includes extern C {...} as necessary) Chris@0: PyMODINIT_FUNC Chris@112: #if (PY_MAJOR_VERSION >= 3) Chris@112: PyInit_vampyhost(void) Chris@112: #else Chris@0: initvampyhost(void) Chris@112: #endif Chris@0: { Chris@0: PyObject *m; Chris@0: Chris@112: #if (PY_MAJOR_VERSION >= 3) Chris@112: #define GOOD_RETURN m Chris@112: #define BAD_RETURN 0 Chris@112: #else Chris@112: #define GOOD_RETURN Chris@112: #define BAD_RETURN Chris@112: #endif Chris@112: Chris@112: if (PyType_Ready(&RealTime_Type) < 0) return BAD_RETURN; Chris@112: if (PyType_Ready(&Plugin_Type) < 0) return BAD_RETURN; Chris@0: Chris@112: #if (PY_MAJOR_VERSION >= 3) Chris@112: m = PyModule_Create(&vampyhostdef); Chris@112: #else Chris@0: m = Py_InitModule3("vampyhost", vampyhost_methods, module_doc); Chris@112: #endif Chris@112: Chris@25: if (!m) { Chris@25: cerr << "ERROR: initvampyhost: Failed to initialise module" << endl; Chris@112: return BAD_RETURN; Chris@25: } Chris@0: Chris@14: import_array(); Chris@14: Chris@17: PyModule_AddObject(m, "RealTime", (PyObject *)&RealTime_Type); Chris@25: PyModule_AddObject(m, "Plugin", (PyObject *)&Plugin_Type); Chris@25: Chris@25: // Some enum types Chris@25: PyObject *dict = PyModule_GetDict(m); Chris@25: if (!dict) { Chris@25: cerr << "ERROR: initvampyhost: Failed to obtain module dictionary" << endl; Chris@112: return BAD_RETURN; Chris@25: } Chris@25: Chris@77: if (setint(dict, "ONE_SAMPLE_PER_STEP", Chris@25: Plugin::OutputDescriptor::OneSamplePerStep) < 0 || Chris@77: setint(dict, "FIXED_SAMPLE_RATE", Chris@25: Plugin::OutputDescriptor::FixedSampleRate) < 0 || Chris@77: setint(dict, "VARIABLE_SAMPLE_RATE", Chris@25: Plugin::OutputDescriptor::VariableSampleRate) < 0 || Chris@77: setint(dict, "TIME_DOMAIN", Chris@25: Plugin::TimeDomain) < 0 || Chris@77: setint(dict, "FREQUENCY_DOMAIN", Chris@47: Plugin::FrequencyDomain) < 0 || Chris@77: setint(dict, "ADAPT_NONE", Chris@47: 0) < 0 || Chris@77: setint(dict, "ADAPT_INPUT_DOMAIN", Chris@57: PluginLoader::ADAPT_INPUT_DOMAIN) < 0 || Chris@77: setint(dict, "ADAPT_CHANNEL_COUNT", Chris@47: PluginLoader::ADAPT_CHANNEL_COUNT) < 0 || Chris@77: setint(dict, "ADAPT_BUFFER_SIZE", Chris@47: PluginLoader::ADAPT_BUFFER_SIZE) < 0 || Chris@77: setint(dict, "ADAPT_ALL_SAFE", Chris@47: PluginLoader::ADAPT_ALL_SAFE) < 0 || Chris@77: setint(dict, "ADAPT_ALL", Chris@139: PluginLoader::ADAPT_ALL) < 0 || Chris@139: setint(dict, "SHIFT_TIMESTAMP", Chris@139: PluginInputDomainAdapter::ShiftTimestamp) < 0 || Chris@139: setint(dict, "SHIFT_DATA", Chris@139: PluginInputDomainAdapter::ShiftData) < 0 || Chris@139: setint(dict, "NO_SHIFT", Chris@139: PluginInputDomainAdapter::NoShift) < 0) { Chris@25: cerr << "ERROR: initvampyhost: Failed to add enums to module dictionary" << endl; Chris@112: return BAD_RETURN; Chris@25: } Chris@112: Chris@112: return GOOD_RETURN; Chris@0: }