changeset 39:13dcfe8c7ed7

Docs and tidying
author Chris Cannam
date Wed, 26 Nov 2014 15:23:56 +0000
parents e881d77da368
children fa3f80d4e340
files PyPluginObject.cpp PyPluginObject.h PyRealTime.cpp test_metadata.py test_plugin_metadata.py vampyhost.cpp
diffstat 6 files changed, 294 insertions(+), 259 deletions(-) [+]
line wrap: on
line diff
--- a/PyPluginObject.cpp	Wed Nov 26 14:27:56 2014 +0000
+++ b/PyPluginObject.cpp	Wed Nov 26 15:23:56 2014 +0000
@@ -55,8 +55,6 @@
 using namespace std;
 using namespace Vamp;
 
-PyDoc_STRVAR(xx_foo_doc, "Some description"); //!!!
-
 //!!! todo: conv errors
 
 static
@@ -69,7 +67,7 @@
     }
     if (!pd || !pd->plugin) {
         PyErr_SetString(PyExc_AttributeError,
-			"Invalid or already deleted plugin handle.");
+                        "Invalid or already deleted plugin handle.");
         return 0;
     } else {
         return pd;
@@ -152,6 +150,15 @@
     }
 
     pd->parameters = params;
+
+    Plugin::ProgramList prl = plugin->getPrograms();
+    PyObject *progs = PyList_New(prl.size());
+
+    for (int i = 0; i < (int)prl.size(); ++i) {
+        PyList_SET_ITEM(progs, i, pystr(prl[i]));
+    }
+
+    pd->programs = progs;
     
     return (PyObject *)pd;
 }
@@ -218,17 +225,17 @@
 }
 
 static PyObject *
-vampyhost_initialise(PyObject *self, PyObject *args)
+initialise(PyObject *self, PyObject *args)
 {
     size_t channels, blockSize, stepSize;
 
     if (!PyArg_ParseTuple (args, "nnn",
-			   (size_t) &channels,
-			   (size_t) &stepSize,
-			   (size_t) &blockSize)) {
-	PyErr_SetString(PyExc_TypeError,
-			"initialise() takes channel count, step size, and block size arguments");
-	return 0;
+                           (size_t) &channels,
+                           (size_t) &stepSize,
+                           (size_t) &blockSize)) {
+        PyErr_SetString(PyExc_TypeError,
+                        "initialise() takes channel count, step size, and block size arguments");
+        return 0;
     }
 
     PyPluginObject *pd = getPluginObject(self);
@@ -239,10 +246,10 @@
     pd->blockSize = blockSize;
 
     if (!pd->plugin->initialise(channels, stepSize, blockSize)) {
-        cerr << "Failed to initialise native plugin adapter with channels = " << channels << ", stepSize = " << stepSize << ", blockSize = " << blockSize << " and ADAPT_ALL_SAFE set" << endl;
-	PyErr_SetString(PyExc_TypeError,
-			"Plugin initialization failed");
-	return 0;
+        cerr << "Failed to initialise native plugin adapter with channels = " << channels << ", stepSize = " << stepSize << ", blockSize = " << blockSize << endl;
+        PyErr_SetString(PyExc_TypeError,
+                        "Plugin initialization failed");
+        return 0;
     }
 
     pd->isInitialised = true;
@@ -251,7 +258,7 @@
 }
 
 static PyObject *
-vampyhost_reset(PyObject *self, PyObject *)
+reset(PyObject *self, PyObject *)
 {
     PyPluginObject *pd = getPluginObject(self);
     if (!pd) return 0;
@@ -267,14 +274,14 @@
 }
 
 static PyObject *
-vampyhost_getParameter(PyObject *self, PyObject *args)
+getParameter(PyObject *self, PyObject *args)
 {
     PyObject *pyParam;
 
     if (!PyArg_ParseTuple(args, "S", &pyParam)) {
-	PyErr_SetString(PyExc_TypeError,
-			"getParameter() takes parameter id (string) argument");
-	return 0; }
+        PyErr_SetString(PyExc_TypeError,
+                        "getParameter() takes parameter id (string) argument");
+        return 0; }
 
     PyPluginObject *pd = getPluginObject(self);
     if (!pd) return 0;
@@ -284,15 +291,15 @@
 }
 
 static PyObject *
-vampyhost_setParameter(PyObject *self, PyObject *args)
+setParameter(PyObject *self, PyObject *args)
 {
     PyObject *pyParam;
     float value;
 
     if (!PyArg_ParseTuple(args, "Sf", &pyParam, &value)) {
-	PyErr_SetString(PyExc_TypeError,
-			"setParameter() takes parameter id (string), and value (float) arguments");
-	return 0; }
+        PyErr_SetString(PyExc_TypeError,
+                        "setParameter() takes parameter id (string), and value (float) arguments");
+        return 0; }
 
     PyPluginObject *pd = getPluginObject(self);
     if (!pd) return 0;
@@ -301,6 +308,23 @@
     return Py_True;
 }
 
+static PyObject *
+selectProgram(PyObject *self, PyObject *args)
+{
+    PyObject *pyParam;
+
+    if (!PyArg_ParseTuple(args, "S", &pyParam)) {
+        PyErr_SetString(PyExc_TypeError,
+                        "selectProgram() takes parameter id (string) argument");
+        return 0; }
+
+    PyPluginObject *pd = getPluginObject(self);
+    if (!pd) return 0;
+
+    pd->plugin->selectProgram(PyString_AS_STRING(pyParam));
+    return Py_True;
+}
+
 static
 PyObject *
 convertFeatureSet(const Plugin::FeatureSet &fs)
