view PyPlugin.cpp @ 3:134313c59d82

* Add global mutex to PyPlugin -- all plugin method calls are strictly serialised in order to avoid problems with Python interpreter's lack of thread safety.
author cannam
date Fri, 14 Mar 2008 12:02:15 +0000
parents e20e214bdfb5
children e1b508f2f914
line wrap: on
line source
/*
    Vamp

    An API for audio analysis and feature extraction plugins.

    Centre for Digital Music, Queen Mary, University of London.
    Copyright 2006 Chris Cannam.
  
    Permission is hereby granted, free of charge, to any person
    obtaining a copy of this software and associated documentation
    files (the "Software"), to deal in the Software without
    restriction, including without limitation the rights to use, copy,
    modify, merge, publish, distribute, sublicense, and/or sell copies
    of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be
    included in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

    Except as contained in this notice, the names of the Centre for
    Digital Music; Queen Mary, University of London; and Chris Cannam
    shall not be used in advertising or otherwise to promote the sale,
    use or other dealings in this Software without prior written
    authorization.
*/



/**
 * This VAMP plugin is a wrapper for Python Scripts. (vampy)
 * Centre for Digital Music, Queen Mary, University of London.
 * Copyright 2008, George Fazekas.

TODO:	needs more complete error checking 
	  	needs correct implementation of Python threading
	  	more efficient data conversion using the buffering interface or ctypes
	 	VAMP programs not implemented
		support multiple plugins per script in scanner
		ensure proper cleanup, host do a good job though

*/

#include <Python.h>
#include "PyPlugin.h"

#ifdef _WIN32
#define pathsep ('\\')
#else 
#define pathsep ('/')
#endif

#define _DEBUG

using std::string;
using std::vector;
using std::cerr;
using std::endl;
using std::map;

// Maps to associate strings with enum values
static std::map<std::string, eOutDescriptors> outKeys;
static std::map<std::string, eSampleTypes> sampleKeys;
static std::map<std::string, eFeatureFields> ffKeys;
static std::map<std::string, p::eParmDescriptors> parmKeys;

Mutex PyPlugin::m_pythonInterpreterMutex;

void initMaps()
{
	outKeys["identifier"] = identifier;
	outKeys["name"] = name;
	outKeys["description"] = description;
	outKeys["unit"] = unit;
	outKeys["hasFixedBinCount"] = hasFixedBinCount; 
	outKeys["binCount"] = binCount;
	outKeys["binNames"] = binNames;
	outKeys["hasKnownExtents"] = hasKnownExtents;
	outKeys["minValue"] = minValue;
	outKeys["maxValue"] = maxValue;
	outKeys["isQuantized"] = isQuantized;
	outKeys["quantizeStep"] = quantizeStep;
	outKeys["sampleType"] = sampleType;
	outKeys["sampleRate"] = sampleRate;

	sampleKeys["OneSamplePerStep"] = OneSamplePerStep;
	sampleKeys["FixedSampleRate"] = FixedSampleRate;
	sampleKeys["VariableSampleRate"] = VariableSampleRate;

	ffKeys["hasTimestamp"] = hasTimestamp;
	ffKeys["timeStamp"] = timeStamp;
	ffKeys["values"] = values;
	ffKeys["label"] = label;

	parmKeys["identifier"] = p::identifier;
	parmKeys["name"] = p::name;
	parmKeys["description"] = p::description;
	parmKeys["unit"] = p::unit;
	parmKeys["minValue"] = p::minValue;
	parmKeys["maxValue"] = p::maxValue;
	parmKeys["defaultValue"] = p::defaultValue;
	parmKeys["isQuantized"] = p::isQuantized;

}


