Mercurial > hg > vampy
view PyPlugin.cpp @ 53:7e59caea821b
* Make a better job of preloading Python, especially when it's in a
framework. Go for the Python file in the frameworks directory in
preference to any libpythonX.Y.dylib. Particularly, don't try to
preload any library without an absolute path until we've exhausted
all our framework possibilities (so as to avoid picking up an
ancient system library).
author | cannam |
---|---|
date | Fri, 09 Oct 2009 13:48:25 +0000 |
parents | c1e4f706ca9a |
children | 5664fe298af2 |
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.) */ #include <Python.h> #include "PyPlugin.h" #include "PyTypeInterface.h" #include <stdlib.h> #include "PyExtensionModule.h" #ifdef _WIN32 #define PATHSEP ('\\') #else #define PATHSEP ('/') #endif using std::string; using std::vector; using std::cerr; using std::endl; using std::map; Mutex PyPlugin::m_pythonInterpreterMutex; PyPlugin::PyPlugin(std::string pluginKey, float inputSampleRate, PyObject *pyClass, int &instcount, bool &numpyInstalled) : Plugin(inputSampleRate), m_pyClass(pyClass), m_instcount(instcount), m_stepSize(0), m_blockSize(0), m_channels(0), m_plugin(pluginKey), m_class(pluginKey.substr(pluginKey.rfind(':')+1,pluginKey.size()-1)), m_path((pluginKey.substr(0,pluginKey.rfind(PATHSEP)))), m_processType(not_implemented), m_pyProcess(NULL), m_inputDomain(TimeDomain), m_quitOnErrorFlag(false), m_debugFlag(false), m_numpyInstalled(numpyInstalled), m_processFailure(false) { m_ti.setInputSampleRate(inputSampleRate); MutexLocker locker(&m_pythonInterpreterMutex); cerr << "Creating instance " << m_instcount << " of " << pluginKey << endl; // Create an instance Py_INCREF(m_pyClass); PyObject *pyInputSampleRate = PyFloat_FromDouble(inputSampleRate); PyObject *args = PyTuple_Pack(1, pyInputSampleRate); m_pyInstance = PyObject_Call(m_pyClass, args, NULL); if (!m_pyInstance || PyErr_Occurred()) { if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } Py_DECREF(m_pyClass); Py_CLEAR(args); Py_CLEAR(pyInputSampleRate); cerr << "PyPlugin::PyPlugin: Failed to create Python plugin instance for key \"" << pluginKey << "\" (is the 1-arg class constructor from sample rate correctly provided?)" << endl; throw std::string("Constructor failed"); } Py_INCREF(m_pyInstance); Py_DECREF(args); Py_DECREF(pyInputSampleRate); m_instcount++; // query and decode vampy flags m_vampyFlags = getBinaryFlags("vampy_flags",vf_NULL); m_debugFlag = (bool) (m_vampyFlags & vf_DEBUG); m_quitOnErrorFlag = (bool) (m_vampyFlags & vf_QUIT); bool st_flag = (bool) (m_vampyFlags & vf_STRICT); m_useRealTimeFlag = (bool) (m_vampyFlags & vf_REALTIME); if (m_debugFlag) cerr << "Debug messages ON for Vampy plugin: " << m_class << endl; else cerr << "Debug messages OFF for Vampy plugin: " << m_class << endl; if (m_debugFlag && m_quitOnErrorFlag) cerr << "Quit on type error ON for: " << m_class << endl; if (m_debugFlag && st_flag) cerr << "Strict type conversion ON for: " << m_class << endl; m_ti.setStrictTypingFlag(st_flag); m_ti.setNumpyInstalled(m_numpyInstalled); } PyPlugin::~PyPlugin() { MutexLocker locker(&m_pythonInterpreterMutex); m_instcount--; // cerr << "Deleting plugin instance. Count: " << m_instcount << endl; if (m_pyInstance) Py_DECREF(m_pyInstance); //we increase the class refcount before creating an instance if (m_pyClass) Py_DECREF(m_pyClass); if (m_pyProcess) Py_CLEAR(m_pyProcess); #ifdef _DEBUG cerr << "PyPlugin::PyPlugin:" << m_class << " instance " << m_instcount << " deleted." << endl; #endif } string PyPlugin::getIdentifier() const { MutexLocker locker(&m_pythonInterpreterMutex); string rString="vampy-xxx"; if (!m_debugFlag) return genericMethodCall("getIdentifier",rString); rString = genericMethodCall("getIdentifier",rString); if (rString == "vampy-xxx") cerr << "Warning: Plugin must return a unique identifier." << endl; return rString; } string PyPlugin::getName() const { MutexLocker locker(&m_pythonInterpreterMutex); string rString="VamPy Plugin (Noname)"; return genericMethodCall("getName",rString); } string PyPlugin::getDescription() const { MutexLocker locker(&m_pythonInterpreterMutex); string rString="Not given. (Hint: Implement getDescription method.)"; return genericMethodCall("getDescription",rString); } string PyPlugin::getMaker() const { MutexLocker locker(&m_pythonInterpreterMutex); string rString="VamPy Plugin."; return genericMethodCall("getMaker",rString); } int PyPlugin::getPluginVersion() const { MutexLocker locker(&m_pythonInterpreterMutex); size_t rValue=2; return genericMethodCall("getPluginVersion",rValue); } string PyPlugin::getCopyright() const { MutexLocker locker(&m_pythonInterpreterMutex); string rString="Licence information not available."; return genericMethodCall("getCopyright",rString); } bool PyPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize) { if (channels < getMinChannelCount() || channels > getMaxChannelCount()) return false; m_inputDomain = getInputDomain(); //Note: placing Mutex before the calls above causes deadlock !! MutexLocker locker(&m_pythonInterpreterMutex); m_stepSize = stepSize; m_blockSize = blockSize; m_channels = channels; //query the process implementation type //two optional flags can be used: 'use_numpy_interface' or 'use_legacy_interface' //if they are not provided, we fall back to the original method setProcessType(); return genericMethodCallArgs<bool>("initialise",channels,stepSize,blockSize); } void PyPlugin::reset() { MutexLocker locker(&m_pythonInterpreterMutex); m_processFailure = false; genericMethodCall("reset"); } PyPlugin::InputDomain PyPlugin::getInputDomain() const { MutexLocker locker(&m_pythonInterpreterMutex); return genericMethodCall("getInputDomain",m_inputDomain); } size_t PyPlugin::getPreferredBlockSize() const { MutexLocker locker(&m_pythonInterpreterMutex); size_t rValue = 0; return genericMethodCall("getPreferredBlockSize",rValue); } size_t PyPlugin::getPreferredStepSize() const { MutexLocker locker(&m_pythonInterpreterMutex); size_t rValue = 0; return genericMethodCall("getPreferredStepSize",rValue); } size_t PyPlugin::getMinChannelCount() const { MutexLocker locker(&m_pythonInterpreterMutex); size_t rValue = 1; return genericMethodCall("getMinChannelCount",rValue); } size_t PyPlugin::getMaxChannelCount() const { MutexLocker locker(&m_pythonInterpreterMutex); size_t rValue = 1; return genericMethodCall("getMaxChannelCount",rValue); } PyPlugin::OutputList PyPlugin::getOutputDescriptors() const { MutexLocker locker(&m_pythonInterpreterMutex); OutputList list; return genericMethodCall("getOutputDescriptors",list); } PyPlugin::ParameterList PyPlugin::getParameterDescriptors() const { MutexLocker locker(&m_pythonInterpreterMutex); ParameterList list; #ifdef _DEBUG ///Note: This function is often called first by the host. if (!m_pyInstance) {cerr << "Error: pyInstance is NULL" << endl; return list;} #endif return genericMethodCall("getParameterDescriptors",list); } void PyPlugin::setParameter(std::string paramid, float newval) { MutexLocker locker(&m_pythonInterpreterMutex); genericMethodCallArgs<NoneType>("setParameter",paramid,newval); } float PyPlugin::getParameter(std::string paramid) const { MutexLocker locker(&m_pythonInterpreterMutex); return genericMethodCallArgs<float>("getParameter",paramid); } #ifdef _DEBUG_VALUES static int proccounter = 0; #endif PyPlugin::FeatureSet PyPlugin::process(const float *const *inputBuffers,Vamp::RealTime timestamp) { MutexLocker locker(&m_pythonInterpreterMutex); #ifdef _DEBUG_VALUES /// we only need this if we'd like to see what frame a set of values belong to cerr << "[Vampy::call] process, frame:" << proccounter << endl; proccounter++; #endif if (m_blockSize == 0 || m_channels == 0) { cerr << "ERROR: PyPlugin::process: " << "Plugin has not been initialised" << endl; return FeatureSet(); } if (m_processType == not_implemented) { cerr << "ERROR: In Python plugin [" << m_class << "] No process implementation found. Returning empty feature set." << endl; return FeatureSet(); } if (m_processFailure) return FeatureSet(); return processMethodCall(inputBuffers,timestamp); } PyPlugin::FeatureSet PyPlugin::getRemainingFeatures() { MutexLocker locker(&m_pythonInterpreterMutex); if (m_processFailure) return FeatureSet(); FeatureSet rValue; return genericMethodCall("getRemainingFeatures",rValue); } bool PyPlugin::getBooleanFlag(char flagName[], bool defValue = false) const { bool rValue = defValue; if (PyObject_HasAttrString(m_pyInstance,flagName)) { PyObject *pyValue = PyObject_GetAttrString(m_pyInstance,flagName); if (!pyValue) { if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} } else { rValue = m_ti.PyValue_To_Bool(pyValue); if (m_ti.error) { Py_CLEAR(pyValue); typeErrorHandler(flagName); rValue = defValue; } else Py_DECREF(pyValue); } } if (m_debugFlag) cerr << FLAG_VALUE << endl; return rValue; } int PyPlugin::getBinaryFlags(char flagName[], eVampyFlags defValue = vf_NULL) const { int rValue = defValue; if (PyObject_HasAttrString(m_pyInstance,flagName)) { PyObject *pyValue = PyObject_GetAttrString(m_pyInstance,flagName); if (!pyValue) { if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} } else { rValue |= (int) m_ti.PyValue_To_Size_t(pyValue); if (m_ti.error) { Py_CLEAR(pyValue); typeErrorHandler(flagName); rValue = defValue; } else Py_DECREF(pyValue); } } if (m_debugFlag) cerr << FLAG_VALUE << endl; return rValue; } void PyPlugin::setProcessType() { //quering process implementation type char legacyMethod[]="process"; char numpyMethod[]="processN"; m_processFailure = false; if (PyObject_HasAttrString(m_pyInstance,legacyMethod) && m_processType == 0) { m_processType = legacyProcess; m_pyProcess = PyString_FromString(legacyMethod); m_pyProcessCallable = PyObject_GetAttr(m_pyInstance,m_pyProcess); } if (PyObject_HasAttrString(m_pyInstance,numpyMethod) && m_processType == 0) { m_processType = numpy_bufferProcess; m_pyProcess = PyString_FromString(numpyMethod); m_pyProcessCallable = PyObject_GetAttr(m_pyInstance,m_pyProcess); } // These flags are optional. If provided, they override the // implementation type making the use of the odd processN() // function redundant. // However, the code above provides backward compatibility. if (m_vampyFlags & vf_BUFFER) { m_processType = numpy_bufferProcess; if (m_debugFlag) cerr << "Process using (numpy) buffer interface." << endl; } if (m_vampyFlags & vf_ARRAY) { #ifdef HAVE_NUMPY if (m_numpyInstalled) { m_processType = numpy_arrayProcess; if (m_debugFlag) cerr << "Process using numpy array interface." << endl; } else { m_processFailure = true; char method[]="initialise::setProcessType"; cerr << PLUGIN_ERROR << "This plugin requests the Numpy array interface by setting " << " the vf_ARRAY flag in its __init__() function." << endl << "However, we could not found a version of Numpy compatible with this build of Vampy." << endl << "If you have a numerical library installed that supports the buffer interface, " << endl << "you can request this interface instead by setting the vf_BUFFER flag." << endl; } #else m_processFailure = true; char method[]="initialise::setProcessType"; cerr << PLUGIN_ERROR << "Error: This version of vampy was compiled without numpy support, " << "however the vf_ARRAY flag is set for plugin: " << m_class << endl << "The default behaviour is: passing a python list of samples for each channel in process() " << "or a list of memory buffers in processN(). " << endl << "This can be used create numpy arrays using the numpy.frombuffer() command." << endl; #endif } if (!m_pyProcessCallable) { m_processType = not_implemented; m_pyProcess = NULL; char method[]="initialise::setProcessType"; cerr << PLUGIN_ERROR << " No process implementation found. Plugin will do nothing." << endl; m_processFailure = true; } } void PyPlugin::typeErrorHandler(char *method, bool process) const { bool strict = false; while (m_ti.error) { PyTypeInterface::ValueError e = m_ti.getError(); #ifdef HAVE_NUMPY // disable the process completely if numpy types are returned // but a compatible version was not loaded. // This is required because if an object is returned from // the wrong build, malloc complains about its size // (i.e. the interpreter doesn't free it properly) // and the process may be leaking. // Note: this only happens in the obscure situation when // someone forces to return wrong numpy types from an // incompatible version using the buffer interface. // In this case the incampatible library is still usable, // but manual conversion to python builtins is required. // If the ARRAY interface is set but Numpy is not installed // the process will be disabled already at initialisation. if (process && !m_numpyInstalled && e.str().find("numpy")!=std::string::npos) { m_processFailure = true; cerr << "Warning: incompatible numpy type encountered. Disabling process." << endl; } #endif cerr << PLUGIN_ERROR << e.str() << endl; if (e.strict) strict = true; // e.print(); } /// quit on hard errors like accessing NULL pointers or strict type conversion /// errors IF the user sets the quitOnErrorFlag in the plugin. /// Otherwise most errors will go unnoticed apart from /// a messages in the terminal. /// It would be best if hosts could catch an exception instead /// and display something meaningful to the user. if (strict && m_quitOnErrorFlag) exit(EXIT_FAILURE); // this would disable all outputs even if some are valid // if (process) m_processFailure = true; }