view vampyhost.cpp @ 8:db72f98403b4 lf-numpy-arrays

Working on the code so that it accepts numpy n-dim arrays directly (output of scikits.audiolab). Not working for stereo.
author luisf <luis.figueira@eecs.qmul.ac.uk>
date Thu, 14 Mar 2013 11:37:07 +0000
parents d29c25695f5e
children 703f52bf8a2e
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */

//include for python extension module: must be first
#include <Python.h>
#include <vampyhost.h>
#include <pyRealTime.h>

//!!! NB all our NumPy stuff is currently using the deprecated API --
//!!! need to work out how to update this
#include "numpy/arrayobject.h"

//includes for vamp host
#include "vamp-hostsdk/Plugin.h"
#include "vamp-hostsdk/PluginHostAdapter.h"
#include "vamp-hostsdk/PluginChannelAdapter.h"
#include "vamp-hostsdk/PluginInputDomainAdapter.h"
#include "vamp-hostsdk/PluginLoader.h"
//#include "vamp/vamp.h"

#include <iostream>
#include <fstream>
#include <set>
#include <sndfile.h>

#include <cstring>
#include <cstdlib>
#include <string>

#include "system.h"

#include <cmath>


using namespace std;
using namespace Vamp;

using Vamp::Plugin;
using Vamp::PluginHostAdapter;
using Vamp::RealTime;
using Vamp::HostExt::PluginLoader;

#define HOST_VERSION "1.1"


/* MODULE HELPER FUNCTIONS */
PyDoc_STRVAR(xx_foo_doc, "Some description"); //!!!

/*obtain C plugin handle and key from pyCobject */
bool getPluginHandle
(PyObject *pyPluginHandle, Plugin **plugin, string **pKey=NULL) {

    //char errormsg[]="Wrong input argument: Plugin Handle required.";

    *plugin = NULL;
    if (!PyCObject_Check(pyPluginHandle)) return false;

    //try to convert to Plugin pointer
    Plugin *p = (Plugin*) PyCObject_AsVoidPtr(pyPluginHandle);
    if (!p) return false;

    string pId;

    if (pKey) {
	*pKey = (string*) PyCObject_GetDesc(pyPluginHandle);
	if (!*pKey) return false;
	pId = *(string*) *pKey;

    } else {

	void *pKey = PyCObject_GetDesc(pyPluginHandle);
	if (!pKey) return false;
	pId = *(string*) pKey;
    }

    string::size_type pos = pId.find(':');
    if (pos == string::npos) return false;

    pId = pId.substr(pId.rfind(':')+1);
    string identifier = p->getIdentifier();

    if (pId.compare(identifier)) return false;

    *plugin = p;
    return true;
}

/*
  ----------------------------------------------------------------
*/



/*
  VAMPYHOST MAIN
  ---------------------------------------------------------------------
*/

/* ENUMERATE PLUGINS*/

static PyObject *
vampyhost_enumeratePlugins(PyObject *self, PyObject *args)
{
    string retType;

    if (!PyArg_ParseTuple(args, "|s:enumeratePlugins", &retType))
	return NULL;

    //list available plugins
    PluginLoader *loader = PluginLoader::getInstance();
    vector<PluginLoader::PluginKey> plugins = loader->listPlugins();

    //library Map
    typedef multimap<string, PluginLoader::PluginKey> LibraryMap;
    LibraryMap libraryMap;

    //New list object
    PyObject *pyList = PyList_New(plugins.size());

    for (size_t i = 0; i < plugins.size(); ++i) {
        string path = loader->getLibraryPathForPlugin(plugins[i]);
        libraryMap.insert(LibraryMap::value_type(path, plugins[i]));

	PyObject *pyPluginKey = PyString_FromString(plugins[i].c_str());
	PyList_SET_ITEM(pyList,i,pyPluginKey);

    }

    PyList_Sort(pyList);
    return pyList;
}


/* GET PLUGIN LIBRARY PATH*/

static PyObject *
vampyhost_getLibraryPath(PyObject *self, PyObject *args)
{
    PyObject *pyPluginKey;

    if (!PyArg_ParseTuple(args, "S", &pyPluginKey)) {
	PyErr_SetString(PyExc_TypeError,
			"String input argument required: pluginKey");
	return NULL; }

    //convert to stl string
    string pluginKey(PyString_AS_STRING(pyPluginKey));

    //check pluginKey Validity
    string::size_type ki = pluginKey.find(':');
    if (ki == string::npos) {
	PyErr_SetString(PyExc_TypeError,
			"String input argument required: pluginLibrary:Identifier");
       	return NULL;
    }

    PluginLoader *loader = PluginLoader::getInstance();
    string path = loader->getLibraryPathForPlugin(pluginKey);
    PyObject *pyPath = PyString_FromString(path.c_str());
    return pyPath;
}


/* GET PLUGIN CATEGORY*/

static PyObject *
vampyhost_getPluginCategory(PyObject *self, PyObject *args)
{
    PyObject *pyPluginKey;

    if (!PyArg_ParseTuple(args, "S", &pyPluginKey)) {
	PyErr_SetString(PyExc_TypeError,
			"String input argument required: pluginKey");
	return NULL; }

    //convert to stl string
    string pluginKey(PyString_AS_STRING(pyPluginKey));

    //check pluginKey Validity
    string::size_type ki = pluginKey.find(':');
    if (ki == string::npos) {
	PyErr_SetString(PyExc_TypeError,
			"String input argument required: pluginLibrary:Identifier");
       	return NULL;
    }

    PluginLoader *loader = PluginLoader::getInstance();
    PluginLoader::PluginCategoryHierarchy
	category = loader->getPluginCategory(pluginKey);
    string catstring;

    if (!category.empty()) {
        catstring = "";
        for (size_t ci = 0; ci < category.size(); ++ci) {
	    catstring.append(category[ci]);
            catstring.append(" ");
        }
	PyObject *pyCat = PyString_FromString(catstring.c_str());
	return pyCat;
    }
    PyObject *pyCat = PyString_FromString("");
    return pyCat;
}



/* GET PLUGIN OUTPUT LIST*/

static PyObject *
vampyhost_getOutputList(PyObject *self, PyObject *args)
{
    PyObject *pyPluginHandle;
    string pluginKey;

    if (!PyArg_ParseTuple(args, "O", &pyPluginHandle)) {
	PyErr_SetString(PyExc_TypeError,
			"Invalid argument: plugin handle or plugin key required.");
	return NULL;
    }

    //check if we have a plugin key string or a handle object
    if (PyString_Check(pyPluginHandle) ) {

	pluginKey.assign(PyString_AS_STRING(pyPluginHandle));
	//check pluginKey Validity
    	string::size_type ki = pluginKey.find(':');
    	if (ki == string::npos) {
	    PyErr_SetString(PyExc_TypeError,
			    "String input argument required: pluginLibrary:Identifier");
	    return NULL;
    	}

    } else {

	string *key;
	Plugin *plugin;

	if ( !getPluginHandle(pyPluginHandle, &plugin, &key) ) {
	    PyErr_SetString(PyExc_TypeError,
			    "Invalid or deleted plugin handle.");
	    return NULL; }
	pluginKey.assign(*key);
    }

    //This code creates new instance of the plugin anyway
    PluginLoader *loader = PluginLoader::getInstance();

    //load plugin
    Plugin *plugin = loader->loadPlugin
        (pluginKey, 48000, PluginLoader::ADAPT_ALL_SAFE);
    if (!plugin) {
	string pyerr("Failed to load plugin: "); pyerr += pluginKey;
	PyErr_SetString(PyExc_TypeError,pyerr.c_str());
	return NULL;
    }

    Plugin::OutputList outputs = plugin->getOutputDescriptors();
    //Plugin::OutputDescriptor od;

    if (outputs.size()<1) {
	string pyerr("Plugin has no output: "); pyerr += pluginKey;
	PyErr_SetString(PyExc_TypeError,pyerr.c_str());
	return NULL;
    }

    //New list object
    PyObject *pyList = PyList_New(outputs.size());

    for (size_t i = 0; i < outputs.size(); ++i) {
	PyObject *pyOutputId =
	    PyString_FromString(outputs[i].identifier.c_str());
	PyList_SET_ITEM(pyList,i,pyOutputId);
    }

    delete plugin;
    return pyList;
}