//missing API helper: convert Python list to C++ vector of strings
//TODO: these could be templates if we need more of this kind
std::vector<std::string> PyList_To_StringVector (PyObject *inputList) {
	
	std::vector<std::string> Output;
	std::string ListElement;
	PyObject *pyString = NULL;
	
	if (!PyList_Check(inputList)) return Output;

	for (Py_ssize_t i = 0; i < PyList_GET_SIZE(inputList); ++i) {
		//Get next list item (Borrowed Reference)
		pyString = PyList_GET_ITEM(inputList,i);
		ListElement = (string) PyString_AsString(PyObject_Str(pyString));
		Output.push_back(ListElement);
	}
	return Output;
}

//missing API helper: convert Python list to C++ vector of floats
std::vector<float> PyList_As_FloatVector (PyObject *inputList) {
	
	std::vector<float> Output;
	float ListElement;
	PyObject *pyFloat = NULL;
	
	if (!PyList_Check(inputList)) return Output; 

	for (Py_ssize_t k = 0; k < PyList_GET_SIZE(inputList); ++k) {
		//Get next list item (Borrowed Reference)
		pyFloat =  PyList_GET_ITEM(inputList,k);
		ListElement = (float) PyFloat_AS_DOUBLE(pyFloat);
		Output.push_back(ListElement);
	}

	return Output;
}

/* TODO: find out why this produces error, also 
		do sg more clever about handling RealTime
Vamp::RealTime 
PyFrame_As_RealTime (PyObject *frameNo,size_t inputSampleRate) {
Vamp::RealTime result =  
Vamp::RealTime::frame2RealTime((size_t)PyInt_AS_LONG(frameNo), inputSampleRate);
return result;
}
*/


PyPlugin::PyPlugin(std::string pluginKey,float inputSampleRate, PyObject *pyInstance) :
    Plugin(inputSampleRate),
	m_pyInstance(pyInstance),
	m_stepSize(0),
    m_previousSample(0.0f),
	m_plugin(pluginKey),
	m_class(pluginKey.substr(pluginKey.rfind(':')+1,pluginKey.size()-1)),
	m_path((pluginKey.substr(0,pluginKey.rfind(pathsep))))
{	
}

PyPlugin::~PyPlugin()
{
	cerr << "PyPlugin::PyPlugin:" << m_class 
	<< " Instance deleted." << endl;
}


string
PyPlugin::getIdentifier() const
{	
	MutexLocker locker(&m_pythonInterpreterMutex);

	char method[]="getIdentifier"; 
	cerr << "[call] " << method << endl;
	string rString="VampPy-x";

	if ( PyObject_HasAttrString(m_pyInstance,method) ) {

		//Call the method
		PyObject *pyString = 
		PyObject_CallMethod(m_pyInstance, method, NULL);

		//Check return value
		if (!PyString_Check(pyString)) {
			Py_CLEAR(pyString);
			cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
			<< "] Expected String return value." << endl;
			return rString;
		}

		rString=PyString_AsString(pyString);
		Py_CLEAR(pyString);
	}
	cerr << "Warning: Plugin must return a unique identifier." << endl;
	return rString;
}


string
PyPlugin::getName() const
{
	MutexLocker locker(&m_pythonInterpreterMutex);

	char method[]="getName";
	cerr << "[call] " << method << endl;
	string rString="VamPy Plugin (Noname)";
	
	if ( PyObject_HasAttrString(m_pyInstance,method) ) {

		//Call the method
		PyObject *pyString = 
		PyObject_CallMethod(m_pyInstance, method, NULL);

		//Check return value
		if (!PyString_Check(pyString)) {
			Py_CLEAR(pyString);
			cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
			<< "] Expected String return value." << endl;
			return rString;
		}

		rString=PyString_AsString(pyString);
		Py_CLEAR(pyString);
	}
    return rString;
}

string
PyPlugin::getDescription() const
{
	MutexLocker locker(&m_pythonInterpreterMutex);

	char method[]="getDescription";
	cerr << "[call] " << method << endl;
	string rString="Not given. (Hint: Implement getDescription method.)";
	
	if ( PyObject_HasAttrString(m_pyInstance,method) ) {

		//Call the method
		PyObject *pyString = 
		PyObject_CallMethod(m_pyInstance, method, NULL);

		//Check return value
		if (!PyString_Check(pyString)) {
			Py_CLEAR(pyString);
			cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
			<< "] Expected String return value." << endl;
			return rString;
		}

		rString=PyString_AsString(pyString);
		Py_CLEAR(pyString);
	}
	return rString;
}

