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: #include fazekasgy@37: #include "PyPlugin.h" fazekasgy@37: #include "PyTypeInterface.h" fazekasgy@37: #include fazekasgy@37: #include "PyExtensionModule.h" fazekasgy@37: fazekasgy@37: fazekasgy@37: #ifdef _WIN32 fazekasgy@37: #define PATHSEP ('\\') fazekasgy@37: #else fazekasgy@37: #define PATHSEP ('/') fazekasgy@37: #endif fazekasgy@37: fazekasgy@37: using std::string; fazekasgy@37: using std::vector; fazekasgy@37: using std::cerr; fazekasgy@37: using std::endl; fazekasgy@37: using std::map; fazekasgy@37: fazekasgy@37: Mutex PyPlugin::m_pythonInterpreterMutex; fazekasgy@37: fazekasgy@37: PyPlugin::PyPlugin(std::string pluginKey, float inputSampleRate, PyObject *pyClass, int &instcount) : fazekasgy@37: Plugin(inputSampleRate), fazekasgy@37: m_pyClass(pyClass), fazekasgy@37: m_instcount(instcount), fazekasgy@37: m_stepSize(0), fazekasgy@37: m_blockSize(0), fazekasgy@37: m_channels(0), fazekasgy@37: m_plugin(pluginKey), fazekasgy@37: m_class(pluginKey.substr(pluginKey.rfind(':')+1,pluginKey.size()-1)), fazekasgy@37: m_path((pluginKey.substr(0,pluginKey.rfind(PATHSEP)))), fazekasgy@37: m_processType(not_implemented), fazekasgy@37: m_pyProcess(NULL), fazekasgy@37: m_inputDomain(TimeDomain), fazekasgy@37: m_quitOnErrorFlag(false), fazekasgy@37: m_debugFlag(false) fazekasgy@37: { fazekasgy@37: m_ti.setInputSampleRate(inputSampleRate); fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: cerr << "Creating instance " << m_instcount << " of " << pluginKey << endl; fazekasgy@37: fazekasgy@37: // Create an instance fazekasgy@37: Py_INCREF(m_pyClass); fazekasgy@37: PyObject *pyInputSampleRate = PyFloat_FromDouble(inputSampleRate); fazekasgy@37: PyObject *args = PyTuple_Pack(1, pyInputSampleRate); fazekasgy@37: m_pyInstance = PyObject_Call(m_pyClass, args, NULL); fazekasgy@37: fazekasgy@37: if (!m_pyInstance || PyErr_Occurred()) { fazekasgy@37: if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } fazekasgy@37: Py_DECREF(m_pyClass); fazekasgy@37: Py_CLEAR(args); fazekasgy@37: Py_CLEAR(pyInputSampleRate); fazekasgy@37: cerr << "PyPlugin::PyPlugin: Failed to create Python plugin instance for key \"" fazekasgy@37: << pluginKey << "\" (is the 1-arg class constructor from sample rate correctly provided?)" << endl; fazekasgy@37: throw std::string("Constructor failed"); fazekasgy@37: } fazekasgy@37: Py_INCREF(m_pyInstance); fazekasgy@37: Py_DECREF(args); fazekasgy@37: Py_DECREF(pyInputSampleRate); fazekasgy@37: fazekasgy@37: m_instcount++; fazekasgy@37: fazekasgy@37: // query and decode vampy flags fazekasgy@37: m_vampyFlags = getBinaryFlags("vampy_flags",vf_NULL); fazekasgy@37: fazekasgy@37: m_debugFlag = (bool) (m_vampyFlags & vf_DEBUG); fazekasgy@37: m_quitOnErrorFlag = (bool) (m_vampyFlags & vf_QUIT); fazekasgy@37: bool st_flag = (bool) (m_vampyFlags & vf_STRICT); fazekasgy@37: m_useRealTimeFlag = (bool) (m_vampyFlags & vf_REALTIME); fazekasgy@37: fazekasgy@37: if (m_debugFlag) cerr << "Debug messages ON for Vampy plugin: " << m_class << endl; fazekasgy@37: else cerr << "Debug messages OFF for Vampy plugin: " << m_class << endl; fazekasgy@37: fazekasgy@37: if (m_debugFlag && m_quitOnErrorFlag) cerr << "Quit on type error ON for: " << m_class << endl; fazekasgy@37: fazekasgy@37: if (m_debugFlag && st_flag) cerr << "Strict type conversion ON for: " << m_class << endl; fazekasgy@37: m_ti.setStrictTypingFlag(st_flag); fazekasgy@37: fazekasgy@37: } fazekasgy@37: fazekasgy@37: PyPlugin::~PyPlugin() fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: m_instcount--; fazekasgy@37: // cerr << "Deleting plugin instance. Count: " << m_instcount << endl; fazekasgy@37: fazekasgy@37: if (m_pyInstance) Py_DECREF(m_pyInstance); fazekasgy@37: //we increase the class refcount before creating an instance fazekasgy@37: if (m_pyClass) Py_DECREF(m_pyClass); fazekasgy@37: if (m_pyProcess) Py_CLEAR(m_pyProcess); fazekasgy@37: fazekasgy@37: #ifdef _DEBUG fazekasgy@37: cerr << "PyPlugin::PyPlugin:" << m_class << " instance " << m_instcount << " deleted." << endl; fazekasgy@37: #endif fazekasgy@37: } fazekasgy@37: fazekasgy@37: string fazekasgy@37: PyPlugin::getIdentifier() const fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: string rString="vampy-xxx"; fazekasgy@37: if (!m_debugFlag) return genericMethodCall("getIdentifier",rString); fazekasgy@37: fazekasgy@37: rString = genericMethodCall("getIdentifier",rString); fazekasgy@37: if (rString == "vampy-xxx") fazekasgy@37: cerr << "Warning: Plugin must return a unique identifier." << endl; fazekasgy@37: return rString; fazekasgy@37: } fazekasgy@37: fazekasgy@37: string fazekasgy@37: PyPlugin::getName() const fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: string rString="VamPy Plugin (Noname)"; fazekasgy@37: return genericMethodCall("getName",rString); fazekasgy@37: } fazekasgy@37: fazekasgy@37: string fazekasgy@37: PyPlugin::getDescription() const fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: string rString="Not given. (Hint: Implement getDescription method.)"; fazekasgy@37: return genericMethodCall("getDescription",rString); fazekasgy@37: } fazekasgy@37: fazekasgy@37: fazekasgy@37: string fazekasgy@37: PyPlugin::getMaker() const fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: string rString="VamPy Plugin."; fazekasgy@37: return genericMethodCall("getMaker",rString); fazekasgy@37: } fazekasgy@37: fazekasgy@37: int fazekasgy@37: PyPlugin::getPluginVersion() const fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: size_t rValue=2; fazekasgy@37: return genericMethodCall("getPluginVersion",rValue); fazekasgy@37: } fazekasgy@37: fazekasgy@37: string fazekasgy@37: PyPlugin::getCopyright() const fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: string rString="Licence information not available."; fazekasgy@37: return genericMethodCall("getCopyright",rString); fazekasgy@37: } fazekasgy@37: fazekasgy@37: fazekasgy@37: bool fazekasgy@37: PyPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize) fazekasgy@37: { fazekasgy@37: fazekasgy@37: if (channels < getMinChannelCount() || fazekasgy@37: channels > getMaxChannelCount()) return false; fazekasgy@37: fazekasgy@37: m_inputDomain = getInputDomain(); fazekasgy@37: fazekasgy@37: //Note: placing Mutex before the calls above causes deadlock !! fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: fazekasgy@37: m_stepSize = stepSize; fazekasgy@37: m_blockSize = blockSize; fazekasgy@37: m_channels = channels; fazekasgy@37: fazekasgy@37: //query the process implementation type fazekasgy@37: //two optional flags can be used: 'use_numpy_interface' or 'use_legacy_interface' fazekasgy@37: //if they are not provided, we fall back to the original method fazekasgy@37: setProcessType(); fazekasgy@37: fazekasgy@37: return genericMethodCallArgs("initialise",channels,stepSize,blockSize); fazekasgy@37: } fazekasgy@37: fazekasgy@37: void fazekasgy@37: PyPlugin::reset() fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: genericMethodCall("reset"); fazekasgy@37: } fazekasgy@37: fazekasgy@37: PyPlugin::InputDomain fazekasgy@37: PyPlugin::getInputDomain() const fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: return genericMethodCall("getInputDomain",m_inputDomain); fazekasgy@37: } fazekasgy@37: fazekasgy@37: size_t fazekasgy@37: PyPlugin::getPreferredBlockSize() const fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: size_t rValue = 0; fazekasgy@37: return genericMethodCall("getPreferredBlockSize",rValue); fazekasgy@37: } fazekasgy@37: fazekasgy@37: size_t fazekasgy@37: PyPlugin::getPreferredStepSize() const fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: size_t rValue = 0; fazekasgy@37: return genericMethodCall("getPreferredStepSize",rValue); fazekasgy@37: } fazekasgy@37: fazekasgy@37: size_t fazekasgy@37: PyPlugin::getMinChannelCount() const fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: size_t rValue = 1; fazekasgy@37: return genericMethodCall("getMinChannelCount",rValue); fazekasgy@37: } fazekasgy@37: fazekasgy@37: size_t fazekasgy@37: PyPlugin::getMaxChannelCount() const fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: size_t rValue = 1; fazekasgy@37: return genericMethodCall("getMaxChannelCount",rValue); fazekasgy@37: } fazekasgy@37: fazekasgy@37: PyPlugin::OutputList fazekasgy@37: PyPlugin::getOutputDescriptors() const fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: OutputList list; fazekasgy@37: return genericMethodCall("getOutputDescriptors",list); fazekasgy@37: } fazekasgy@37: fazekasgy@37: PyPlugin::ParameterList fazekasgy@37: PyPlugin::getParameterDescriptors() const fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: ParameterList list; fazekasgy@37: #ifdef _DEBUG fazekasgy@37: ///Note: This function is often called first by the host. fazekasgy@37: if (!m_pyInstance) {cerr << "Error: pyInstance is NULL" << endl; return list;} fazekasgy@37: #endif fazekasgy@37: fazekasgy@37: return genericMethodCall("getParameterDescriptors",list); fazekasgy@37: } fazekasgy@37: fazekasgy@37: void PyPlugin::setParameter(std::string paramid, float newval) fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: genericMethodCallArgs("setParameter",paramid,newval); fazekasgy@37: } fazekasgy@37: fazekasgy@37: float PyPlugin::getParameter(std::string paramid) const fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: return genericMethodCallArgs("getParameter",paramid); fazekasgy@37: } fazekasgy@37: fazekasgy@37: #ifdef _DEBUG_VALUES fazekasgy@37: static int proccounter = 0; fazekasgy@37: #endif fazekasgy@37: fazekasgy@37: PyPlugin::FeatureSet fazekasgy@37: PyPlugin::process(const float *const *inputBuffers,Vamp::RealTime timestamp) fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: fazekasgy@37: #ifdef _DEBUG_VALUES fazekasgy@37: /// we only need this if we'd like to see what frame a set of values belong to fazekasgy@37: cerr << "[Vampy::call] process, frame:" << proccounter << endl; fazekasgy@37: proccounter++; fazekasgy@37: #endif fazekasgy@37: fazekasgy@37: if (m_blockSize == 0 || m_channels == 0) { fazekasgy@37: cerr << "ERROR: PyPlugin::process: " fazekasgy@37: << "Plugin has not been initialised" << endl; fazekasgy@37: return FeatureSet(); fazekasgy@37: } fazekasgy@37: fazekasgy@37: if (m_processType == not_implemented) { fazekasgy@37: cerr << "ERROR: In Python plugin [" << m_class fazekasgy@37: << "] No process implementation found. Returning empty feature set." << endl; fazekasgy@37: return FeatureSet(); fazekasgy@37: } fazekasgy@37: fazekasgy@37: return processMethodCall(inputBuffers,timestamp); fazekasgy@37: fazekasgy@37: } fazekasgy@37: fazekasgy@37: PyPlugin::FeatureSet fazekasgy@37: PyPlugin::getRemainingFeatures() fazekasgy@37: { fazekasgy@37: MutexLocker locker(&m_pythonInterpreterMutex); fazekasgy@37: FeatureSet rValue; fazekasgy@37: return genericMethodCall("getRemainingFeatures",rValue); fazekasgy@37: } fazekasgy@37: fazekasgy@37: bool fazekasgy@37: PyPlugin::getBooleanFlag(char flagName[], bool defValue = false) const fazekasgy@37: { fazekasgy@37: bool rValue = defValue; fazekasgy@37: if (PyObject_HasAttrString(m_pyInstance,flagName)) fazekasgy@37: { fazekasgy@37: PyObject *pyValue = PyObject_GetAttrString(m_pyInstance,flagName); fazekasgy@37: if (!pyValue) fazekasgy@37: { fazekasgy@37: if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} fazekasgy@37: } else { fazekasgy@37: rValue = m_ti.PyValue_To_Bool(pyValue); fazekasgy@37: if (m_ti.error) { fazekasgy@37: Py_CLEAR(pyValue); fazekasgy@37: typeErrorHandler(flagName); fazekasgy@37: rValue = defValue; fazekasgy@37: } else Py_DECREF(pyValue); fazekasgy@37: } fazekasgy@37: } fazekasgy@37: if (m_debugFlag) cerr << FLAG_VALUE << endl; fazekasgy@37: return rValue; fazekasgy@37: } fazekasgy@37: fazekasgy@37: int fazekasgy@37: PyPlugin::getBinaryFlags(char flagName[], eVampyFlags defValue = vf_NULL) const fazekasgy@37: { fazekasgy@37: int rValue = defValue; fazekasgy@37: if (PyObject_HasAttrString(m_pyInstance,flagName)) fazekasgy@37: { fazekasgy@37: PyObject *pyValue = PyObject_GetAttrString(m_pyInstance,flagName); fazekasgy@37: if (!pyValue) fazekasgy@37: { fazekasgy@37: if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} fazekasgy@37: } else { fazekasgy@37: rValue |= (int) m_ti.PyValue_To_Size_t(pyValue); fazekasgy@37: if (m_ti.error) { fazekasgy@37: Py_CLEAR(pyValue); fazekasgy@37: typeErrorHandler(flagName); fazekasgy@37: rValue = defValue; fazekasgy@37: } else Py_DECREF(pyValue); fazekasgy@37: } fazekasgy@37: } fazekasgy@37: if (m_debugFlag) cerr << FLAG_VALUE << endl; fazekasgy@37: return rValue; fazekasgy@37: } fazekasgy@37: fazekasgy@37: fazekasgy@37: void fazekasgy@37: PyPlugin::setProcessType() fazekasgy@37: { fazekasgy@37: //quering process implementation type fazekasgy@37: char legacyMethod[]="process"; fazekasgy@37: char numpyMethod[]="processN"; fazekasgy@37: fazekasgy@37: if (PyObject_HasAttrString(m_pyInstance,legacyMethod) && fazekasgy@37: m_processType == 0) fazekasgy@37: { fazekasgy@37: m_processType = legacyProcess; fazekasgy@37: m_pyProcess = PyString_FromString(legacyMethod); fazekasgy@37: m_pyProcessCallable = PyObject_GetAttr(m_pyInstance,m_pyProcess); fazekasgy@37: } fazekasgy@37: fazekasgy@37: if (PyObject_HasAttrString(m_pyInstance,numpyMethod) && fazekasgy@37: m_processType == 0) fazekasgy@37: { fazekasgy@37: m_processType = numpy_bufferProcess; fazekasgy@37: m_pyProcess = PyString_FromString(numpyMethod); fazekasgy@37: m_pyProcessCallable = PyObject_GetAttr(m_pyInstance,m_pyProcess); fazekasgy@37: } fazekasgy@37: fazekasgy@37: // These flags are optional. If provided, they override the fazekasgy@37: // implementation type making the use of the odd processN() fazekasgy@37: // function redundant. fazekasgy@37: // However, the code above provides backward compatibility. fazekasgy@37: fazekasgy@37: if (m_vampyFlags & vf_BUFFER) { fazekasgy@37: m_processType = numpy_bufferProcess; fazekasgy@37: if (m_debugFlag) cerr << "Process using (numpy) buffer interface." << endl; fazekasgy@37: } fazekasgy@37: fazekasgy@37: if (m_vampyFlags & vf_ARRAY) { fazekasgy@37: #ifdef HAVE_NUMPY fazekasgy@37: m_processType = numpy_arrayProcess; fazekasgy@37: if (m_debugFlag) cerr << "Process using numpy array interface." << endl; fazekasgy@37: #else fazekasgy@37: cerr << "Error: This version of vampy was compiled without numpy support, " fazekasgy@37: << "however the vf_ARRAY flag is set for plugin: " << m_class << endl fazekasgy@37: << "The default behaviour is: passing a python list of samples for each channel in process() " fazekasgy@37: << "or a list of memory buffers in processN(). " << endl fazekasgy@37: << "This can be used create numpy arrays using the numpy.frombuffer() command." << endl; fazekasgy@37: #endif fazekasgy@37: } fazekasgy@37: fazekasgy@37: if (!m_processType) fazekasgy@37: { fazekasgy@37: m_processType = not_implemented; fazekasgy@37: m_pyProcess = NULL; fazekasgy@37: m_pyProcessCallable = NULL; fazekasgy@37: char method[]="initialise::setProcessType"; fazekasgy@37: cerr << PLUGIN_ERROR << " No process implementation found. Plugin will do nothing." << endl; fazekasgy@37: } fazekasgy@37: } fazekasgy@37: fazekasgy@37: void fazekasgy@37: PyPlugin::typeErrorHandler(char *method) const fazekasgy@37: { fazekasgy@37: bool strict = false; fazekasgy@37: while (m_ti.error) { fazekasgy@37: PyTypeInterface::ValueError e = m_ti.getError(); fazekasgy@37: cerr << PLUGIN_ERROR << e.str() << endl; fazekasgy@37: if (e.strict) strict = true; fazekasgy@37: // e.print(); fazekasgy@37: } fazekasgy@37: /// quit on hard errors like accessing NULL pointers or strict type conversion fazekasgy@37: /// errors IF the user sets the quitOnErrorFlag in the plugin. fazekasgy@37: /// Otherwise most errors will go unnoticed apart from fazekasgy@37: /// a messages in the terminal. fazekasgy@37: /// It would be best if hosts could catch an exception instead fazekasgy@37: /// and display something meaningful to the user. fazekasgy@37: if (strict && m_quitOnErrorFlag) exit(EXIT_FAILURE); fazekasgy@37: } fazekasgy@37: