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@120
|
63 #if (VAMP_SDK_MAJOR_VERSION != 2 || VAMP_SDK_MINOR_VERSION < 6)
|
Chris@120
|
64 #error "Vamp plugin SDK v2, version 2.6 or newer required"
|
Chris@120
|
65 #endif
|
Chris@120
|
66
|
Chris@0
|
67 using namespace std;
|
Chris@0
|
68 using namespace Vamp;
|
Chris@31
|
69 using namespace Vamp::HostExt;
|
Chris@21
|
70
|
Chris@0
|
71 static PyObject *
|
Chris@79
|
72 list_plugins(PyObject *self, PyObject *)
|
Chris@0
|
73 {
|
Chris@0
|
74 PluginLoader *loader = PluginLoader::getInstance();
|
Chris@0
|
75 vector<PluginLoader::PluginKey> plugins = loader->listPlugins();
|
Chris@29
|
76 VectorConversion conv;
|
Chris@15
|
77 return conv.PyValue_From_StringVector(plugins);
|
Chris@0
|
78 }
|
Chris@0
|
79
|
Chris@15
|
80 static PyObject *
|
Chris@79
|
81 get_plugin_path(PyObject *self, PyObject *)
|
Chris@15
|
82 {
|
Chris@15
|
83 vector<string> path = PluginHostAdapter::getPluginPath();
|
Chris@29
|
84 VectorConversion conv;
|
Chris@15
|
85 return conv.PyValue_From_StringVector(path);
|
Chris@15
|
86 }
|
Chris@0
|
87
|
Chris@15
|
88 static string toPluginKey(PyObject *pyPluginKey)
|
Chris@0
|
89 {
|
Chris@36
|
90 // convert to stl string
|
Chris@112
|
91 string pluginKey(StringConversion().py2string(pyPluginKey));
|
Chris@0
|
92
|
Chris@36
|
93 // check pluginKey validity
|
Chris@0
|
94 string::size_type ki = pluginKey.find(':');
|
Chris@0
|
95 if (ki == string::npos) {
|
Chris@39
|
96 PyErr_SetString(PyExc_TypeError,
|
Chris@39
|
97 "Plugin key must be of the form library:identifier");
|
Chris@39
|
98 return "";
|
Chris@0
|
99 }
|
Chris@0
|
100
|
Chris@15
|
101 return pluginKey;
|
Chris@15
|
102 }
|
Chris@15
|
103
|
Chris@15
|
104 static PyObject *
|
Chris@79
|
105 get_library_for(PyObject *self, PyObject *args)
|
Chris@15
|
106 {
|
Chris@15
|
107 PyObject *pyPluginKey;
|
Chris@15
|
108
|
Chris@112
|
109 if (!PyArg_ParseTuple(args,
|
Chris@112
|
110 #if (PY_MAJOR_VERSION >= 3)
|
Chris@112
|
111 "U",
|
Chris@112
|
112 #else
|
Chris@112
|
113 "S",
|
Chris@112
|
114 #endif
|
Chris@112
|
115 &pyPluginKey)) {
|
Chris@39
|
116 PyErr_SetString(PyExc_TypeError,
|
Chris@79
|
117 "get_library_for() takes plugin key (string) argument");
|
Chris@39
|
118 return 0; }
|
Chris@15
|
119
|
Chris@15
|
120 string pluginKey = toPluginKey(pyPluginKey);
|
Chris@16
|
121 if (pluginKey == "") return 0;
|
Chris@15
|
122
|
Chris@0
|
123 PluginLoader *loader = PluginLoader::getInstance();
|
Chris@0
|
124 string path = loader->getLibraryPathForPlugin(pluginKey);
|
Chris@112
|
125 PyObject *pyPath = StringConversion().string2py(path.c_str());
|
Chris@0
|
126 return pyPath;
|
Chris@0
|
127 }
|
Chris@0
|
128
|
Chris@0
|
129 static PyObject *
|
Chris@79
|
130 get_category_of(PyObject *self, PyObject *args)
|
Chris@0
|
131 {
|
Chris@0
|
132 PyObject *pyPluginKey;
|
Chris@0
|
133
|
Chris@112
|
134 if (!PyArg_ParseTuple(args,
|
Chris@112
|
135 #if (PY_MAJOR_VERSION >= 3)
|
Chris@112
|
136 "U",
|
Chris@112
|
137 #else
|
Chris@112
|
138 "S",
|
Chris@112
|
139 #endif
|
Chris@112
|
140 &pyPluginKey)) {
|
Chris@39
|
141 PyErr_SetString(PyExc_TypeError,
|
Chris@79
|
142 "get_category_of() takes plugin key (string) argument");
|
Chris@39
|
143 return 0; }
|
Chris@0
|
144
|
Chris@15
|
145 string pluginKey = toPluginKey(pyPluginKey);
|
Chris@16
|
146 if (pluginKey == "") return 0;
|
Chris@0
|
147
|
Chris@0
|
148 PluginLoader *loader = PluginLoader::getInstance();
|
luis@7
|
149 PluginLoader::PluginCategoryHierarchy
|
Chris@39
|
150 category = loader->getPluginCategory(pluginKey);
|
Chris@0
|
151
|
Chris@29
|
152 VectorConversion conv;
|
Chris@15
|
153 return conv.PyValue_From_StringVector(category);
|
Chris@0
|
154 }
|
Chris@0
|
155
|
Chris@0
|
156 static PyObject *
|
Chris@79
|
157 get_outputs_of(PyObject *self, PyObject *args)
|
Chris@0
|
158 {
|
Chris@31
|
159 PyObject *pyPluginKey;
|
Chris@31
|
160
|
Chris@112
|
161 if (!PyArg_ParseTuple(args,
|
Chris@112
|
162 #if (PY_MAJOR_VERSION >= 3)
|
Chris@112
|
163 "U",
|
Chris@112
|
164 #else
|
Chris@112
|
165 "S",
|
Chris@112
|
166 #endif
|
Chris@112
|
167 &pyPluginKey)) {
|
Chris@39
|
168 PyErr_SetString(PyExc_TypeError,
|
Chris@79
|
169 "get_outputs_of() takes plugin key (string) argument");
|
Chris@39
|
170 return 0; }
|
Chris@31
|
171
|
Chris@15
|
172 Plugin::OutputList outputs;
|
Chris@0
|
173
|
Chris@31
|
174 string pluginKey = toPluginKey(pyPluginKey);
|
Chris@31
|
175 if (pluginKey == "") return 0;
|
Chris@31
|
176
|
Chris@31
|
177 PluginLoader *loader = PluginLoader::getInstance();
|
Chris@31
|
178
|
Chris@47
|
179 Plugin *plugin = loader->loadPlugin(pluginKey, 48000, 0);
|
Chris@31
|
180 if (!plugin) {
|
Chris@31
|
181 string pyerr("Failed to load plugin: "); pyerr += pluginKey;
|
Chris@134
|
182 PyErr_SetString(PyExc_TypeError, pyerr.c_str());
|
Chris@31
|
183 return 0;
|
Chris@0
|
184 }
|
Chris@0
|
185
|
Chris@31
|
186 outputs = plugin->getOutputDescriptors();
|
luis@7
|
187
|
Chris@0
|
188 PyObject *pyList = PyList_New(outputs.size());
|
Chris@0
|
189
|
Chris@0
|
190 for (size_t i = 0; i < outputs.size(); ++i) {
|
Chris@39
|
191 PyObject *pyOutputId =
|
Chris@112
|
192 StringConversion().string2py(outputs[i].identifier.c_str());
|
Chris@39
|
193 PyList_SET_ITEM(pyList, i, pyOutputId);
|
Chris@0
|
194 }
|
Chris@0
|
195
|
Chris@0
|
196 return pyList;
|
Chris@0
|
197 }
|
Chris@0
|
198
|
Chris@0
|
199 static PyObject *
|
Chris@79
|
200 load_plugin(PyObject *self, PyObject *args)
|
Chris@0
|
201 {
|
Chris@0
|
202 PyObject *pyPluginKey;
|
Chris@0
|
203 float inputSampleRate;
|
Chris@112
|
204 ssize_t adapterFlags;
|
Chris@0
|
205
|
Chris@112
|
206 if (!PyArg_ParseTuple(args,
|
Chris@112
|
207 #if (PY_MAJOR_VERSION >= 3)
|
Chris@112
|
208 "Ufn",
|
Chris@112
|
209 #else
|
Chris@112
|
210 "Sfn",
|
Chris@112
|
211 #endif
|
Chris@39
|
212 &pyPluginKey,
|
Chris@47
|
213 &inputSampleRate,
|
Chris@47
|
214 &adapterFlags)) {
|
Chris@39
|
215 PyErr_SetString(PyExc_TypeError,
|
Chris@79
|
216 "load_plugin() takes plugin key (string), sample rate (float), and adapter flags (int) arguments");
|
Chris@39
|
217 return 0; }
|
Chris@0
|
218
|
Chris@15
|
219 string pluginKey = toPluginKey(pyPluginKey);
|
Chris@16
|
220 if (pluginKey == "") return 0;
|
Chris@0
|
221
|
Chris@0
|
222 PluginLoader *loader = PluginLoader::getInstance();
|
luis@7
|
223
|
Chris@47
|
224 Plugin *plugin = loader->loadPlugin(pluginKey,
|
Chris@47
|
225 inputSampleRate,
|
Chris@47
|
226 adapterFlags);
|
luis@7
|
227 if (!plugin) {
|
Chris@39
|
228 string pyerr("Failed to load plugin: "); pyerr += pluginKey;
|
Chris@39
|
229 PyErr_SetString(PyExc_TypeError,pyerr.c_str());
|
Chris@39
|
230 return 0;
|
luis@7
|
231 }
|
Chris@15
|
232
|
Chris@31
|
233 return PyPluginObject_From_Plugin(plugin);
|
Chris@0
|
234 }
|
Chris@0
|
235
|
Chris@60
|
236 static PyObject *
|
Chris@79
|
237 frame_to_realtime(PyObject *self, PyObject *args)
|
Chris@60
|
238 {
|
Chris@112
|
239 ssize_t frame;
|
Chris@112
|
240 float rate;
|
Chris@60
|
241
|
Chris@112
|
242 if (!PyArg_ParseTuple(args, "nf",
|
Chris@60
|
243 &frame,
|
Chris@60
|
244 &rate)) {
|
Chris@60
|
245 PyErr_SetString(PyExc_TypeError,
|
Chris@112
|
246 "frame_to_realtime() takes frame (int) and sample rate (float) arguments");
|
Chris@60
|
247 return 0; }
|
Chris@60
|
248
|
Chris@60
|
249 RealTime rt = RealTime::frame2RealTime(frame, rate);
|
Chris@60
|
250 return PyRealTime_FromRealTime(rt);
|
Chris@60
|
251 }
|
Chris@60
|
252
|
Chris@18
|
253 // module methods table
|
Chris@0
|
254 static PyMethodDef vampyhost_methods[] = {
|
Chris@60
|
255
|
Chris@79
|
256 {"list_plugins", list_plugins, METH_NOARGS,
|
Chris@79
|
257 "list_plugins() -> Return a list of the plugin keys of all installed Vamp plugins." },
|
Chris@0
|
258
|
Chris@79
|
259 {"get_plugin_path", get_plugin_path, METH_NOARGS,
|
Chris@79
|
260 "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
|
261
|
Chris@79
|
262 {"get_category_of", get_category_of, METH_VARARGS,
|
Chris@139
|
263 "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
|
264
|
Chris@79
|
265 {"get_library_for", get_library_for, METH_VARARGS,
|
Chris@139
|
266 "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
|
267
|
Chris@79
|
268 {"get_outputs_of", get_outputs_of, METH_VARARGS,
|
Chris@139
|
269 "get_outputs_of(plugin_key) -> Return a list of the output identifiers of the plugin with the given key, if installed."},
|
Chris@0
|
270
|
Chris@79
|
271 {"load_plugin", load_plugin, METH_VARARGS,
|
Chris@139
|
272 "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
|
273
|
Chris@79
|
274 {"frame_to_realtime", frame_to_realtime, METH_VARARGS,
|
Chris@79
|
275 "frame_to_realtime() -> Convert sample frame number and sample rate to a RealTime object." },
|
Chris@60
|
276
|
Chris@39
|
277 {0, 0} /* sentinel */
|
Chris@0
|
278 };
|
Chris@0
|
279
|
Chris@25
|
280 static int
|
Chris@25
|
281 setint(PyObject *d, const char *name, int value)
|
Chris@25
|
282 {
|
Chris@25
|
283 PyObject *v;
|
Chris@25
|
284 int err;
|
Chris@112
|
285 #if (PY_MAJOR_VERSION >= 3)
|
Chris@112
|
286 v = PyLong_FromLong((long)value);
|
Chris@112
|
287 #else
|
Chris@25
|
288 v = PyInt_FromLong((long)value);
|
Chris@112
|
289 #endif
|
Chris@25
|
290 err = PyDict_SetItemString(d, name, v);
|
Chris@25
|
291 Py_XDECREF(v);
|
Chris@25
|
292 return err;
|
Chris@25
|
293 }
|
Chris@14
|
294
|
Chris@0
|
295 /* Initialization function for the module (*must* be called initxx) */
|
Chris@0
|
296
|
Chris@112
|
297 #if (PY_MAJOR_VERSION >= 3)
|
Chris@112
|
298 static struct PyModuleDef vampyhostdef = {
|
Chris@112
|
299 PyModuleDef_HEAD_INIT,
|
Chris@112
|
300 "vampyhost",
|
Chris@112
|
301 "Load and run Vamp audio analysis plugins.",
|
Chris@112
|
302 -1,
|
Chris@112
|
303 vampyhost_methods,
|
Chris@112
|
304 0, 0, 0, 0
|
Chris@112
|
305 };
|
Chris@112
|
306 #else
|
Chris@112
|
307 PyDoc_STRVAR(module_doc, "Load and run Vamp audio analysis plugins.");
|
Chris@112
|
308 #endif
|
Chris@112
|
309
|
Chris@25
|
310 // module initialization (includes extern C {...} as necessary)
|
Chris@0
|
311 PyMODINIT_FUNC
|
Chris@112
|
312 #if (PY_MAJOR_VERSION >= 3)
|
Chris@112
|
313 PyInit_vampyhost(void)
|
Chris@112
|
314 #else
|
Chris@0
|
315 initvampyhost(void)
|
Chris@112
|
316 #endif
|
Chris@0
|
317 {
|
Chris@0
|
318 PyObject *m;
|
Chris@0
|
319
|
Chris@112
|
320 #if (PY_MAJOR_VERSION >= 3)
|
Chris@112
|
321 #define GOOD_RETURN m
|
Chris@112
|
322 #define BAD_RETURN 0
|
Chris@112
|
323 #else
|
Chris@112
|
324 #define GOOD_RETURN
|
Chris@112
|
325 #define BAD_RETURN
|
Chris@112
|
326 #endif
|
Chris@112
|
327
|
Chris@112
|
328 if (PyType_Ready(&RealTime_Type) < 0) return BAD_RETURN;
|
Chris@112
|
329 if (PyType_Ready(&Plugin_Type) < 0) return BAD_RETURN;
|
Chris@0
|
330
|
Chris@112
|
331 #if (PY_MAJOR_VERSION >= 3)
|
Chris@112
|
332 m = PyModule_Create(&vampyhostdef);
|
Chris@112
|
333 #else
|
Chris@0
|
334 m = Py_InitModule3("vampyhost", vampyhost_methods, module_doc);
|
Chris@112
|
335 #endif
|
Chris@112
|
336
|
Chris@25
|
337 if (!m) {
|
Chris@25
|
338 cerr << "ERROR: initvampyhost: Failed to initialise module" << endl;
|
Chris@112
|
339 return BAD_RETURN;
|
Chris@25
|
340 }
|
Chris@0
|
341
|
Chris@14
|
342 import_array();
|
Chris@14
|
343
|
Chris@17
|
344 PyModule_AddObject(m, "RealTime", (PyObject *)&RealTime_Type);
|
Chris@25
|
345 PyModule_AddObject(m, "Plugin", (PyObject *)&Plugin_Type);
|
Chris@25
|
346
|
Chris@25
|
347 // Some enum types
|
Chris@25
|
348 PyObject *dict = PyModule_GetDict(m);
|
Chris@25
|
349 if (!dict) {
|
Chris@25
|
350 cerr << "ERROR: initvampyhost: Failed to obtain module dictionary" << endl;
|
Chris@112
|
351 return BAD_RETURN;
|
Chris@25
|
352 }
|
Chris@25
|
353
|
Chris@77
|
354 if (setint(dict, "ONE_SAMPLE_PER_STEP",
|
Chris@25
|
355 Plugin::OutputDescriptor::OneSamplePerStep) < 0 ||
|
Chris@77
|
356 setint(dict, "FIXED_SAMPLE_RATE",
|
Chris@25
|
357 Plugin::OutputDescriptor::FixedSampleRate) < 0 ||
|
Chris@77
|
358 setint(dict, "VARIABLE_SAMPLE_RATE",
|
Chris@25
|
359 Plugin::OutputDescriptor::VariableSampleRate) < 0 ||
|
Chris@77
|
360 setint(dict, "TIME_DOMAIN",
|
Chris@25
|
361 Plugin::TimeDomain) < 0 ||
|
Chris@77
|
362 setint(dict, "FREQUENCY_DOMAIN",
|
Chris@47
|
363 Plugin::FrequencyDomain) < 0 ||
|
Chris@77
|
364 setint(dict, "ADAPT_NONE",
|
Chris@47
|
365 0) < 0 ||
|
Chris@77
|
366 setint(dict, "ADAPT_INPUT_DOMAIN",
|
Chris@57
|
367 PluginLoader::ADAPT_INPUT_DOMAIN) < 0 ||
|
Chris@77
|
368 setint(dict, "ADAPT_CHANNEL_COUNT",
|
Chris@47
|
369 PluginLoader::ADAPT_CHANNEL_COUNT) < 0 ||
|
Chris@77
|
370 setint(dict, "ADAPT_BUFFER_SIZE",
|
Chris@47
|
371 PluginLoader::ADAPT_BUFFER_SIZE) < 0 ||
|
Chris@77
|
372 setint(dict, "ADAPT_ALL_SAFE",
|
Chris@47
|
373 PluginLoader::ADAPT_ALL_SAFE) < 0 ||
|
Chris@77
|
374 setint(dict, "ADAPT_ALL",
|
Chris@139
|
375 PluginLoader::ADAPT_ALL) < 0 ||
|
Chris@139
|
376 setint(dict, "SHIFT_TIMESTAMP",
|
Chris@139
|
377 PluginInputDomainAdapter::ShiftTimestamp) < 0 ||
|
Chris@139
|
378 setint(dict, "SHIFT_DATA",
|
Chris@139
|
379 PluginInputDomainAdapter::ShiftData) < 0 ||
|
Chris@139
|
380 setint(dict, "NO_SHIFT",
|
Chris@139
|
381 PluginInputDomainAdapter::NoShift) < 0) {
|
Chris@25
|
382 cerr << "ERROR: initvampyhost: Failed to add enums to module dictionary" << endl;
|
Chris@112
|
383 return BAD_RETURN;
|
Chris@25
|
384 }
|
Chris@112
|
385
|
Chris@112
|
386 return GOOD_RETURN;
|
Chris@0
|
387 }
|