/* LOAD PLUGIN */

static PyObject *
vampyhost_loadPlugin(PyObject *self, PyObject *args)
{
    PyObject *pyPluginKey;
    float inputSampleRate;

    if (!PyArg_ParseTuple(args, "Sf",
			  &pyPluginKey,
			  &inputSampleRate)) {
	PyErr_SetString(PyExc_TypeError,
			"String input argument required: pluginKey");
	return NULL; }

    //convert to stl string
    string pluginKey(PyString_AS_STRING(pyPluginKey));

    //check pluginKey Validity
    string::size_type ki = pluginKey.find(':');
    if (ki == string::npos) {
	PyErr_SetString(PyExc_TypeError,
			"String input argument required: pluginLibrary:Identifier");
       	return NULL;
    }

    PluginLoader *loader = PluginLoader::getInstance();

    //load plugin
    Plugin *plugin = loader->loadPlugin (pluginKey, inputSampleRate,
                                         PluginLoader::ADAPT_ALL_SAFE);
    if (!plugin) {
	string pyerr("Failed to load plugin: "); pyerr += pluginKey;
	PyErr_SetString(PyExc_TypeError,pyerr.c_str());
	return NULL;
    }
    //void *identifier = (void*) new string(pluginKey);
    PyPluginDescriptor *pd = new PyPluginDescriptor;

    pd->key = pluginKey;
    pd->isInitialised = false;
    pd->inputSampleRate = inputSampleRate;

    //New PyCObject
    //PyObject *pyPluginHandle = PyCObject_FromVoidPtrAndDesc(
    //(void*) plugin, identifier, NULL);

    PyObject *pyPluginHandle = PyCObject_FromVoidPtrAndDesc(
	(void*) plugin, (void*) pd, NULL);

    return pyPluginHandle;
}



/* UNLOAD PLUGIN */

static PyObject *
vampyhost_unloadPlugin(PyObject *self, PyObject *args)
{
    PyObject *pyPluginHandle;

    if (!PyArg_ParseTuple(args, "O", &pyPluginHandle)) {
	PyErr_SetString(PyExc_TypeError,
			"Wrong input argument: Plugin Handle required.");
	return NULL; }

    string *key;
    Plugin *plugin;

    if ( !getPluginHandle(pyPluginHandle, &plugin, &key) ) {
	PyErr_SetString(PyExc_TypeError,
			"Invalid or already deleted plugin handle.");
	return NULL; }

/*	Prevent repeated calls from causing segfault
	sice it will fail type checking the 2nd time:						*/
    PyCObject_SetVoidPtr(pyPluginHandle,NULL);

    PyPluginDescriptor *pd = (PyPluginDescriptor*) key;

    delete plugin;
    delete pd;
    return pyPluginHandle;

}


/* INITIALISE PLUGIN */