@@ -353,24 +377,24 @@
 }
 
 static PyObject *
-vampyhost_process(PyObject *self, PyObject *args)
+process(PyObject *self, PyObject *args)
 {
     PyObject *pyBuffer;
     PyObject *pyRealTime;
 
     if (!PyArg_ParseTuple(args, "OO",
-			  &pyBuffer,			// Audio data
-			  &pyRealTime)) {		// TimeStamp
-	PyErr_SetString(PyExc_TypeError,
-			"process() takes plugin handle (object), buffer (2D array of channels * samples floats) and timestamp (RealTime) arguments");
-	return 0; }
+                          &pyBuffer,                    // Audio data
+                          &pyRealTime)) {               // TimeStamp
+        PyErr_SetString(PyExc_TypeError,
+                        "process() takes plugin handle (object), buffer (2D array of channels * samples floats) and timestamp (RealTime) arguments");
+        return 0; }
 
     if (!PyRealTime_Check(pyRealTime)) {
-	PyErr_SetString(PyExc_TypeError,"Valid timestamp required.");
-	return 0; }
+        PyErr_SetString(PyExc_TypeError,"Valid timestamp required.");
+        return 0; }
 
     if (!PyList_Check(pyBuffer)) {
-	PyErr_SetString(PyExc_TypeError, "List of NumPy Array required for process input.");
+        PyErr_SetString(PyExc_TypeError, "List of NumPy Array required for process input.");
         return 0;
     }
 
@@ -378,16 +402,16 @@
     if (!pd) return 0;
 
     if (!pd->isInitialised) {
-	PyErr_SetString(PyExc_StandardError,
-			"Plugin has not been initialised.");
-	return 0;
+        PyErr_SetString(PyExc_StandardError,
+                        "Plugin has not been initialised.");
+        return 0;
     }
 
     int channels =  pd->channels;
 
     if (PyList_GET_SIZE(pyBuffer) != channels) {
         cerr << "Wrong number of channels: got " << PyList_GET_SIZE(pyBuffer) << ", expected " << channels << endl;
-	PyErr_SetString(PyExc_TypeError, "Wrong number of channels");
+        PyErr_SetString(PyExc_TypeError, "Wrong number of channels");
         return 0;
     }
 
@@ -420,15 +444,15 @@
 }
 
 static PyObject *
-vampyhost_getRemainingFeatures(PyObject *self, PyObject *)
+getRemainingFeatures(PyObject *self, PyObject *)
 {
     PyPluginObject *pd = getPluginObject(self);
     if (!pd) return 0;
 
     if (!pd->isInitialised) {
-	PyErr_SetString(PyExc_StandardError,
-			"Plugin has not been initialised.");
-	return 0;
+        PyErr_SetString(PyExc_StandardError,
+                        "Plugin has not been initialised.");
+        return 0;
     }
 
     Plugin::FeatureSet fs = pd->plugin->getRemainingFeatures();
@@ -437,7 +461,7 @@
 }
 
 static PyObject *
-vampyhost_getPreferredBlockSize(PyObject *self, PyObject *)
+getPreferredBlockSize(PyObject *self, PyObject *)
 {
     PyPluginObject *pd = getPluginObject(self);
     if (!pd) return 0;
@@ -445,7 +469,7 @@
 }
 
 static PyObject *
-vampyhost_getPreferredStepSize(PyObject *self, PyObject *)
+getPreferredStepSize(PyObject *self, PyObject *)
 {
     PyPluginObject *pd = getPluginObject(self);
     if (!pd) return 0;
@@ -453,7 +477,7 @@
 }
 
 static PyObject *
-vampyhost_getMinChannelCount(PyObject *self, PyObject *)
+getMinChannelCount(PyObject *self, PyObject *)
 {
     PyPluginObject *pd = getPluginObject(self);
     if (!pd) return 0;
@@ -461,7 +485,7 @@
 }
 
 static PyObject *
-vampyhost_getMaxChannelCount(PyObject *self, PyObject *)
+getMaxChannelCount(PyObject *self, PyObject *)
 {
     PyPluginObject *pd = getPluginObject(self);
     if (!pd) return 0;
@@ -469,7 +493,7 @@
 }
     
 static PyObject *
-vampyhost_unload(PyObject *self, PyObject *)
+unload(PyObject *self, PyObject *)
 {
     PyPluginObject *pd = getPluginObject(self);
     if (!pd) return 0;
@@ -484,13 +508,16 @@
 static PyMemberDef PyPluginObject_members[] =
 {
     {(char *)"info", T_OBJECT, offsetof(PyPluginObject, info), READONLY,
-     xx_foo_doc},
+     (char *)"info -> A read-only dictionary of plugin metadata."},
 
     {(char *)"inputDomain", T_INT, offsetof(PyPluginObject, inputDomain), READONLY,
-     xx_foo_doc},
+     (char *)"inputDomain -> The format of input audio required by the plugin, either vampyhost.TimeDomain or vampyhost.FrequencyDomain."},
 
     {(char *)"parameters", T_OBJECT, offsetof(PyPluginObject, parameters), READONLY,
-     xx_foo_doc},
+     (char *)"parameters -> A list of metadata dictionaries describing the plugin's configurable parameters."},
+
+    {(char *)"programs", T_OBJECT, offsetof(PyPluginObject, programs), READONLY,
+     (char *)"programs -> A list of the programs available for this plugin, if any."},
     
     {0, 0}
 };