string
PyPlugin::getMaker() const
{
	MutexLocker locker(&m_pythonInterpreterMutex);

	char method[]="getMaker";
	cerr << "[call] " << method << endl;
	string rString="Generic VamPy Plugin.";
	
	if ( PyObject_HasAttrString(m_pyInstance,method) ) {

		//Call the method
		PyObject *pyString = 
		PyObject_CallMethod(m_pyInstance, method, NULL);

		//Check return value
		if (!PyString_Check(pyString)) {
			Py_CLEAR(pyString);
			cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
			<< "] Expected String return value." << endl;
			return rString;
		}

		rString=PyString_AsString(pyString);
		Py_CLEAR(pyString);
	}
    return rString;
}

int
PyPlugin::getPluginVersion() const
{
    return 2;
}

string
PyPlugin::getCopyright() const
{
	MutexLocker locker(&m_pythonInterpreterMutex);

	char method[]="getCopyright";
	cerr << "[call] " << method << endl;
	string rString="BSD License";
	
	if ( PyObject_HasAttrString(m_pyInstance,method) ) {

		//Call the method
		PyObject *pyString = 
		PyObject_CallMethod(m_pyInstance, method, NULL);

		//Check return value
		if (!PyString_Check(pyString)) {
			Py_CLEAR(pyString);
			cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
			<< "] Expected String return value." << endl;
			return rString;
		}

		
		rString=PyString_AsString(pyString);
		Py_CLEAR(pyString);
	}
    return rString;
}


bool
PyPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize)
{
	MutexLocker locker(&m_pythonInterpreterMutex);

    if (channels < getMinChannelCount() ||
	channels > getMaxChannelCount()) return false;

    m_stepSize = std::min(stepSize, blockSize);
	char method[]="initialise";
	cerr << "[call] " << method << endl;

		//Check if the method is implemented in Python else return false
		if (PyObject_HasAttrString(m_pyInstance,method)) {
   			
			PyObject *pyMethod = PyString_FromString(method);
			PyObject *pyChannels = PyInt_FromSsize_t((Py_ssize_t)channels);
			PyObject *pyStepSize = PyInt_FromSsize_t((Py_ssize_t)m_stepSize);
			PyObject *pyBlockSize = PyInt_FromSsize_t((Py_ssize_t)blockSize);
			PyObject *pyInputSampleRate = PyFloat_FromDouble((double)m_inputSampleRate);
			
			//Call the method
			PyObject *pyBool = 
			PyObject_CallMethodObjArgs(m_pyInstance,pyMethod,pyChannels,pyStepSize,pyBlockSize,pyInputSampleRate,NULL);
			
			Py_DECREF(pyMethod);
			Py_DECREF(pyChannels);
			Py_DECREF(pyStepSize);
			Py_DECREF(pyBlockSize);
			Py_DECREF(pyInputSampleRate);

			//Check return value
			if (!PyBool_Check(pyBool)) {
				Py_CLEAR(pyBool);
				cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
				<< "] Expected Bool return value." << endl;
				return false;
			}

			if (pyBool == Py_True) {  
				Py_CLEAR(pyBool); 
				return true;
			} else {
				Py_CLEAR(pyBool); 
				return false;}								
		} 
    	return true;
}

void
PyPlugin::reset()
{
	//!!! implement this!
    m_previousSample = 0.0f;
}