static PyObject *
vampyhost_initialise(PyObject *self, PyObject *args)
{
    PyObject *pyPluginHandle;
    size_t channels, blockSize, stepSize;

    if (!PyArg_ParseTuple (args, "Onnn",  &pyPluginHandle,
			   (size_t) &channels,
			   (size_t) &stepSize,
			   (size_t) &blockSize))
    {
	PyErr_SetString(PyExc_TypeError,
			"Wrong input arguments: requires a valid plugin handle,channels,stepSize,blockSize.");
	return NULL;
    }

    Plugin *plugin;
    string *key;

    if ( !getPluginHandle(pyPluginHandle, &plugin, &key) ) {
	PyErr_SetString(PyExc_TypeError,
			"Invalid plugin handle.");
	return NULL; }

    // here we cast the void pointer as PyPluginDescriptor instead of string
    PyPluginDescriptor *plugDesc = (PyPluginDescriptor*) key;

    plugDesc->channels = channels;
    plugDesc->stepSize = stepSize;
    plugDesc->blockSize = blockSize;

    if (!plugin->initialise(channels, stepSize, blockSize)) {
        std::cerr << "Failed to initialise native plugin adapter with channels = " << channels << ", stepSize = " << stepSize << ", blockSize = " << blockSize << " and ADAPT_ALL_SAFE set" << std::endl;
	PyErr_SetString(PyExc_TypeError,
			"Plugin initialization failed.");
	return NULL;
    }

    plugDesc->identifier =
	plugDesc->key.substr(plugDesc->key.rfind(':')+1);
    plugDesc->isInitialised = true;

    return Py_True;
}

// These conversion functions are borrowed from PyTypeInterface in VamPy

template<typename RET, typename DTYPE>
static
RET *pyArrayConvert(char* raw_data_ptr, long length, size_t strides)
{
    RET *rValue = new RET[length];

    /// check if the array is continuous, if not use strides info
    if (sizeof(DTYPE)!=strides) {
        char* data = (char*) raw_data_ptr;
        for (long i = 0; i<length; ++i){
            rValue[i] = (RET)(*((DTYPE*)data));
            data += strides;
        }
        return rValue;
    }

    DTYPE* data = (DTYPE*) raw_data_ptr;
    for (long i = 0; i<length; ++i){
        rValue[i] = (RET)data[i];
    }

    return rValue;
}

static float *
pyArrayToFloatArray(PyObject *pyValue)
{
    if (!PyArray_Check(pyValue)) {
        cerr << "pyArrayToFloatArray: Failed, object has no array interface" << endl;
        return 0;
    }

    PyArrayObject* pyArray = (PyArrayObject*) pyValue;
    PyArray_Descr* descr = pyArray->descr;

    /// check raw data and descriptor pointers
    if (pyArray->data == 0 || descr == 0) {
        cerr << "pyArrayToFloatArray: Failed, NumPy array has NULL data or descriptor" << endl;
        return 0;
    }

    /// check dimensions
    if (pyArray->nd != 1) {
        cerr << "pyArrayToFloatArray: Failed, NumPy array is multi-dimensional" << endl;
        return 0;
    }

    /// check strides (useful if array is not continuous)
    size_t strides = *((size_t*) pyArray->strides);

    /// convert the array
    switch (descr->type_num) {
    case NPY_FLOAT : // dtype='float32'
        return pyArrayConvert<float,float>(pyArray->data,pyArray->dimensions[0],strides);
    case NPY_DOUBLE : // dtype='float64'
        return pyArrayConvert<float,double>(pyArray->data,pyArray->dimensions[0],strides);
    default:
        cerr << "pyArrayToFloatArray: Failed: Unsupported value type " << descr->type_num << " in NumPy array object (only float32, float64 supported)" << endl;
        return 0;
    }
}


/* RUN PROCESS */

