changeset 4:825d787f12df

Toward using NumPy structures instead of low-level strings
author Chris Cannam
date Thu, 31 Jan 2013 17:44:37 +0000
parents f12ab1553882
children 8a534e43eb22
files Makefile vampyhost.cpp vampyhost.h vampyhost_test.py
diffstat 4 files changed, 102 insertions(+), 122 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Thu Jan 31 13:31:52 2013 +0000
+++ b/Makefile	Thu Jan 31 17:44:37 2013 +0000
@@ -1,8 +1,9 @@
 
 PY_INCLUDE_PATH	:= /usr/include/python2.7
+NUMPY_INCLUDE_PATH := /usr/lib/python2.7/site-packages/numpy/core/include
 
-CFLAGS		:= -O2 -fPIC -Wall -I$(PY_INCLUDE_PATH) -I.
-CXXFLAGS	:= -O2 -fPIC -Wall -I$(PY_INCLUDE_PATH) -I.
+CFLAGS		:= -O2 -fPIC -Wall -I$(PY_INCLUDE_PATH) -I$(NUMPY_INCLUDE_PATH) -I.
+CXXFLAGS	:= -O2 -fPIC -Wall -I$(PY_INCLUDE_PATH) -I$(NUMPY_INCLUDE_PATH) -I.
 
 LDFLAGS		:= -shared -lpython2.7 -lvamp-hostsdk
 #LDFLAGS		:= -dynamiclib -lpython2.5 /usr/lib/libvamp-hostsdk.a
--- a/vampyhost.cpp	Thu Jan 31 13:31:52 2013 +0000
+++ b/vampyhost.cpp	Thu Jan 31 17:44:37 2013 +0000
@@ -5,6 +5,10 @@
 #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"