PyPlugin::InputDomain PyPlugin::getInputDomain() const 
{ 
	MutexLocker locker(&m_pythonInterpreterMutex);

	char method[]="getInputDomain";
	cerr << "[call] " << method << endl;
	PyPlugin::InputDomain rValue = TimeDomain; // TimeDomain
	
	if ( PyObject_HasAttrString(m_pyInstance,method) ) {

		PyObject *pyString = PyObject_CallMethod(m_pyInstance, method, NULL);
		
		//Check return value
		if (!PyString_Check(pyString)) {
			Py_CLEAR(pyString);
			cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
			<< "] Expected String return value." << endl;
			return rValue;
		}
		
		string domain = (string) PyString_AsString(pyString);
		if (domain == "FrequencyDomain") rValue = FrequencyDomain;
		Py_CLEAR(pyString);
	}
    return rValue; 
}

size_t PyPlugin::getPreferredBlockSize() const 
{ 
	MutexLocker locker(&m_pythonInterpreterMutex);

	char method[]="getPreferredBlockSize";
	cerr << "[call] " << method << endl;
	size_t rValue=0; //not set by default
	if ( PyObject_HasAttrString(m_pyInstance,method) ) {
		PyObject *pyInt = PyObject_CallMethod(m_pyInstance, method, NULL);
		
		//Check return value
		if (!PyInt_Check(pyInt)) {
			Py_CLEAR(pyInt);
			cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
			<< "] Expected Integer return value." << endl;
			return rValue;
		}
		
		rValue=(size_t)PyInt_AS_LONG(pyInt);
		Py_CLEAR(pyInt);
	}
    return rValue; 
}

//size_t PyPlugin::getPreferredStepSize() const { return 0; }
size_t PyPlugin::getPreferredStepSize() const 
{ 
	MutexLocker locker(&m_pythonInterpreterMutex);

	char method[]="getPreferredStepSize";
	cerr << "[call] " << method << endl;
	size_t rValue=0; //not set by default
	if ( PyObject_HasAttrString(m_pyInstance,method) ) {
		PyObject *pyInt = PyObject_CallMethod(m_pyInstance, method, NULL);
		
		//Check return value
		if (!PyInt_Check(pyInt)) {
			Py_CLEAR(pyInt);
			cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
			<< "] Expected Integer return value." << endl;
			return rValue;
		}
		
		rValue=(size_t)PyInt_AS_LONG(pyInt);
		Py_CLEAR(pyInt);
	}
    return rValue; 
}

size_t PyPlugin::getMinChannelCount() const 
{ 
	MutexLocker locker(&m_pythonInterpreterMutex);

	char method[]="getMinChannelCount";
	cerr << "[call] " << method << endl;
	size_t rValue=1; //default value
	if ( PyObject_HasAttrString(m_pyInstance,method) ) {
		PyObject *pyInt = PyObject_CallMethod(m_pyInstance, method, NULL);

		//Check return value
		if (!PyInt_Check(pyInt)) {
			Py_CLEAR(pyInt);
			cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
			<< "] Expected String return value." << endl;
			return rValue;
		}

		rValue=(size_t)PyInt_AS_LONG(pyInt);
		Py_CLEAR(pyInt);
	}
    return rValue; 
}

size_t PyPlugin::getMaxChannelCount() const 
{ 
	MutexLocker locker(&m_pythonInterpreterMutex);

	char method[]="getMaxChannelCount";	
	cerr << "[call] " << method << endl;
	size_t rValue=1; //default value
	if ( PyObject_HasAttrString(m_pyInstance,method) ) {
		PyObject *pyInt = PyObject_CallMethod(m_pyInstance, method, NULL);

		//Check return value
		if (!PyInt_Check(pyInt)) {
			Py_CLEAR(pyInt);
			cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
			<< "] Expected String return value." << endl;
			return rValue;
		}

		rValue=(size_t)PyInt_AS_LONG(pyInt);
		Py_CLEAR(pyInt);
	}
    return rValue; 
}