static PyObject *
vampyhost_process(PyObject *self, PyObject *args)
{
    PyObject *pyPluginHandle;
    PyArrayObject *pyBuffer;
    PyObject *pyRealTime;

    if (!PyArg_ParseTuple(args, "OOO",
			  &pyPluginHandle,	// C object holding a pointer to a plugin and its descriptor
			  &pyBuffer,			// Audio data (NumPy ndim array)
			  &pyRealTime)) {		// TimeStamp
	PyErr_SetString(PyExc_TypeError,
			"Required: plugin handle, buffer, timestmap.");
	return NULL; }

    if (!PyRealTime_Check(pyRealTime)) {
	PyErr_SetString(PyExc_TypeError,"Valid timestamp required.");
	return NULL; }

    string *key;
    Plugin *plugin;

    if (!getPluginHandle(pyPluginHandle, &plugin, &key)) {
    PyErr_SetString(PyExc_AttributeError,
            "Invalid or already deleted plugin handle.");
    return NULL;
    }

    PyPluginDescriptor *pd = (PyPluginDescriptor*) key;

    if (!pd->isInitialised) {
    PyErr_SetString(PyExc_StandardError,
            "Plugin has not been initialised.");
    return NULL; }

    size_t channels =  pd->channels;
    size_t blockSize = pd->blockSize;

    if (!PyArray_Check(pyBuffer)) {
        PyErr_SetString(PyExc_TypeError, "Argument is not a Numpy array.");
        return NULL;
    }

    if (pyBuffer->nd != channels) {
        cerr << "Wrong number of channels: got " << pyBuffer->nd << ", expected " << channels << endl;
	PyErr_SetString(PyExc_TypeError, "Wrong number of channels");
        return NULL;
    }

    int n = pyBuffer->dimensions[0];
    int m = pyBuffer->dimensions[1];

    cout << "Kind :" << pyBuffer->descr->kind << endl;
    cout << "Strides 0 :" << pyBuffer->strides[0] << endl;
    cout << "Strides 1 :" << pyBuffer->strides[1] << endl;
    cout << "Flags:" << pyBuffer->flags << endl;

    float **inbuf = new float *[channels];
    cout << "Created inbuf with #channels: " << channels << endl;

    for (int c = 0; c < channels; ++c) {

        // cout << "[Host] Converting channel #" << c << endl;
        // PyObject *cbuf = PyList_GET_ITEM(pyBuffer, c);
        // cout << "Ok1..." << endl;
        // inbuf[c] = pyArrayToFloatArray(cbuf);

        inbuf[c] = pyArrayToFloatArray((PyObject*) pyBuffer);

        cout << "Ok2..." << endl;

        if (!inbuf[c]) {
            PyErr_SetString(PyExc_TypeError,"NumPy Array required for each channel in process input.");
            return NULL;
        }

        cout << "[Host] Converted channel #" << c << endl;

    }

    RealTime timeStamp = *PyRealTime_AsPointer(pyRealTime);

    cout << "[Host] Gonna call plugin->process" << endl;

    //Call process and store the output
    pd->output = plugin->process(inbuf, timeStamp);

    cout << "[Host] Called plugin->process" << endl;

    /* TODO:  DO SOMETHONG WITH THE FEATURE SET HERE */
/// convert to appropriate python objects, reuse types and conversion utilities from Vampy ...


    for (int c = 0; c < channels; ++c){
	delete[] inbuf[c];
    }
    delete[] inbuf;





    return NULL; //!!! Need to return actual features!
}

/* GET / SET OUTPUT */

