view PyTypeInterface.cpp @ 72:ffaa1fb3d7de vampyhost

inline is not a useful keyword with contemporary compilers
author Chris Cannam
date Mon, 24 Nov 2014 09:50:49 +0000
parents 40a01bb24209
children
line wrap: on
line source
/* -*- c-basic-offset: 8 indent-tabs-mode: t -*- */
/*

 * 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>

#ifdef HAVE_NUMPY
#define PY_ARRAY_UNIQUE_SYMBOL VAMPY_ARRAY_API
#define NO_IMPORT_ARRAY
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include "numpy/arrayobject.h"
#endif

#include "PyTypeInterface.h"
#include "PyRealTime.h"
#include "PyExtensionModule.h"
#include <math.h>
#include <float.h>
#include <limits.h>
#ifndef SIZE_T_MAX
#define SIZE_T_MAX ((size_t) -1)
#endif

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

static std::map<std::string, o::eOutDescriptors> outKeys;
static std::map<std::string, p::eParmDescriptors> parmKeys;
static std::map<std::string, eSampleTypes> sampleKeys;
static std::map<std::string, eFeatureFields> ffKeys;
static bool isMapInitialised = false;

/*  Note: NO FUNCTION IN THIS CLASS SHOULD ALTER REFERENCE COUNTS
	(EXCEPT FOR TEMPORARY PYTHON OBJECTS)! */

PyTypeInterface::PyTypeInterface() : 
	m_strict(false),
	m_error(false),
	m_numpyInstalled(false),
	error(m_error) // const public reference for easy access
{
}

PyTypeInterface::~PyTypeInterface()
{
}

/// FeatureSet (an integer map of FeatureLists)
Vamp::Plugin::FeatureSet
PyTypeInterface::PyValue_To_FeatureSet(PyObject* pyValue) const
{
	Vamp::Plugin::FeatureSet rFeatureSet;

	/// Convert PyFeatureSet 
	if (PyFeatureSet_CheckExact(pyValue)) {

		Py_ssize_t pyPos = 0;
		PyObject *pyKey, *pyDictValue; // Borrowed References
		int key;
		// bool it_error = false;

		m_error = false;
		while (PyDict_Next(pyValue, &pyPos, &pyKey, &pyDictValue))
		{
			key = (int) PyInt_AS_LONG(pyKey);
#ifdef _DEBUG_VALUES			
			cerr << "key: '" << key << "' value: '" << PyValue_To_String(pyDictValue) << "' " << endl;
#endif			
			// DictValue -> Vamp::FeatureList
			PyValue_To_rValue(pyDictValue,rFeatureSet[key]);
			if (m_error) {
				// it_error = true;
				lastError() << " in output number: " << key;
			}
		}
		// if (it_error) m_error = true;
		if (!m_errorQueue.empty()) {
			setValueError("Error while converting FeatureSet.",m_strict);
		} 
		return rFeatureSet;
	}

	/// Convert Python list (backward compatibility)
	if (PyList_Check(pyValue)) {
		
		PyObject *pyFeatureList; // This will be borrowed reference

		//Parse Output List for each element (FeatureSet)
		m_error = false;
		for (Py_ssize_t i = 0; i < PyList_GET_SIZE(pyValue); ++i) {
			//Get i-th FeatureList (Borrowed Reference)
			pyFeatureList = PyList_GET_ITEM(pyValue,i);
			PyValue_To_rValue(pyFeatureList,rFeatureSet[i]);
			if (m_error) {
				lastError() << " in output number: " << i;
			}
		}
		if (!m_errorQueue.empty()) m_error = true; 
		return rFeatureSet;
	}

	/// accept None return values
	if (pyValue == Py_None) return rFeatureSet;

	/// give up
	std::string msg = "Unsupported return type. Expected list or vampy.FeatureSet(). ";
	setValueError(msg,m_strict);
#ifdef _DEBUG
	cerr << "PyTypeInterface::PyValue_To_FeatureSet failed. Error: " << msg << endl;
#endif			
	return rFeatureSet;
}

Vamp::RealTime
PyTypeInterface::PyValue_To_RealTime(PyObject* pyValue) const
{
// We accept integer sample counts (for backward compatibility)
// or PyRealTime objects and convert them to Vamp::RealTime
	
	if (PyRealTime_CheckExact(pyValue))
	{
		/// just create a copy of the wrapped object
		return Vamp::RealTime(*PyRealTime_AS_REALTIME(pyValue));
	}

	// assume integer sample count
	long sampleCount = m_conv.PyValue_To_Long(pyValue);
	if (m_conv.error) {
		std::string msg = "Unexpected value passed as RealTime.\nMust be vampy.RealTime type or integer sample count.";
		setValueError(msg,m_strict);
#ifdef _DEBUG
		cerr << "PyTypeInterface::PyValue_To_RealTime failed. " << msg << endl;
#endif		
		return Vamp::RealTime();
	}

#ifdef _DEBUG_VALUES
	Vamp::RealTime rt = 
		Vamp::RealTime::frame2RealTime(sampleCount,m_inputSampleRate );
	cerr << "RealTime: " << (long)sampleCount << ", ->" << rt.toString() << endl;
	return rt;
#else
	return Vamp::RealTime::frame2RealTime(sampleCount,m_inputSampleRate );	
#endif

}

Vamp::Plugin::OutputDescriptor::SampleType 
PyTypeInterface::PyValue_To_SampleType(PyObject* pyValue) const
{
	/// convert simulated enum values 
	/// { OneSamplePerStep,FixedSampleRate,VariableSampleRate }
	if (PyInt_CheckExact(pyValue)) {
		long lst = PyInt_AS_LONG(pyValue);
		if (lst<0 || lst>2) {
			setValueError("Overflow error. SampleType has to be one of { OneSamplePerStep,FixedSampleRate,VariableSampleRate }\n(an integer in the range of 0..2) or a string value naming the type.",m_strict);
			return Vamp::Plugin::OutputDescriptor::SampleType();
		}
		return (Vamp::Plugin::OutputDescriptor::SampleType) lst; 
	}
	
	/// convert string (backward compatible)
	if (PyString_CheckExact(pyValue)) {
		Vamp::Plugin::OutputDescriptor::SampleType st;
		st = (Vamp::Plugin::OutputDescriptor::SampleType) sampleKeys[m_conv.PyValue_To_String(pyValue)]; 
		if (m_conv.error) {
			std::string msg = "Unexpected value passed as SampleType. Must be one of { OneSamplePerStep,FixedSampleRate,VariableSampleRate }\n(an integer in the range of 0..2) or a string value naming the type.";
			setValueError(msg,m_strict);
			return Vamp::Plugin::OutputDescriptor::SampleType();
		}
		return st;
	}

	/// give up
	std::string msg = "Unsupported return type. Expected one of { OneSamplePerStep,FixedSampleRate,VariableSampleRate }\n(an integer in the range of 0..2) or a string value naming the type.";
	setValueError(msg,m_strict);
#ifdef _DEBUG
	cerr << "PyTypeInterface::PyValue_To_SampleType failed. Error: " << msg << endl;
#endif			
	return Vamp::Plugin::OutputDescriptor::SampleType();
}

Vamp::Plugin::InputDomain 
PyTypeInterface::PyValue_To_InputDomain(PyObject* pyValue) const
{
	/// convert simulated enum values { TimeDomain,FrequencyDomain }
	if (PyInt_CheckExact(pyValue)) {
		long lst = PyInt_AS_LONG(pyValue);
		if (lst!=0 && lst!=1) {
			setValueError("Overflow error. InputDomain has to be one of { TimeDomain,FrequencyDomain }\n(an integer in the range of 0..1) or a string value naming the type.",m_strict);
			return Vamp::Plugin::InputDomain();
		}
		return (Vamp::Plugin::InputDomain) lst; 
	}
	
	/// convert string (backward compatible)
	if (PyString_CheckExact(pyValue)) {
		Vamp::Plugin::InputDomain id;
		id = (m_conv.PyValue_To_String(pyValue) == "FrequencyDomain")?Vamp::Plugin::FrequencyDomain:Vamp::Plugin::TimeDomain;
		if (m_conv.error) 
		{
			std::string msg = "Unexpected value passed as SampleType. Must be one of { TimeDomain,FrequencyDomain }\n(an integer in the range of 0..1) or a string value naming the type.";
			setValueError(msg,m_strict);
			return Vamp::Plugin::InputDomain();
		}
		return id;
	}

	/// give up
	std::string msg = "Unsupported return type. Expected one of { TimeDomain,FrequencyDomain }\n(an integer in the range of 0..1) or a string value naming the type.";
	setValueError(msg,m_strict);
#ifdef _DEBUG
	cerr << "PyTypeInterface::PyValue_To_InputDomain failed. Error: " << msg << endl;
#endif			
	return Vamp::Plugin::InputDomain();
}