PyPlugin::OutputList
PyPlugin::getOutputDescriptors() const
{
	MutexLocker locker(&m_pythonInterpreterMutex);

	//PyEval_AcquireThread(newThreadState);
	OutputList list;
	OutputDescriptor od;
	char method[]="getOutputDescriptors";
	cerr << "[call] " << method << endl;

	//Check if the method is implemented in Python
	if ( PyObject_HasAttrString(m_pyInstance,method) ) {
			
		//Call the method: must return list object (new reference)
		PyObject *pyList = 
		PyObject_CallMethod(m_pyInstance,method, NULL);

		//Check return type
		if (! PyList_Check(pyList) ) {
			Py_CLEAR(pyList);
			cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
			<< "] Expected List return type." << endl;
			return list;
		}

		//These will all be borrowed references (no need to DECREF)
		PyObject *pyDict, *pyKey, *pyValue;
		
		//Parse Output List
		for (Py_ssize_t i = 0; i < PyList_GET_SIZE(pyList); ++i) {
	
			//Get i-th VAMP output descriptor (Borrowed Reference)
			pyDict = PyList_GET_ITEM(pyList,i);
			
			//We only care about dictionaries holding output descriptors
			if ( !PyDict_Check(pyDict) ) continue;
			
			Py_ssize_t pyPos = NULL;
			initMaps();

			//Python Sequence Iterator
			while (PyDict_Next(pyDict, &pyPos, &pyKey, &pyValue)) 
			{		
				switch (outKeys[PyString_AsString(pyKey)]) 
				{
					case not_found : 	
						cerr << "Unknown key in VAMP OutputDescriptor: " << PyString_AsString(pyKey) << endl; 
						break;
					case identifier: 	
						od.identifier = PyString_AsString(pyValue); 
						break;				
					case name: 			
						od.name = PyString_AsString(pyValue); 
						break;
					case description: 	
						od.description = PyString_AsString(pyValue); 
						break; 								
					case unit: 			
						od.unit = PyString_AsString(pyValue); 
						break; 													
					case hasFixedBinCount:
						od.hasFixedBinCount = (bool) PyInt_AS_LONG(pyValue); 
						break;
					case binCount:
						od.binCount = (size_t) PyInt_AS_LONG(pyValue);
						break;
					case binNames:
						od.binNames = PyList_To_StringVector(pyValue);
						break;
					case hasKnownExtents:
						od.hasKnownExtents = (bool) PyInt_AS_LONG(pyValue); 
						break;					
					case minValue:
						od.minValue = (float) PyFloat_AS_DOUBLE(pyValue);
						break;
					case maxValue:
						od.maxValue = (float) PyFloat_AS_DOUBLE(pyValue);
						break;
					case isQuantized:
						od.isQuantized = (bool) PyInt_AS_LONG(pyValue); 
						break;					
					case quantizeStep:
						od.quantizeStep = (float) PyFloat_AS_DOUBLE(pyValue);
						break;
					case sampleType: 				
						od.sampleType = (OutputDescriptor::SampleType) sampleKeys[PyString_AsString(pyValue)];
						break;
					case sampleRate:
						od.sampleRate = (float) PyFloat_AS_DOUBLE(pyValue);
						break;					
					default : 	
						cerr << "Invalid key in VAMP OutputDescriptor: " << PyString_AsString(pyKey) << endl; 
				} // switch					
			} // while dict keys
			list.push_back(od);
		} // for each memeber in outputlist
		Py_CLEAR(pyList);
	} // if method is implemented
    //PyEval_ReleaseThread(newThreadState);
	return list;
}