//getOutput(plugin,outputNo)
static PyObject *
vampyhost_getOutput(PyObject *self, PyObject *args) {

    PyObject *pyPluginHandle;
//	PyObject *pyBuffer;
//	PyObject *pyRealTime;
    PyObject *pyOutput;

    if (!PyArg_ParseTuple(args, "OO",
			  &pyPluginHandle,	// C object holding a pointer to a plugin and its descriptor
			  &pyOutput)) {		// Output reference
	PyErr_SetString(PyExc_TypeError,
			"Required: plugin handle, buffer, timestmap.");
	return NULL; }

    string *key;
    Plugin *plugin;

    if ( !getPluginHandle(pyPluginHandle, &plugin, &key) ) {
	PyErr_SetString(PyExc_AttributeError,
			"Invalid or already deleted plugin handle.");
	return NULL; }

    PyPluginDescriptor *pd = (PyPluginDescriptor*) key;

    unsigned int outputNo = (unsigned int) PyInt_AS_LONG(pyOutput);

    //Get output list: but we don't need it
    //Plugin::FeatureList features = pd->output[outputNo];

    size_t outLength = pd->output[outputNo].size();

    //New PyList for the featurelist
    PyObject *pyFeatureList = PyList_New(outLength);

    for (size_t i = 0; i < outLength; ++i) {
	// Test:
	/*
	  XxoObject *pyFeature = PyObject_New(XxoObject, &Xxo_Type);
	  if (pyFeature == NULL) break; //return NULL;

	  pyFeature->x_attr = NULL;
	  pyFeature->feature = &pd->output[outputNo][i];

	  PyList_SET_ITEM(pyFeatureList,i,(PyObject*)pyFeature);
	*/
    }

    Py_INCREF(pyFeatureList);
    return pyFeatureList;

// EXPLAIN WHAT WE NEED TO DO HERE:
// We have the block output in pd->output
// FeatureSet[output] -> [Feature[x]] -> Feature.hasTimestamp = v
// Vamp::Plugin::FeatureSet output; = pd->output
// typedef std::vector<Feature> FeatureList;
// typedef std::map<int, FeatureList> FeatureSet; // key is output no

    // 	THIS IS FOR OUTPUT id LOOKUP LATER
    //     Plugin::OutputList outputs = plugin->getOutputDescriptors();
    //
    // if (outputs.size()<1) {
    // 	string pyerr("Plugin has no output: "); pyerr += pluginKey;
    // 	PyErr_SetString(PyExc_TypeError,pyerr.c_str());
    // 	return NULL;
    // }
    //
    // //New list object
    // PyObject *pyList = PyList_New(outputs.size());
    //
    //     for (size_t i = 0; i < outputs.size(); ++i) {
    // 	PyObject *pyOutputId =
    // 	PyString_FromString(outputs[i].identifier.c_str());
    // 	PyList_SET_ITEM(pyList,i,pyOutputId);
    //     }

}




/* List of functions defined in this module */
//module methods table
static PyMethodDef vampyhost_methods[] = {

    {"enumeratePlugins",	vampyhost_enumeratePlugins,	METH_VARARGS,
     xx_foo_doc},

    {"getLibraryPath",	vampyhost_getLibraryPath, METH_VARARGS,
     xx_foo_doc},

    {"getPluginCategory",	vampyhost_getPluginCategory, METH_VARARGS,
     xx_foo_doc},

    {"getOutputList",	vampyhost_getOutputList, METH_VARARGS,
     xx_foo_doc},

    {"loadPlugin",	vampyhost_loadPlugin, METH_VARARGS,
     xx_foo_doc},

    {"process",	vampyhost_process, METH_VARARGS,
     xx_foo_doc},

    {"unloadPlugin",	vampyhost_unloadPlugin, METH_VARARGS,
     xx_foo_doc},

    {"initialise",	vampyhost_initialise, METH_VARARGS,
     xx_foo_doc},

    {"getOutput",	vampyhost_getOutput, METH_VARARGS,
     xx_foo_doc},

    /* Add RealTime Module Methods */

    {"frame2RealTime",	(PyCFunction)RealTime_frame2RealTime,	METH_VARARGS,
     PyDoc_STR("frame2RealTime((int64)frame, (uint32)sampleRate ) -> returns new RealTime object from frame.")},

    {"realtime",	(PyCFunction)RealTime_new,		METH_VARARGS,
     PyDoc_STR("realtime() -> returns new RealTime object")},

    {NULL,		NULL}		/* sentinel */
};

//Documentation for our new module
PyDoc_STRVAR(module_doc, "This is a template module just for instruction.");

/* Initialization function for the module (*must* be called initxx) */

//module initialization (includes extern C {...} as necessary)
PyMODINIT_FUNC
initvampyhost(void)
{
    PyObject *m;

    /* Finalize the type object including setting type of the new type
     * object; doing it here is required for portability to Windows
     * without requiring C++. */

    if (PyType_Ready(&RealTime_Type) < 0)
	return;
//	PyModule_AddObject(m, "Real_Time", (PyObject *)&RealTime_Type);

    /* Create the module and add the functions */
    m = Py_InitModule3("vampyhost", vampyhost_methods, module_doc);
    if (m == NULL) return;

    // Numpy array library initialization function
    import_array();

    // PyModule_AddObject(m, "realtime", (PyObject *)&RealTime_Type);

}