annotate native/vampyhost.cpp @ 117:2370b942cd32

Docs, make collect() return a dict so it can be more easily tested for shape, and rationalise some function naming etc
author Chris Cannam
date Wed, 17 Jun 2015 15:31:16 +0100
parents 9343eee50605
children 7c0c11577819
rev   line source
Chris@0 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@0 2
Chris@26 3 /*
Chris@26 4 VampyHost
Chris@26 5
Chris@26 6 Use Vamp audio analysis plugins in Python
Chris@26 7
Chris@26 8 Gyorgy Fazekas and Chris Cannam
Chris@26 9 Centre for Digital Music, Queen Mary, University of London
Chris@117 10 Copyright 2008-2015 Queen Mary, University of London
Chris@26 11
Chris@26 12 Permission is hereby granted, free of charge, to any person
Chris@26 13 obtaining a copy of this software and associated documentation
Chris@26 14 files (the "Software"), to deal in the Software without
Chris@26 15 restriction, including without limitation the rights to use, copy,
Chris@26 16 modify, merge, publish, distribute, sublicense, and/or sell copies
Chris@26 17 of the Software, and to permit persons to whom the Software is
Chris@26 18 furnished to do so, subject to the following conditions:
Chris@26 19
Chris@26 20 The above copyright notice and this permission notice shall be
Chris@26 21 included in all copies or substantial portions of the Software.
Chris@26 22
Chris@26 23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
Chris@26 24 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
Chris@26 25 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
Chris@26 26 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
Chris@26 27 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
Chris@26 28 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
Chris@26 29 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Chris@26 30
Chris@26 31 Except as contained in this notice, the names of the Centre for
Chris@26 32 Digital Music; Queen Mary, University of London; and the authors
Chris@26 33 shall not be used in advertising or otherwise to promote the sale,
Chris@26 34 use or other dealings in this Software without prior written
Chris@26 35 authorization.
Chris@26 36 */
Chris@26 37
Chris@39 38 // include for python extension module: must be first
Chris@0 39 #include <Python.h>
Chris@14 40
Chris@14 41 // define a unique API pointer
Chris@27 42 #define PY_ARRAY_UNIQUE_SYMBOL VAMPYHOST_ARRAY_API
Chris@14 43 #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
Chris@14 44 #include "numpy/arrayobject.h"
Chris@14 45
Chris@31 46 #include "PyRealTime.h"
Chris@31 47 #include "PyPluginObject.h"
Chris@12 48
Chris@1 49 #include "vamp-hostsdk/PluginHostAdapter.h"
Chris@1 50 #include "vamp-hostsdk/PluginChannelAdapter.h"
Chris@1 51 #include "vamp-hostsdk/PluginInputDomainAdapter.h"
Chris@1 52 #include "vamp-hostsdk/PluginLoader.h"
Chris@16 53
Chris@29 54 #include "VectorConversion.h"
Chris@112 55 #include "StringConversion.h"
Chris@16 56 #include "PyRealTime.h"
Chris@0 57
Chris@0 58 #include <iostream>
Chris@0 59 #include <string>
Chris@0 60
Chris@0 61 #include <cmath>
Chris@0 62
Chris@0 63 using namespace std;
Chris@0 64 using namespace Vamp;
Chris@31 65 using namespace Vamp::HostExt;
Chris@21 66
Chris@0 67 static PyObject *
Chris@79 68 list_plugins(PyObject *self, PyObject *)
Chris@0 69 {
Chris@0 70 PluginLoader *loader = PluginLoader::getInstance();
Chris@0 71 vector<PluginLoader::PluginKey> plugins = loader->listPlugins();
Chris@29 72 VectorConversion conv;
Chris@15 73 return conv.PyValue_From_StringVector(plugins);
Chris@0 74 }
Chris@0 75
Chris@15 76 static PyObject *
Chris@79 77 get_plugin_path(PyObject *self, PyObject *)
Chris@15 78 {
Chris@15 79 vector<string> path = PluginHostAdapter::getPluginPath();
Chris@29 80 VectorConversion conv;
Chris@15 81 return conv.PyValue_From_StringVector(path);
Chris@15 82 }
Chris@0 83
Chris@15 84 static string toPluginKey(PyObject *pyPluginKey)
Chris@0 85 {
Chris@36 86 // convert to stl string
Chris@112 87 string pluginKey(StringConversion().py2string(pyPluginKey));
Chris@0 88
Chris@36 89 // check pluginKey validity
Chris@0 90 string::size_type ki = pluginKey.find(':');
Chris@0 91 if (ki == string::npos) {
Chris@39 92 PyErr_SetString(PyExc_TypeError,
Chris@39 93 "Plugin key must be of the form library:identifier");
Chris@39 94 return "";
Chris@0 95 }
Chris@0 96
Chris@15 97 return pluginKey;
Chris@15 98 }
Chris@15 99
Chris@15 100 static PyObject *
Chris@79 101 get_library_for(PyObject *self, PyObject *args)
Chris@15 102 {
Chris@15 103 PyObject *pyPluginKey;
Chris@15 104
Chris@112 105 if (!PyArg_ParseTuple(args,
Chris@112 106 #if (PY_MAJOR_VERSION >= 3)
Chris@112 107 "U",
Chris@112 108 #else
Chris@112 109 "S",
Chris@112 110 #endif
Chris@112 111 &pyPluginKey)) {
Chris@39 112 PyErr_SetString(PyExc_TypeError,
Chris@79 113 "get_library_for() takes plugin key (string) argument");
Chris@39 114 return 0; }
Chris@15 115
Chris@15 116 string pluginKey = toPluginKey(pyPluginKey);
Chris@16 117 if (pluginKey == "") return 0;
Chris@15 118
Chris@0 119 PluginLoader *loader = PluginLoader::getInstance();
Chris@0 120 string path = loader->getLibraryPathForPlugin(pluginKey);
Chris@112 121 PyObject *pyPath = StringConversion().string2py(path.c_str());
Chris@0 122 return pyPath;
Chris@0 123 }
Chris@0 124
Chris@0 125 static PyObject *
Chris@79 126 get_category_of(PyObject *self, PyObject *args)
Chris@0 127 {
Chris@0 128 PyObject *pyPluginKey;
Chris@0 129
Chris@112 130 if (!PyArg_ParseTuple(args,
Chris@112 131 #if (PY_MAJOR_VERSION >= 3)
Chris@112 132 "U",
Chris@112 133 #else
Chris@112 134 "S",
Chris@112 135 #endif
Chris@112 136 &pyPluginKey)) {
Chris@39 137 PyErr_SetString(PyExc_TypeError,
Chris@79 138 "get_category_of() takes plugin key (string) argument");
Chris@39 139 return 0; }
Chris@0 140
Chris@15 141 string pluginKey = toPluginKey(pyPluginKey);
Chris@16 142 if (pluginKey == "") return 0;
Chris@0 143
Chris@0 144 PluginLoader *loader = PluginLoader::getInstance();
luis@7 145 PluginLoader::PluginCategoryHierarchy
Chris@39 146 category = loader->getPluginCategory(pluginKey);
Chris@0 147
Chris@29 148 VectorConversion conv;
Chris@15 149 return conv.PyValue_From_StringVector(category);
Chris@0 150 }
Chris@0 151
Chris@0 152 static PyObject *
Chris@79 153 get_outputs_of(PyObject *self, PyObject *args)
Chris@0 154 {
Chris@31 155 PyObject *pyPluginKey;
Chris@31 156
Chris@112 157 if (!PyArg_ParseTuple(args,
Chris@112 158 #if (PY_MAJOR_VERSION >= 3)
Chris@112 159 "U",
Chris@112 160 #else
Chris@112 161 "S",
Chris@112 162 #endif
Chris@112 163 &pyPluginKey)) {
Chris@39 164 PyErr_SetString(PyExc_TypeError,
Chris@79 165 "get_outputs_of() takes plugin key (string) argument");
Chris@39 166 return 0; }
Chris@31 167
Chris@15 168 Plugin::OutputList outputs;
Chris@0 169
Chris@31 170 string pluginKey = toPluginKey(pyPluginKey);
Chris@31 171 if (pluginKey == "") return 0;
Chris@31 172
Chris@31 173 PluginLoader *loader = PluginLoader::getInstance();
Chris@31 174
Chris@47 175 Plugin *plugin = loader->loadPlugin(pluginKey, 48000, 0);
Chris@31 176 if (!plugin) {
Chris@31 177 string pyerr("Failed to load plugin: "); pyerr += pluginKey;
Chris@31 178 PyErr_SetString(PyExc_TypeError,pyerr.c_str());
Chris@31 179 return 0;
Chris@0 180 }
Chris@0 181
Chris@31 182 outputs = plugin->getOutputDescriptors();
luis@7 183
Chris@0 184 PyObject *pyList = PyList_New(outputs.size());
Chris@0 185
Chris@0 186 for (size_t i = 0; i < outputs.size(); ++i) {
Chris@39 187 PyObject *pyOutputId =
Chris@112 188 StringConversion().string2py(outputs[i].identifier.c_str());
Chris@39 189 PyList_SET_ITEM(pyList, i, pyOutputId);
Chris@0 190 }
Chris@0 191
Chris@0 192 return pyList;
Chris@0 193 }
Chris@0 194
Chris@0 195 static PyObject *
Chris@79 196 load_plugin(PyObject *self, PyObject *args)
Chris@0 197 {
Chris@0 198 PyObject *pyPluginKey;
Chris@0 199 float inputSampleRate;
Chris@112 200 ssize_t adapterFlags;
Chris@0 201
Chris@112 202 if (!PyArg_ParseTuple(args,
Chris@112 203 #if (PY_MAJOR_VERSION >= 3)
Chris@112 204 "Ufn",
Chris@112 205 #else
Chris@112 206 "Sfn",
Chris@112 207 #endif
Chris@39 208 &pyPluginKey,
Chris@47 209 &inputSampleRate,
Chris@47 210 &adapterFlags)) {
Chris@39 211 PyErr_SetString(PyExc_TypeError,
Chris@79 212 "load_plugin() takes plugin key (string), sample rate (float), and adapter flags (int) arguments");
Chris@39 213 return 0; }
Chris@0 214
Chris@15 215 string pluginKey = toPluginKey(pyPluginKey);
Chris@16 216 if (pluginKey == "") return 0;
Chris@0 217
Chris@0 218 PluginLoader *loader = PluginLoader::getInstance();
luis@7 219
Chris@47 220 Plugin *plugin = loader->loadPlugin(pluginKey,
Chris@47 221 inputSampleRate,
Chris@47 222 adapterFlags);
luis@7 223 if (!plugin) {
Chris@39 224 string pyerr("Failed to load plugin: "); pyerr += pluginKey;
Chris@39 225 PyErr_SetString(PyExc_TypeError,pyerr.c_str());
Chris@39 226 return 0;
luis@7 227 }
Chris@15 228
Chris@31 229 return PyPluginObject_From_Plugin(plugin);
Chris@0 230 }
Chris@0 231
Chris@60 232 static PyObject *
Chris@79 233 frame_to_realtime(PyObject *self, PyObject *args)
Chris@60 234 {
Chris@112 235 ssize_t frame;
Chris@112 236 float rate;
Chris@60 237
Chris@112 238 if (!PyArg_ParseTuple(args, "nf",
Chris@60 239 &frame,
Chris@60 240 &rate)) {
Chris@60 241 PyErr_SetString(PyExc_TypeError,
Chris@112 242 "frame_to_realtime() takes frame (int) and sample rate (float) arguments");
Chris@60 243 return 0; }
Chris@60 244
Chris@60 245 RealTime rt = RealTime::frame2RealTime(frame, rate);
Chris@60 246 return PyRealTime_FromRealTime(rt);
Chris@60 247 }
Chris@60 248
Chris@18 249 // module methods table
Chris@0 250 static PyMethodDef vampyhost_methods[] = {
Chris@60 251
Chris@79 252 {"list_plugins", list_plugins, METH_NOARGS,
Chris@79 253 "list_plugins() -> Return a list of the plugin keys of all installed Vamp plugins." },
Chris@0 254
Chris@79 255 {"get_plugin_path", get_plugin_path, METH_NOARGS,
Chris@79 256 "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 257
Chris@79 258 {"get_category_of", get_category_of, METH_VARARGS,
Chris@79 259 "get_category_of(pluginKey) -> 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 260
Chris@79 261 {"get_library_for", get_library_for, METH_VARARGS,
Chris@79 262 "get_library_for(pluginKey) -> 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 263
Chris@79 264 {"get_outputs_of", get_outputs_of, METH_VARARGS,
Chris@79 265 "get_outputs_of(pluginKey) -> Return a list of the output identifiers of the plugin with the given key, if installed."},
Chris@0 266
Chris@79 267 {"load_plugin", load_plugin, METH_VARARGS,
Chris@79 268 "load_plugin(pluginKey, samplerate) -> Load the plugin that has the given key, if installed, and return the plugin object."},
Chris@0 269
Chris@79 270 {"frame_to_realtime", frame_to_realtime, METH_VARARGS,
Chris@79 271 "frame_to_realtime() -> Convert sample frame number and sample rate to a RealTime object." },
Chris@60 272
Chris@39 273 {0, 0} /* sentinel */
Chris@0 274 };
Chris@0 275
Chris@25 276 static int
Chris@25 277 setint(PyObject *d, const char *name, int value)
Chris@25 278 {
Chris@25 279 PyObject *v;
Chris@25 280 int err;
Chris@112 281 #if (PY_MAJOR_VERSION >= 3)
Chris@112 282 v = PyLong_FromLong((long)value);
Chris@112 283 #else
Chris@25 284 v = PyInt_FromLong((long)value);
Chris@112 285 #endif
Chris@25 286 err = PyDict_SetItemString(d, name, v);
Chris@25 287 Py_XDECREF(v);
Chris@25 288 return err;
Chris@25 289 }
Chris@14 290
Chris@0 291 /* Initialization function for the module (*must* be called initxx) */
Chris@0 292
Chris@112 293 #if (PY_MAJOR_VERSION >= 3)
Chris@112 294 static struct PyModuleDef vampyhostdef = {
Chris@112 295 PyModuleDef_HEAD_INIT,
Chris@112 296 "vampyhost",
Chris@112 297 "Load and run Vamp audio analysis plugins.",
Chris@112 298 -1,
Chris@112 299 vampyhost_methods,
Chris@112 300 0, 0, 0, 0
Chris@112 301 };
Chris@112 302 #else
Chris@112 303 PyDoc_STRVAR(module_doc, "Load and run Vamp audio analysis plugins.");
Chris@112 304 #endif
Chris@112 305
Chris@25 306 // module initialization (includes extern C {...} as necessary)
Chris@0 307 PyMODINIT_FUNC
Chris@112 308 #if (PY_MAJOR_VERSION >= 3)
Chris@112 309 PyInit_vampyhost(void)
Chris@112 310 #else
Chris@0 311 initvampyhost(void)
Chris@112 312 #endif
Chris@0 313 {
Chris@0 314 PyObject *m;
Chris@0 315
Chris@112 316 #if (PY_MAJOR_VERSION >= 3)
Chris@112 317 #define GOOD_RETURN m
Chris@112 318 #define BAD_RETURN 0
Chris@112 319 #else
Chris@112 320 #define GOOD_RETURN
Chris@112 321 #define BAD_RETURN
Chris@112 322 #endif
Chris@112 323
Chris@112 324 if (PyType_Ready(&RealTime_Type) < 0) return BAD_RETURN;
Chris@112 325 if (PyType_Ready(&Plugin_Type) < 0) return BAD_RETURN;
Chris@0 326
Chris@112 327 #if (PY_MAJOR_VERSION >= 3)
Chris@112 328 m = PyModule_Create(&vampyhostdef);
Chris@112 329 #else
Chris@0 330 m = Py_InitModule3("vampyhost", vampyhost_methods, module_doc);
Chris@112 331 #endif
Chris@112 332
Chris@25 333 if (!m) {
Chris@25 334 cerr << "ERROR: initvampyhost: Failed to initialise module" << endl;
Chris@112 335 return BAD_RETURN;
Chris@25 336 }
Chris@0 337
Chris@14 338 import_array();
Chris@14 339
Chris@17 340 PyModule_AddObject(m, "RealTime", (PyObject *)&RealTime_Type);
Chris@25 341 PyModule_AddObject(m, "Plugin", (PyObject *)&Plugin_Type);
Chris@25 342
Chris@25 343 // Some enum types
Chris@25 344 PyObject *dict = PyModule_GetDict(m);
Chris@25 345 if (!dict) {
Chris@25 346 cerr << "ERROR: initvampyhost: Failed to obtain module dictionary" << endl;
Chris@112 347 return BAD_RETURN;
Chris@25 348 }
Chris@25 349
Chris@77 350 if (setint(dict, "ONE_SAMPLE_PER_STEP",
Chris@25 351 Plugin::OutputDescriptor::OneSamplePerStep) < 0 ||
Chris@77 352 setint(dict, "FIXED_SAMPLE_RATE",
Chris@25 353 Plugin::OutputDescriptor::FixedSampleRate) < 0 ||
Chris@77 354 setint(dict, "VARIABLE_SAMPLE_RATE",
Chris@25 355 Plugin::OutputDescriptor::VariableSampleRate) < 0 ||
Chris@77 356 setint(dict, "TIME_DOMAIN",
Chris@25 357 Plugin::TimeDomain) < 0 ||
Chris@77 358 setint(dict, "FREQUENCY_DOMAIN",
Chris@47 359 Plugin::FrequencyDomain) < 0 ||
Chris@77 360 setint(dict, "ADAPT_NONE",
Chris@47 361 0) < 0 ||
Chris@77 362 setint(dict, "ADAPT_INPUT_DOMAIN",
Chris@57 363 PluginLoader::ADAPT_INPUT_DOMAIN) < 0 ||
Chris@77 364 setint(dict, "ADAPT_CHANNEL_COUNT",
Chris@47 365 PluginLoader::ADAPT_CHANNEL_COUNT) < 0 ||
Chris@77 366 setint(dict, "ADAPT_BUFFER_SIZE",
Chris@47 367 PluginLoader::ADAPT_BUFFER_SIZE) < 0 ||
Chris@77 368 setint(dict, "ADAPT_ALL_SAFE",
Chris@47 369 PluginLoader::ADAPT_ALL_SAFE) < 0 ||
Chris@77 370 setint(dict, "ADAPT_ALL",
Chris@47 371 PluginLoader::ADAPT_ALL) < 0) {
Chris@25 372 cerr << "ERROR: initvampyhost: Failed to add enums to module dictionary" << endl;
Chris@112 373 return BAD_RETURN;
Chris@25 374 }
Chris@112 375
Chris@112 376 return GOOD_RETURN;
Chris@0 377 }