PyPlugin::ParameterList
PyPlugin::getParameterDescriptors() const
{
	MutexLocker locker(&m_pythonInterpreterMutex);

	ParameterList list;
	ParameterDescriptor pd;
	char method[]="getParameterDescriptors";
	cerr << "[call] " << method << endl;

	//Check if the method is implemented in Python
	if ( PyObject_HasAttrString(m_pyInstance,method) ) {
			
		//Call the method: must return list object (new reference)
		PyObject *pyList = 
		PyObject_CallMethod(m_pyInstance,method, NULL);

		//Check return type
		if (! PyList_Check(pyList) ) {
			Py_CLEAR(pyList);
			cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
			<< "] Expected List return type." << endl;
			return list;
		}


		//These will all be borrowed references (no need to DECREF)
		PyObject *pyDict, *pyKey, *pyValue;
		
		//Parse Output List
		for (Py_ssize_t i = 0; i < PyList_GET_SIZE(pyList); ++i) {
	
			//Get i-th VAMP output descriptor (Borrowed Reference)
			pyDict = PyList_GET_ITEM(pyList,i);
			
			//We only care about dictionaries holding output descriptors
			if ( !PyDict_Check(pyDict) ) continue;
			
			Py_ssize_t pyPos = NULL;
			initMaps();

			//Python Sequence Iterator
			while (PyDict_Next(pyDict, &pyPos, &pyKey, &pyValue)) 
			{		
				switch (parmKeys[PyString_AsString(pyKey)]) 
				{
					case not_found : 	
						cerr << "Unknown key in VAMP OutputDescriptor: " << PyString_AsString(pyKey) << endl; 
						break;
					case p::identifier: 	
						pd.identifier = PyString_AsString(pyValue); 
						break;				
					case p::name: 			
						pd.name = PyString_AsString(pyValue); 
						break;
					case p::description: 	
						pd.description = PyString_AsString(pyValue); 
						break; 								
					case p::unit: 			
						pd.unit = PyString_AsString(pyValue); 
						break; 																		
					case p::minValue:
						pd.minValue = (float) PyFloat_AS_DOUBLE(pyValue);
						break;
					case p::maxValue:
						pd.maxValue = (float) PyFloat_AS_DOUBLE(pyValue);
						break;
					case p::defaultValue:
						pd.defaultValue = (float) PyFloat_AS_DOUBLE(pyValue);
						break;
					case p::isQuantized:
						pd.isQuantized = (bool) PyInt_AS_LONG(pyValue); 
						break;					
					default : 	
						cerr << "Invalid key in VAMP OutputDescriptor: " << PyString_AsString(pyKey) << endl; 
				} // switch					
			} // while dict keys
			list.push_back(pd);
		} // for each memeber in outputlist
		Py_CLEAR(pyList);
	} // if 
	return list;
}

void PyPlugin::setParameter(std::string paramid, float newval)
{
	MutexLocker locker(&m_pythonInterpreterMutex);

	char method[]="setParameter";
	cerr << "[call] " << method << endl;

		//Check if the method is implemented in Python
		if (PyObject_HasAttrString(m_pyInstance,method)) {
   			
			PyObject *pyMethod = PyString_FromString(method);
			PyObject *pyParamid = PyString_FromString(paramid.c_str());
			PyObject *pyNewval = PyFloat_FromDouble((double)newval);
			
			//Call the method
			PyObject *pyBool = 
			PyObject_CallMethodObjArgs(m_pyInstance,pyMethod,pyParamid,pyNewval,NULL);

			//This only happens if there is a syntax error or so
			if (pyBool == NULL) {
				cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
				<< "] Error setting parameter: " << paramid << endl;
				if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); }
			}

			Py_DECREF(pyMethod);
			Py_DECREF(pyParamid);
			Py_DECREF(pyNewval);
		}
}

float PyPlugin::getParameter(std::string paramid) const
{
	MutexLocker locker(&m_pythonInterpreterMutex);

	char method[]="getParameter";
	cerr << "[call] " << method << endl;
	float rValue = 0.0f;
	
		//Check if the method is implemented in Python
		if (PyObject_HasAttrString(m_pyInstance,method)) {
   			
			PyObject *pyMethod = PyString_FromString(method);
			PyObject *pyParamid = PyString_FromString(paramid.c_str());
			
			//Call the method
			PyObject *pyFloat = 
			PyObject_CallMethodObjArgs(m_pyInstance,pyMethod,pyParamid,NULL);

			//Check return type
			if (! PyFloat_Check(pyFloat) ) {
				cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
					<< "] Expected Float return type." << endl;
				if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); }
				Py_CLEAR(pyFloat);
				return rValue;
			}
	
			rValue = (float) PyFloat_AS_DOUBLE(pyFloat); 
			
			Py_DECREF(pyMethod);
			Py_DECREF(pyParamid);
			Py_DECREF(pyFloat);
		}

    return rValue;
}