/* Convert Sample Buffers to Python */

/// passing the sample buffers as builtin python lists
/// Optimization: using fast sequence protocol
PyObject*
PyTypeInterface::InputBuffers_As_PythonLists(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize, const Vamp::Plugin::InputDomain& dtype)
{
	//create a list of lists (new references)
	PyObject *pyChannelList = PyList_New((Py_ssize_t) channels);
	
	// Pack samples into a Python List Object
	// pyFloat/pyComplex types will always be new references, 
	// they will be freed when the lists are deallocated.
	
	PyObject **pyChannelListArray =  PySequence_Fast_ITEMS(pyChannelList);
	for (size_t i=0; i < channels; ++i) {
		
        size_t arraySize;
		if (dtype==Vamp::Plugin::FrequencyDomain) 
			arraySize = (blockSize / 2) + 1; //blockSize + 2; if cplx list isn't used
		else 
			arraySize = blockSize;

		PyObject *pySampleList = PyList_New((Py_ssize_t) arraySize);
		PyObject **pySampleListArray =  PySequence_Fast_ITEMS(pySampleList);
		
		// Note: passing a complex list crashes the C-style plugin
		// when it tries to convert it to a numpy array directly.
		// This plugin will be obsolete, but we have to find a way
		// to prevent such crash: possibly a numpy bug, 
		// works fine above 1.0.4
		
		switch (dtype) //(Vamp::Plugin::TimeDomain)
		{
			case Vamp::Plugin::TimeDomain :

			for (size_t j = 0; j < arraySize; ++j) {
				PyObject *pyFloat=PyFloat_FromDouble(
					(double) inputBuffers[i][j]);
				pySampleListArray[j] = pyFloat;
			}
			break;

			case Vamp::Plugin::FrequencyDomain :

			size_t k = 0;
			for (size_t j = 0; j < arraySize; ++j) {
				PyObject *pyComplex=PyComplex_FromDoubles(
					(double) inputBuffers[i][k], 
					(double) inputBuffers[i][k+1]);
				pySampleListArray[j] = pyComplex;
				k += 2;
			}
			break;
			
		}
		pyChannelListArray[i] = pySampleList;
	}
	return pyChannelList;
}

/// numpy buffer interface: passing the sample buffers as shared memory buffers
/// Optimization: using sequence protocol for creating the buffer list
PyObject*
PyTypeInterface::InputBuffers_As_SharedMemoryList(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize, const Vamp::Plugin::InputDomain& dtype)
{	
	//create a list of buffers (returns new references)
	PyObject *pyChannelList = PyList_New((Py_ssize_t) channels);
	PyObject **pyChannelListArray =  PySequence_Fast_ITEMS(pyChannelList);

	// Expose memory using the Buffer Interface.		
	// This will pass a pointer which can be recasted in Python code 
	// as complex or float array using Numpy's frombuffer() method
	// (this will not copy values just keep the starting adresses 
	// for each channel in a list)
	Py_ssize_t bufferSize;
	
	if (dtype==Vamp::Plugin::FrequencyDomain) 
		bufferSize = (Py_ssize_t) sizeof(float) * (blockSize+2);
	else 
		bufferSize = (Py_ssize_t) sizeof(float) * blockSize;
	
	for (size_t i=0; i < channels; ++i) {
		PyObject *pyBuffer = PyBuffer_FromMemory
		((void *) (float *) inputBuffers[i],bufferSize);
		pyChannelListArray[i] = pyBuffer;
	}
	return pyChannelList;
}


