Mercurial > hg > vampy
diff PyPlugin.cpp @ 37:27bab3a16c9a vampy2final
new branch Vampy2final
author | fazekasgy |
---|---|
date | Mon, 05 Oct 2009 11:28:00 +0000 |
parents | |
children | c1e4f706ca9a |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyPlugin.cpp Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,426 @@ +/* + + * 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) : + 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_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); + +} + +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); + 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(); + } + + return processMethodCall(inputBuffers,timestamp); + +} + +PyPlugin::FeatureSet +PyPlugin::getRemainingFeatures() +{ + MutexLocker locker(&m_pythonInterpreterMutex); + 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"; + + 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 + m_processType = numpy_arrayProcess; + if (m_debugFlag) cerr << "Process using numpy array interface." << endl; +#else + cerr << "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_processType) + { + m_processType = not_implemented; + m_pyProcess = NULL; + m_pyProcessCallable = NULL; + char method[]="initialise::setProcessType"; + cerr << PLUGIN_ERROR << " No process implementation found. Plugin will do nothing." << endl; + } +} + +void +PyPlugin::typeErrorHandler(char *method) const +{ + bool strict = false; + while (m_ti.error) { + PyTypeInterface::ValueError e = m_ti.getError(); + 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); +} +