#ifdef _DEBUG
static int proccounter = 0;
#endif

PyPlugin::FeatureSet
PyPlugin::process(const float *const *inputBuffers,
                      Vamp::RealTime timestamp)
{
	MutexLocker locker(&m_pythonInterpreterMutex);

    if (m_stepSize == 0) {
	cerr << "ERROR: PyPlugin::process: "
	     << "Plugin has not been initialised" << endl;
	return FeatureSet();
    }
	static char method[]="process";

#ifdef _DEBUG	
	cerr << "[call] " << method << " frame:" << proccounter << endl;
	proccounter++;
	//cerr << "C: proc..." << proccounter << " " << endl;
#endif

	//proces::Check if the method is implemented in Python
	if ( PyObject_HasAttrString(m_pyInstance,method) )
	{	
		
		//Pack method name into Python Object
		PyObject *pyMethod = PyString_FromString(method);

		//Declare new list object
		PyObject *pyFloat, *pyList;
		pyList = PyList_New((Py_ssize_t) m_stepSize);

		//Pack samples into a Python List Object
		//pyFloat will always be new references, 
		//these will be discarded when the list is deallocated
		for (size_t i = 0; i < m_stepSize; ++i) {
		pyFloat=PyFloat_FromDouble((double) inputBuffers[0][i]);
		PyList_SET_ITEM(pyList, (Py_ssize_t) i, pyFloat);
		}
		
		//Call python process (returns new reference)
		PyObject *pyOutputList = 
		PyObject_CallMethodObjArgs(m_pyInstance,pyMethod,pyList,NULL);

		//Check return type
		if (pyOutputList == NULL || !PyList_Check(pyOutputList) ) {
			if (pyOutputList == NULL) {				
				cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
				<< "] Unexpected result." << endl;
				if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); }
			} else {
				cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
				<< "] Expected List return type." << endl;				
			}
			Py_CLEAR(pyMethod);
			Py_CLEAR(pyOutputList);
			return FeatureSet();
		}
			
		Py_DECREF(pyMethod);
		// Py_DECREF(pyList); 
		// This appears to be tracked by the cyclic garbage collector
		// hence decrefing produces GC error
#ifdef _DEBUG								
		cerr << "Process Returned Features" << endl;
#endif
		// These will ALL be borrowed references
		PyObject *pyFeatureList, *pyDict, *pyKey, *pyValue;

		FeatureSet returnFeatures;

		//Parse Output List for each element (FeatureSet)
		for (Py_ssize_t i = 0; 
					i < PyList_GET_SIZE(pyOutputList); ++i) {
			//cerr << "output (FeatureSet): " << i << endl; 

			//Get i-th FeatureList (Borrowed Reference)
			pyFeatureList = PyList_GET_ITEM(pyOutputList,i);

			//Parse FeatureList for each element (Feature)
			for (Py_ssize_t j = 0; j < PyList_GET_SIZE(pyFeatureList); ++j) {				
				//cerr << "element (FeatureList): " << j << endl; 

				//Get j-th Feature (Borrowed Reference)
				pyDict = PyList_GET_ITEM(pyFeatureList,j);

				//We only care about dictionaries holding a Feature struct
				if ( !PyDict_Check(pyDict) ) continue;

				Py_ssize_t pyPos = NULL;
				bool emptyFeature = true;
				Feature feature;

				//process::Python Sequence Iterator for dictionary
				while (PyDict_Next(pyDict, &pyPos, &pyKey, &pyValue)) 
				{	
					emptyFeature = false;
					switch (ffKeys[PyString_AsString(pyKey)]) 
					{
						case not_found : 	
							cerr << "Unknown key in VAMP FeatureSet: " 
							<< PyString_AsString(pyKey) << endl; 
							break;
						case hasTimestamp: 	
							feature.hasTimestamp = (bool) PyInt_AS_LONG(pyValue); 
							break;				
						case timeStamp: 			
							feature.timestamp = timestamp + 
							Vamp::RealTime::frame2RealTime
							((size_t)PyInt_AS_LONG(pyValue), (size_t)m_inputSampleRate);
							break;
						case values: 	
							feature.values = PyList_As_FloatVector(pyValue); 
							break; 								
						case label: 			
							feature.label = PyString_AsString(pyValue); 
							break; 													
						default : 	
							cerr << "Invalid key in VAMP FeatureSet: " 
							<< PyString_AsString(pyKey) << endl; 
					} // switch					

				} // while 
				if (emptyFeature) cerr << "Warning: This feature is empty or badly formatted." << endl;
				else returnFeatures[i].push_back(feature);

			}// for j = FeatureList			

		}//for i = FeatureSet
		Py_CLEAR(pyOutputList);
		return returnFeatures;

	}//if (has_attribute)
	return FeatureSet(); 
}