/// numpy array interface: passing the sample buffers as 2D numpy array
/// Optimization: using array API (needs numpy headers)
#ifdef HAVE_NUMPY
PyObject*
PyTypeInterface::InputBuffers_As_NumpyArray(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize, const Vamp::Plugin::InputDomain& dtype)
{	
/*
NOTE: We create a list of 1D Numpy arrays for each channel instead
of a matrix, because the address space of inputBuffers doesn't seem
to be continuous. Although the array strides could be calculated for
2 channels (i.e. inputBuffers[1] - inputBuffers[0]) i'm not sure
if this can be trusted, especially for more than 2 channels.

	cerr << "First channel: " << inputBuffers[0][0] << " address: " <<  inputBuffers[0] << endl;
	if (channels == 2)
		cerr << "Second channel: " << inputBuffers[1][0] << " address: " <<  inputBuffers[1] << endl;

*/	
	
	// create a list of arrays (returns new references)
	PyObject *pyChannelList = PyList_New((Py_ssize_t) channels);
	PyObject **pyChannelListArray =  PySequence_Fast_ITEMS(pyChannelList);
	
	// Expose memory using the Numpy Array Interface.		
	// This will wrap an array objects around the data.
	// (will not copy values just steal the starting adresses)

	int arraySize, typenum;
	
	switch (dtype)
	{
		case Vamp::Plugin::TimeDomain :
		typenum = dtype_float32; //NPY_FLOAT; 
		arraySize = (int) blockSize;
		break;

		case Vamp::Plugin::FrequencyDomain :
		typenum = dtype_complex64; //NPY_CFLOAT;
		arraySize = (int) (blockSize / 2) + 1;
		break;
		
		default :
		cerr << "PyTypeInterface::InputBuffers_As_NumpyArray: Error: Unsupported numpy array data type." << endl;
		return pyChannelList;
	}

	// size for each dimension
	npy_intp ndims[1]={arraySize}; 
	
	for (size_t i=0; i < channels; ++i) {
		PyObject *pyChannelArray = 
			//args: (dimensions, size in each dim, type kind, pointer to continuous array)
			PyArray_SimpleNewFromData(1, ndims, typenum, (void*) inputBuffers[i]);
		// make it read-only: set all flags to false except NPY_C_CONTIGUOUS
		//!!! what about NPY_ARRAY_OWNDATA?
		PyArray_CLEARFLAGS((PyArrayObject *)pyChannelArray, 0xff);
		PyArray_ENABLEFLAGS((PyArrayObject *)pyChannelArray, NPY_ARRAY_C_CONTIGUOUS);
		pyChannelListArray[i] = pyChannelArray;
	}
	return pyChannelList;
}
#endif

/// OutputDescriptor
void
PyTypeInterface::SetValue(Vamp::Plugin::OutputDescriptor& od, std::string& key, PyObject* pyValue) const
{
	switch (outKeys[key])
	{
		case o::not_found:
			setValueError("Unknown key in Vamp OutputDescriptor",m_strict);
			cerr << "Unknown key in Vamp OutputDescriptor: " << key << endl;
			break;
			case o::identifier: 
			_convert(pyValue,od.identifier);
			break;				
		case o::name: 			
			_convert(pyValue,od.name);
			break;
		case o::description:
			_convert(pyValue,od.description);
			break;
		case o::unit:
			_convert(pyValue,od.unit);
			break;
		case o::hasFixedBinCount:
			_convert(pyValue,od.hasFixedBinCount);
			break;
		case o::binCount:
			_convert(pyValue,od.binCount);
			break;
		case o::binNames:
			_convert(pyValue,od.binNames);
			break;
		case o::hasKnownExtents:
			_convert(pyValue,od.hasKnownExtents);
			break;
		case o::minValue:
			_convert(pyValue,od.minValue);
			break;
		case o::maxValue:
			_convert(pyValue,od.maxValue);
			break;
		case o::isQuantized:
			_convert(pyValue,od.isQuantized);
			break;					
		case o::quantizeStep:
			_convert(pyValue,od.quantizeStep);
			break;
		case o::sampleType:
			_convert(pyValue,od.sampleType);
			break;
		case o::sampleRate:
			_convert(pyValue,od.sampleRate);
			break;
		case o::hasDuration:
			_convert(pyValue,od.hasDuration);
			break;
		default:
			setValueError("Unknown key in Vamp OutputDescriptor",m_strict);
			cerr << "Invalid key in Vamp OutputDescriptor: " << key << endl;
	}
}

