view PyPlugin.h @ 120:a38d318c85a9 tip

MSVC fixes
author Chris Cannam
date Wed, 18 Dec 2019 16:51:20 +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