Chris@66: /* -*- c-basic-offset: 8 indent-tabs-mode: t -*- */ fazekasgy@37: /* fazekasgy@37: fazekasgy@37: * Vampy : This plugin is a wrapper around the Vamp plugin API. fazekasgy@37: * It allows for writing Vamp plugins in Python. fazekasgy@37: fazekasgy@37: * Centre for Digital Music, Queen Mary University of London. fazekasgy@37: * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources fazekasgy@37: * for licence information.) fazekasgy@37: fazekasgy@37: */ fazekasgy@37: fazekasgy@37: /* fazekasgy@37: PyTypeInterface: Type safe conversion utilities between Python types Chris@71: and Vamp API types. See PyTypeConversions for basic C/C++ types. fazekasgy@37: */ fazekasgy@37: fazekasgy@37: #ifndef _PY_TYPE_INTERFACE_H_ fazekasgy@37: #define _PY_TYPE_INTERFACE_H_ fazekasgy@37: #include fazekasgy@37: #ifdef HAVE_NUMPY fazekasgy@37: #define PY_ARRAY_UNIQUE_SYMBOL VAMPY_ARRAY_API fazekasgy@37: #define NO_IMPORT_ARRAY Chris@66: #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION fazekasgy@37: #include "numpy/arrayobject.h" fazekasgy@37: #endif fazekasgy@37: #include "PyExtensionModule.h" Chris@71: #include "PyTypeConversions.h" fazekasgy@37: #include fazekasgy@37: #include fazekasgy@37: #include fazekasgy@37: #include fazekasgy@37: #include "vamp-sdk/Plugin.h" fazekasgy@37: fazekasgy@37: using std::cerr; fazekasgy@37: using std::endl; fazekasgy@37: fazekasgy@37: namespace o { fazekasgy@37: enum eOutDescriptors { fazekasgy@37: not_found, fazekasgy@37: identifier, fazekasgy@37: name, fazekasgy@37: description, fazekasgy@37: unit, fazekasgy@37: hasFixedBinCount, fazekasgy@37: binCount, fazekasgy@37: binNames, fazekasgy@37: hasKnownExtents, fazekasgy@37: minValue, fazekasgy@37: maxValue, fazekasgy@37: isQuantized, fazekasgy@37: quantizeStep, fazekasgy@37: sampleType, fazekasgy@37: sampleRate, fazekasgy@37: hasDuration, fazekasgy@37: endNode fazekasgy@37: }; fazekasgy@37: } fazekasgy@37: fazekasgy@37: namespace p { fazekasgy@37: enum eParmDescriptors { fazekasgy@37: not_found, fazekasgy@37: identifier, fazekasgy@37: name, fazekasgy@37: description, fazekasgy@37: unit, fazekasgy@37: minValue, fazekasgy@37: maxValue, fazekasgy@37: defaultValue, fazekasgy@37: isQuantized, gyorgyf@62: quantizeStep, gyorgyf@62: valueNames fazekasgy@37: }; fazekasgy@37: } fazekasgy@37: fazekasgy@37: enum eSampleTypes { fazekasgy@37: OneSamplePerStep, fazekasgy@37: FixedSampleRate, fazekasgy@37: VariableSampleRate fazekasgy@37: }; fazekasgy@37: fazekasgy@37: enum eFeatureFields { fazekasgy@37: unknown, fazekasgy@37: hasTimestamp, fazekasgy@37: timestamp, fazekasgy@37: hasDuration, fazekasgy@37: duration, fazekasgy@37: values, fazekasgy@37: label fazekasgy@37: }; fazekasgy@37: fazekasgy@37: class PyTypeInterface fazekasgy@37: { Chris@71: PyTypeConversions m_conv; Chris@71: fazekasgy@37: public: fazekasgy@37: PyTypeInterface(); fazekasgy@37: ~PyTypeInterface(); fazekasgy@37: fazekasgy@37: // Utilities Chris@71: void setStrictTypingFlag(bool b) {m_strict = b; m_conv.setStrictTypingFlag(b);} Chris@71: void setNumpyInstalled(bool b) {m_numpyInstalled = b; m_conv.setNumpyInstalled(b); } fazekasgy@37: ValueError getError() const; fazekasgy@37: std::string PyValue_Get_TypeName(PyObject*) const; fazekasgy@37: bool initMaps() const; fazekasgy@37: fazekasgy@37: // Input buffers to Python fazekasgy@37: PyObject* InputBuffers_As_PythonLists(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize, const Vamp::Plugin::InputDomain& dtype); fazekasgy@47: PyObject* InputBuffers_As_SharedMemoryList(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize, const Vamp::Plugin::InputDomain& dtype); fazekasgy@37: fazekasgy@37: // Numpy types fazekasgy@37: #ifdef HAVE_NUMPY fazekasgy@37: PyObject* InputBuffers_As_NumpyArray(const float *const *inputBuffers, const size_t&, const size_t&, const Vamp::Plugin::InputDomain& dtype); fazekasgy@37: #endif fazekasgy@37: Chris@71: /* Template functions */ fazekasgy@37: fazekasgy@37: fazekasgy@37: /// Common wrappers to set values in Vamp API structs. (to be used in template functions) fazekasgy@37: void SetValue(Vamp::Plugin::OutputDescriptor& od, std::string& key, PyObject* pyValue) const; fazekasgy@37: void SetValue(Vamp::Plugin::ParameterDescriptor& od, std::string& key, PyObject* pyValue) const; fazekasgy@37: bool SetValue(Vamp::Plugin::Feature& od, std::string& key, PyObject* pyValue) const; fazekasgy@37: PyObject* GetDescriptor_As_Dict(PyObject* pyValue) const fazekasgy@37: { fazekasgy@37: if PyFeature_CheckExact(pyValue) return PyFeature_AS_DICT(pyValue); fazekasgy@37: if PyOutputDescriptor_CheckExact(pyValue) return PyOutputDescriptor_AS_DICT(pyValue); fazekasgy@37: if PyParameterDescriptor_CheckExact(pyValue) return PyParameterDescriptor_AS_DICT(pyValue); fazekasgy@37: return NULL; fazekasgy@37: } fazekasgy@37: fazekasgy@37: //returns e.g. Vamp::Plugin::OutputDescriptor or Vamp::Plugin::Feature fazekasgy@37: template fazekasgy@37: RET PyValue_To_VampDescriptor(PyObject* pyValue) const fazekasgy@37: { fazekasgy@37: PyObject* pyDict; fazekasgy@37: fazekasgy@37: // Descriptors encoded as dicts fazekasgy@37: pyDict = GetDescriptor_As_Dict(pyValue); fazekasgy@37: if (!pyDict) pyDict = pyValue; fazekasgy@37: fazekasgy@37: // TODO: support full mapping protocol as fallback. fazekasgy@37: if (!PyDict_Check(pyDict)) { fazekasgy@37: 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: #ifdef _DEBUG fazekasgy@37: cerr << "PyTypeInterface::PyValue_To_VampDescriptor failed. Error: Unexpected return type." << endl; fazekasgy@37: #endif fazekasgy@37: return RET(); fazekasgy@37: } fazekasgy@37: fazekasgy@37: Py_ssize_t pyPos = 0; fazekasgy@37: PyObject *pyKey, *pyDictValue; fazekasgy@37: initMaps(); fazekasgy@37: int errors = 0; fazekasgy@37: m_error = false; fazekasgy@37: RET rd; fazekasgy@37: fazekasgy@37: //Python Dictionary Iterator: fazekasgy@37: while (PyDict_Next(pyDict, &pyPos, &pyKey, &pyDictValue)) fazekasgy@37: { Chris@71: std::string key = m_conv.PyValue_To_String(pyKey); fazekasgy@37: #ifdef _DEBUG_VALUES Chris@71: cerr << "key: '" << key << "' value: '" << m_conv.PyValue_To_String(pyDictValue) << "' " << endl; fazekasgy@37: #endif fazekasgy@37: SetValue(rd,key,pyDictValue); fazekasgy@37: if (m_error) { fazekasgy@37: errors++; fazekasgy@37: lastError() << "attribute '" << key << "'";// << " of " << getDescriptorId(rd); fazekasgy@37: } fazekasgy@37: } fazekasgy@37: if (errors) { fazekasgy@37: lastError() << " of " << getDescriptorId(rd); fazekasgy@37: m_error = true; fazekasgy@37: #ifdef _DEBUG fazekasgy@37: cerr << "PyTypeInterface::PyValue_To_VampDescriptor: Warning: Value error in descriptor." << endl; fazekasgy@37: #endif fazekasgy@37: } fazekasgy@37: return rd; fazekasgy@37: } fazekasgy@37: fazekasgy@37: /// Convert a sequence (tipically list) of PySomething to fazekasgy@37: /// OutputList,ParameterList or FeatureList fazekasgy@37: /// fazekasgy@37: template fazekasgy@37: RET PyValue_To_VampList(PyObject* pyValue) const fazekasgy@37: { fazekasgy@37: RET list; // e.g. Vamp::Plugin::OutputList fazekasgy@37: ELEM element; // e.g. Vamp::Plugin::OutputDescriptor fazekasgy@37: fazekasgy@37: /// convert lists (ParameterList, OutputList, FeatureList) fazekasgy@37: if (PyList_Check(pyValue)) { fazekasgy@37: PyObject *pyDict; //This reference will be borrowed fazekasgy@37: m_error = false; int errors = 0; fazekasgy@37: for (Py_ssize_t i = 0; i < PyList_GET_SIZE(pyValue); ++i) { fazekasgy@37: //Get i-th Vamp output descriptor (Borrowed Reference) fazekasgy@37: pyDict = PyList_GET_ITEM(pyValue,i); fazekasgy@37: element = PyValue_To_VampDescriptor(pyDict); fazekasgy@37: if (m_error) errors++; fazekasgy@37: // Check for empty Feature/Descriptor as before? fazekasgy@37: list.push_back(element); fazekasgy@37: } fazekasgy@37: if (errors) m_error=true; fazekasgy@37: return list; fazekasgy@37: } fazekasgy@37: fazekasgy@37: /// convert other types implementing the sequence protocol fazekasgy@37: if (PySequence_Check(pyValue)) { fazekasgy@37: PyObject *pySequence = PySequence_Fast(pyValue,"Returned value can not be converted to list or tuple."); fazekasgy@37: PyObject **pyElements = PySequence_Fast_ITEMS(pySequence); fazekasgy@37: m_error = false; int errors = 0; fazekasgy@37: for (Py_ssize_t i = 0; i < PySequence_Fast_GET_SIZE(pySequence); ++i) fazekasgy@37: { fazekasgy@37: element = PyValue_To_VampDescriptor(pyElements[i]); fazekasgy@37: if (m_error) errors++; fazekasgy@37: list.push_back(element); fazekasgy@37: } fazekasgy@37: if (errors) m_error=true; fazekasgy@37: Py_XDECREF(pySequence); fazekasgy@37: return list; fazekasgy@37: } fazekasgy@37: fazekasgy@37: // accept None as an empty list fazekasgy@37: if (pyValue == Py_None) return list; fazekasgy@37: fazekasgy@37: // in strict mode, returning a single value is not allowed fazekasgy@37: if (m_strict) { fazekasgy@37: setValueError("Strict conversion error: object is not list or iterable sequence.",m_strict); fazekasgy@37: return list; fazekasgy@37: } fazekasgy@37: fazekasgy@37: /// try to insert single, non-iterable values. i.e. feature <- [feature] fazekasgy@37: element = PyValue_To_VampDescriptor(pyValue); fazekasgy@37: if (m_error) { fazekasgy@37: setValueError("Could not insert returned value to Vamp List.",m_strict); fazekasgy@37: return list; fazekasgy@37: } fazekasgy@37: list.push_back(element); fazekasgy@37: return list; fazekasgy@37: fazekasgy@37: #ifdef _DEBUG fazekasgy@37: cerr << "PyTypeInterface::PyValue_To_VampList failed. Expected iterable return type." << endl; fazekasgy@37: #endif fazekasgy@37: fazekasgy@37: } fazekasgy@37: fazekasgy@37: //Vamp specific types fazekasgy@37: Vamp::Plugin::FeatureSet PyValue_To_FeatureSet(PyObject*) const; Chris@72: void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::FeatureSet &r) const fazekasgy@37: { r = this->PyValue_To_FeatureSet(pyValue); } fazekasgy@37: fazekasgy@37: Vamp::RealTime PyValue_To_RealTime(PyObject*) const; Chris@72: void PyValue_To_rValue(PyObject *pyValue, Vamp::RealTime &r) const fazekasgy@37: { r = this->PyValue_To_RealTime(pyValue); } fazekasgy@37: fazekasgy@37: Vamp::Plugin::OutputDescriptor::SampleType PyValue_To_SampleType(PyObject*) const; fazekasgy@37: fazekasgy@37: Vamp::Plugin::InputDomain PyValue_To_InputDomain(PyObject*) const; Chris@72: void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::InputDomain &r) const fazekasgy@37: { r = this->PyValue_To_InputDomain(pyValue); } fazekasgy@37: fazekasgy@37: /* Overloaded PyValue_To_rValue() to support generic functions */ Chris@72: void PyValue_To_rValue(PyObject *pyValue, float &defValue) const Chris@71: { float tmp = m_conv.PyValue_To_Float(pyValue); fazekasgy@37: if(!m_error) defValue = tmp; } Chris@72: void PyValue_To_rValue(PyObject *pyValue, size_t &defValue) const Chris@71: { size_t tmp = m_conv.PyValue_To_Size_t(pyValue); fazekasgy@37: if(!m_error) defValue = tmp; } Chris@72: void PyValue_To_rValue(PyObject *pyValue, bool &defValue) const Chris@71: { bool tmp = m_conv.PyValue_To_Bool(pyValue); fazekasgy@37: if(!m_error) defValue = tmp; } Chris@72: void PyValue_To_rValue(PyObject *pyValue, std::string &defValue) const Chris@71: { std::string tmp = m_conv.PyValue_To_String(pyValue); fazekasgy@37: if(!m_error) defValue = tmp; } fazekasgy@37: /*used by templates where we expect no return value, if there is one it will be ignored*/ Chris@72: void PyValue_To_rValue(PyObject *pyValue, NoneType &defValue) const fazekasgy@37: { if (m_strict && pyValue != Py_None) fazekasgy@37: setValueError("Strict conversion error: Expected 'None' type.",m_strict); fazekasgy@37: } Chris@71: fazekasgy@37: /* convert sequence types to Vamp List types */ Chris@72: void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::OutputList &r) const fazekasgy@37: { r = this->PyValue_To_VampList(pyValue); } Chris@72: void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::ParameterList &r) const fazekasgy@37: { r = this->PyValue_To_VampList(pyValue); } Chris@72: void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::FeatureList &r) const fazekasgy@37: { r = this->PyValue_To_VampList(pyValue); } fazekasgy@37: fazekasgy@37: /// this is only needed for RealTime->Frame conversion fazekasgy@37: void setInputSampleRate(float inputSampleRate) fazekasgy@37: { m_inputSampleRate = (unsigned int) inputSampleRate; } fazekasgy@37: fazekasgy@37: private: fazekasgy@37: bool m_strict; fazekasgy@37: mutable bool m_error; fazekasgy@37: mutable std::queue m_errorQueue; fazekasgy@37: unsigned int m_inputSampleRate; fazekasgy@51: bool m_numpyInstalled; fazekasgy@37: fazekasgy@37: void setValueError(std::string,bool) const; fazekasgy@37: ValueError& lastError() const; fazekasgy@37: fazekasgy@37: /* Overloaded _convert(), bypasses error checking to avoid doing it twice in internals. */ Chris@72: void _convert(PyObject *pyValue,float &r) const Chris@71: { r = m_conv.PyValue_To_Float(pyValue); } Chris@72: void _convert(PyObject *pyValue,size_t &r) const Chris@71: { r = m_conv.PyValue_To_Size_t(pyValue); } Chris@72: void _convert(PyObject *pyValue,bool &r) const Chris@71: { r = m_conv.PyValue_To_Bool(pyValue); } Chris@72: void _convert(PyObject *pyValue,std::string &r) const Chris@71: { r = m_conv.PyValue_To_String(pyValue); } Chris@72: void _convert(PyObject *pyValue,std::vector &r) const Chris@71: { r = m_conv.PyValue_To_StringVector(pyValue); } Chris@72: void _convert(PyObject *pyValue,std::vector &r) const Chris@71: { r = m_conv.PyValue_To_FloatVector(pyValue); } Chris@72: void _convert(PyObject *pyValue,Vamp::RealTime &r) const fazekasgy@37: { r = PyValue_To_RealTime(pyValue); } Chris@72: void _convert(PyObject *pyValue,Vamp::Plugin::OutputDescriptor::SampleType &r) const fazekasgy@37: { r = PyValue_To_SampleType(pyValue); } Chris@72: // void _convert(PyObject *pyValue,Vamp::Plugin::InputDomain &r) const Chris@71: // { r = m_conv.PyValue_To_InputDomain(pyValue); } fazekasgy@37: fazekasgy@37: fazekasgy@37: /* Identify descriptors for error reporting */ Chris@72: std::string getDescriptorId(Vamp::Plugin::OutputDescriptor d) const fazekasgy@37: {return std::string("Output Descriptor '") + d.identifier +"' ";} Chris@72: std::string getDescriptorId(Vamp::Plugin::ParameterDescriptor d) const fazekasgy@37: {return std::string("Parameter Descriptor '") + d.identifier +"' ";} Chris@72: std::string getDescriptorId(Vamp::Plugin::Feature f) const fazekasgy@37: {return std::string("Feature (") + f.label + ")"; } fazekasgy@37: fazekasgy@37: public: fazekasgy@37: const bool& error; fazekasgy@37: fazekasgy@37: }; fazekasgy@37: fazekasgy@37: #endif