@@ -498,40 +525,43 @@
 static PyMethodDef PyPluginObject_methods[] =
 {
     {"getOutputs", getOutputs, METH_NOARGS,
-     xx_foo_doc},
+     "getOutputs() -> Obtain the output descriptors for all of the plugin's outputs."},
 
-    {"getParameterValue",	vampyhost_getParameter, METH_VARARGS,
-     xx_foo_doc}, //!!! fix all these!
+    {"getParameterValue", getParameter, METH_VARARGS,
+     "getParameterValue(identifier) -> Return the value of the parameter with the given identifier."},
 
-    {"setParameterValue",	vampyhost_setParameter, METH_VARARGS,
-     xx_foo_doc},
+    {"setParameterValue", setParameter, METH_VARARGS,
+     "setParameterValue(identifier, value) -> Set the parameter with the given identifier to the given value."},
 
-    {"getPreferredBlockSize",	vampyhost_getPreferredBlockSize, METH_VARARGS,
-     xx_foo_doc}, //!!! fix all these!
+    {"selectProgram", selectProgram, METH_VARARGS,
+     "selectProgram(name) -> Select the processing program with the given name."},
+    
+    {"getPreferredBlockSize", getPreferredBlockSize, METH_VARARGS,
+     "getPreferredBlockSize() -> Return the plugin's preferred processing block size, or 0 if the plugin accepts any block size."},
 
-    {"getPreferredStepSize",	vampyhost_getPreferredStepSize, METH_VARARGS,
-     xx_foo_doc},
+    {"getPreferredStepSize", getPreferredStepSize, METH_VARARGS,
+     "getPreferredStepSize() -> Return the plugin's preferred processing step size, or 0 if the plugin allows the host to select. If this is 0, the host should normally choose the same step as block size for time-domain plugins, or half the block size for frequency-domain plugins."},
 
-    {"getMinChannelCount",	vampyhost_getMinChannelCount, METH_VARARGS,
-     xx_foo_doc}, //!!! fix all these!
+    {"getMinChannelCount", getMinChannelCount, METH_VARARGS,
+     "getMinChannelCount() -> Return the minimum number of channels of audio data the plugin accepts as input."},
 
-    {"getMaxChannelCount",	vampyhost_getMaxChannelCount, METH_VARARGS,
-     xx_foo_doc},
+    {"getMaxChannelCount", getMaxChannelCount, METH_VARARGS,
+     "getMaxChannelCount() -> Return the maximum number of channels of audio data the plugin accepts as input."},
     
-    {"initialise",	vampyhost_initialise, METH_VARARGS,
-     xx_foo_doc},
+    {"initialise", initialise, METH_VARARGS,
+     "initialise(channels, stepSize, blockSize) -> Initialise the plugin for the given number of channels and processing frame sizes. This must be called before process() can be used."},
 
-    {"reset",	vampyhost_reset, METH_NOARGS,
-     xx_foo_doc},
+    {"reset", reset, METH_NOARGS,
+     "reset() -> Reset the plugin after processing, to prepare for another processing run with the same parameters."},
 
-    {"process",	vampyhost_process, METH_VARARGS,
-     xx_foo_doc},
+    {"process", process, METH_VARARGS,
+     "process(block, timestamp) -> Provide one processing frame to the plugin, with its timestamp, and obtain any features that were extracted immediately from this frame."},
 
-    {"getRemainingFeatures",	vampyhost_getRemainingFeatures, METH_NOARGS,
-     xx_foo_doc},
+    {"getRemainingFeatures", getRemainingFeatures, METH_NOARGS,
+     "getRemainingFeatures() -> Obtain any features extracted at the end of processing."},
 
-    {"unload", vampyhost_unload, METH_NOARGS,
-     xx_foo_doc},
+    {"unload", unload, METH_NOARGS,
+     "unload() -> Dispose of the plugin. You cannot use the plugin object again after calling this. Note that unloading also happens automatically when the plugin object's reference count reaches zero; this function is only necessary if you wish to ensure the native part of the plugin is disposed of before then."},
     
     {0, 0}
 };
@@ -540,45 +570,45 @@
 PyTypeObject Plugin_Type = 
 {
     PyObject_HEAD_INIT(NULL)
-    0,						/*ob_size*/
-    "vampyhost.Plugin",				/*tp_name*/
-    sizeof(PyPluginObject),	/*tp_basicsize*/
-    0,		/*tp_itemsize*/
+    0,                                  /*ob_size*/
+    "vampyhost.Plugin",                 /*tp_name*/
+    sizeof(PyPluginObject),             /*tp_basicsize*/
+    0,                                  /*tp_itemsize*/
     (destructor)PyPluginObject_dealloc, /*tp_dealloc*/
-    0,						/*tp_print*/
-    0, /*tp_getattr*/
-    0, /*tp_setattr*/
-    0,						/*tp_compare*/
-    0,			/*tp_repr*/
-    0,	/*tp_as_number*/
-    0,						/*tp_as_sequence*/
-    0,						/*tp_as_mapping*/
-    0,						/*tp_hash*/
-    0,                      /*tp_call*/
-    0,                      /*tp_str*/
-    PyObject_GenericGetAttr,                      /*tp_getattro*/
-    PyObject_GenericSetAttr,                      /*tp_setattro*/
-    0,                      /*tp_as_buffer*/
-    Py_TPFLAGS_DEFAULT,     /*tp_flags*/
-    "Plugin Object",      /*tp_doc*/
-    0,                      /*tp_traverse*/
-    0,                      /*tp_clear*/
-    0,                      /*tp_richcompare*/
-    0,                      /*tp_weaklistoffset*/
-    0,                      /*tp_iter*/
-    0,                      /*tp_iternext*/
-    PyPluginObject_methods,       /*tp_methods*/ 
-    PyPluginObject_members,                      /*tp_members*/
-    0,                      /*tp_getset*/
-    0,                      /*tp_base*/
-    0,                      /*tp_dict*/
-    0,                      /*tp_descr_get*/
-    0,                      /*tp_descr_set*/
-    0,                      /*tp_dictoffset*/
-    0,                      /*tp_init*/
-    PyType_GenericAlloc,         /*tp_alloc*/
-    0,           /*tp_new*/
-    PyObject_Del,			/*tp_free*/
-    0,                      /*tp_is_gc*/
+    0,                                  /*tp_print*/
+    0,                                  /*tp_getattr*/
+    0,                                  /*tp_setattr*/
+    0,                                  /*tp_compare*/
+    0,                                  /*tp_repr*/
+    0,                                  /*tp_as_number*/
+    0,                                  /*tp_as_sequence*/
+    0,                                  /*tp_as_mapping*/
+    0,                                  /*tp_hash*/
+    0,                                  /*tp_call*/
+    0,                                  /*tp_str*/
+    PyObject_GenericGetAttr,            /*tp_getattro*/
+    PyObject_GenericSetAttr,            /*tp_setattro*/
+    0,                                  /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT,                 /*tp_flags*/
+    "Vamp plugin object.",                    /*tp_doc*/
+    0,                                  /*tp_traverse*/
+    0,                                  /*tp_clear*/
+    0,                                  /*tp_richcompare*/
+    0,                                  /*tp_weaklistoffset*/
+    0,                                  /*tp_iter*/
+    0,                                  /*tp_iternext*/
+    PyPluginObject_methods,             /*tp_methods*/ 
+    PyPluginObject_members,             /*tp_members*/
+    0,                                  /*tp_getset*/
+    0,                                  /*tp_base*/
+    0,                                  /*tp_dict*/
+    0,                                  /*tp_descr_get*/
+    0,                                  /*tp_descr_set*/
+    0,                                  /*tp_dictoffset*/
+    0,                                  /*tp_init*/
+    PyType_GenericAlloc,                /*tp_alloc*/
+    0,                                  /*tp_new*/
+    PyObject_Del,                       /*tp_free*/
+    0,                                  /*tp_is_gc*/
 };
 
--- a/PyPluginObject.h	Wed Nov 26 14:27:56 2014 +0000
+++ b/PyPluginObject.h	Wed Nov 26 15:23:56 2014 +0000
@@ -54,6 +54,7 @@
     PyObject *info;
     int inputDomain;
     PyObject *parameters;
+    PyObject *programs;
 };
 
 PyAPI_DATA(PyTypeObject) Plugin_Type;
--- a/PyRealTime.cpp	Wed Nov 26 14:27:56 2014 +0000
+++ b/PyRealTime.cpp	Wed Nov 26 15:23:56 2014 +0000
@@ -52,51 +52,51 @@
     const char *fmt = NULL;
 
     if (
-	/// new RealTime from ('format',float) e.g. ('seconds',2.34123)   
-	!PyArg_ParseTuple(args, "|sd:RealTime.new ", 
-			  (const char *) &fmt, 
-			  (double *) &unary) 	&&
+        /// new RealTime from ('format',float) e.g. ('seconds',2.34123)   
+        !PyArg_ParseTuple(args, "|sd:RealTime.new ", 
+                          (const char *) &fmt, 
+                          (double *) &unary)    &&
 
-	/// new RealTime from (sec{int},nsec{int}) e.g. (2,34)
-	!PyArg_ParseTuple(args, "|II:RealTime.new ", 
-			  (unsigned int*) &sec, 
-			  (unsigned int*) &nsec) 
-		
-	) { 
-	PyErr_SetString(PyExc_TypeError, 
-			"RealTime initialised with wrong arguments.");
-	return NULL; 
+        /// new RealTime from (sec{int},nsec{int}) e.g. (2,34)
+        !PyArg_ParseTuple(args, "|II:RealTime.new ", 
+                          (unsigned int*) &sec, 
+                          (unsigned int*) &nsec) 
+                
+        ) { 
+        PyErr_SetString(PyExc_TypeError, 
+                        "RealTime initialised with wrong arguments.");
+        return NULL; 
     }
 
     // PyErr_Clear();
 
     // RealTimeObject *self = PyObject_New(RealTimeObject, &RealTime_Type); 
     RealTimeObject *self = (RealTimeObject*)type->tp_alloc(type, 0);
-	
+        
     if (self == NULL) return NULL;
 
     self->rt = NULL;
 
     if (sec == 0 && nsec == 0 && fmt == 0) 
-	self->rt = new RealTime();
+        self->rt = new RealTime();
     else if (fmt == 0)