/// ParameterDescriptor
void
PyTypeInterface::SetValue(Vamp::Plugin::ParameterDescriptor& pd, std::string& key, PyObject* pyValue) const
{
	switch (parmKeys[key]) 
	{
		case p::not_found :
			setValueError("Unknown key in Vamp ParameterDescriptor",m_strict);
			cerr << "Unknown key in Vamp ParameterDescriptor: " << key << endl; 
			break;
		case p::identifier:
			_convert(pyValue,pd.identifier);
			break;				
		case p::name:
			_convert(pyValue,pd.name);
			break;
		case p::description: 	
			_convert(pyValue,pd.description);
			break; 								
		case p::unit:
			_convert(pyValue,pd.unit);
			break; 																		
		case p::minValue:	
			_convert(pyValue,pd.minValue);
			break;
		case p::maxValue:
			_convert(pyValue,pd.maxValue);
			break;
		case p::defaultValue:
			_convert(pyValue,pd.defaultValue);
			break;
		case p::isQuantized:
			_convert(pyValue,pd.isQuantized);
			break;									
		case p::quantizeStep:
			_convert(pyValue,pd.quantizeStep);
			break;
		case p::valueNames:
			_convert(pyValue,pd.valueNames);
			break;
		default :
		 	setValueError("Unknown key in Vamp ParameterDescriptor",m_strict);
			cerr << "Invalid key in Vamp ParameterDescriptor: " << key << endl; 
	}
}

/// Feature (it's like a Descriptor)
bool
PyTypeInterface::SetValue(Vamp::Plugin::Feature& feature, std::string& key, PyObject* pyValue) const
{
	bool found = true;
	switch (ffKeys[key])
	{
		case unknown :
			setValueError("Unknown key in Vamp Feature",m_strict);
			cerr << "Unknown key in Vamp Feature: " << key << endl; 
			found = false;
			break;
		case hasTimestamp:
			_convert(pyValue,feature.hasTimestamp);
			break;				
		case timestamp:
			_convert(pyValue,feature.timestamp);
			break;
		case hasDuration: 	
			_convert(pyValue,feature.hasDuration);
			break;
		case duration:
			_convert(pyValue,feature.duration);
			break;
		case values:
			_convert(pyValue,feature.values);
			break; 								
		case label:
			_convert(pyValue,feature.label);
			break;
		default:
			setValueError("Unknown key in Vamp Feature",m_strict);
			found = false;
	}
	return found;
}


/* Error handling */

void
PyTypeInterface::setValueError (std::string message, bool strict) const
{
	m_error = true;
	m_errorQueue.push(ValueError(message,strict));
}

/// return a reference to the last error or creates a new one.
ValueError&
PyTypeInterface::lastError() const 
{
	m_error = false;
	if (!m_errorQueue.empty()) return m_errorQueue.back();
	else {
		m_errorQueue.push(ValueError("Type conversion error.",m_strict));
		return m_errorQueue.back();
	}
}

/// helper function to iterate over the error message queue:
/// pops the oldest item
ValueError 
PyTypeInterface::getError() const
{
	if (!m_errorQueue.empty()) {
		ValueError e = m_errorQueue.front();
		m_errorQueue.pop();
		if (m_errorQueue.empty()) m_error = false;
		return e;
	}
	else {
		m_error = false;
		return ValueError();
	}
}

/* Utilities */

bool
PyTypeInterface::initMaps() const
{

	if (isMapInitialised) return true;

	outKeys["identifier"] = o::identifier;
	outKeys["name"] = o::name;
	outKeys["description"] = o::description;
	outKeys["unit"] = o::unit;
	outKeys["hasFixedBinCount"] = o::hasFixedBinCount; 
	outKeys["binCount"] = o::binCount;
	outKeys["binNames"] = o::binNames;
	outKeys["hasKnownExtents"] = o::hasKnownExtents;
	outKeys["minValue"] = o::minValue;
	outKeys["maxValue"] = o::maxValue;
	outKeys["isQuantized"] = o::isQuantized;
	outKeys["quantizeStep"] = o::quantizeStep;
	outKeys["sampleType"] = o::sampleType;
	outKeys["sampleRate"] = o::sampleRate;
	outKeys["hasDuration"] = o::hasDuration;

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

	ffKeys["hasTimestamp"] = hasTimestamp;
	ffKeys["timestamp"] = timestamp; // this is the correct one
	ffKeys["timeStamp"] = timestamp; // backward compatible
	ffKeys["hasDuration"] = hasDuration;
	ffKeys["duration"] = duration;
	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;
	parmKeys["quantizeStep"] = p::quantizeStep;
	parmKeys["valueNames"] = p::valueNames;

	isMapInitialised = true;
	return true;
}