view 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 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) :
	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);
}