-	self->rt = new RealTime(sec,nsec);
+        self->rt = new RealTime(sec,nsec);
     else { 
         /// new RealTime from seconds or milliseconds: i.e. >>>RealTime('seconds',12.3)
-	if (!string(fmt).compare("float") ||
-	    !string(fmt).compare("seconds"))  
-	    self->rt = new RealTime( 
-		RealTime::fromSeconds((double) unary)); 
+        if (!string(fmt).compare("float") ||
+            !string(fmt).compare("seconds"))  
+            self->rt = new RealTime( 
+                RealTime::fromSeconds((double) unary)); 
 
-	if (!string(fmt).compare("milliseconds")) {
-	    self->rt = new RealTime( 
-		RealTime::fromSeconds((double) unary / 1000.0)); }
+        if (!string(fmt).compare("milliseconds")) {
+            self->rt = new RealTime( 
+                RealTime::fromSeconds((double) unary / 1000.0)); }
     }
 
     if (!self->rt) { 
-	PyErr_SetString(PyExc_TypeError, 
-			"RealTime initialised with wrong arguments.");
-	return NULL; 
+        PyErr_SetString(PyExc_TypeError, 
+                        "RealTime initialised with wrong arguments.");
+        return NULL; 
     }
 
     return (PyObject *) self;
@@ -106,7 +106,7 @@
 static void
 RealTimeObject_dealloc(RealTimeObject *self)
 {
-    if (self->rt) delete self->rt; 	//delete the C object
+    if (self->rt) delete self->rt;      //delete the C object
     PyObject_Del(self); //delete the Python object (original)
     /// this requires PyType_Ready() which fills ob_type
     // self->ob_type->tp_free((PyObject*)self); 
@@ -134,17 +134,17 @@
 RealTime_toFrame(PyObject *self, PyObject *args)
 {
     unsigned int samplerate;
-	
+        
     if ( !PyArg_ParseTuple(args, "I:realtime.toFrame object ", 
-			   (unsigned int *) &samplerate )) {
-	PyErr_SetString(PyExc_ValueError,"Integer Sample Rate Required.");
-	return NULL;
+                           (unsigned int *) &samplerate )) {
+        PyErr_SetString(PyExc_ValueError,"Integer Sample Rate Required.");
+        return NULL;
     }
-	
+        
     return Py_BuildValue("k", 
-			 RealTime::realTime2Frame( 
-			     *(const RealTime*) ((RealTimeObject*)self)->rt, 
-			     (unsigned int) samplerate));
+                         RealTime::realTime2Frame( 
+                             *(const RealTime*) ((RealTimeObject*)self)->rt, 
+                             (unsigned int) samplerate));
 }
 
 /* Conversion of realtime to a double precision floating point value */