@@ -367,8 +371,6 @@
 {
     PyObject *pyPluginHandle;
     size_t channels,blockSize,stepSize;
-    //PyObject pyInputSampleType;
-    bool mixChannels = false;
 
     if (!PyArg_ParseTuple (args, "Oiii",  &pyPluginHandle, 
 			   (size_t) &channels, 
@@ -394,23 +396,7 @@
     plugDesc->channels = channels;
     plugDesc->stepSize = stepSize;
     plugDesc->blockSize = blockSize;
-    plugDesc->inputSampleType = PyPluginDescriptor::int16;
-    plugDesc->sampleSize = 2;
 
-/*!!! not a problem with plugin loader adapter
-    plugDesc->mixChannels = mixChannels; 
-
-    size_t minch = plugin->getMinChannelCount();
-    size_t maxch = plugin->getMaxChannelCount();
-    if (mixChannels) channels = 1;
-*/
-    /* TODO: DO WE WANT TO MIX IT DOWN? */
-/*
-    if (maxch < channels || channels < minch) {
-	PyErr_SetString(PyExc_TypeError,
-			"Invalid number of channels.");
-	return NULL; }
-*/
     if (!plugin->initialise(channels, stepSize, blockSize)) {
 	PyErr_SetString(PyExc_TypeError,
 			"Plugin initialization failed.");
@@ -423,6 +409,70 @@
     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 */
 
@@ -445,19 +495,14 @@
 	PyErr_SetString(PyExc_TypeError,"Valid timestamp required.");
 	return NULL; }
 
-    // RealTime *rt = PyRealTime_AsPointer(pyRealTime);
-    // if (!rt) return NULL;
-    // cerr << ">>>sec: " << rt->sec << " nsec: " << rt->nsec << endl;
-    // 
-    // PyObject *rrt = PyRealTime_FromRealTime (rt);
-
-    string *key;	
+    string *key;
     Plugin *plugin; 
 
-    if ( !getPluginHandle(pyPluginHandle, &plugin, &key) ) {
+    if (!getPluginHandle(pyPluginHandle, &plugin, &key)) {
 	PyErr_SetString(PyExc_AttributeError,
 			"Invalid or already deleted plugin handle.");
-	return NULL; }
+	return NULL;
+    }
 
     PyPluginDescriptor *pd = (PyPluginDescriptor*) key;
 
@@ -469,75 +514,42 @@
     size_t channels =  pd->channels;	
     size_t blockSize = pd->blockSize;
 
-/*
-  Handle the case when we get the data as a character buffer
-  Handle SampleFormats: int16, float32	
-
-*/		
-
-    if (PyString_Check(pyBuffer)) {
-	cerr << ">>> String obj passed in." << endl;
+    if (!PyList_Check(pyBuffer)) {
+	PyErr_SetString(PyExc_TypeError, "List of NumPy Array required for process input.");
+        return NULL;
     }
 
-    size_t sample_size = sizeof(short);
+    if (PyList_GET_SIZE(pyBuffer) != channels) {
+	PyErr_SetString(PyExc_TypeError, "Wrong number of channels");
+        return NULL;
+    }
 
-    long buflen = (long) PyString_GET_SIZE(pyBuffer);
+    float **inbuf = new float *[channels];
 
-    size_t input_length = 
-	static_cast <size_t> (buflen/channels/sample_size);
-
-    if (input_length == pd->blockSize) {
-	cerr << ">>> A full block has been passed in." << endl; }
-    short *input = 
-	reinterpret_cast <short*> (PyString_AS_STRING(pyBuffer));
-
-    //convert int16 PCM data to 32-bit floats
-    float **plugbuf = new float*[channels];
-    float normfact = 1.0f / static_cast <float> (SHRT_MAX);
-		
-    for (size_t c = 0; c < channels; ++c) {
-
-	plugbuf[c] = new float[blockSize+2];
-  
-      	size_t j = 0;
-        while (j < input_length) {
-	    plugbuf[c][j] = normfact *
-		static_cast <float> (input[j * channels + c]);
-	    ++j; 
+    for (int c = 0; c < channels; ++c) {
+        PyObject *cbuf = PyList_GET_ITEM(pyBuffer, c);
+        inbuf[c] = pyArrayToFloatArray(cbuf);
+        if (!inbuf[c]) {
+            PyErr_SetString(PyExc_TypeError,"NumPy Array required for each channel in process input.");
+            return NULL;
         }
-        while (j < blockSize) {
-            plugbuf[c][j] = 0.0f;
-            ++j;
-        }
-    }	
-
-    const char *output = reinterpret_cast <const char*> (plugbuf[0]);
-    Py_ssize_t len = (Py_ssize_t) channels*blockSize*4;
-	
-    PyObject* pyReturnBuffer = 
-	PyString_FromStringAndSize(output,len);
-
-    // long frame = 1;
-    // unsigned int samplerate = (unsigned int) pd->inputSampleRate;
+    }
 
     RealTime timeStamp = *PyRealTime_AsPointer(pyRealTime);
 
     //Call process and store the output
-    pd->output = plugin->process(
-	plugbuf, 
-	timeStamp);
+    pd->output = plugin->process(inbuf, timeStamp);
 
     /* TODO:  DO SOMETHONG WITH THE FEATURE SET HERE */
 /// convert to appropriate python objects, reuse types and conversion utilities from Vampy ...
 
 
-    //We can safely delete here
-    for(size_t k=0; k<channels; k++){
-	delete[] plugbuf[k];
+    for (int c = 0; c < channels; ++c){
+	delete[] inbuf[c];
     }
-    delete[] plugbuf;
+    delete[] inbuf;
 
-    return pyReturnBuffer;
+    return NULL; //!!! Need to return actual features!
 
 }
 
--- a/vampyhost.h	Thu Jan 31 13:31:52 2013 +0000
+++ b/vampyhost.h	Thu Jan 31 17:44:37 2013 +0000
@@ -6,22 +6,6 @@
 #include "vamp-hostsdk/Plugin.h"
 #include <string>
 
-// structure of NumPy array intrface (just a hack, shouldn't be needed here...)
-typedef struct {
-    int two;              /* contains the integer 2 -- simple sanity check */
-    int nd;               /* number of dimensions */
-    char typekind;        /* kind in array --- character code of typestr */
-    int itemsize;         /* size of each element */
-    int flags;            /* flags indicating how the data should be interpreted */
-                          /*   must set ARR_HAS_DESCR bit to validate descr */
-    Py_intptr_t *shape;   /* A length-nd array of shape information */
-    Py_intptr_t *strides; /* A length-nd array of stride information */
-    void *data;           /* A pointer to the first element of the array */
-    PyObject *descr;      /* NULL or data-description (same as descr key */
-                          /*        of __array_interface__) -- must set ARR_HAS_DESCR */
-                          /*        flag or this will be ignored. */
-} PyArrayInterface;
-
 //structure for holding plugin instance data
 typedef struct {
     std::string key;
@@ -31,12 +15,6 @@
     size_t channels;
     size_t blockSize;
     size_t stepSize;
-    size_t sampleSize;
-    bool mixChannels;
-    enum InputSampleType {
-	int16,
-	float32 }; 
-    InputSampleType inputSampleType;
     Vamp::Plugin::FeatureSet output;
 } PyPluginDescriptor;
 
--- a/vampyhost_test.py	Thu Jan 31 13:31:52 2013 +0000
+++ b/vampyhost_test.py	Thu Jan 31 17:44:37 2013 +0000
@@ -4,10 +4,10 @@
 
 sys.path.append(os.getcwd())
 
+import scikits.audiolab as al;
+
 #from melscale import melscale
 #from melscale import initialize
-import wave
-from wave import *
 from pylab import *
 # from melscale import *
 from numpy import *
@@ -20,28 +20,17 @@
 #import pyRealTime
 #from pyRealTime import *
 
-
 #deal with an audio file
 wavfile='test.wav'
 
-wavobj = wave.open(wavfile,'r')
-samplerate = wavobj.getframerate()
+audio, samplerate, format = al.wavread(wavfile);
+
 print "samplerate: ",samplerate
-print "number of samples (frames): ",wavobj.getnframes() #total number of samples 4647744
-channels = wavobj.getnchannels();
+print "number of samples (frames): ",audio.size #total number of samples 4647744
+channels = audio[0].size
 print "channels: ",channels
-print "sample-width: ",wavobj.getsampwidth()
-print "position: ",wavobj.tell()
-#wavobj.setpos(1000000)
 
-print wavobj.tell()
-audio = wavobj.readframes(1024) #returns an 8-bit buffer
-print wavobj.tell()
-print dir(audio)
-print len(audio)
-wavobj.close()
-
-
+#!!! continue with this lark
 
 rt=realtime(4,70)