PyPlugin::FeatureSet
PyPlugin::getRemainingFeatures()
{
	MutexLocker locker(&m_pythonInterpreterMutex);

	static char method[]="getRemainingFeatures";
	cerr << "[call] " << method << endl;

	//check if the method is implemented
	if ( ! PyObject_HasAttrString(m_pyInstance,method) ) {
		return FeatureSet(); 
		}
			
		PyObject *pyMethod = PyString_FromString(method);		

		PyObject *pyOutputList = 
		PyObject_CallMethod(m_pyInstance,method, NULL);
					
		//Check return type
		if (pyOutputList == NULL || !PyList_Check(pyOutputList) ) {
			if (pyOutputList == NULL) {				
				cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
				<< "] Unexpected result." << endl;
				if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); }
			} else {
				cerr << "ERROR: In Python plugin [" << m_class << "::" << method 
				<< "] Expected List return type." << endl;				
			}
			Py_CLEAR(pyMethod);
			Py_CLEAR(pyOutputList);
			return FeatureSet();
		}
		Py_DECREF(pyMethod);
	
		PyObject *pyFeatureList, *pyDict, *pyKey, *pyValue;
		FeatureSet returnFeatures;

		for (Py_ssize_t i = 0; i < PyList_GET_SIZE(pyOutputList); ++i) {

			pyFeatureList = PyList_GET_ITEM(pyOutputList,i);

			for (Py_ssize_t j = 0; j < PyList_GET_SIZE(pyFeatureList); ++j) {				

				pyDict = PyList_GET_ITEM(pyFeatureList,j);

				if ( !PyDict_Check(pyDict) ) continue;

				Py_ssize_t pyPos = NULL;
				bool emptyFeature = true;
				Feature feature;

				while (PyDict_Next(pyDict, &pyPos, &pyKey, &pyValue)) 
				{	
					emptyFeature = false;
					switch (ffKeys[PyString_AsString(pyKey)]) 
					{
						case not_found : 	
							cerr << "Unknown key in VAMP FeatureSet: " 
							<< PyString_AsString(pyKey) << endl; 
							break;
						case hasTimestamp: 	
							feature.hasTimestamp = (bool) PyInt_AS_LONG(pyValue); 
							break;				
						// TODO: clarify what to do here
						case timeStamp: 			
							feature.timestamp =  
							Vamp::RealTime::frame2RealTime
							((size_t)PyInt_AS_LONG(pyValue), (size_t)m_inputSampleRate);
							break;
						case values: 	
							feature.values = PyList_As_FloatVector(pyValue); 
							break; 								
						case label: 			
							feature.label = PyString_AsString(pyValue); 
							break; 													
					} // switch					
				} // while 
				if (emptyFeature) cerr << "Warning: This feature is empty or badly formatted." << endl;
				else returnFeatures[i].push_back(feature);
			}// for j 			
		}//for i 
		Py_CLEAR(pyOutputList);
		return returnFeatures;
}