@@ -153,27 +153,27 @@
 RealTime_float(PyObject *s)
 {
     double drt = ((double) ((RealTimeObject*)s)->rt->sec + 
-		  (double)((double) ((RealTimeObject*)s)->rt->nsec)/1000000000);
-    return PyFloat_FromDouble(drt);	
+                  (double)((double) ((RealTimeObject*)s)->rt->nsec)/1000000000);
+    return PyFloat_FromDouble(drt);     
 }
 
 
 /* Type object's (RealTime) methods table */
 static PyMethodDef RealTime_methods[] = 
 {
-    {"values",	(PyCFunction)RealTime_values,	METH_NOARGS,
+    {"values",  (PyCFunction)RealTime_values,   METH_NOARGS,
      PyDoc_STR("values() -> Tuple of sec,nsec representation.")},
 
-    {"toString",	(PyCFunction)RealTime_toString,	METH_NOARGS,
+    {"toString",        (PyCFunction)RealTime_toString, METH_NOARGS,
      PyDoc_STR("toString() -> Return a user-readable string to the nearest millisecond in a form like HH:MM:SS.mmm")},
 
-    {"toFrame",	(PyCFunction)RealTime_toFrame,	METH_VARARGS,
+    {"toFrame", (PyCFunction)RealTime_toFrame,  METH_VARARGS,
      PyDoc_STR("toFrame(samplerate) -> Sample count for given sample rate.")},
 
-    {"toFloat",	(PyCFunction)RealTime_float,	METH_NOARGS,
+    {"toFloat", (PyCFunction)RealTime_float,    METH_NOARGS,
      PyDoc_STR("toFloat() -> Floating point representation.")},
-	
-    {NULL,		NULL}		/* sentinel */
+        
+    {NULL,              NULL}           /* sentinel */
 };
 
 
@@ -187,13 +187,13 @@
 {
 
     if ( !string(name).compare("sec")) { 
-	self->rt->sec= (int) PyInt_AS_LONG(value);
-	return 0;
+        self->rt->sec= (int) PyInt_AS_LONG(value);
+        return 0;
     }
 
     if ( !string(name).compare("nsec")) { 
-	self->rt->nsec= (int) PyInt_AS_LONG(value);
-	return 0;
+        self->rt->nsec= (int) PyInt_AS_LONG(value);
+        return 0;
     }
 
     return -1;
@@ -204,17 +204,17 @@
 {
 
     if ( !string(name).compare("sec") ) { 
-	return PyInt_FromSsize_t(
-	    (Py_ssize_t) self->rt->sec); 
+        return PyInt_FromSsize_t(
+            (Py_ssize_t) self->rt->sec); 
     } 
 
     if ( !string(name).compare("nsec") ) { 
-	return PyInt_FromSsize_t(
-	    (Py_ssize_t) self->rt->nsec); 
+        return PyInt_FromSsize_t(
+            (Py_ssize_t) self->rt->nsec); 
     } 
 
     return Py_FindMethod(RealTime_methods, 
-			 (PyObject *)self, name);
+                         (PyObject *)self, name);
 }
 
 /* String representation called by e.g. str(realtime), print realtime*/
@@ -222,7 +222,7 @@
 RealTime_repr(PyObject *self)
 {
     return Py_BuildValue("s",
-			 ((RealTimeObject*)self)->rt->toString().c_str());
+                         ((RealTimeObject*)self)->rt->toString().c_str());
 }
 
 
@@ -233,11 +233,11 @@
 RealTime_add(PyObject *s, PyObject *w)
 {
     RealTimeObject *result = 
-	PyObject_New(RealTimeObject, &RealTime_Type); 
+        PyObject_New(RealTimeObject, &RealTime_Type); 
     if (result == NULL) return NULL;
 
     result->rt = new RealTime(
-	*((RealTimeObject*)s)->rt + *((RealTimeObject*)w)->rt);
+        *((RealTimeObject*)s)->rt + *((RealTimeObject*)w)->rt);
     return (PyObject*)result;
 }
 
@@ -245,39 +245,39 @@
 RealTime_subtract(PyObject *s, PyObject *w)
 {
     RealTimeObject *result = 
-	PyObject_New(RealTimeObject, &RealTime_Type); 
+        PyObject_New(RealTimeObject, &RealTime_Type); 
     if (result == NULL) return NULL;
 
     result->rt = new RealTime(
-	*((RealTimeObject*)s)->rt - *((RealTimeObject*)w)->rt);
+        *((RealTimeObject*)s)->rt - *((RealTimeObject*)w)->rt);
     return (PyObject*)result;
 }
 
 static PyNumberMethods realtime_as_number = 
 {
-    RealTime_add,			/*nb_add*/
-    RealTime_subtract,		/*nb_subtract*/
-    0,						/*nb_multiply*/
-    0,				 		/*nb_divide*/
-    0,						/*nb_remainder*/
-    0,      	            /*nb_divmod*/
-    0,                   	/*nb_power*/
-    0,                  	/*nb_neg*/
-    0,                		/*nb_pos*/
-    0,                  	/*(unaryfunc)array_abs,*/
-    0,                    	/*nb_nonzero*/
-    0,                    	/*nb_invert*/
-    0,       				/*nb_lshift*/
-    0,      				/*nb_rshift*/
-    0,      				/*nb_and*/
-    0,      				/*nb_xor*/
-    0,       				/*nb_or*/
+    RealTime_add,                       /*nb_add*/
+    RealTime_subtract,          /*nb_subtract*/
+    0,                                          /*nb_multiply*/
+    0,                                          /*nb_divide*/
+    0,                                          /*nb_remainder*/
+    0,                      /*nb_divmod*/
+    0,                          /*nb_power*/
+    0,                          /*nb_neg*/
+    0,                          /*nb_pos*/
+    0,                          /*(unaryfunc)array_abs,*/
+    0,                          /*nb_nonzero*/
+    0,                          /*nb_invert*/
+    0,                                  /*nb_lshift*/
+    0,                                  /*nb_rshift*/
+    0,                                  /*nb_and*/
+    0,                                  /*nb_xor*/
+    0,                                  /*nb_or*/
     0,                      /*nb_coerce*/
-    0,						/*nb_int*/
-    0,				        /*nb_long*/
+    0,                                          /*nb_int*/
+    0,                                  /*nb_long*/
     (unaryfunc)RealTime_float,/*nb_float*/
-    0,               		/*nb_oct*/
-    0,               		/*nb_hex*/
+    0,                          /*nb_oct*/
+    0,                          /*nb_hex*/
 };
 
 /* REAL-TIME TYPE OBJECT */
@@ -289,28 +289,28 @@
 PyTypeObject RealTime_Type = 
 {
     PyObject_HEAD_INIT(NULL)
-    0,						/*ob_size*/
-    "vampy.RealTime",				/*tp_name*/
-    sizeof(RealTimeObject),	/*tp_basicsize*/
-    0,//sizeof(RealTime),		/*tp_itemsize*/
-    /*	 	methods	 	*/
+    0,                          /*ob_size*/
+    "vampy.RealTime",           /*tp_name*/
+    sizeof(RealTimeObject),     /*tp_basicsize*/
+    0,                          /*tp_itemsize*/
+    /*          methods         */
     (destructor)RealTimeObject_dealloc, /*tp_dealloc*/
-    0,						/*tp_print*/
+    0,                                  /*tp_print*/
     (getattrfunc)RealTime_getattr, /*tp_getattr*/
     (setattrfunc)RealTime_setattr, /*tp_setattr*/
-    0,						/*tp_compare*/
-    RealTime_repr,			/*tp_repr*/
-    &realtime_as_number,	/*tp_as_number*/
-    0,						/*tp_as_sequence*/
-    0,						/*tp_as_mapping*/
-    0,						/*tp_hash*/
-    0,//(ternaryfunc)RealTime_new,                      /*tp_call*/
+    0,                             /*tp_compare*/
+    RealTime_repr,                 /*tp_repr*/
+    &realtime_as_number,        /*tp_as_number*/
+    0,                          /*tp_as_sequence*/
+    0,                          /*tp_as_mapping*/
+    0,                          /*tp_hash*/
+    0,                      /*tp_call*/
     0,                      /*tp_str*/
     0,                      /*tp_getattro*/
     0,                      /*tp_setattro*/
     0,                      /*tp_as_buffer*/
     Py_TPFLAGS_DEFAULT,     /*tp_flags*/
-    "RealTime Object",      /*tp_doc*/
+    "RealTime object, used for Vamp plugin timestamps.",      /*tp_doc*/
     0,                      /*tp_traverse*/
     0,                      /*tp_clear*/
     0,                      /*tp_richcompare*/
@@ -328,7 +328,7 @@
     0,                      /*tp_init*/
     RealTime_alloc,         /*tp_alloc*/
     RealTime_new,           /*tp_new*/
-    RealTime_free,			/*tp_free*/
+    RealTime_free,          /*tp_free*/
     0,                      /*tp_is_gc*/
 };
 
@@ -341,7 +341,7 @@
 PyRealTime_FromRealTime(const Vamp::RealTime& rt) {
 
     RealTimeObject *self =
-	PyObject_New(RealTimeObject, &RealTime_Type); 
+        PyObject_New(RealTimeObject, &RealTime_Type); 
     if (self == NULL) return NULL;
 
     self->rt = new RealTime(rt);
@@ -355,9 +355,9 @@
     RealTimeObject *s = (RealTimeObject*) self; 
 
     if (!PyRealTime_Check(s)) {
-	PyErr_SetString(PyExc_TypeError, "RealTime Object Expected.");
-	cerr << "in call PyRealTime_AsPointer(): RealTime Object Expected. " << endl;
-	return NULL; }
+        PyErr_SetString(PyExc_TypeError, "RealTime Object Expected.");
+        cerr << "in call PyRealTime_AsPointer(): RealTime Object Expected. " << endl;
+        return NULL; }
     return s->rt; 
 };
 
