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@31: #include "PyPluginObject.h" 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@31: #define NO_IMPORT_ARRAY Chris@14: #include "numpy/arrayobject.h" Chris@14: Chris@102: #if PY_MAJOR_VERSION < 3 Chris@102: #include "intobject.h" Chris@102: #endif Chris@102: Chris@33: #include "structmember.h" Chris@33: Chris@48: #include "FloatConversion.h" Chris@29: #include "VectorConversion.h" Chris@112: #include "StringConversion.h" Chris@16: #include "PyRealTime.h" Chris@0: Chris@139: #include "vamp-hostsdk/PluginWrapper.h" Chris@139: #include "vamp-hostsdk/PluginInputDomainAdapter.h" Chris@139: Chris@0: #include Chris@31: #include Chris@33: #include Chris@49: #include Chris@0: Chris@0: using namespace std; Chris@0: using namespace Vamp; Chris@139: using namespace Vamp::HostExt; Chris@0: Chris@31: static Chris@21: PyPluginObject * Chris@21: getPluginObject(PyObject *pyPluginHandle) Chris@21: { Chris@21: PyPluginObject *pd = 0; Chris@21: if (PyPlugin_Check(pyPluginHandle)) { Chris@21: pd = (PyPluginObject *)pyPluginHandle; Chris@16: } Chris@16: if (!pd || !pd->plugin) { Chris@16: PyErr_SetString(PyExc_AttributeError, Chris@39: "Invalid or already deleted plugin handle."); Chris@16: return 0; Chris@0: } else { Chris@16: return pd; Chris@0: } Chris@0: } Chris@0: Chris@134: static int Chris@134: setint(PyObject *d, const char *name, int value) Chris@134: { Chris@134: PyObject *v; Chris@134: int err; Chris@134: #if (PY_MAJOR_VERSION >= 3) Chris@134: v = PyLong_FromLong((long)value); Chris@134: #else Chris@134: v = PyInt_FromLong((long)value); Chris@134: #endif Chris@134: err = PyDict_SetItemString(d, name, v); Chris@134: Py_XDECREF(v); Chris@134: return err; Chris@134: } Chris@134: Chris@134: static int Chris@134: setfloat(PyObject *d, const char *name, double value) Chris@134: { Chris@134: PyObject *v; Chris@134: int err; Chris@134: v = PyFloat_FromDouble(value); Chris@134: err = PyDict_SetItemString(d, name, v); Chris@134: Py_XDECREF(v); Chris@134: return err; Chris@134: } Chris@134: Chris@134: static int Chris@134: setstring(PyObject *d, const char *name, string value) Chris@134: { Chris@134: PyObject *v; Chris@134: int err; Chris@134: v = StringConversion().string2py(value); Chris@134: err = PyDict_SetItemString(d, name, v); Chris@134: Py_XDECREF(v); Chris@134: return err; Chris@134: } Chris@134: Chris@31: PyObject * Chris@31: PyPluginObject_From_Plugin(Plugin *plugin) Chris@0: { Chris@124: PyPluginObject *pd = PyObject_New(PyPluginObject, &Plugin_Type); Chris@124: if (!pd) return 0; Chris@124: Chris@21: pd->plugin = plugin; Chris@21: pd->isInitialised = false; Chris@21: pd->channels = 0; Chris@21: pd->blockSize = 0; Chris@21: pd->stepSize = 0; Chris@134: pd->info = 0; Chris@134: pd->parameters = 0; Chris@134: pd->programs = 0; Chris@34: Chris@112: StringConversion strconv; Chris@112: Chris@34: PyObject *infodict = PyDict_New(); Chris@134: setint(infodict, "apiVersion", plugin->getVampApiVersion()); Chris@134: setint(infodict, "pluginVersion", plugin->getPluginVersion()); Chris@134: setstring(infodict, "identifier", plugin->getIdentifier()); Chris@134: setstring(infodict, "name", plugin->getName()); Chris@134: setstring(infodict, "description", plugin->getDescription()); Chris@134: setstring(infodict, "maker", plugin->getMaker()); Chris@134: setstring(infodict, "copyright", plugin->getCopyright()); Chris@34: pd->info = infodict; Chris@34: Chris@82: pd->inputDomain = plugin->getInputDomain(); Chris@34: Chris@35: VectorConversion conv; Chris@35: Chris@34: Plugin::ParameterList pl = plugin->getParameterDescriptors(); Chris@34: PyObject *params = PyList_New(pl.size()); Chris@34: Chris@34: for (int i = 0; i < (int)pl.size(); ++i) { Chris@34: PyObject *paramdict = PyDict_New(); Chris@134: setstring(paramdict, "identifier", pl[i].identifier); Chris@134: setstring(paramdict, "name", pl[i].name); Chris@134: setstring(paramdict, "description", pl[i].description); Chris@134: setstring(paramdict, "unit", pl[i].unit); Chris@134: setfloat(paramdict, "minValue", pl[i].minValue); Chris@134: setfloat(paramdict, "maxValue", pl[i].maxValue); Chris@134: setfloat(paramdict, "defaultValue", pl[i].defaultValue); Chris@34: if (pl[i].isQuantized) { Chris@134: PyDict_SetItemString(paramdict, "isQuantized", Py_True); Chris@134: setfloat(paramdict, "quantizeStep", pl[i].quantizeStep); Chris@34: if (!pl[i].valueNames.empty()) { Chris@134: PyObject *vv = conv.PyValue_From_StringVector(pl[i].valueNames); Chris@134: PyDict_SetItemString(paramdict, "valueNames", vv); Chris@134: Py_DECREF(vv); Chris@34: } Chris@34: } else { Chris@134: PyDict_SetItemString(paramdict, "isQuantized", Py_False); Chris@34: } Chris@34: Chris@34: PyList_SET_ITEM(params, i, paramdict); Chris@34: } Chris@34: Chris@34: pd->parameters = params; Chris@39: Chris@39: Plugin::ProgramList prl = plugin->getPrograms(); Chris@39: PyObject *progs = PyList_New(prl.size()); Chris@39: Chris@39: for (int i = 0; i < (int)prl.size(); ++i) { Chris@112: PyList_SET_ITEM(progs, i, strconv.string2py(prl[i])); Chris@39: } Chris@39: Chris@39: pd->programs = progs; Chris@37: Chris@37: return (PyObject *)pd; Chris@37: } Chris@35: Chris@37: static void Chris@37: PyPluginObject_dealloc(PyPluginObject *self) Chris@37: { Chris@115: // cerr << "PyPluginObject_dealloc: plugin object " << self << ", plugin " << self->plugin << endl; Chris@115: Chris@37: delete self->plugin; Chris@134: Py_XDECREF(self->info); Chris@134: Py_XDECREF(self->parameters); Chris@134: Py_XDECREF(self->programs); Chris@37: PyObject_Del(self); Chris@37: } Chris@37: Chris@37: static PyObject * Chris@87: convertOutput(const Plugin::OutputDescriptor &desc, int ix) Chris@86: { Chris@93: VectorConversion conv; Chris@112: StringConversion strconv; Chris@93: Chris@86: PyObject *outdict = PyDict_New(); Chris@134: setstring(outdict, "identifier", desc.identifier); Chris@134: setstring(outdict, "name", desc.name); Chris@134: setstring(outdict, "description", desc.description); Chris@134: setstring(outdict, "unit", desc.unit); Chris@93: if (desc.hasFixedBinCount) { Chris@134: PyDict_SetItemString(outdict, "hasFixedBinCount", Py_True); Chris@134: setint(outdict, "binCount", desc.binCount); Chris@93: if (!desc.binNames.empty()) { Chris@134: PyObject *vv = conv.PyValue_From_StringVector(desc.binNames); Chris@134: PyDict_SetItemString(outdict, "binNames", vv); Chris@134: Py_DECREF(vv); Chris@93: } Chris@134: } else { Chris@134: PyDict_SetItemString(outdict, "hasFixedBinCount", Py_False); Chris@93: } Chris@93: if (!desc.hasFixedBinCount || Chris@93: (desc.hasFixedBinCount && (desc.binCount > 0))) { Chris@86: if (desc.hasKnownExtents) { Chris@134: PyDict_SetItemString(outdict, "hasKnownExtents", Py_True); Chris@134: setfloat(outdict, "minValue", desc.minValue); Chris@134: setfloat(outdict, "maxValue", desc.maxValue); Chris@86: } else { Chris@134: PyDict_SetItemString(outdict, "hasKnownExtents", Py_False); Chris@86: } Chris@86: if (desc.isQuantized) { Chris@134: PyDict_SetItemString(outdict, "isQuantized", Py_True); Chris@134: setfloat(outdict, "quantizeStep", desc.quantizeStep); Chris@86: } else { Chris@134: PyDict_SetItemString(outdict, "isQuantized", Py_False); Chris@86: } Chris@86: } Chris@134: setint(outdict, "sampleType", (int)desc.sampleType); Chris@134: setfloat(outdict, "sampleRate", desc.sampleRate); Chris@86: PyDict_SetItemString Chris@109: (outdict, "hasDuration", desc.hasDuration ? Py_True : Py_False); Chris@134: setint(outdict, "output_index", ix); Chris@86: return outdict; Chris@86: } Chris@86: Chris@86: static PyObject * Chris@86: get_output(PyObject *self, PyObject *args) Chris@86: { Chris@86: PyPluginObject *pd = getPluginObject(self); Chris@86: if (!pd) return 0; Chris@86: Chris@112: ssize_t n = -1; Chris@86: PyObject *pyId = 0; Chris@86: Chris@86: if (!PyArg_ParseTuple(args, "n", &n) && Chris@112: !PyArg_ParseTuple(args, Chris@112: #if (PY_MAJOR_VERSION >= 3) Chris@112: "U", Chris@112: #else Chris@112: "S", Chris@112: #endif Chris@112: &pyId)) { Chris@86: PyErr_SetString(PyExc_TypeError, Chris@86: "get_output takes either output id (string) or output index (int) argument"); Chris@86: return 0; Chris@86: } Chris@86: Chris@87: PyErr_Clear(); Chris@87: Chris@86: Plugin::OutputList ol = pd->plugin->getOutputDescriptors(); Chris@86: Chris@112: StringConversion strconv; Chris@112: Chris@86: if (pyId) { Chris@112: string id = strconv.py2string(pyId); Chris@86: for (int i = 0; i < int(ol.size()); ++i) { Chris@86: if (ol[i].identifier == id) { Chris@87: return convertOutput(ol[i], i); Chris@86: } Chris@86: } Chris@86: } else { Chris@86: if (n >= 0 && n < int(ol.size())) { Chris@87: return convertOutput(ol[n], n); Chris@86: } Chris@86: } Chris@86: Chris@102: PyErr_SetString(PyExc_Exception, Chris@86: "unknown output id or output index out of range"); Chris@86: return 0; Chris@86: } Chris@86: Chris@86: static PyObject * Chris@80: get_outputs(PyObject *self, PyObject *args) Chris@37: { Chris@37: PyPluginObject *pd = getPluginObject(self); Chris@37: if (!pd) return 0; Chris@37: Plugin::OutputList ol = pd->plugin->getOutputDescriptors(); Chris@35: PyObject *outputs = PyList_New(ol.size()); Chris@35: Chris@35: for (int i = 0; i < (int)ol.size(); ++i) { Chris@87: PyObject *outdict = convertOutput(ol[i], i); Chris@35: PyList_SET_ITEM(outputs, i, outdict); Chris@35: } Chris@35: Chris@37: return outputs; Chris@33: } Chris@33: Chris@0: static PyObject * Chris@139: set_process_timestamp_method(PyObject *self, PyObject *args) Chris@139: { Chris@139: ssize_t method; Chris@139: Chris@139: if (!PyArg_ParseTuple(args, Chris@139: "n", Chris@139: &method)) { Chris@139: PyErr_SetString(PyExc_TypeError, Chris@139: "set_process_timestamp_method() takes method (int) argument"); Chris@139: return 0; } Chris@139: Chris@139: PyPluginObject *pd = getPluginObject(self); Chris@139: if (!pd) return 0; Chris@139: Chris@139: PluginWrapper *wrapper = dynamic_cast(pd->plugin); Chris@139: if (!wrapper) { Chris@139: PyErr_SetString(PyExc_Exception, Chris@139: "Plugin was not loaded with ADAPT_INPUT_DOMAIN flag (no wrapper present)"); Chris@139: return 0; Chris@139: } Chris@139: Chris@139: PluginInputDomainAdapter *adapter = wrapper->getWrapper(); Chris@139: if (!adapter) { Chris@139: Py_RETURN_FALSE; Chris@139: } Chris@139: Chris@139: adapter->setProcessTimestampMethod Chris@139: (PluginInputDomainAdapter::ProcessTimestampMethod(method)); Chris@139: Py_RETURN_TRUE; Chris@139: } Chris@139: Chris@139: static PyObject * Chris@39: initialise(PyObject *self, PyObject *args) Chris@0: { Chris@112: ssize_t channels, blockSize, stepSize; Chris@0: Chris@23: if (!PyArg_ParseTuple (args, "nnn", Chris@112: &channels, Chris@112: &stepSize, Chris@112: &blockSize)) { Chris@39: PyErr_SetString(PyExc_TypeError, Chris@147: "initialise() takes channel count (int), step size (int), and block size (int) arguments"); Chris@39: return 0; Chris@0: } Chris@0: Chris@23: PyPluginObject *pd = getPluginObject(self); Chris@16: if (!pd) return 0; Chris@0: Chris@16: pd->channels = channels; Chris@16: pd->stepSize = stepSize; Chris@16: pd->blockSize = blockSize; Chris@0: Chris@16: if (!pd->plugin->initialise(channels, stepSize, blockSize)) { Chris@39: cerr << "Failed to initialise native plugin adapter with channels = " << channels << ", stepSize = " << stepSize << ", blockSize = " << blockSize << endl; Chris@39: PyErr_SetString(PyExc_TypeError, Chris@39: "Plugin initialization failed"); Chris@39: return 0; Chris@6: } Chris@0: Chris@16: pd->isInitialised = true; luis@7: Chris@134: Py_RETURN_TRUE; Chris@0: } Chris@0: Chris@0: static PyObject * Chris@39: reset(PyObject *self, PyObject *) Chris@18: { Chris@23: PyPluginObject *pd = getPluginObject(self); Chris@18: if (!pd) return 0; Chris@18: Chris@134: if (!pd->isInitialised || !pd->plugin) { Chris@102: PyErr_SetString(PyExc_Exception, Chris@18: "Plugin has not been initialised"); Chris@18: return 0; Chris@18: } Chris@18: Chris@18: pd->plugin->reset(); Chris@134: Py_RETURN_TRUE; Chris@18: } Chris@18: Chris@49: static bool Chris@49: hasParameter(PyPluginObject *pd, string id) Chris@49: { Chris@49: PluginBase::ParameterList pl = pd->plugin->getParameterDescriptors(); Chris@49: for (int i = 0; i < (int)pl.size(); ++i) { Chris@49: if (pl[i].identifier == id) { Chris@49: return true; Chris@49: } Chris@49: } Chris@49: return false; Chris@49: } Chris@49: Chris@18: static PyObject * Chris@80: get_parameter_value(PyObject *self, PyObject *args) Chris@20: { Chris@20: PyObject *pyParam; Chris@20: 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: &pyParam)) { Chris@39: PyErr_SetString(PyExc_TypeError, Chris@80: "get_parameter_value() takes parameter id (string) argument"); Chris@39: return 0; } Chris@20: Chris@23: PyPluginObject *pd = getPluginObject(self); Chris@20: if (!pd) return 0; Chris@20: Chris@112: StringConversion strconv; Chris@112: Chris@112: string param = strconv.py2string(pyParam); Chris@49: Chris@49: if (!hasParameter(pd, param)) { Chris@102: PyErr_SetString(PyExc_Exception, Chris@49: (string("Unknown parameter id \"") + param + "\"").c_str()); Chris@49: return 0; Chris@49: } Chris@49: Chris@49: float value = pd->plugin->getParameter(param); Chris@20: return PyFloat_FromDouble(double(value)); Chris@20: } Chris@20: Chris@20: static PyObject * Chris@80: set_parameter_value(PyObject *self, PyObject *args) Chris@20: { Chris@20: PyObject *pyParam; Chris@20: float value; Chris@20: Chris@112: if (!PyArg_ParseTuple(args, Chris@112: #if (PY_MAJOR_VERSION >= 3) Chris@112: "Uf", Chris@112: #else Chris@112: "Sf", Chris@112: #endif Chris@112: &pyParam, &value)) { Chris@39: PyErr_SetString(PyExc_TypeError, Chris@80: "set_parameter_value() takes parameter id (string), and value (float) arguments"); Chris@39: return 0; } Chris@20: Chris@23: PyPluginObject *pd = getPluginObject(self); Chris@20: if (!pd) return 0; Chris@20: Chris@112: StringConversion strconv; Chris@112: Chris@112: string param = strconv.py2string(pyParam); Chris@49: Chris@49: if (!hasParameter(pd, param)) { Chris@102: PyErr_SetString(PyExc_Exception, Chris@49: (string("Unknown parameter id \"") + param + "\"").c_str()); Chris@49: return 0; Chris@49: } Chris@49: Chris@49: pd->plugin->setParameter(param, value); Chris@134: Py_RETURN_TRUE; Chris@20: } Chris@20: Chris@39: static PyObject * Chris@80: set_parameter_values(PyObject *self, PyObject *args) Chris@48: { Chris@48: PyObject *dict; Chris@48: Chris@48: if (!PyArg_ParseTuple(args, "O", &dict)) { Chris@48: PyErr_SetString(PyExc_TypeError, Chris@80: "set_parameter_values() takes dict argument"); Chris@134: return 0; Chris@134: } Chris@48: Chris@48: if (!PyDict_Check(dict)) { Chris@48: PyErr_SetString(PyExc_TypeError, Chris@80: "set_parameter_values() takes dict argument"); Chris@134: return 0; Chris@134: } Chris@48: Chris@48: PyPluginObject *pd = getPluginObject(self); Chris@48: if (!pd) return 0; Chris@48: Chris@49: PluginBase::ParameterList pl = pd->plugin->getParameterDescriptors(); Chris@49: set paramIds; Chris@49: for (int i = 0; i < (int)pl.size(); ++i) { Chris@49: paramIds.insert(pl[i].identifier); Chris@49: } Chris@49: Chris@48: Py_ssize_t pos = 0; Chris@48: PyObject *key, *value; Chris@48: while (PyDict_Next(dict, &pos, &key, &value)) { Chris@102: #if PY_MAJOR_VERSION >= 3 Chris@102: if (!key || !PyUnicode_CheckExact(key)) { Chris@102: #else Chris@48: if (!key || !PyString_CheckExact(key)) { Chris@102: #endif Chris@48: PyErr_SetString(PyExc_TypeError, Chris@48: "Parameter dict keys must all have string type"); Chris@48: return 0; Chris@48: } Chris@48: if (!value || !FloatConversion::check(value)) { Chris@48: PyErr_SetString(PyExc_TypeError, Chris@48: "Parameter dict values must be convertible to float"); Chris@48: return 0; Chris@48: } Chris@112: StringConversion strconv; Chris@112: string param = strconv.py2string(key); Chris@49: if (paramIds.find(param) == paramIds.end()) { Chris@102: PyErr_SetString(PyExc_Exception, Chris@49: (string("Unknown parameter id \"") + param + "\"").c_str()); Chris@49: return 0; Chris@49: } Chris@49: pd->plugin->setParameter(param, FloatConversion::convert(value)); Chris@48: } Chris@134: Chris@134: Py_RETURN_TRUE; Chris@48: } Chris@48: Chris@48: static PyObject * Chris@80: select_program(PyObject *self, PyObject *args) Chris@39: { Chris@39: PyObject *pyParam; Chris@39: 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: &pyParam)) { Chris@39: PyErr_SetString(PyExc_TypeError, Chris@80: "select_program() takes parameter id (string) argument"); Chris@134: return 0; Chris@134: } Chris@39: Chris@39: PyPluginObject *pd = getPluginObject(self); Chris@39: if (!pd) return 0; Chris@39: Chris@112: StringConversion strconv; Chris@112: Chris@112: pd->plugin->selectProgram(strconv.py2string(pyParam)); Chris@134: Py_RETURN_TRUE; Chris@39: } Chris@39: Chris@35: static Chris@35: PyObject * Chris@35: convertFeatureSet(const Plugin::FeatureSet &fs) Chris@35: { Chris@35: VectorConversion conv; Chris@35: Chris@35: PyObject *pyFs = PyDict_New(); Chris@35: Chris@35: for (Plugin::FeatureSet::const_iterator fsi = fs.begin(); Chris@35: fsi != fs.end(); ++fsi) { Chris@35: Chris@35: int fno = fsi->first; Chris@35: const Plugin::FeatureList &fl = fsi->second; Chris@35: Chris@35: if (!fl.empty()) { Chris@35: Chris@35: PyObject *pyFl = PyList_New(fl.size()); Chris@35: Chris@35: for (int fli = 0; fli < (int)fl.size(); ++fli) { Chris@35: Chris@35: const Plugin::Feature &f = fl[fli]; Chris@35: PyObject *pyF = PyDict_New(); Chris@35: Chris@35: if (f.hasTimestamp) { Chris@134: PyObject *rt = PyRealTime_FromRealTime(f.timestamp); Chris@134: PyDict_SetItemString(pyF, "timestamp", rt); Chris@134: Py_DECREF(rt); Chris@35: } Chris@35: if (f.hasDuration) { Chris@134: PyObject *rt = PyRealTime_FromRealTime(f.duration); Chris@134: PyDict_SetItemString(pyF, "duration", rt); Chris@134: Py_DECREF(rt); Chris@35: } Chris@35: Chris@112: StringConversion strconv; Chris@134: Chris@134: setstring(pyF, "label", f.label); Chris@35: Chris@35: if (!f.values.empty()) { Chris@134: PyObject *vv = conv.PyArray_From_FloatVector(f.values); Chris@134: PyDict_SetItemString(pyF, "values", vv); Chris@134: Py_DECREF(vv); Chris@35: } Chris@35: Chris@35: PyList_SET_ITEM(pyFl, fli, pyF); Chris@35: } Chris@35: Chris@102: PyObject *pyN = PyLong_FromLong(fno); Chris@35: PyDict_SetItem(pyFs, pyN, pyFl); Chris@134: Py_DECREF(pyN); Chris@134: Py_DECREF(pyFl); Chris@35: } Chris@35: } Chris@35: Chris@35: return pyFs; Chris@35: } Chris@35: Chris@40: static vector > Chris@40: convertPluginInput(PyObject *pyBuffer, int channels, int blockSize) Chris@40: { Chris@40: vector > data; Chris@40: Chris@40: VectorConversion conv; Chris@40: Chris@40: if (PyArray_CheckExact(pyBuffer)) { Chris@40: Chris@40: data = conv.Py2DArray_To_FloatVector(pyBuffer); Chris@40: Chris@40: if (conv.error) { Chris@40: PyErr_SetString(PyExc_TypeError, conv.getError().str().c_str()); Chris@40: return data; Chris@40: } Chris@40: Chris@41: if ((int)data.size() != channels) { Chris@41: // cerr << "Wrong number of channels: got " << data.size() << ", expected " << channels << endl; Chris@41: PyErr_SetString(PyExc_TypeError, "Wrong number of channels"); Chris@41: return vector >(); Chris@41: } Chris@41: Chris@40: } else { Chris@40: Chris@40: if (!PyList_Check(pyBuffer)) { Chris@43: PyErr_SetString(PyExc_TypeError, "List of NumPy arrays or lists of numbers required for process input"); Chris@40: return data; Chris@40: } Chris@43: Chris@40: if (PyList_GET_SIZE(pyBuffer) != channels) { Chris@41: // cerr << "Wrong number of channels: got " << PyList_GET_SIZE(pyBuffer) << ", expected " << channels << endl; Chris@40: PyErr_SetString(PyExc_TypeError, "Wrong number of channels"); Chris@40: return data; Chris@40: } Chris@40: Chris@40: for (int c = 0; c < channels; ++c) { Chris@40: PyObject *cbuf = PyList_GET_ITEM(pyBuffer, c); Chris@40: data.push_back(conv.PyValue_To_FloatVector(cbuf)); Chris@43: if (conv.error) { Chris@43: PyErr_SetString(PyExc_TypeError, conv.getError().str().c_str()); Chris@43: return vector >(); Chris@43: } Chris@40: } Chris@41: } Chris@40: Chris@41: for (int c = 0; c < channels; ++c) { Chris@41: if ((int)data[c].size() != blockSize) { Chris@41: // cerr << "Wrong number of samples on channel " << c << ": expected " << blockSize << " (plugin's block size), got " << data[c].size() << endl; Chris@46: PyErr_SetString(PyExc_TypeError, "Wrong number of samples for process block"); Chris@41: return vector >(); Chris@40: } Chris@40: } Chris@40: Chris@40: return data; Chris@40: } Chris@40: Chris@20: static PyObject * Chris@80: process_block(PyObject *self, PyObject *args) Chris@0: { Chris@0: PyObject *pyBuffer; Chris@0: PyObject *pyRealTime; Chris@0: Chris@23: if (!PyArg_ParseTuple(args, "OO", Chris@39: &pyBuffer, // Audio data Chris@39: &pyRealTime)) { // TimeStamp Chris@39: PyErr_SetString(PyExc_TypeError, Chris@80: "process_block() takes buffer (2D array or list of arrays, one row per channel) and timestamp (RealTime) arguments"); Chris@39: return 0; } Chris@0: Chris@0: if (!PyRealTime_Check(pyRealTime)) { Chris@40: PyErr_SetString(PyExc_TypeError, "Valid timestamp required."); Chris@39: return 0; } Chris@0: Chris@23: PyPluginObject *pd = getPluginObject(self); Chris@16: if (!pd) return 0; Chris@0: Chris@0: if (!pd->isInitialised) { Chris@102: PyErr_SetString(PyExc_Exception, Chris@39: "Plugin has not been initialised."); Chris@39: return 0; Chris@16: } Chris@0: Chris@40: int channels = pd->channels; Chris@40: vector > data = Chris@40: convertPluginInput(pyBuffer, channels, pd->blockSize); Chris@40: if (data.empty()) return 0; Chris@0: Chris@4: float **inbuf = new float *[channels]; Chris@4: for (int c = 0; c < channels; ++c) { Chris@12: inbuf[c] = &data[c][0]; Chris@4: } Chris@12: RealTime timeStamp = *PyRealTime_AsRealTime(pyRealTime); Chris@18: Plugin::FeatureSet fs = pd->plugin->process(inbuf, timeStamp); Chris@4: delete[] inbuf; Chris@0: Chris@35: return convertFeatureSet(fs); Chris@35: } Chris@0: Chris@35: static PyObject * Chris@80: get_remaining_features(PyObject *self, PyObject *) Chris@35: { Chris@35: PyPluginObject *pd = getPluginObject(self); Chris@35: if (!pd) return 0; Chris@18: Chris@35: if (!pd->isInitialised) { Chris@102: PyErr_SetString(PyExc_Exception, Chris@39: "Plugin has not been initialised."); Chris@39: return 0; Chris@35: } Chris@18: Chris@35: Plugin::FeatureSet fs = pd->plugin->getRemainingFeatures(); Chris@18: Chris@35: return convertFeatureSet(fs); Chris@0: } Chris@0: Chris@23: static PyObject * Chris@80: get_preferred_block_size(PyObject *self, PyObject *) Chris@37: { Chris@37: PyPluginObject *pd = getPluginObject(self); Chris@37: if (!pd) return 0; Chris@102: return PyLong_FromLong(pd->plugin->getPreferredBlockSize()); Chris@37: } Chris@37: Chris@37: static PyObject * Chris@80: get_preferred_step_size(PyObject *self, PyObject *) Chris@37: { Chris@37: PyPluginObject *pd = getPluginObject(self); Chris@37: if (!pd) return 0; Chris@102: return PyLong_FromLong(pd->plugin->getPreferredStepSize()); Chris@37: } Chris@37: Chris@37: static PyObject * Chris@80: get_min_channel_count(PyObject *self, PyObject *) Chris@37: { Chris@37: PyPluginObject *pd = getPluginObject(self); Chris@37: if (!pd) return 0; Chris@102: return PyLong_FromLong(pd->plugin->getMinChannelCount()); Chris@37: } Chris@37: Chris@37: static PyObject * Chris@80: get_max_channel_count(PyObject *self, PyObject *) Chris@37: { Chris@37: PyPluginObject *pd = getPluginObject(self); Chris@37: if (!pd) return 0; Chris@102: return PyLong_FromLong(pd->plugin->getMaxChannelCount()); Chris@37: } Chris@37: Chris@37: static PyObject * Chris@39: unload(PyObject *self, PyObject *) Chris@23: { Chris@23: PyPluginObject *pd = getPluginObject(self); Chris@23: if (!pd) return 0; Chris@23: Chris@115: // cerr << "unload: unloading plugin object " << pd << ", plugin " << pd->plugin << endl; Chris@115: Chris@23: delete pd->plugin; Chris@32: pd->plugin = 0; // This is checked by getPluginObject, so we avoid Chris@32: // blowing up if called repeatedly Chris@23: Chris@134: Py_RETURN_TRUE; Chris@23: } Chris@23: Chris@33: static PyMemberDef PyPluginObject_members[] = Chris@33: { Chris@34: {(char *)"info", T_OBJECT, offsetof(PyPluginObject, info), READONLY, Chris@39: (char *)"info -> A read-only dictionary of plugin metadata."}, Chris@34: Chris@110: {(char *)"inputDomain", T_INT, offsetof(PyPluginObject, inputDomain), READONLY, Chris@110: (char *)"inputDomain -> The format of input audio required by the plugin, either vampyhost.TIME_DOMAIN or vampyhost.FREQUENCY_DOMAIN."}, Chris@34: Chris@34: {(char *)"parameters", T_OBJECT, offsetof(PyPluginObject, parameters), READONLY, Chris@39: (char *)"parameters -> A list of metadata dictionaries describing the plugin's configurable parameters."}, Chris@39: Chris@39: {(char *)"programs", T_OBJECT, offsetof(PyPluginObject, programs), READONLY, Chris@39: (char *)"programs -> A list of the programs available for this plugin, if any."}, Chris@33: Chris@33: {0, 0} Chris@33: }; Chris@33: Chris@21: static PyMethodDef PyPluginObject_methods[] = Chris@21: { Chris@80: {"get_outputs", get_outputs, METH_NOARGS, Chris@80: "get_outputs() -> Obtain the output descriptors for all of the plugin's outputs."}, Chris@37: Chris@86: {"get_output", get_output, METH_VARARGS, Chris@86: "get_output(out) -> Obtain the output descriptor for a single output, by either id (string) or index (int)."}, Chris@86: Chris@80: {"get_parameter_value", get_parameter_value, METH_VARARGS, Chris@80: "get_parameter_value(identifier) -> Return the value of the parameter with the given identifier."}, Chris@23: Chris@80: {"set_parameter_value", set_parameter_value, METH_VARARGS, Chris@80: "set_parameter_value(identifier, value) -> Set the parameter with the given identifier to the given value."}, Chris@37: Chris@80: {"set_parameter_values", set_parameter_values, METH_VARARGS, Chris@80: "set_parameter_values(dict) -> Set multiple parameters to values corresponding to the key/value pairs in the dict. Any parameters not mentioned in the dict are unchanged."}, Chris@139: Chris@80: {"select_program", select_program, METH_VARARGS, Chris@80: "select_program(name) -> Select the processing program with the given name."}, Chris@39: Chris@80: {"get_preferred_block_size", get_preferred_block_size, METH_VARARGS, Chris@80: "get_preferred_block_size() -> Return the plugin's preferred processing block size, or 0 if the plugin accepts any block size."}, Chris@37: Chris@80: {"get_preferred_step_size", get_preferred_step_size, METH_VARARGS, Chris@80: "get_preferred_step_size() -> Return the plugin's preferred processing step size, or 0 if the plugin allows the host to select. If this is 0, the host should normally choose the same step as block size for time-domain plugins, or half the block size for frequency-domain plugins."}, Chris@37: Chris@80: {"get_min_channel_count", get_min_channel_count, METH_VARARGS, Chris@80: "get_min_channel_count() -> Return the minimum number of channels of audio data the plugin accepts as input."}, Chris@37: Chris@80: {"get_max_channel_count", get_max_channel_count, METH_VARARGS, Chris@80: "get_max_channel_count() -> Return the maximum number of channels of audio data the plugin accepts as input."}, Chris@139: Chris@139: {"set_process_timestamp_method", set_process_timestamp_method, METH_VARARGS, Chris@139: "set_process_timestamp_method(method) -> Set the method used for timestamp adjustment in plugins using frequency-domain input, where that input is being automatically converted for a plugin loaded with the ADAPT_INPUT_DOMAIN flag set (or one of ADAPT_ALL_SAFE or ADAPT_ALL). The method must be one of SHIFT_TIMESTAMP, SHIFT_DATA, or NO_SHIFT. The default is SHIFT_TIMESTAMP."}, Chris@33: Chris@39: {"initialise", initialise, METH_VARARGS, Chris@80: "initialise(channels, stepSize, blockSize) -> Initialise the plugin for the given number of channels and processing frame sizes. This must be called before process_block() can be used."}, Chris@23: Chris@39: {"reset", reset, METH_NOARGS, Chris@39: "reset() -> Reset the plugin after processing, to prepare for another processing run with the same parameters."}, Chris@23: Chris@80: {"process_block", process_block, METH_VARARGS, Chris@80: "process_block(block, timestamp) -> Provide one processing frame to the plugin, with its timestamp, and obtain any features that were extracted immediately from this frame."}, Chris@23: Chris@80: {"get_remaining_features", get_remaining_features, METH_NOARGS, Chris@80: "get_remaining_features() -> Obtain any features extracted at the end of processing."}, Chris@35: Chris@39: {"unload", unload, METH_NOARGS, Chris@39: "unload() -> Dispose of the plugin. You cannot use the plugin object again after calling this. Note that unloading also happens automatically when the plugin object's reference count reaches zero; this function is only necessary if you wish to ensure the native part of the plugin is disposed of before then."}, Chris@23: Chris@21: {0, 0} Chris@21: }; Chris@21: Chris@21: /* Doc:: 10.3 Type Objects */ /* static */ Chris@21: PyTypeObject Plugin_Type = Chris@21: { Chris@112: PyVarObject_HEAD_INIT(NULL, 0) Chris@39: "vampyhost.Plugin", /*tp_name*/ Chris@39: sizeof(PyPluginObject), /*tp_basicsize*/ Chris@39: 0, /*tp_itemsize*/ Chris@21: (destructor)PyPluginObject_dealloc, /*tp_dealloc*/ Chris@39: 0, /*tp_print*/ Chris@39: 0, /*tp_getattr*/ Chris@39: 0, /*tp_setattr*/ Chris@39: 0, /*tp_compare*/ Chris@39: 0, /*tp_repr*/ Chris@39: 0, /*tp_as_number*/ Chris@39: 0, /*tp_as_sequence*/ Chris@39: 0, /*tp_as_mapping*/ Chris@39: 0, /*tp_hash*/ Chris@39: 0, /*tp_call*/ Chris@39: 0, /*tp_str*/ Chris@39: PyObject_GenericGetAttr, /*tp_getattro*/ Chris@39: PyObject_GenericSetAttr, /*tp_setattro*/ Chris@39: 0, /*tp_as_buffer*/ Chris@39: Py_TPFLAGS_DEFAULT, /*tp_flags*/ Chris@40: "Plugin object, providing a low-level API for running a Vamp plugin.", /*tp_doc*/ Chris@39: 0, /*tp_traverse*/ Chris@39: 0, /*tp_clear*/ Chris@39: 0, /*tp_richcompare*/ Chris@39: 0, /*tp_weaklistoffset*/ Chris@39: 0, /*tp_iter*/ Chris@39: 0, /*tp_iternext*/ Chris@39: PyPluginObject_methods, /*tp_methods*/ Chris@39: PyPluginObject_members, /*tp_members*/ Chris@39: 0, /*tp_getset*/ Chris@39: 0, /*tp_base*/ Chris@39: 0, /*tp_dict*/ Chris@39: 0, /*tp_descr_get*/ Chris@39: 0, /*tp_descr_set*/ Chris@39: 0, /*tp_dictoffset*/ Chris@39: 0, /*tp_init*/ Chris@124: 0, /*tp_alloc*/ Chris@39: 0, /*tp_new*/ Chris@124: 0, /*tp_free*/ Chris@39: 0, /*tp_is_gc*/ Chris@21: }; Chris@0: