Chris@66
|
1 /* -*- c-basic-offset: 8 indent-tabs-mode: t -*- */
|
fazekasgy@37
|
2 /*
|
fazekasgy@37
|
3
|
fazekasgy@37
|
4 * Vampy : This plugin is a wrapper around the Vamp plugin API.
|
fazekasgy@37
|
5 * It allows for writing Vamp plugins in Python.
|
fazekasgy@37
|
6
|
fazekasgy@37
|
7 * Centre for Digital Music, Queen Mary University of London.
|
fazekasgy@37
|
8 * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources
|
fazekasgy@37
|
9 * for licence information.)
|
fazekasgy@37
|
10
|
fazekasgy@37
|
11 */
|
fazekasgy@37
|
12
|
fazekasgy@37
|
13 /*
|
fazekasgy@37
|
14 PyTypeInterface: Type safe conversion utilities between Python types
|
Chris@71
|
15 and Vamp API types. See PyTypeConversions for basic C/C++ types.
|
fazekasgy@37
|
16 */
|
fazekasgy@37
|
17
|
fazekasgy@37
|
18 #ifndef _PY_TYPE_INTERFACE_H_
|
fazekasgy@37
|
19 #define _PY_TYPE_INTERFACE_H_
|
fazekasgy@37
|
20 #include <Python.h>
|
fazekasgy@37
|
21 #ifdef HAVE_NUMPY
|
fazekasgy@37
|
22 #define PY_ARRAY_UNIQUE_SYMBOL VAMPY_ARRAY_API
|
fazekasgy@37
|
23 #define NO_IMPORT_ARRAY
|
Chris@66
|
24 #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
|
fazekasgy@37
|
25 #include "numpy/arrayobject.h"
|
fazekasgy@37
|
26 #endif
|
fazekasgy@37
|
27 #include "PyExtensionModule.h"
|
Chris@71
|
28 #include "PyTypeConversions.h"
|
fazekasgy@37
|
29 #include <vector>
|
fazekasgy@37
|
30 #include <queue>
|
fazekasgy@37
|
31 #include <string>
|
fazekasgy@37
|
32 #include <sstream>
|
fazekasgy@37
|
33 #include "vamp-sdk/Plugin.h"
|
fazekasgy@37
|
34
|
fazekasgy@37
|
35 using std::cerr;
|
fazekasgy@37
|
36 using std::endl;
|
fazekasgy@37
|
37
|
fazekasgy@37
|
38 namespace o {
|
fazekasgy@37
|
39 enum eOutDescriptors {
|
fazekasgy@37
|
40 not_found,
|
fazekasgy@37
|
41 identifier,
|
fazekasgy@37
|
42 name,
|
fazekasgy@37
|
43 description,
|
fazekasgy@37
|
44 unit,
|
fazekasgy@37
|
45 hasFixedBinCount,
|
fazekasgy@37
|
46 binCount,
|
fazekasgy@37
|
47 binNames,
|
fazekasgy@37
|
48 hasKnownExtents,
|
fazekasgy@37
|
49 minValue,
|
fazekasgy@37
|
50 maxValue,
|
fazekasgy@37
|
51 isQuantized,
|
fazekasgy@37
|
52 quantizeStep,
|
fazekasgy@37
|
53 sampleType,
|
fazekasgy@37
|
54 sampleRate,
|
fazekasgy@37
|
55 hasDuration,
|
fazekasgy@37
|
56 endNode
|
fazekasgy@37
|
57 };
|
fazekasgy@37
|
58 }
|
fazekasgy@37
|
59
|
fazekasgy@37
|
60 namespace p {
|
fazekasgy@37
|
61 enum eParmDescriptors {
|
fazekasgy@37
|
62 not_found,
|
fazekasgy@37
|
63 identifier,
|
fazekasgy@37
|
64 name,
|
fazekasgy@37
|
65 description,
|
fazekasgy@37
|
66 unit,
|
fazekasgy@37
|
67 minValue,
|
fazekasgy@37
|
68 maxValue,
|
fazekasgy@37
|
69 defaultValue,
|
fazekasgy@37
|
70 isQuantized,
|
gyorgyf@62
|
71 quantizeStep,
|
gyorgyf@62
|
72 valueNames
|
fazekasgy@37
|
73 };
|
fazekasgy@37
|
74 }
|
fazekasgy@37
|
75
|
fazekasgy@37
|
76 enum eSampleTypes {
|
fazekasgy@37
|
77 OneSamplePerStep,
|
fazekasgy@37
|
78 FixedSampleRate,
|
fazekasgy@37
|
79 VariableSampleRate
|
fazekasgy@37
|
80 };
|
fazekasgy@37
|
81
|
fazekasgy@37
|
82 enum eFeatureFields {
|
fazekasgy@37
|
83 unknown,
|
fazekasgy@37
|
84 hasTimestamp,
|
fazekasgy@37
|
85 timestamp,
|
fazekasgy@37
|
86 hasDuration,
|
fazekasgy@37
|
87 duration,
|
fazekasgy@37
|
88 values,
|
fazekasgy@37
|
89 label
|
fazekasgy@37
|
90 };
|
fazekasgy@37
|
91
|
fazekasgy@37
|
92 class PyTypeInterface
|
fazekasgy@37
|
93 {
|
Chris@71
|
94 PyTypeConversions m_conv;
|
Chris@71
|
95
|
fazekasgy@37
|
96 public:
|
fazekasgy@37
|
97 PyTypeInterface();
|
fazekasgy@37
|
98 ~PyTypeInterface();
|
fazekasgy@37
|
99
|
fazekasgy@37
|
100 // Utilities
|
Chris@71
|
101 void setStrictTypingFlag(bool b) {m_strict = b; m_conv.setStrictTypingFlag(b);}
|
Chris@71
|
102 void setNumpyInstalled(bool b) {m_numpyInstalled = b; m_conv.setNumpyInstalled(b); }
|
fazekasgy@37
|
103 ValueError getError() const;
|
fazekasgy@37
|
104 std::string PyValue_Get_TypeName(PyObject*) const;
|
fazekasgy@37
|
105 bool initMaps() const;
|
fazekasgy@37
|
106
|
fazekasgy@37
|
107 // Input buffers to Python
|
fazekasgy@37
|
108 PyObject* InputBuffers_As_PythonLists(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize, const Vamp::Plugin::InputDomain& dtype);
|
fazekasgy@47
|
109 PyObject* InputBuffers_As_SharedMemoryList(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize, const Vamp::Plugin::InputDomain& dtype);
|
fazekasgy@37
|
110
|
fazekasgy@37
|
111 // Numpy types
|
fazekasgy@37
|
112 #ifdef HAVE_NUMPY
|
fazekasgy@37
|
113 PyObject* InputBuffers_As_NumpyArray(const float *const *inputBuffers, const size_t&, const size_t&, const Vamp::Plugin::InputDomain& dtype);
|
fazekasgy@37
|
114 #endif
|
fazekasgy@37
|
115
|
Chris@71
|
116 /* Template functions */
|
fazekasgy@37
|
117
|
fazekasgy@37
|
118
|
fazekasgy@37
|
119 /// Common wrappers to set values in Vamp API structs. (to be used in template functions)
|
fazekasgy@37
|
120 void SetValue(Vamp::Plugin::OutputDescriptor& od, std::string& key, PyObject* pyValue) const;
|
fazekasgy@37
|
121 void SetValue(Vamp::Plugin::ParameterDescriptor& od, std::string& key, PyObject* pyValue) const;
|
fazekasgy@37
|
122 bool SetValue(Vamp::Plugin::Feature& od, std::string& key, PyObject* pyValue) const;
|
fazekasgy@37
|
123 PyObject* GetDescriptor_As_Dict(PyObject* pyValue) const
|
fazekasgy@37
|
124 {
|
fazekasgy@37
|
125 if PyFeature_CheckExact(pyValue) return PyFeature_AS_DICT(pyValue);
|
fazekasgy@37
|
126 if PyOutputDescriptor_CheckExact(pyValue) return PyOutputDescriptor_AS_DICT(pyValue);
|
fazekasgy@37
|
127 if PyParameterDescriptor_CheckExact(pyValue) return PyParameterDescriptor_AS_DICT(pyValue);
|
fazekasgy@37
|
128 return NULL;
|
fazekasgy@37
|
129 }
|
fazekasgy@37
|
130
|
fazekasgy@37
|
131 //returns e.g. Vamp::Plugin::OutputDescriptor or Vamp::Plugin::Feature
|
fazekasgy@37
|
132 template<typename RET>
|
fazekasgy@37
|
133 RET PyValue_To_VampDescriptor(PyObject* pyValue) const
|
fazekasgy@37
|
134 {
|
fazekasgy@37
|
135 PyObject* pyDict;
|
fazekasgy@37
|
136
|
fazekasgy@37
|
137 // Descriptors encoded as dicts
|
fazekasgy@37
|
138 pyDict = GetDescriptor_As_Dict(pyValue);
|
fazekasgy@37
|
139 if (!pyDict) pyDict = pyValue;
|
fazekasgy@37
|
140
|
fazekasgy@37
|
141 // TODO: support full mapping protocol as fallback.
|
fazekasgy@37
|
142 if (!PyDict_Check(pyDict)) {
|
fazekasgy@37
|
143 setValueError("Error while converting descriptor or feature object.\nThe value is neither a dictionary nor a Vamp Feature or Descriptor type.",m_strict);
|
fazekasgy@37
|
144 #ifdef _DEBUG
|
fazekasgy@37
|
145 cerr << "PyTypeInterface::PyValue_To_VampDescriptor failed. Error: Unexpected return type." << endl;
|
fazekasgy@37
|
146 #endif
|
fazekasgy@37
|
147 return RET();
|
fazekasgy@37
|
148 }
|
fazekasgy@37
|
149
|
fazekasgy@37
|
150 Py_ssize_t pyPos = 0;
|
fazekasgy@37
|
151 PyObject *pyKey, *pyDictValue;
|
fazekasgy@37
|
152 initMaps();
|
fazekasgy@37
|
153 int errors = 0;
|
fazekasgy@37
|
154 m_error = false;
|
fazekasgy@37
|
155 RET rd;
|
fazekasgy@37
|
156
|
fazekasgy@37
|
157 //Python Dictionary Iterator:
|
fazekasgy@37
|
158 while (PyDict_Next(pyDict, &pyPos, &pyKey, &pyDictValue))
|
fazekasgy@37
|
159 {
|
Chris@71
|
160 std::string key = m_conv.PyValue_To_String(pyKey);
|
fazekasgy@37
|
161 #ifdef _DEBUG_VALUES
|
Chris@71
|
162 cerr << "key: '" << key << "' value: '" << m_conv.PyValue_To_String(pyDictValue) << "' " << endl;
|
fazekasgy@37
|
163 #endif
|
fazekasgy@37
|
164 SetValue(rd,key,pyDictValue);
|
fazekasgy@37
|
165 if (m_error) {
|
fazekasgy@37
|
166 errors++;
|
fazekasgy@37
|
167 lastError() << "attribute '" << key << "'";// << " of " << getDescriptorId(rd);
|
fazekasgy@37
|
168 }
|
fazekasgy@37
|
169 }
|
fazekasgy@37
|
170 if (errors) {
|
fazekasgy@37
|
171 lastError() << " of " << getDescriptorId(rd);
|
fazekasgy@37
|
172 m_error = true;
|
fazekasgy@37
|
173 #ifdef _DEBUG
|
fazekasgy@37
|
174 cerr << "PyTypeInterface::PyValue_To_VampDescriptor: Warning: Value error in descriptor." << endl;
|
fazekasgy@37
|
175 #endif
|
fazekasgy@37
|
176 }
|
fazekasgy@37
|
177 return rd;
|
fazekasgy@37
|
178 }
|
fazekasgy@37
|
179
|
fazekasgy@37
|
180 /// Convert a sequence (tipically list) of PySomething to
|
fazekasgy@37
|
181 /// OutputList,ParameterList or FeatureList
|
fazekasgy@37
|
182 /// <OutputList> <OutputDescriptor>
|
fazekasgy@37
|
183 template<typename RET,typename ELEM>
|
fazekasgy@37
|
184 RET PyValue_To_VampList(PyObject* pyValue) const
|
fazekasgy@37
|
185 {
|
fazekasgy@37
|
186 RET list; // e.g. Vamp::Plugin::OutputList
|
fazekasgy@37
|
187 ELEM element; // e.g. Vamp::Plugin::OutputDescriptor
|
fazekasgy@37
|
188
|
fazekasgy@37
|
189 /// convert lists (ParameterList, OutputList, FeatureList)
|
fazekasgy@37
|
190 if (PyList_Check(pyValue)) {
|
fazekasgy@37
|
191 PyObject *pyDict; //This reference will be borrowed
|
fazekasgy@37
|
192 m_error = false; int errors = 0;
|
fazekasgy@37
|
193 for (Py_ssize_t i = 0; i < PyList_GET_SIZE(pyValue); ++i) {
|
fazekasgy@37
|
194 //Get i-th Vamp output descriptor (Borrowed Reference)
|
fazekasgy@37
|
195 pyDict = PyList_GET_ITEM(pyValue,i);
|
fazekasgy@37
|
196 element = PyValue_To_VampDescriptor<ELEM>(pyDict);
|
fazekasgy@37
|
197 if (m_error) errors++;
|
fazekasgy@37
|
198 // Check for empty Feature/Descriptor as before?
|
fazekasgy@37
|
199 list.push_back(element);
|
fazekasgy@37
|
200 }
|
fazekasgy@37
|
201 if (errors) m_error=true;
|
fazekasgy@37
|
202 return list;
|
fazekasgy@37
|
203 }
|
fazekasgy@37
|
204
|
fazekasgy@37
|
205 /// convert other types implementing the sequence protocol
|
fazekasgy@37
|
206 if (PySequence_Check(pyValue)) {
|
fazekasgy@37
|
207 PyObject *pySequence = PySequence_Fast(pyValue,"Returned value can not be converted to list or tuple.");
|
fazekasgy@37
|
208 PyObject **pyElements = PySequence_Fast_ITEMS(pySequence);
|
fazekasgy@37
|
209 m_error = false; int errors = 0;
|
fazekasgy@37
|
210 for (Py_ssize_t i = 0; i < PySequence_Fast_GET_SIZE(pySequence); ++i)
|
fazekasgy@37
|
211 {
|
fazekasgy@37
|
212 element = PyValue_To_VampDescriptor<ELEM>(pyElements[i]);
|
fazekasgy@37
|
213 if (m_error) errors++;
|
fazekasgy@37
|
214 list.push_back(element);
|
fazekasgy@37
|
215 }
|
fazekasgy@37
|
216 if (errors) m_error=true;
|
fazekasgy@37
|
217 Py_XDECREF(pySequence);
|
fazekasgy@37
|
218 return list;
|
fazekasgy@37
|
219 }
|
fazekasgy@37
|
220
|
fazekasgy@37
|
221 // accept None as an empty list
|
fazekasgy@37
|
222 if (pyValue == Py_None) return list;
|
fazekasgy@37
|
223
|
fazekasgy@37
|
224 // in strict mode, returning a single value is not allowed
|
fazekasgy@37
|
225 if (m_strict) {
|
fazekasgy@37
|
226 setValueError("Strict conversion error: object is not list or iterable sequence.",m_strict);
|
fazekasgy@37
|
227 return list;
|
fazekasgy@37
|
228 }
|
fazekasgy@37
|
229
|
fazekasgy@37
|
230 /// try to insert single, non-iterable values. i.e. feature <- [feature]
|
fazekasgy@37
|
231 element = PyValue_To_VampDescriptor<ELEM>(pyValue);
|
fazekasgy@37
|
232 if (m_error) {
|
fazekasgy@37
|
233 setValueError("Could not insert returned value to Vamp List.",m_strict);
|
fazekasgy@37
|
234 return list;
|
fazekasgy@37
|
235 }
|
fazekasgy@37
|
236 list.push_back(element);
|
fazekasgy@37
|
237 return list;
|
fazekasgy@37
|
238
|
fazekasgy@37
|
239 #ifdef _DEBUG
|
fazekasgy@37
|
240 cerr << "PyTypeInterface::PyValue_To_VampList failed. Expected iterable return type." << endl;
|
fazekasgy@37
|
241 #endif
|
fazekasgy@37
|
242
|
fazekasgy@37
|
243 }
|
fazekasgy@37
|
244
|
fazekasgy@37
|
245 //Vamp specific types
|
fazekasgy@37
|
246 Vamp::Plugin::FeatureSet PyValue_To_FeatureSet(PyObject*) const;
|
Chris@72
|
247 void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::FeatureSet &r) const
|
fazekasgy@37
|
248 { r = this->PyValue_To_FeatureSet(pyValue); }
|
fazekasgy@37
|
249
|
fazekasgy@37
|
250 Vamp::RealTime PyValue_To_RealTime(PyObject*) const;
|
Chris@72
|
251 void PyValue_To_rValue(PyObject *pyValue, Vamp::RealTime &r) const
|
fazekasgy@37
|
252 { r = this->PyValue_To_RealTime(pyValue); }
|
fazekasgy@37
|
253
|
fazekasgy@37
|
254 Vamp::Plugin::OutputDescriptor::SampleType PyValue_To_SampleType(PyObject*) const;
|
fazekasgy@37
|
255
|
fazekasgy@37
|
256 Vamp::Plugin::InputDomain PyValue_To_InputDomain(PyObject*) const;
|
Chris@72
|
257 void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::InputDomain &r) const
|
fazekasgy@37
|
258 { r = this->PyValue_To_InputDomain(pyValue); }
|
fazekasgy@37
|
259
|
fazekasgy@37
|
260 /* Overloaded PyValue_To_rValue() to support generic functions */
|
Chris@72
|
261 void PyValue_To_rValue(PyObject *pyValue, float &defValue) const
|
Chris@71
|
262 { float tmp = m_conv.PyValue_To_Float(pyValue);
|
fazekasgy@37
|
263 if(!m_error) defValue = tmp; }
|
Chris@72
|
264 void PyValue_To_rValue(PyObject *pyValue, size_t &defValue) const
|
Chris@71
|
265 { size_t tmp = m_conv.PyValue_To_Size_t(pyValue);
|
fazekasgy@37
|
266 if(!m_error) defValue = tmp; }
|
Chris@72
|
267 void PyValue_To_rValue(PyObject *pyValue, bool &defValue) const
|
Chris@71
|
268 { bool tmp = m_conv.PyValue_To_Bool(pyValue);
|
fazekasgy@37
|
269 if(!m_error) defValue = tmp; }
|
Chris@72
|
270 void PyValue_To_rValue(PyObject *pyValue, std::string &defValue) const
|
Chris@71
|
271 { std::string tmp = m_conv.PyValue_To_String(pyValue);
|
fazekasgy@37
|
272 if(!m_error) defValue = tmp; }
|
fazekasgy@37
|
273 /*used by templates where we expect no return value, if there is one it will be ignored*/
|
Chris@72
|
274 void PyValue_To_rValue(PyObject *pyValue, NoneType &defValue) const
|
fazekasgy@37
|
275 { if (m_strict && pyValue != Py_None)
|
fazekasgy@37
|
276 setValueError("Strict conversion error: Expected 'None' type.",m_strict);
|
fazekasgy@37
|
277 }
|
Chris@71
|
278
|
fazekasgy@37
|
279 /* convert sequence types to Vamp List types */
|
Chris@72
|
280 void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::OutputList &r) const
|
fazekasgy@37
|
281 { r = this->PyValue_To_VampList<Vamp::Plugin::OutputList,Vamp::Plugin::OutputDescriptor>(pyValue); }
|
Chris@72
|
282 void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::ParameterList &r) const
|
fazekasgy@37
|
283 { r = this->PyValue_To_VampList<Vamp::Plugin::ParameterList,Vamp::Plugin::ParameterDescriptor>(pyValue); }
|
Chris@72
|
284 void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::FeatureList &r) const
|
fazekasgy@37
|
285 { r = this->PyValue_To_VampList<Vamp::Plugin::FeatureList,Vamp::Plugin::Feature>(pyValue); }
|
fazekasgy@37
|
286
|
fazekasgy@37
|
287 /// this is only needed for RealTime->Frame conversion
|
fazekasgy@37
|
288 void setInputSampleRate(float inputSampleRate)
|
fazekasgy@37
|
289 { m_inputSampleRate = (unsigned int) inputSampleRate; }
|
fazekasgy@37
|
290
|
fazekasgy@37
|
291 private:
|
fazekasgy@37
|
292 bool m_strict;
|
fazekasgy@37
|
293 mutable bool m_error;
|
fazekasgy@37
|
294 mutable std::queue<ValueError> m_errorQueue;
|
fazekasgy@37
|
295 unsigned int m_inputSampleRate;
|
fazekasgy@51
|
296 bool m_numpyInstalled;
|
fazekasgy@37
|
297
|
fazekasgy@37
|
298 void setValueError(std::string,bool) const;
|
fazekasgy@37
|
299 ValueError& lastError() const;
|
fazekasgy@37
|
300
|
fazekasgy@37
|
301 /* Overloaded _convert(), bypasses error checking to avoid doing it twice in internals. */
|
Chris@72
|
302 void _convert(PyObject *pyValue,float &r) const
|
Chris@71
|
303 { r = m_conv.PyValue_To_Float(pyValue); }
|
Chris@72
|
304 void _convert(PyObject *pyValue,size_t &r) const
|
Chris@71
|
305 { r = m_conv.PyValue_To_Size_t(pyValue); }
|
Chris@72
|
306 void _convert(PyObject *pyValue,bool &r) const
|
Chris@71
|
307 { r = m_conv.PyValue_To_Bool(pyValue); }
|
Chris@72
|
308 void _convert(PyObject *pyValue,std::string &r) const
|
Chris@71
|
309 { r = m_conv.PyValue_To_String(pyValue); }
|
Chris@72
|
310 void _convert(PyObject *pyValue,std::vector<std::string> &r) const
|
Chris@71
|
311 { r = m_conv.PyValue_To_StringVector(pyValue); }
|
Chris@72
|
312 void _convert(PyObject *pyValue,std::vector<float> &r) const
|
Chris@71
|
313 { r = m_conv.PyValue_To_FloatVector(pyValue); }
|
Chris@72
|
314 void _convert(PyObject *pyValue,Vamp::RealTime &r) const
|
fazekasgy@37
|
315 { r = PyValue_To_RealTime(pyValue); }
|
Chris@72
|
316 void _convert(PyObject *pyValue,Vamp::Plugin::OutputDescriptor::SampleType &r) const
|
fazekasgy@37
|
317 { r = PyValue_To_SampleType(pyValue); }
|
Chris@72
|
318 // void _convert(PyObject *pyValue,Vamp::Plugin::InputDomain &r) const
|
Chris@71
|
319 // { r = m_conv.PyValue_To_InputDomain(pyValue); }
|
fazekasgy@37
|
320
|
fazekasgy@37
|
321
|
fazekasgy@37
|
322 /* Identify descriptors for error reporting */
|
Chris@72
|
323 std::string getDescriptorId(Vamp::Plugin::OutputDescriptor d) const
|
fazekasgy@37
|
324 {return std::string("Output Descriptor '") + d.identifier +"' ";}
|
Chris@72
|
325 std::string getDescriptorId(Vamp::Plugin::ParameterDescriptor d) const
|
fazekasgy@37
|
326 {return std::string("Parameter Descriptor '") + d.identifier +"' ";}
|
Chris@72
|
327 std::string getDescriptorId(Vamp::Plugin::Feature f) const
|
fazekasgy@37
|
328 {return std::string("Feature (") + f.label + ")"; }
|
fazekasgy@37
|
329
|
fazekasgy@37
|
330 public:
|
fazekasgy@37
|
331 const bool& error;
|
fazekasgy@37
|
332
|
fazekasgy@37
|
333 };
|
fazekasgy@37
|
334
|
fazekasgy@37
|
335 #endif
|