--- a/test_metadata.py	Wed Nov 26 14:27:56 2014 +0000
+++ b/test_metadata.py	Wed Nov 26 15:23:56 2014 +0000
@@ -17,7 +17,14 @@
 
 def test_getlibrary():
     lib = vh.getLibraryFor(testPluginKey)
-    assert lib != ""
+    assert lib.find("vamp-test-plugin") >= 0
+    try:
+        lib = vh.getLibraryFor("not a well-formatted plugin key")
+        assert False
+    except TypeError:
+        pass
+    lib = vh.getLibraryFor("nonexistent-library:nonexistent-plugin")
+    assert lib == ""
 
 def test_getoutputlist():
     outputs = vh.getOutputsOf(testPluginKey)
--- a/test_plugin_metadata.py	Wed Nov 26 14:27:56 2014 +0000
+++ b/test_plugin_metadata.py	Wed Nov 26 15:23:56 2014 +0000
@@ -7,14 +7,14 @@
 
 def test_inputdomain():
     plug = vh.loadPlugin(testPluginKey, rate)
-    assert(plug.inputDomain == vh.TimeDomain)
+    assert plug.inputDomain == vh.TimeDomain
 
 def test_info():
     plug = vh.loadPlugin(testPluginKey, rate)
-    assert(plug.info["identifier"] == "vamp-test-plugin")
+    assert plug.info["identifier"] == "vamp-test-plugin"
     
 def test_parameterdescriptors():
     plug = vh.loadPlugin(testPluginKey, rate)
-    assert(plug.parameters[0]["identifier"] == "produce_output")
+    assert plug.parameters[0]["identifier"] == "produce_output"
 
     
--- a/vampyhost.cpp	Wed Nov 26 14:27:56 2014 +0000
+++ b/vampyhost.cpp	Wed Nov 26 15:23:56 2014 +0000
@@ -35,7 +35,7 @@
     authorization.
 */
 
-//include for python extension module: must be first
+// include for python extension module: must be first
 #include <Python.h>
 
 // define a unique API pointer 
@@ -63,12 +63,10 @@
 using namespace Vamp;
 using namespace Vamp::HostExt;
 
-PyDoc_STRVAR(xx_foo_doc, "Some description"); //!!!
-
 //!!! todo: conv errors
 
 static PyObject *
-vampyhost_enumeratePlugins(PyObject *self, PyObject *)
+enumeratePlugins(PyObject *self, PyObject *)
 {
     PluginLoader *loader = PluginLoader::getInstance();
     vector<PluginLoader::PluginKey> plugins = loader->listPlugins();
@@ -77,7 +75,7 @@
 }
 
 static PyObject *
-vampyhost_getPluginPath(PyObject *self, PyObject *)
+getPluginPath(PyObject *self, PyObject *)
 {
     vector<string> path = PluginHostAdapter::getPluginPath();
     VectorConversion conv;
@@ -92,23 +90,23 @@
     // check pluginKey validity
     string::size_type ki = pluginKey.find(':');
     if (ki == string::npos) {
-	PyErr_SetString(PyExc_TypeError,
-			"Plugin key must be of the form library:identifier");
-       	return "";
+        PyErr_SetString(PyExc_TypeError,
+                        "Plugin key must be of the form library:identifier");
+        return "";
     }
 
     return pluginKey;
 }
 
 static PyObject *
-vampyhost_getLibraryFor(PyObject *self, PyObject *args)
+getLibraryFor(PyObject *self, PyObject *args)
 {
     PyObject *pyPluginKey;
 
     if (!PyArg_ParseTuple(args, "S", &pyPluginKey)) {
-	PyErr_SetString(PyExc_TypeError,
-			"getLibraryPathForPlugin() takes plugin key (string) argument");
-	return 0; }
+        PyErr_SetString(PyExc_TypeError,
+                        "getLibraryPathForPlugin() takes plugin key (string) argument");
+        return 0; }
 
     string pluginKey = toPluginKey(pyPluginKey);
     if (pluginKey == "") return 0;
