cannam@18: /* -*- c-basic-offset: 8 indent-tabs-mode: t -*- */
fazekasgy@0: /*
fazekasgy@0:     Vamp
fazekasgy@0: 
fazekasgy@0:     An API for audio analysis and feature extraction plugins.
fazekasgy@0: 
fazekasgy@0:     Centre for Digital Music, Queen Mary, University of London.
fazekasgy@0:     Copyright 2006 Chris Cannam.
fazekasgy@0:   
fazekasgy@0:     Permission is hereby granted, free of charge, to any person
fazekasgy@0:     obtaining a copy of this software and associated documentation
fazekasgy@0:     files (the "Software"), to deal in the Software without
fazekasgy@0:     restriction, including without limitation the rights to use, copy,
fazekasgy@0:     modify, merge, publish, distribute, sublicense, and/or sell copies
fazekasgy@0:     of the Software, and to permit persons to whom the Software is
fazekasgy@0:     furnished to do so, subject to the following conditions:
fazekasgy@0: 
fazekasgy@0:     The above copyright notice and this permission notice shall be
fazekasgy@0:     included in all copies or substantial portions of the Software.
fazekasgy@0: 
fazekasgy@0:     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
fazekasgy@0:     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
fazekasgy@0:     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
fazekasgy@0:     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
fazekasgy@0:     ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
fazekasgy@0:     CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
fazekasgy@0:     WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
fazekasgy@0: 
fazekasgy@0:     Except as contained in this notice, the names of the Centre for
fazekasgy@0:     Digital Music; Queen Mary, University of London; and Chris Cannam
fazekasgy@0:     shall not be used in advertising or otherwise to promote the sale,
fazekasgy@0:     use or other dealings in this Software without prior written
fazekasgy@0:     authorization.
fazekasgy@0: */
fazekasgy@0: 
fazekasgy@0: #ifndef _PYTHON_WRAPPER_PLUGIN_H_
fazekasgy@0: #define _PYTHON_WRAPPER_PLUGIN_H_
fazekasgy@0: 
fazekasgy@31: #define _CLASS_METHOD_ m_class << "::" << method
fazekasgy@31: #define PLUGIN_ERROR "ERROR: In Vampy plugin [" << _CLASS_METHOD_ << "]" << endl << "Cause: "
fazekasgy@31: #define DEBUG_NAME "[Vampy::call] " << _CLASS_METHOD_ << " "
fazekasgy@31: #define DEAFULT_RETURN "Method [" << _CLASS_METHOD_ << "] is not implemented." << endl << "Returning default value: " << rValue
fazekasgy@31: #define FLAG_VALUE "Flag: " << flagName << ": " << ((rValue==0)?"False":"True")
fazekasgy@31: 
fazekasgy@0: #include "vamp-sdk/Plugin.h"
cannam@3: #include <Python.h>
fazekasgy@31: // #include <typeinfo>
fazekasgy@31: // #include <stdarg.h>
fazekasgy@31: #include "PyTypeInterface.h"
cannam@3: 
cannam@3: #include "Mutex.h"
fazekasgy@0: 
fazekasgy@31: using std::string;
fazekasgy@31: using std::cerr;
fazekasgy@31: using std::endl;
fazekasgy@0: 
fazekasgy@6: enum eProcessType {
fazekasgy@6: 	not_implemented,
fazekasgy@6: 	legacyProcess,
fazekasgy@6: 	numpyProcess
fazekasgy@6: 	};
fazekasgy@0: 
fazekasgy@0: class PyPlugin : public Vamp::Plugin
fazekasgy@0: {
fazekasgy@0: public:
fazekasgy@31: 	PyPlugin(std::string plugin,float inputSampleRate, PyObject *pyClass, int &instcount);
cannam@18: 	virtual ~PyPlugin();
fazekasgy@0: 
cannam@18: 	bool initialise(size_t channels, size_t stepSize, size_t blockSize);
cannam@18: 	void reset();
fazekasgy@6: 
fazekasgy@0: 	InputDomain getInputDomain() const;
fazekasgy@0: 	size_t getPreferredBlockSize() const;
fazekasgy@0: 	size_t getPreferredStepSize() const; 
fazekasgy@0: 	size_t getMinChannelCount() const; 
fazekasgy@0: 	size_t getMaxChannelCount() const;
fazekasgy@0: 
cannam@18: 	std::string getIdentifier() const;
cannam@18: 	std::string getName() const;
cannam@18: 	std::string getDescription() const;
cannam@18: 	std::string getMaker() const;
cannam@18: 	int getPluginVersion() const;
cannam@18: 	std::string getCopyright() const;
cannam@18: 	
cannam@18: 	OutputList getOutputDescriptors() const;
cannam@18: 	ParameterList getParameterDescriptors() const;
fazekasgy@0: 	float getParameter(std::string paramid) const;
fazekasgy@0: 	void setParameter(std::string paramid, float newval);
fazekasgy@0:     
cannam@18: 	FeatureSet process(const float *const *inputBuffers,
cannam@18: 			   Vamp::RealTime timestamp);
fazekasgy@0: 
cannam@18: 	FeatureSet getRemainingFeatures();
fazekasgy@31: 	
fazekasgy@0: protected:
fazekasgy@31: 	static Mutex m_pythonInterpreterMutex;
cannam@24: 	PyObject *m_pyClass;
fazekasgy@0: 	PyObject *m_pyInstance;
fazekasgy@31: 	int &m_instcount;
cannam@18: 	size_t m_stepSize;
cannam@18: 	size_t m_blockSize;
cannam@18: 	size_t m_channels;
fazekasgy@0: 	std::string m_plugin;
fazekasgy@0: 	std::string m_class;
fazekasgy@0: 	std::string m_path;
fazekasgy@6: 	int m_processType;
fazekasgy@6: 	PyObject *m_pyProcess;
fazekasgy@6: 	InputDomain m_inputDomain;
fazekasgy@31: 	PyTypeInterface m_ti;
fazekasgy@31: 	bool m_quitOnErrorFlag;
fazekasgy@31: 	bool m_debugFlag;
fazekasgy@31: 
fazekasgy@31: 	void setProcessType();
fazekasgy@6: 	
fazekasgy@31: 	PyObject* numpyProcessCall(const float *const *inputBuffers, Vamp::RealTime timestamp);
fazekasgy@31: 	PyObject* legacyProcessCall(const float *const *inputBuffers, Vamp::RealTime timestamp);
fazekasgy@31: 	
fazekasgy@31: 	bool getBooleanFlag(char flagName[],bool) const;
fazekasgy@31: /*
fazekasgy@31: 		Flags may be used to control the behaviour of the interface.
fazekasgy@31: 		Flags can be set in any Vampy plugin's __init__() function.
fazekasgy@31: 		Their scope is limited to an instance.
fazekasgy@31: 		Default values for all flags are False.
fazekasgy@31: 		Python Example:
fazekasgy@31: 		def __init__(self,inputSampleRate):
fazekasgy@31: 			self.use_strict_type_conversion = True
fazekasgy@31: 			self.vampy_debug_messages = True
fazekasgy@31: 			self.use_realtime_timestamp = False
fazekasgy@31: 			self.use_numpy_interface = False
fazekasgy@31: 			self.quit_on_type_error = False
fazekasgy@31: 			
fazekasgy@31: */
fazekasgy@31: 	
fazekasgy@31: 	void genericMethodCall(char *method) const
fazekasgy@31: 	{
fazekasgy@31: 		if (m_debugFlag) cerr << DEBUG_NAME << endl;
fazekasgy@31: 		if ( PyObject_HasAttrString(m_pyInstance,method) ) 
fazekasgy@31: 		{
fazekasgy@31: 			PyObject *pyValue = PyObject_CallMethod(m_pyInstance, method, NULL);
fazekasgy@31: 			if (!pyValue) {
fazekasgy@31: 				cerr << PLUGIN_ERROR << "Failed to call method." << endl;
fazekasgy@31: 				if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();}
fazekasgy@31: 			}
fazekasgy@31: 		}
fazekasgy@31: 	}
fazekasgy@31: 	
fazekasgy@31: 	template<typename RET> 
fazekasgy@31: 	RET &genericMethodCall(char *method, RET &rValue) const
fazekasgy@31: 	{
fazekasgy@31: 		if (m_debugFlag) cerr << DEBUG_NAME << endl;
fazekasgy@31: 		if ( PyObject_HasAttrString(m_pyInstance,method) ) 
fazekasgy@31: 		{
fazekasgy@31: 			PyObject *pyValue = PyObject_CallMethod(m_pyInstance, method, NULL);
fazekasgy@31: 			if (pyValue) {
fazekasgy@31: 				m_ti.PyValue_To_rValue(pyValue,rValue);
fazekasgy@31: 				if (!m_ti.error) {
fazekasgy@31: 					Py_DECREF(pyValue);
fazekasgy@31: 					return rValue;
fazekasgy@31: 				} else {
fazekasgy@31: 					cerr << PLUGIN_ERROR << m_ti.lastError().message << endl;
fazekasgy@31: 					Py_CLEAR(pyValue);
fazekasgy@31: 					if (m_quitOnErrorFlag) exit(EXIT_FAILURE);
fazekasgy@31: 					return rValue;
fazekasgy@31: 				}
fazekasgy@31: 			} else {
fazekasgy@31: 				cerr << PLUGIN_ERROR << "Failed to call method." << endl;
fazekasgy@31: 				if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();}
fazekasgy@31: 				return rValue;
fazekasgy@31: 			}
fazekasgy@31: 		}
fazekasgy@31: 		// TODO: this fails to generalise because the << operator
fazekasgy@31: 		// doesn't accept all types.
fazekasgy@31: 		// if (m_debugFlag) cerr << DEAFULT_RETURN << endl;
fazekasgy@31: 		return rValue;
fazekasgy@31: 	}
cannam@3: 
fazekasgy@31: 	template<typename RET,typename A1>
fazekasgy@31: 	RET genericMethodCallArgs(char *method, A1 arg1) const
fazekasgy@31: 	{
fazekasgy@31: 		RET rValue = RET();
fazekasgy@31: 		if (m_debugFlag) cerr << DEBUG_NAME << endl;
fazekasgy@31: 		if (!PyObject_HasAttrString(m_pyInstance,method)) {
fazekasgy@31: 			// if (m_debugFlag) cerr << DEAFULT_RETURN << endl;
fazekasgy@31: 			return rValue;
fazekasgy@31: 		}
fazekasgy@31: 		
fazekasgy@31: 		// These functions always return valid PyObjects 
fazekasgy@31: 		PyObject *pyMethod = m_ti.PyValue_From_CValue(method);
fazekasgy@31: 		PyObject* pyTuple = PyTuple_New(3);
fazekasgy@31: 		if (!pyTuple) return rValue;
fazekasgy@31: 		
fazekasgy@31: 		PyObject *pyArg1 = m_ti.PyValue_From_CValue(arg1);
fazekasgy@31: 				
fazekasgy@31: 		PyObject *pyValue = PyObject_CallMethodObjArgs(m_pyInstance,pyMethod,pyArg1,NULL);
fazekasgy@31: 		if (!pyValue) 
fazekasgy@31: 		{
fazekasgy@31: 			cerr << PLUGIN_ERROR << "Failed to call method." << endl;
fazekasgy@31: 			if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();}
fazekasgy@31: 		}
fazekasgy@31: 			
fazekasgy@31: 		Py_DECREF(pyMethod);
fazekasgy@31: 		Py_DECREF(pyArg1);
fazekasgy@31: 
fazekasgy@31: 		m_ti.PyValue_To_rValue(pyValue,rValue);
fazekasgy@31: 		if (!m_ti.error) {
fazekasgy@31: 			Py_DECREF(pyValue);
fazekasgy@31: 		} else {
fazekasgy@31: 			cerr << PLUGIN_ERROR << m_ti.lastError().message << endl;
fazekasgy@31: 			Py_CLEAR(pyValue);
fazekasgy@31: 			if (m_quitOnErrorFlag) exit(EXIT_FAILURE);
fazekasgy@31: 		}
fazekasgy@31: 		return rValue;
fazekasgy@31: 	}
fazekasgy@31: 
fazekasgy@31: 	template<typename RET,typename A1,typename A2>
fazekasgy@31: 	RET genericMethodCallArgs(char *method, A1 arg1, A2 arg2)
fazekasgy@31: 	{
fazekasgy@31: 		RET rValue = RET();
fazekasgy@31: 		if (m_debugFlag) cerr << DEBUG_NAME << endl;
fazekasgy@31: 		if (!PyObject_HasAttrString(m_pyInstance,method)) {
fazekasgy@31: 			// if (m_debugFlag) cerr << DEAFULT_RETURN << endl;
fazekasgy@31: 			return rValue;
fazekasgy@31: 		}
fazekasgy@31: 		
fazekasgy@31: 		// These functions always return valid PyObjects 
fazekasgy@31: 		PyObject *pyMethod = m_ti.PyValue_From_CValue(method);
fazekasgy@31: 		PyObject* pyTuple = PyTuple_New(3);
fazekasgy@31: 		if (!pyTuple) return rValue;
fazekasgy@31: 		
fazekasgy@31: 		PyObject *pyArg1 = m_ti.PyValue_From_CValue(arg1);
fazekasgy@31: 		PyObject *pyArg2 = m_ti.PyValue_From_CValue(arg2);
fazekasgy@31: 				
fazekasgy@31: 		PyObject *pyValue = PyObject_CallMethodObjArgs(m_pyInstance,pyMethod,pyArg1,pyArg2,NULL);
fazekasgy@31: 		if (!pyValue) 
fazekasgy@31: 		{
fazekasgy@31: 			cerr << PLUGIN_ERROR << "Failed to call method." << endl;
fazekasgy@31: 			if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();}
fazekasgy@31: 		}
fazekasgy@31: 			
fazekasgy@31: 		Py_DECREF(pyMethod);
fazekasgy@31: 		Py_DECREF(pyArg1);
fazekasgy@31: 		Py_DECREF(pyArg2);
fazekasgy@31: 
fazekasgy@31: 		m_ti.PyValue_To_rValue(pyValue,rValue);
fazekasgy@31: 		if (!m_ti.error) {
fazekasgy@31: 			Py_DECREF(pyValue);
fazekasgy@31: 		} else {
fazekasgy@31: 			cerr << PLUGIN_ERROR << m_ti.lastError().message << endl;
fazekasgy@31: 			Py_CLEAR(pyValue);
fazekasgy@31: 			if (m_quitOnErrorFlag) exit(EXIT_FAILURE);
fazekasgy@31: 		}
fazekasgy@31: 		return rValue;
fazekasgy@31: 	}
fazekasgy@31: 
fazekasgy@31: 	
fazekasgy@31: 	template<typename RET,typename A1,typename A2,typename A3>
fazekasgy@31: 	RET genericMethodCallArgs(char *method, A1 arg1, A2 arg2, A3 arg3)
fazekasgy@31: 	{
fazekasgy@31: 		RET rValue = RET();
fazekasgy@31: 		if (m_debugFlag) cerr << DEBUG_NAME << endl;
fazekasgy@31: 		if (!PyObject_HasAttrString(m_pyInstance,method)) {
fazekasgy@31: 			if (m_debugFlag) cerr << DEAFULT_RETURN << endl;
fazekasgy@31: 			return rValue;
fazekasgy@31: 		}
fazekasgy@31: 		
fazekasgy@31: 		// These functions always return valid PyObjects 
fazekasgy@31: 		PyObject *pyMethod = m_ti.PyValue_From_CValue(method);
fazekasgy@31: 		PyObject* pyTuple = PyTuple_New(3);
fazekasgy@31: 		if (!pyTuple) return rValue;
fazekasgy@31: 		
fazekasgy@31: 		PyObject *pyArg1 = m_ti.PyValue_From_CValue(arg1);
fazekasgy@31: 		PyObject *pyArg2 = m_ti.PyValue_From_CValue(arg2);
fazekasgy@31: 		PyObject *pyArg3 = m_ti.PyValue_From_CValue(arg3);
fazekasgy@31: 		
fazekasgy@31: 		// TODO: Pack it in a tuple to avoid va_list parsing!
fazekasgy@31: 		
fazekasgy@31: 		// callable = PyObject_GetAttr(callable, name);
fazekasgy@31: 		// if (callable == NULL)
fazekasgy@31: 		// 	return NULL;
fazekasgy@31: 		// PyObject* args; // pyTuple of input arguments
fazekasgy@31: 		//tmp = PyObject_Call(callable, args, NULL);
fazekasgy@31: 		
fazekasgy@31: 		
fazekasgy@31: 		PyObject *pyValue = PyObject_CallMethodObjArgs(m_pyInstance,pyMethod,pyArg1,pyArg2,pyArg3,NULL);
fazekasgy@31: 		if (!pyValue) 
fazekasgy@31: 		{
fazekasgy@31: 			cerr << PLUGIN_ERROR << "Failed to call method." << endl;
fazekasgy@31: 			if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();}
fazekasgy@31: 		}
fazekasgy@31: 			
fazekasgy@31: 		Py_DECREF(pyMethod);
fazekasgy@31: 		Py_DECREF(pyArg1);
fazekasgy@31: 		Py_DECREF(pyArg2);
fazekasgy@31: 		Py_DECREF(pyArg3);
fazekasgy@31: 
fazekasgy@31: 		m_ti.PyValue_To_rValue(pyValue,rValue);
fazekasgy@31: 		if (!m_ti.error) {
fazekasgy@31: 			Py_DECREF(pyValue);
fazekasgy@31: 		} else {
fazekasgy@31: 			cerr << PLUGIN_ERROR << m_ti.lastError().message << endl;
fazekasgy@31: 			Py_CLEAR(pyValue);
fazekasgy@31: 			if (m_quitOnErrorFlag) exit(EXIT_FAILURE);
fazekasgy@31: 		}
fazekasgy@31: 		return rValue;
fazekasgy@31: 	}
fazekasgy@31: 
fazekasgy@0: };
fazekasgy@0: 
fazekasgy@0: 
fazekasgy@0: #endif