Mercurial > hg > vampy
view PyPlugin.h @ 92:a6718f9fe942
If a module appears to redefine one of our own types, refuse to load it. Also clear out the class dict for all refused modules now, so that we don't get stale names on the next scan due to not having cleared the module on unload
author | Chris Cannam |
---|---|
date | Mon, 14 Jan 2019 16:19:44 +0000 |
parents | f5c028376bf9 |
children |
line wrap: on
line source
/* * Vampy : This plugin is a wrapper around the Vamp plugin API. * It allows for writing Vamp plugins in Python. * Centre for Digital Music, Queen Mary University of London. * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources * for licence information.) */ #ifndef _PYTHON_WRAPPER_PLUGIN_H_ #define _PYTHON_WRAPPER_PLUGIN_H_ #define _CLASS_METHOD_ m_class << "::" << method #define PLUGIN_ERROR "ERROR: In Vampy plugin [" << _CLASS_METHOD_ << "]" << endl << "Cause: " #define DEBUG_NAME "[Vampy::call] " << _CLASS_METHOD_ << " " #define DEFAULT_RETURN "Method [" << _CLASS_METHOD_ << "] is not implemented. Returning default value." #define FLAG_VALUE "Flag: " << flagName << ": " << ((rValue==0)?"False":"True") #include <Python.h> #include "PyExtensionModule.h" #include "PyTypeInterface.h" #include "vamp-sdk/Plugin.h" #include "Mutex.h" using std::string; using std::cerr; using std::endl; enum eProcessType { not_implemented, legacyProcess, numpyProcess, numpy_bufferProcess, numpy_arrayProcess }; class PyPlugin : public Vamp::Plugin { public: PyPlugin(std::string plugin,float inputSampleRate, PyObject *pyClass, int &instcount, bool &numpyInstalled); virtual ~PyPlugin(); bool initialise(size_t channels, size_t stepSize, size_t blockSize); void reset(); InputDomain getInputDomain() const; size_t getPreferredBlockSize() const; size_t getPreferredStepSize() const; size_t getMinChannelCount() const; size_t getMaxChannelCount() const; std::string getIdentifier() const; std::string getName() const; std::string getDescription() const; std::string getMaker() const; int getPluginVersion() const; std::string getCopyright() const; OutputList getOutputDescriptors() const; ParameterList getParameterDescriptors() const; float getParameter(std::string paramid) const; void setParameter(std::string paramid, float newval); FeatureSet process(const float *const *inputBuffers, Vamp::RealTime timestamp); FeatureSet getRemainingFeatures(); protected: static Mutex m_pythonInterpreterMutex; PyObject *m_pyClass; PyObject *m_pyInstance; int &m_instcount; size_t m_stepSize; size_t m_blockSize; size_t m_channels; std::string m_plugin; std::string m_class; std::string m_path; eProcessType m_processType; PyObject *m_pyProcess; PyObject *m_pyProcessCallable; mutable InputDomain m_inputDomain; PyTypeInterface m_ti; int m_vampyFlags; bool m_quitOnErrorFlag; bool m_debugFlag; bool m_useRealTimeFlag; bool m_numpyInstalled; mutable bool m_processFailure; void setProcessType(); FeatureSet processMethodCall(const float *const *inputBuffers,Vamp::RealTime timestamp); bool getBooleanFlag(const char flagName[],bool) const; int getBinaryFlags(const char flagName[], eVampyFlags) const; void typeErrorHandler(const char *method, bool process = false) const; /// simple 'void return' call with no args void genericMethodCall(const char *method) const { if (m_debugFlag) cerr << DEBUG_NAME << endl; if ( PyObject_HasAttrString(m_pyInstance,method) ) { PyObject *pyValue = PyObject_CallMethod(m_pyInstance, (char *)method, NULL); if (!pyValue) { cerr << PLUGIN_ERROR << "Failed to call method." << endl; if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} } } } /// 'no arg with default return value' call template<typename RET> RET &genericMethodCall(const char *method, RET &rValue) const { if (m_debugFlag) cerr << DEBUG_NAME << endl; if ( PyObject_HasAttrString(m_pyInstance,method) ) { PyObject *pyValue = PyObject_CallMethod(m_pyInstance, (char *)method, NULL); if (!pyValue) { cerr << PLUGIN_ERROR << "Failed to call method." << endl; if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} return rValue; } /// convert the returned value m_ti.PyValue_To_rValue(pyValue,rValue); if (!m_ti.error) { Py_DECREF(pyValue); } else { Py_CLEAR(pyValue); typeErrorHandler(method); } return rValue; } if (m_debugFlag) cerr << DEFAULT_RETURN << endl; return rValue; } /// unary call template<typename RET,typename A1> RET genericMethodCallArgs(const char *method, A1 arg1) const { RET rValue = RET(); if (m_debugFlag) cerr << DEBUG_NAME << endl; if (!PyObject_HasAttrString(m_pyInstance,method)) { if (m_debugFlag) cerr << DEFAULT_RETURN << endl; return rValue; } /// prepare arguments for fast method call PyObject *pyMethod = m_ti.PyValue_From_CValue(method); PyObject *pyCallable = PyObject_GetAttr(m_pyInstance,pyMethod); PyObject* pyArgs = PyTuple_New(1); if (!(pyArgs && pyCallable && pyMethod)) { cerr << PLUGIN_ERROR << "Failed to prepare argument for calling method." << endl; Py_CLEAR(pyMethod); Py_CLEAR(pyCallable); Py_CLEAR(pyArgs); return rValue; } PyObject *pyArg1 = m_ti.PyValue_From_CValue(arg1); if (m_ti.error) { cerr << PLUGIN_ERROR << "Failed to convert argument for calling method." << endl; typeErrorHandler(method); Py_CLEAR(pyMethod); Py_CLEAR(pyCallable); Py_CLEAR(pyArg1); Py_CLEAR(pyArgs); return rValue; } PyTuple_SET_ITEM(pyArgs, 0, pyArg1); Py_INCREF(pyArg1); /// call the method PyObject *pyValue = PyObject_Call(pyCallable,pyArgs,NULL); if (!pyValue) { cerr << PLUGIN_ERROR << "Failed to call method." << endl; if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} Py_CLEAR(pyMethod); Py_CLEAR(pyCallable); Py_CLEAR(pyArg1); Py_CLEAR(pyArgs); return rValue; } Py_DECREF(pyMethod); Py_DECREF(pyCallable); Py_DECREF(pyArg1); Py_DECREF(pyArgs); /// convert the returned value m_ti.PyValue_To_rValue(pyValue,rValue); if (!m_ti.error) { Py_DECREF(pyValue); } else { Py_CLEAR(pyValue); typeErrorHandler(method); } return rValue; } /// binary call template<typename RET,typename A1,typename A2> RET genericMethodCallArgs(const char *method, A1 arg1, A2 arg2) const { RET rValue = RET(); if (m_debugFlag) cerr << DEBUG_NAME << endl; if (!PyObject_HasAttrString(m_pyInstance,method)) { if (m_debugFlag) cerr << DEFAULT_RETURN << endl; return rValue; } /// prepare arguments for fast method call PyObject *pyMethod = m_ti.PyValue_From_CValue(method); PyObject *pyCallable = PyObject_GetAttr(m_pyInstance,pyMethod); PyObject* pyArgs = PyTuple_New(2); if (!(pyArgs && pyCallable && pyMethod)) { cerr << PLUGIN_ERROR << "Failed to prepare arguments for calling method." << endl; Py_CLEAR(pyMethod); Py_CLEAR(pyCallable); Py_CLEAR(pyArgs); return rValue; } PyObject *pyArg1 = m_ti.PyValue_From_CValue(arg1); PyObject *pyArg2 = m_ti.PyValue_From_CValue(arg2); if (m_ti.error) { cerr << PLUGIN_ERROR << "Failed to convert arguments for calling method." << endl; typeErrorHandler(method); Py_CLEAR(pyMethod); Py_CLEAR(pyCallable); Py_CLEAR(pyArg1); Py_CLEAR(pyArg2); Py_CLEAR(pyArgs); return rValue; } PyTuple_SET_ITEM(pyArgs, 0, pyArg1); Py_INCREF(pyArg1); PyTuple_SET_ITEM(pyArgs, 1, pyArg2); Py_INCREF(pyArg2); // calls the method PyObject *pyValue = PyObject_Call(pyCallable,pyArgs,NULL); if (!pyValue) { cerr << PLUGIN_ERROR << "Failed to call method." << endl; if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} Py_CLEAR(pyMethod); Py_CLEAR(pyCallable); Py_CLEAR(pyArg1); Py_CLEAR(pyArg2); Py_CLEAR(pyArgs); return rValue; } Py_DECREF(pyMethod); Py_DECREF(pyCallable); Py_DECREF(pyArg1); Py_DECREF(pyArg2); Py_DECREF(pyArgs); /// convert the returned value m_ti.PyValue_To_rValue(pyValue,rValue); if (!m_ti.error) { Py_DECREF(pyValue); } else { Py_CLEAR(pyValue); typeErrorHandler(method); } return rValue; } /// trenary call template<typename RET,typename A1,typename A2,typename A3> RET genericMethodCallArgs(const char *method, A1 arg1, A2 arg2, A3 arg3) const { RET rValue = RET(); if (m_debugFlag) cerr << DEBUG_NAME << endl; if (!PyObject_HasAttrString(m_pyInstance,method)) { if (m_debugFlag) cerr << DEFAULT_RETURN << endl; return rValue; } /// prepare arguments for fast method call PyObject *pyMethod = m_ti.PyValue_From_CValue(method); PyObject *pyCallable = PyObject_GetAttr(m_pyInstance,pyMethod); PyObject* pyArgs = PyTuple_New(3); if (!(pyArgs && pyCallable && pyMethod)) { cerr << PLUGIN_ERROR << "Failed to prepare arguments for calling method." << endl; Py_CLEAR(pyMethod); Py_CLEAR(pyCallable); Py_CLEAR(pyArgs); return rValue; } PyObject *pyArg1 = m_ti.PyValue_From_CValue(arg1); PyObject *pyArg2 = m_ti.PyValue_From_CValue(arg2); PyObject *pyArg3 = m_ti.PyValue_From_CValue(arg3); if (m_ti.error) { cerr << PLUGIN_ERROR << "Failed to convert arguments for calling method." << endl; typeErrorHandler(method); Py_CLEAR(pyMethod); Py_CLEAR(pyCallable); Py_CLEAR(pyArg1); Py_CLEAR(pyArg2); Py_CLEAR(pyArg3); Py_CLEAR(pyArgs); return rValue; } /// Optimization: Pack args in a tuple to avoid va_list parsing. PyTuple_SET_ITEM(pyArgs, 0, pyArg1); Py_INCREF(pyArg1); PyTuple_SET_ITEM(pyArgs, 1, pyArg2); Py_INCREF(pyArg2); PyTuple_SET_ITEM(pyArgs, 2, pyArg3); Py_INCREF(pyArg3); // PyObject *pyValue = PyObject_CallMethodObjArgs(m_pyInstance,pyMethod,pyArg1,pyArg2,pyArg3,NULL); /// fast method call PyObject *pyValue = PyObject_Call(pyCallable,pyArgs,NULL); if (!pyValue) { cerr << PLUGIN_ERROR << "Failed to call method." << endl; if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} Py_CLEAR(pyMethod); Py_CLEAR(pyCallable); Py_CLEAR(pyArg1); Py_CLEAR(pyArg2); Py_CLEAR(pyArg3); Py_CLEAR(pyArgs); return rValue; } Py_DECREF(pyMethod); Py_DECREF(pyCallable); Py_DECREF(pyArg1); Py_DECREF(pyArg2); Py_DECREF(pyArg3); Py_DECREF(pyArgs); /// convert the returned value m_ti.PyValue_To_rValue(pyValue,rValue); if (!m_ti.error) { Py_DECREF(pyValue); } else { Py_CLEAR(pyValue); typeErrorHandler(method); } return rValue; } }; /// optimised process call inline PyPlugin::FeatureSet PyPlugin::processMethodCall(const float *const *inputBuffers,Vamp::RealTime timestamp) { /// Optimizations: 1) we avoid ...ObjArg functions since we know /// the number of arguments, and we don't like va_list parsing /// in the process. 2) Also: we're supposed to incref args, /// but instead, we let the arguments tuple steal the references /// and decref them when it is deallocated. /// 3) all conversions are now using the fast sequence protocol /// (indexing the underlying object array). FeatureSet rFeatureSet; PyObject *pyChannelList = NULL; if (m_processType == numpy_bufferProcess) { pyChannelList = m_ti.InputBuffers_As_SharedMemoryList( inputBuffers,m_channels,m_blockSize,m_inputDomain); } if (m_processType == legacyProcess) { pyChannelList = m_ti.InputBuffers_As_PythonLists( inputBuffers,m_channels,m_blockSize,m_inputDomain); } #ifdef HAVE_NUMPY if (m_processType == numpy_arrayProcess) { pyChannelList = m_ti.InputBuffers_As_NumpyArray( inputBuffers,m_channels,m_blockSize,m_inputDomain); } #endif /// we don't expect these to fail unless out of memory (which is very unlikely on modern systems) #ifdef _DEBUG if (!pyChannelList) { if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} std::string method = PyString_AsString(m_pyProcess); cerr << PLUGIN_ERROR << "Failed to create channel list." << endl; return rFeatureSet; } #endif PyObject *pyTimeStamp = NULL; if (m_useRealTimeFlag) { //(1) pass TimeStamp as PyRealTime object pyTimeStamp = PyRealTime_FromRealTime(timestamp); } else { //(2) pass TimeStamp as frame count (long Sample Count) pyTimeStamp = PyLong_FromLong(Vamp::RealTime::realTime2Frame (timestamp, (unsigned int) m_inputSampleRate)); } #ifdef _DEBUG if (!pyTimeStamp) { if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} std::string method = PyString_AsString(m_pyProcess); cerr << PLUGIN_ERROR << "Failed to create RealTime time stamp." << endl; Py_DECREF(pyChannelList); return rFeatureSet; } #endif /// Old method: Call python process (returns new reference) /// PyObject *pyValue = PyObject_CallMethodObjArgs /// (m_pyInstance,m_pyProcess,pyChannelList,pyTimeStamp,NULL); PyObject *pyArgs = PyTuple_New(2); PyTuple_SET_ITEM(pyArgs, 0, pyChannelList); PyTuple_SET_ITEM(pyArgs, 1, pyTimeStamp); /// Call python process (returns new reference) {kwArgs = NULL} PyObject *pyValue = PyObject_Call(m_pyProcessCallable,pyArgs,NULL); if (!pyValue) { if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} std::string method = PyString_AsString(m_pyProcess); cerr << PLUGIN_ERROR << "An error occurred while evaluating Python process." << endl; Py_CLEAR(pyValue); Py_CLEAR(pyArgs); return rFeatureSet; } rFeatureSet = m_ti.PyValue_To_FeatureSet(pyValue); if (!m_ti.error) { Py_DECREF(pyValue); Py_DECREF(pyArgs); } else { typeErrorHandler(PyString_AsString(m_pyProcess),true); Py_CLEAR(pyValue); Py_CLEAR(pyArgs); } return rFeatureSet; } #endif