@@ -120,35 +118,35 @@
 }
 
 static PyObject *
-vampyhost_getPluginCategory(PyObject *self, PyObject *args)
+getPluginCategory(PyObject *self, PyObject *args)
 {
     PyObject *pyPluginKey;
 
     if (!PyArg_ParseTuple(args, "S", &pyPluginKey)) {
-	PyErr_SetString(PyExc_TypeError,
-			"getPluginCategory() takes plugin key (string) argument");
-	return 0; }
+        PyErr_SetString(PyExc_TypeError,
+                        "getPluginCategory() takes plugin key (string) argument");
+        return 0; }
 
     string pluginKey = toPluginKey(pyPluginKey);
     if (pluginKey == "") return 0;
 
     PluginLoader *loader = PluginLoader::getInstance();
     PluginLoader::PluginCategoryHierarchy
-	category = loader->getPluginCategory(pluginKey);
+        category = loader->getPluginCategory(pluginKey);
 
     VectorConversion conv;
     return conv.PyValue_From_StringVector(category);
 }
 
 static PyObject *
-vampyhost_getOutputList(PyObject *self, PyObject *args)
+getOutputList(PyObject *self, PyObject *args)
 {
     PyObject *pyPluginKey;
 
     if (!PyArg_ParseTuple(args, "S", &pyPluginKey)) {
-	PyErr_SetString(PyExc_TypeError,
-			"getOutputList() takes plugin key (string) argument");
-	return 0; }
+        PyErr_SetString(PyExc_TypeError,
+                        "getOutputList() takes plugin key (string) argument");
+        return 0; }
 
     Plugin::OutputList outputs;
 
@@ -170,26 +168,26 @@
     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);
+        PyObject *pyOutputId =
+            PyString_FromString(outputs[i].identifier.c_str());
+        PyList_SET_ITEM(pyList, i, pyOutputId);
     }
 
     return pyList;
 }
 
 static PyObject *
-vampyhost_loadPlugin(PyObject *self, PyObject *args)
+loadPlugin(PyObject *self, PyObject *args)
 {
     PyObject *pyPluginKey;
     float inputSampleRate;
 
     if (!PyArg_ParseTuple(args, "Sf",
-			  &pyPluginKey,
-			  &inputSampleRate)) {
-	PyErr_SetString(PyExc_TypeError,
-			"loadPlugin() takes plugin key (string) and sample rate (float) arguments");
-	return 0; }
+                          &pyPluginKey,
+                          &inputSampleRate)) {
+        PyErr_SetString(PyExc_TypeError,
+                        "loadPlugin() takes plugin key (string) and sample rate (float) arguments");
+        return 0; }
 
     string pluginKey = toPluginKey(pyPluginKey);
     if (pluginKey == "") return 0;
@@ -199,9 +197,9 @@
     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 0;
+        string pyerr("Failed to load plugin: "); pyerr += pluginKey;
+        PyErr_SetString(PyExc_TypeError,pyerr.c_str());
+        return 0;
     }
 
     return PyPluginObject_From_Plugin(plugin);
@@ -210,29 +208,28 @@
 // module methods table
 static PyMethodDef vampyhost_methods[] = {
 
-    {"listPlugins",	vampyhost_enumeratePlugins,	METH_NOARGS,
-     xx_foo_doc},
+    {"listPlugins", enumeratePlugins, METH_NOARGS,
+     "listPlugins() -> Return a list of the plugin keys of all installed Vamp plugins." },
 
-    {"getPluginPath",	vampyhost_getPluginPath, METH_NOARGS,
-     xx_foo_doc},
+    {"getPluginPath", getPluginPath, METH_NOARGS,
+     "getPluginPath() -> Return a list of directories which will be searched for Vamp plugins. This may be changed by setting the VAMP_PATH environment variable."},
 
-    {"getCategoryOf",	vampyhost_getPluginCategory, METH_VARARGS,
-     xx_foo_doc},
+    {"getCategoryOf", getPluginCategory, METH_VARARGS,
+     "getCategoryOf(pluginKey) -> Return the category of a Vamp plugin given its key, if known. The category is expressed as a list of nested types from least to most specific."},
 
-    {"getLibraryFor",	vampyhost_getLibraryFor, METH_VARARGS,
-     xx_foo_doc},
+    {"getLibraryFor", getLibraryFor, METH_VARARGS,
+     "getLibraryFor(pluginKey) -> Return the file path of the Vamp plugin library in which the given plugin key is found, or an empty string if the plugin is not installed."},
 
-    {"getOutputsOf",	vampyhost_getOutputList, METH_VARARGS,
-     xx_foo_doc},
+    {"getOutputsOf", getOutputList, METH_VARARGS,
+     "getOutputsOf(pluginKey) -> Return a list of the output identifiers of the plugin with the given key, if installed."},
 
-    {"loadPlugin",	vampyhost_loadPlugin, METH_VARARGS,
-     xx_foo_doc},
+    {"loadPlugin", loadPlugin, METH_VARARGS,
+     "loadPlugin(pluginKey, samplerate) -> Load the plugin that has the given key, if installed, and return the plugin object."},
 
-    {0,		0}		/* sentinel */
+    {0, 0}              /* sentinel */
 };
 
-//Documentation for our new module
-PyDoc_STRVAR(module_doc, "This is a template module just for instruction.");
+PyDoc_STRVAR(module_doc, "Load and run Vamp audio analysis plugins.");
 
 static int
 setint(PyObject *d, const char *name, int value)