changeset 58:c38e12d4bbdd

Merge from branch outputid-string-in-featureset
author Chris Cannam <c.cannam@qmul.ac.uk>
date Mon, 19 Sep 2016 14:48:43 +0100
parents ce6cb3308bd7 (current diff) 7aec704705c7 (diff)
children 77833938f0f8
files
diffstat 16 files changed, 666 insertions(+), 249 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Wed Sep 14 14:43:37 2016 +0100
+++ b/Makefile	Mon Sep 19 14:48:43 2016 +0100
@@ -1,5 +1,5 @@
 
-CXXFLAGS	:= -Wall -Werror -g -std=c++11
+CXXFLAGS	:= -Wall -Werror -g3 -std=c++11
 INCFLAGS	:= -Ivamp-plugin-sdk -Ijson -I/usr/local/include -Icapnproto -I.
 
 LDFLAGS		:= vamp-plugin-sdk/libvamp-hostsdk.a -L/usr/local/lib -lcapnp -lkj -ldl
--- a/bits/CountingPluginHandleMapper.h	Wed Sep 14 14:43:37 2016 +0100
+++ b/bits/CountingPluginHandleMapper.h	Mon Sep 19 14:48:43 2016 +0100
@@ -36,6 +36,8 @@
 #define VAMPIPE_COUNTING_PLUGIN_HANDLE_MAPPER_H
 
 #include "PluginHandleMapper.h"
+#include "PluginOutputIdMapper.h"
+#include "DefaultPluginOutputIdMapper.h"
 
 #include <set>
 #include <map>
@@ -50,17 +52,20 @@
 
     void addPlugin(Vamp::Plugin *p) {
 	if (m_rplugins.find(p) == m_rplugins.end()) {
-	    int32_t h = m_nextHandle++;
+	    Handle h = m_nextHandle++;
 	    m_plugins[h] = p;
 	    m_rplugins[p] = h;
+            m_outputMappers[h] =
+                std::make_shared<DefaultPluginOutputIdMapper>(p);
 	}
     }
 
-    void removePlugin(int32_t h) {
+    void removePlugin(Handle h) {
 	if (m_plugins.find(h) == m_plugins.end()) {
 	    throw NotFound();
 	}
 	Vamp::Plugin *p = m_plugins[h];
+        m_outputMappers.erase(h);
 	m_plugins.erase(h);
 	if (isConfigured(h)) {
 	    m_configuredPlugins.erase(h);
@@ -69,38 +74,52 @@
 	m_rplugins.erase(p);
     }
     
-    int32_t pluginToHandle(Vamp::Plugin *p) const {
+    Handle pluginToHandle(Vamp::Plugin *p) const {
 	if (m_rplugins.find(p) == m_rplugins.end()) {
 	    throw NotFound();
 	}
 	return m_rplugins.at(p);
     }
     
-    Vamp::Plugin *handleToPlugin(int32_t h) const {
+    Vamp::Plugin *handleToPlugin(Handle h) const {
 	if (m_plugins.find(h) == m_plugins.end()) {
 	    throw NotFound();
 	}
 	return m_plugins.at(h);
     }
 
-    bool isConfigured(int32_t h) const {
+    const std::shared_ptr<PluginOutputIdMapper> pluginToOutputIdMapper
+    (Vamp::Plugin *p) const {
+        // pluginToHandle checks the plugin has been registered with us
+        return m_outputMappers.at(pluginToHandle(p));
+    }
+
+    const std::shared_ptr<PluginOutputIdMapper> handleToOutputIdMapper
+    (Handle h) const {
+	if (m_plugins.find(h) == m_plugins.end()) {
+	    throw NotFound();
+	}
+        return m_outputMappers.at(h);
+    }
+
+    bool isConfigured(Handle h) const {
 	return m_configuredPlugins.find(h) != m_configuredPlugins.end();
     }
 
-    void markConfigured(int32_t h, int channelCount, int blockSize) {
+    void markConfigured(Handle h, int channelCount, int blockSize) {
 	m_configuredPlugins.insert(h);
 	m_channelCounts[h] = channelCount;
 	m_blockSizes[h] = blockSize;
     }
 
-    int getChannelCount(int32_t h) const {
+    int getChannelCount(Handle h) const {
 	if (m_channelCounts.find(h) == m_channelCounts.end()) {
 	    throw NotFound();
 	}
 	return m_channelCounts.at(h);
     }
 
-    int getBlockSize(int32_t h) const {
+    int getBlockSize(Handle h) const {
 	if (m_blockSizes.find(h) == m_blockSizes.end()) {
 	    throw NotFound();
 	}
@@ -108,12 +127,13 @@
     }
     
 private:
-    int32_t m_nextHandle; // NB plugin handle type must fit in JSON number
-    std::map<uint32_t, Vamp::Plugin *> m_plugins;
-    std::map<Vamp::Plugin *, uint32_t> m_rplugins;
-    std::set<uint32_t> m_configuredPlugins;
-    std::map<uint32_t, int> m_channelCounts;
-    std::map<uint32_t, int> m_blockSizes;
+    Handle m_nextHandle; // NB plugin handle type must fit in JSON number
+    std::map<Handle, Vamp::Plugin *> m_plugins;
+    std::map<Vamp::Plugin *, Handle> m_rplugins;
+    std::set<Handle> m_configuredPlugins;
+    std::map<Handle, int> m_channelCounts;
+    std::map<Handle, int> m_blockSizes;
+    std::map<Handle, std::shared_ptr<PluginOutputIdMapper>> m_outputMappers;
 };
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bits/DefaultPluginOutputIdMapper.h	Mon Sep 19 14:48:43 2016 +0100
@@ -0,0 +1,74 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Vampipe
+
+    Centre for Digital Music, Queen Mary, University of London.
+    Copyright 2006-2016 Chris Cannam and QMUL.
+  
+    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.
+*/
+
+#ifndef VAMPIPE_DEFAULT_PLUGIN_ID_MAPPER_H
+#define VAMPIPE_DEFAULT_PLUGIN_ID_MAPPER_H
+
+#include <vamp-hostsdk/Plugin.h>
+
+namespace vampipe {
+
+class DefaultPluginOutputIdMapper : public PluginOutputIdMapper
+{
+public:
+    DefaultPluginOutputIdMapper(Vamp::Plugin *p) {
+	Vamp::Plugin::OutputList outputs = p->getOutputDescriptors();
+	for (const auto &d: outputs) {
+	    m_ids.push_back(d.identifier);
+	}
+    }
+
+    virtual int idToIndex(std::string outputId) const {
+	int n = int(m_ids.size());
+	for (int i = 0; i < n; ++i) {
+	    if (outputId == m_ids[i]) {
+		return i;
+	    }
+	}
+	//!!! todo: this should in fact throw, or otherwise return an error
+	return 0;
+    }
+
+    virtual std::string indexToId(int index) const {
+	//!!! todo: this should in fact throw, or otherwise return an error
+	return m_ids[index];
+    }
+
+private:
+    std::vector<std::string> m_ids;
+};
+
+}
+
+#endif
--- a/bits/PluginHandleMapper.h	Wed Sep 14 14:43:37 2016 +0100
+++ b/bits/PluginHandleMapper.h	Mon Sep 19 14:48:43 2016 +0100
@@ -35,7 +35,10 @@
 #ifndef VAMPIPE_PLUGIN_HANDLE_MAPPER_H
 #define VAMPIPE_PLUGIN_HANDLE_MAPPER_H
 
+#include "PluginOutputIdMapper.h"
+
 #include <vamp-hostsdk/Plugin.h>
+#include <memory>
 
 namespace vampipe {
 
@@ -44,13 +47,23 @@
     // NB the handle type must fit in a JSON number
     
 public:
+    typedef int32_t Handle;
+
+    virtual ~PluginHandleMapper() { }
+    
     class NotFound : virtual public std::runtime_error {
     public:
         NotFound() : runtime_error("plugin or handle not found in mapper") { }
     };
     
-    virtual int32_t pluginToHandle(Vamp::Plugin *) const = 0; // may throw NotFound
-    virtual Vamp::Plugin *handleToPlugin(int32_t)  const = 0; // may throw NotFound
+    virtual Handle pluginToHandle(Vamp::Plugin *) const = 0; // may throw NotFound
+    virtual Vamp::Plugin *handleToPlugin(Handle)  const = 0; // may throw NotFound
+
+    virtual const std::shared_ptr<PluginOutputIdMapper> pluginToOutputIdMapper
+    (Vamp::Plugin *p) const = 0; // may throw NotFound
+
+    virtual const std::shared_ptr<PluginOutputIdMapper> handleToOutputIdMapper
+    (Handle h) const = 0; // may throw NotFound
 };
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bits/PluginOutputIdMapper.h	Mon Sep 19 14:48:43 2016 +0100
@@ -0,0 +1,57 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Vampipe
+
+    Centre for Digital Music, Queen Mary, University of London.
+    Copyright 2006-2016 Chris Cannam and QMUL.
+  
+    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.
+*/
+
+#ifndef VAMPIPE_PLUGIN_ID_MAPPER_H
+#define VAMPIPE_PLUGIN_ID_MAPPER_H
+
+#include <vamp-hostsdk/Plugin.h>
+
+#include <map>
+#include <string>
+
+namespace vampipe {
+
+//!!! doc interface
+class PluginOutputIdMapper
+{
+public:
+    virtual ~PluginOutputIdMapper() { }
+    
+    virtual int idToIndex(std::string outputId) const = 0;
+    virtual std::string indexToId(int index) const = 0;
+};
+
+}
+
+#endif
--- a/bits/PreservingPluginHandleMapper.h	Wed Sep 14 14:43:37 2016 +0100
+++ b/bits/PreservingPluginHandleMapper.h	Mon Sep 19 14:48:43 2016 +0100
@@ -36,17 +36,25 @@
 #define VAMPIPE_PRESERVING_PLUGIN_HANDLE_MAPPER_H
 
 #include "PluginHandleMapper.h"
+#include "PreservingPluginOutputIdMapper.h"
 
 #include <iostream>
 
 namespace vampipe {
 
+//!!! document -- this is a passthrough thing for a single plugin
+//!!! handle only, it does not use actually valid Plugin pointers at
+//!!! all
+
 class PreservingPluginHandleMapper : public PluginHandleMapper
 {
 public:
-    PreservingPluginHandleMapper() : m_handle(0), m_plugin(0) { }
+    PreservingPluginHandleMapper() :
+        m_handle(0),
+        m_plugin(0),
+        m_omapper(std::make_shared<PreservingPluginOutputIdMapper>()) { }
 
-    virtual int32_t pluginToHandle(Vamp::Plugin *p) const {
+    virtual Handle pluginToHandle(Vamp::Plugin *p) const {
 	if (p == m_plugin) return m_handle;
 	else {
 	    std::cerr << "PreservingPluginHandleMapper: p = " << p
@@ -57,15 +65,26 @@
 	}
     }
 
-    virtual Vamp::Plugin *handleToPlugin(int32_t h) const {
+    virtual Vamp::Plugin *handleToPlugin(Handle h) const {
 	m_handle = h;
 	m_plugin = reinterpret_cast<Vamp::Plugin *>(h);
 	return m_plugin;
     }
 
+    virtual const std::shared_ptr<PluginOutputIdMapper> pluginToOutputIdMapper
+    (Vamp::Plugin *) const {
+        return m_omapper;
+    }
+        
+    virtual const std::shared_ptr<PluginOutputIdMapper> handleToOutputIdMapper
+    (Handle h) const {
+        return m_omapper;
+    }
+    
 private:
-    mutable int32_t m_handle;
+    mutable Handle m_handle;
     mutable Vamp::Plugin *m_plugin;
+    std::shared_ptr<PreservingPluginOutputIdMapper> m_omapper;
 };
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bits/PreservingPluginOutputIdMapper.h	Mon Sep 19 14:48:43 2016 +0100
@@ -0,0 +1,76 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Vampipe
+
+    Centre for Digital Music, Queen Mary, University of London.
+    Copyright 2006-2016 Chris Cannam and QMUL.
+  
+    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.
+*/
+
+#ifndef VAMPIPE_PRESERVING_PLUGIN_OUTPUT_ID_MAPPER_H
+#define VAMPIPE_PRESERVING_PLUGIN_OUTPUT_ID_MAPPER_H
+
+#include "PluginOutputIdMapper.h"
+
+#include <iostream>
+
+namespace vampipe {
+
+//!!! document -- this is a passthrough thing that invents its
+//!!! numerical ids, they have no correspondence with any real plugin
+
+class PreservingPluginOutputIdMapper : public PluginOutputIdMapper
+{
+public:
+    PreservingPluginOutputIdMapper() { }
+
+    virtual int idToIndex(std::string outputId) const {
+	int n = int(m_ids.size());
+	int i = 0;
+	while (i < n) {
+	    if (outputId == m_ids[i]) {
+		return i;
+	    }
+	    ++i;
+	}
+	m_ids.push_back(outputId);
+	return i;
+    }
+
+    virtual std::string indexToId(int index) const {
+	//!!! todo: this should in fact throw, or otherwise return an error
+	return m_ids[index];
+    }
+
+private:
+    mutable std::vector<std::string> m_ids;
+};
+
+}
+
+#endif
--- a/bits/RequestOrResponse.h	Wed Sep 14 14:43:37 2016 +0100
+++ b/bits/RequestOrResponse.h	Mon Sep 19 14:48:43 2016 +0100
@@ -55,22 +55,21 @@
     RequestOrResponse() : // nothing by default
 	direction(Request),
 	type(RRType::NotValid),
-	success(false),
-	finishPlugin(0) { }
+	success(false) { }
 
     Direction direction;
     RRType type;
     bool success;
     std::string errorText;
 
-    std::vector<Vamp::HostExt::PluginStaticData> listResponse;
+    Vamp::HostExt::ListResponse listResponse;
     Vamp::HostExt::LoadRequest loadRequest;
     Vamp::HostExt::LoadResponse loadResponse;
     Vamp::HostExt::ConfigurationRequest configurationRequest;
     Vamp::HostExt::ConfigurationResponse configurationResponse;
     Vamp::HostExt::ProcessRequest processRequest;
     Vamp::HostExt::ProcessResponse processResponse;
-    Vamp::Plugin *finishPlugin;
+    Vamp::HostExt::FinishRequest finishRequest;
     Vamp::HostExt::ProcessResponse finishResponse;
 };
 
--- a/capnproto/VampnProto.h	Wed Sep 14 14:43:37 2016 +0100
+++ b/capnproto/VampnProto.h	Mon Sep 19 14:48:43 2016 +0100
@@ -42,6 +42,7 @@
 #include <vamp-hostsdk/PluginStaticData.h>
 
 #include "bits/PluginHandleMapper.h"
+#include "bits/PluginOutputIdMapper.h"
 #include "bits/RequestResponseType.h"
 
 namespace vampipe
@@ -294,13 +295,14 @@
     
     static void
     buildFeatureSet(FeatureSet::Builder &b,
-                    const Vamp::Plugin::FeatureSet &fs) {
+                    const Vamp::Plugin::FeatureSet &fs,
+                    const PluginOutputIdMapper &omapper) {
 
         auto featureset = b.initFeaturePairs(fs.size());
         int ix = 0;
         for (const auto &fsi : fs) {
             auto fspair = featureset[ix];
-            fspair.setOutput(fsi.first);
+            fspair.setOutput(omapper.indexToId(fsi.first));
             auto featurelist = fspair.initFeatures(fsi.second.size());
             for (size_t j = 0; j < fsi.second.size(); ++j) {
                 auto feature = featurelist[j];
@@ -312,7 +314,8 @@
 
     static void
     readFeatureSet(Vamp::Plugin::FeatureSet &fs,
-                   const FeatureSet::Reader &r) {
+                   const FeatureSet::Reader &r,
+                   const PluginOutputIdMapper &omapper) {
 
         fs.clear();
         auto pp = r.getFeaturePairs();
@@ -324,7 +327,7 @@
                 readFeature(vf, f);
                 vfl.push_back(vf);
             }
-            fs[p.getOutput()] = vfl;
+            fs[omapper.idToIndex(p.getOutput())] = vfl;
         }
     }
     
@@ -526,9 +529,9 @@
     static void
     buildLoadResponse(LoadResponse::Builder &b,
                       const Vamp::HostExt::LoadResponse &resp,
-                      PluginHandleMapper &mapper) {
+                      const PluginHandleMapper &pmapper) {
 
-        b.setPluginHandle(mapper.pluginToHandle(resp.plugin));
+        b.setPluginHandle(pmapper.pluginToHandle(resp.plugin));
         auto sd = b.initStaticData();
         buildPluginStaticData(sd, resp.staticData);
         auto conf = b.initDefaultConfiguration();
@@ -538,9 +541,9 @@
     static void
     readLoadResponse(Vamp::HostExt::LoadResponse &resp,
                      const LoadResponse::Reader &r,
-                     PluginHandleMapper &mapper) {
+                     const PluginHandleMapper &pmapper) {
 
-        resp.plugin = mapper.handleToPlugin(r.getPluginHandle());
+        resp.plugin = pmapper.handleToPlugin(r.getPluginHandle());
         readPluginStaticData(resp.staticData, r.getStaticData());
         readPluginConfiguration(resp.defaultConfiguration,
                                 r.getDefaultConfiguration());
@@ -549,9 +552,9 @@
     static void
     buildConfigurationRequest(ConfigurationRequest::Builder &b,
                               const Vamp::HostExt::ConfigurationRequest &cr,
-                              PluginHandleMapper &mapper) {
+                              const PluginHandleMapper &pmapper) {
 
-        b.setPluginHandle(mapper.pluginToHandle(cr.plugin));
+        b.setPluginHandle(pmapper.pluginToHandle(cr.plugin));
         auto c = b.initConfiguration();
         buildPluginConfiguration(c, cr.configuration);
     }
@@ -559,18 +562,20 @@
     static void
     readConfigurationRequest(Vamp::HostExt::ConfigurationRequest &cr,
                              const ConfigurationRequest::Reader &r,
-                             PluginHandleMapper &mapper) {
+                             const PluginHandleMapper &pmapper) {
 
         auto h = r.getPluginHandle();
-        cr.plugin = mapper.handleToPlugin(h);
+        cr.plugin = pmapper.handleToPlugin(h);
         auto c = r.getConfiguration();
         readPluginConfiguration(cr.configuration, c);
     }
 
     static void
     buildConfigurationResponse(ConfigurationResponse::Builder &b,
-                               const Vamp::HostExt::ConfigurationResponse &cr) {
+                               const Vamp::HostExt::ConfigurationResponse &cr,
+                               const PluginHandleMapper &pmapper) {
 
+        b.setPluginHandle(pmapper.pluginToHandle(cr.plugin));
         auto olist = b.initOutputs(cr.outputs.size());
         for (size_t i = 0; i < cr.outputs.size(); ++i) {
             auto od = olist[i];
@@ -580,8 +585,10 @@
 
     static void
     readConfigurationResponse(Vamp::HostExt::ConfigurationResponse &cr,
-                              const ConfigurationResponse::Reader &r) {
+                              const ConfigurationResponse::Reader &r,
+                              const PluginHandleMapper &pmapper) {
 
+        cr.plugin = pmapper.handleToPlugin(r.getPluginHandle());
         cr.outputs.clear();
         auto oo = r.getOutputs();
         for (const auto &o: oo) {
@@ -629,9 +636,9 @@
     static void
     buildProcessRequest(ProcessRequest::Builder &b,
                         const Vamp::HostExt::ProcessRequest &pr,
-                        PluginHandleMapper &mapper) {
+                        const PluginHandleMapper &pmapper) {
 
-        b.setPluginHandle(mapper.pluginToHandle(pr.plugin));
+        b.setPluginHandle(pmapper.pluginToHandle(pr.plugin));
         auto input = b.initInput();
         buildProcessInput(input, pr.timestamp, pr.inputBuffers);
     }
@@ -639,26 +646,33 @@
     static void
     readProcessRequest(Vamp::HostExt::ProcessRequest &pr,
                        const ProcessRequest::Reader &r,
-                       PluginHandleMapper &mapper) {
+                       const PluginHandleMapper &pmapper) {
 
         auto h = r.getPluginHandle();
-        pr.plugin = mapper.handleToPlugin(h);
+        pr.plugin = pmapper.handleToPlugin(h);
         readProcessInput(pr.timestamp, pr.inputBuffers, r.getInput());
     }
 
     static void
     buildProcessResponse(ProcessResponse::Builder &b,
-                         const Vamp::HostExt::ProcessResponse &pr) {
+                         const Vamp::HostExt::ProcessResponse &pr,
+                         const PluginHandleMapper &pmapper) {
 
+        b.setPluginHandle(pmapper.pluginToHandle(pr.plugin));
         auto f = b.initFeatures();
-        buildFeatureSet(f, pr.features);
+        buildFeatureSet(f, pr.features,
+                        *pmapper.pluginToOutputIdMapper(pr.plugin));
     }
     
     static void
     readProcessResponse(Vamp::HostExt::ProcessResponse &pr,
-                        const ProcessResponse::Reader &r) {
+                        const ProcessResponse::Reader &r,
+                        const PluginHandleMapper &pmapper) {
 
-        readFeatureSet(pr.features, r.getFeatures());
+        auto h = r.getPluginHandle();
+        pr.plugin = pmapper.handleToPlugin(h);
+        readFeatureSet(pr.features, r.getFeatures(),
+                       *pmapper.handleToOutputIdMapper(r.getPluginHandle()));
     }
 
     static void
@@ -668,15 +682,13 @@
 
     static void
     buildVampResponse_List(VampResponse::Builder &b,
-                           std::string errorText,
-                           const std::vector<Vamp::HostExt::PluginStaticData> &d) {
-        b.setSuccess(errorText == "");
-        b.setErrorText(errorText);
+                           const Vamp::HostExt::ListResponse &resp) {
+        b.setSuccess(true);
         auto r = b.getResponse().initList();
-        auto p = r.initPlugins(d.size());
-        for (size_t i = 0; i < d.size(); ++i) {
+        auto p = r.initPlugins(resp.pluginData.size());
+        for (size_t i = 0; i < resp.pluginData.size(); ++i) {
             auto pd = p[i];
-            buildPluginStaticData(pd, d[i]);
+            buildPluginStaticData(pd, resp.pluginData[i]);
         }
     }
     
@@ -690,63 +702,101 @@
     static void
     buildVampResponse_Load(VampResponse::Builder &b,
                            const Vamp::HostExt::LoadResponse &resp,
-                           PluginHandleMapper &mapper) {
+                           const PluginHandleMapper &pmapper) {
         b.setSuccess(resp.plugin != 0);
-        b.setErrorText("");
         auto u = b.getResponse().initLoad();
-        buildLoadResponse(u, resp, mapper);
+        buildLoadResponse(u, resp, pmapper);
     }
 
     static void
     buildVampRequest_Configure(VampRequest::Builder &b,
                                const Vamp::HostExt::ConfigurationRequest &cr,
-                               PluginHandleMapper &mapper) {
+                               const PluginHandleMapper &pmapper) {
         auto u = b.getRequest().initConfigure();
-        buildConfigurationRequest(u, cr, mapper);
+        buildConfigurationRequest(u, cr, pmapper);
     }
 
     static void
     buildVampResponse_Configure(VampResponse::Builder &b,
-                                const Vamp::HostExt::ConfigurationResponse &cr) {
+                                const Vamp::HostExt::ConfigurationResponse &cr,
+                                const PluginHandleMapper &pmapper) {
         b.setSuccess(!cr.outputs.empty());
-        b.setErrorText("");
         auto u = b.getResponse().initConfigure();
-        buildConfigurationResponse(u, cr);
+        buildConfigurationResponse(u, cr, pmapper);
     }
     
     static void
     buildVampRequest_Process(VampRequest::Builder &b,
                              const Vamp::HostExt::ProcessRequest &pr,
-                             PluginHandleMapper &mapper) {
+                             const PluginHandleMapper &pmapper) {
         auto u = b.getRequest().initProcess();
-        buildProcessRequest(u, pr, mapper);
+        buildProcessRequest(u, pr, pmapper);
     }
     
     static void
     buildVampResponse_Process(VampResponse::Builder &b,
-                              const Vamp::HostExt::ProcessResponse &pr) {
+                              const Vamp::HostExt::ProcessResponse &pr,
+                              const PluginHandleMapper &pmapper) {
         b.setSuccess(true);
-        b.setErrorText("");
         auto u = b.getResponse().initProcess();
-        buildProcessResponse(u, pr);
+        buildProcessResponse(u, pr, pmapper);
     }
     
     static void
     buildVampRequest_Finish(VampRequest::Builder &b,
-                            Vamp::Plugin *p,
-                            PluginHandleMapper &mapper) {
+                            const Vamp::HostExt::FinishRequest &req,
+                            const PluginHandleMapper &pmapper) {
 
         auto u = b.getRequest().initFinish();
-        u.setPluginHandle(mapper.pluginToHandle(p));
+        u.setPluginHandle(pmapper.pluginToHandle(req.plugin));
     }
     
     static void
     buildVampResponse_Finish(VampResponse::Builder &b,
-                             const Vamp::HostExt::ProcessResponse &pr) {
+                             const Vamp::HostExt::ProcessResponse &pr,
+                             const PluginHandleMapper &pmapper) {
 
-        buildVampResponse_Process(b, pr);
+        buildVampResponse_Process(b, pr, pmapper);
     }
 
+    static void
+    buildVampResponse_Error(VampResponse::Builder &b,
+                            const std::string &errorText,
+                            RRType responseType)
+    {
+        std::string type;
+
+        if (responseType == RRType::List) {
+            type = "list";
+            b.getResponse().initList();
+        } else if (responseType == RRType::Load) {
+            type = "load";
+            b.getResponse().initLoad();
+        } else if (responseType == RRType::Configure) {
+            type = "configure";
+            b.getResponse().initConfigure();
+        } else if (responseType == RRType::Process) {
+            type = "process";
+            b.getResponse().initProcess();
+        } else if (responseType == RRType::Finish) {
+            type = "finish";
+            b.getResponse().initFinish();
+        } else {
+            type = "invalid";
+        }
+
+        b.setSuccess(false);
+        b.setErrorText(std::string("error in ") + type + " request: " + errorText);
+    }
+
+    static void
+    buildVampResponse_Exception(VampResponse::Builder &b,
+                                const std::exception &e,
+                                RRType responseType)
+    {
+        return buildVampResponse_Error(b, e.what(), responseType);
+    }
+    
     static RRType
     getRequestResponseType(const VampRequest::Reader &r) {
         switch (r.getRequest().which()) {
@@ -789,18 +839,18 @@
     }
 
     static void
-    readVampResponse_List(std::vector<Vamp::HostExt::PluginStaticData> &v,
+    readVampResponse_List(Vamp::HostExt::ListResponse &resp,
                           const VampResponse::Reader &r) {
         if (getRequestResponseType(r) != RRType::List) {
             throw std::logic_error("not a list response");
         }
-        v.clear();
+        resp.pluginData.clear();
         if (r.getSuccess()) {
             auto pp = r.getResponse().getList().getPlugins();
             for (const auto &p: pp) {
                 Vamp::HostExt::PluginStaticData psd;
                 readPluginStaticData(psd, p);
-                v.push_back(psd);
+                resp.pluginData.push_back(psd);
             }
         }
     }
@@ -817,80 +867,85 @@
     static void
     readVampResponse_Load(Vamp::HostExt::LoadResponse &resp,
                           const VampResponse::Reader &r,
-                          PluginHandleMapper &mapper) {
+                          const PluginHandleMapper &pmapper) {
         if (getRequestResponseType(r) != RRType::Load) {
             throw std::logic_error("not a load response");
         }
         resp = {};
         if (r.getSuccess()) {
-            readLoadResponse(resp, r.getResponse().getLoad(), mapper);
+            readLoadResponse(resp, r.getResponse().getLoad(), pmapper);
         }
     }
     
     static void
     readVampRequest_Configure(Vamp::HostExt::ConfigurationRequest &req,
                               const VampRequest::Reader &r,
-                              PluginHandleMapper &mapper) {
+                              const PluginHandleMapper &pmapper) {
         if (getRequestResponseType(r) != RRType::Configure) {
             throw std::logic_error("not a configuration request");
         }
-        readConfigurationRequest(req, r.getRequest().getConfigure(), mapper);
+        readConfigurationRequest(req, r.getRequest().getConfigure(), pmapper);
     }
 
     static void
     readVampResponse_Configure(Vamp::HostExt::ConfigurationResponse &resp,
-                               const VampResponse::Reader &r) {
+                               const VampResponse::Reader &r,
+                               const PluginHandleMapper &pmapper) {
         if (getRequestResponseType(r) != RRType::Configure) {
             throw std::logic_error("not a configuration response");
         }
         resp = {};
         if (r.getSuccess()) {
-            readConfigurationResponse(resp, r.getResponse().getConfigure());
+            readConfigurationResponse(resp,
+                                      r.getResponse().getConfigure(),
+                                      pmapper);
         }
     }
     
     static void
     readVampRequest_Process(Vamp::HostExt::ProcessRequest &req,
                             const VampRequest::Reader &r,
-                            PluginHandleMapper &mapper) {
+                            const PluginHandleMapper &pmapper) {
         if (getRequestResponseType(r) != RRType::Process) {
             throw std::logic_error("not a process request");
         }
-        readProcessRequest(req, r.getRequest().getProcess(), mapper);
+        readProcessRequest(req, r.getRequest().getProcess(), pmapper);
     }
 
     static void
     readVampResponse_Process(Vamp::HostExt::ProcessResponse &resp,
-                             const VampResponse::Reader &r) {
+                             const VampResponse::Reader &r,
+                             const PluginHandleMapper &pmapper) {
         if (getRequestResponseType(r) != RRType::Process) {
             throw std::logic_error("not a process response");
         }
         resp = {};
         if (r.getSuccess()) {
-            readProcessResponse(resp, r.getResponse().getProcess());
+            readProcessResponse(resp, r.getResponse().getProcess(), pmapper);
         }
     }
     
     static void
-    readVampRequest_Finish(Vamp::Plugin *&finishPlugin,
+    readVampRequest_Finish(Vamp::HostExt::FinishRequest &req,
                            const VampRequest::Reader &r,
-                           PluginHandleMapper &mapper) {
+                           const PluginHandleMapper &pmapper) {
         if (getRequestResponseType(r) != RRType::Finish) {
             throw std::logic_error("not a finish request");
         }
-        finishPlugin = mapper.handleToPlugin
+        req.plugin = pmapper.handleToPlugin
             (r.getRequest().getFinish().getPluginHandle());
     }
 
     static void
     readVampResponse_Finish(Vamp::HostExt::ProcessResponse &resp,
-                            const VampResponse::Reader &r) {
+                            const VampResponse::Reader &r,
+                            const PluginHandleMapper &pmapper) {
         if (getRequestResponseType(r) != RRType::Finish) {
             throw std::logic_error("not a finish response");
         }
         resp = {};
         if (r.getSuccess()) {
-            readProcessResponse(resp, r.getResponse().getFinish());
+            readProcessResponse(resp, r.getResponse().getFinish(), pmapper);
         }
     }
 };
--- a/capnproto/vamp.capnp	Wed Sep 14 14:43:37 2016 +0100
+++ b/capnproto/vamp.capnp	Mon Sep 19 14:48:43 2016 +0100
@@ -96,7 +96,7 @@
 
 struct FeatureSet {
     struct FSPair {
-        output         @0  :Int32;
+        output         @0  :Text;
         features       @1  :List(Feature) = [];
     }
     featurePairs       @0  :List(FSPair);
@@ -136,7 +136,8 @@
 }
 
 struct ConfigurationResponse {
-    outputs            @0  :List(OutputDescriptor);
+    pluginHandle       @0  :Int32;
+    outputs            @1  :List(OutputDescriptor);
 }
 
 struct ProcessRequest {
@@ -144,14 +145,15 @@
     input              @1  :ProcessInput;
 }
 
+struct ProcessResponse {
+    pluginHandle       @0  :Int32;
+    features           @1  :FeatureSet;
+}
+
 struct FinishRequest {
     pluginHandle       @0  :Int32;
 }
 
-struct ProcessResponse {
-    features           @0  :FeatureSet;
-}
-
 struct VampRequest {
     request :union {
 	list           @0  :Void;
--- a/json/VampJson.h	Wed Sep 14 14:43:37 2016 +0100
+++ b/json/VampJson.h	Mon Sep 19 14:48:43 2016 +0100
@@ -47,6 +47,7 @@
 #include <vamp-hostsdk/PluginLoader.h>
 
 #include "bits/PluginHandleMapper.h"
+#include "bits/PluginOutputIdMapper.h"
 #include "bits/RequestResponseType.h"
 
 namespace vampipe {
@@ -392,6 +393,7 @@
 
     static json11::Json
     fromFeatureSet(const Vamp::Plugin::FeatureSet &fs,
+                   const PluginOutputIdMapper &omapper,
                    BufferSerialisation serialisation) {
 
         json11::Json::object jo;
@@ -400,10 +402,7 @@
             for (const Vamp::Plugin::Feature &f: fsi.second) {
                 fj.push_back(fromFeature(f, serialisation));
             }
-            std::stringstream sstr;
-            sstr << fsi.first;
-            std::string n = sstr.str();
-            jo[n] = fj;
+            jo[omapper.indexToId(fsi.first)] = fj;
         }
         return json11::Json(jo);
     }
@@ -423,18 +422,18 @@
     }
 
     static Vamp::Plugin::FeatureSet
-    toFeatureSet(json11::Json j, BufferSerialisation &serialisation) {
+    toFeatureSet(json11::Json j,
+                 const PluginOutputIdMapper &omapper,
+                 BufferSerialisation &serialisation) {
 
         Vamp::Plugin::FeatureSet fs;
         if (!j.is_object()) {
             throw Failure("object expected for feature set");
         }
         for (auto &entry : j.object_items()) {
-            std::string nstr = entry.first;
-            size_t count = 0;
-            int n = stoi(nstr, &count);
-            if (n < 0 || fs.find(n) != fs.end() || count < nstr.size()) {
-                throw Failure("invalid or duplicate numerical index for output");
+            int n = omapper.idToIndex(entry.first);
+            if (fs.find(n) != fs.end()) {
+                throw Failure("duplicate numerical index for output");
             }
             fs[n] = toFeatureList(entry.second, serialisation);
         }
@@ -744,10 +743,10 @@
 
     static json11::Json
     fromLoadResponse(const Vamp::HostExt::LoadResponse &resp,
-                     const PluginHandleMapper &mapper) {
+                     const PluginHandleMapper &pmapper) {
 
         json11::Json::object jo;
-        jo["pluginHandle"] = double(mapper.pluginToHandle(resp.plugin));
+        jo["pluginHandle"] = double(pmapper.pluginToHandle(resp.plugin));
         jo["staticData"] = fromPluginStaticData(resp.staticData);
         jo["defaultConfiguration"] =
             fromPluginConfiguration(resp.defaultConfiguration);
@@ -756,7 +755,7 @@
 
     static Vamp::HostExt::LoadResponse
     toLoadResponse(json11::Json j,
-                   const PluginHandleMapper &mapper) {
+                   const PluginHandleMapper &pmapper) {
 
         std::string err;
 
@@ -768,7 +767,7 @@
         }
 
         Vamp::HostExt::LoadResponse resp;
-        resp.plugin = mapper.handleToPlugin(j["pluginHandle"].int_value());
+        resp.plugin = pmapper.handleToPlugin(j["pluginHandle"].int_value());
         resp.staticData = toPluginStaticData(j["staticData"]);
         resp.defaultConfiguration = toPluginConfiguration(j["defaultConfiguration"]);
         return resp;
@@ -776,11 +775,11 @@
 
     static json11::Json
     fromConfigurationRequest(const Vamp::HostExt::ConfigurationRequest &cr,
-                             const PluginHandleMapper &mapper) {
+                             const PluginHandleMapper &pmapper) {
 
         json11::Json::object jo;
 
-        jo["pluginHandle"] = mapper.pluginToHandle(cr.plugin);
+        jo["pluginHandle"] = pmapper.pluginToHandle(cr.plugin);
         jo["configuration"] = fromPluginConfiguration(cr.configuration);
         
         return json11::Json(jo);
@@ -788,7 +787,7 @@
 
     static Vamp::HostExt::ConfigurationRequest
     toConfigurationRequest(json11::Json j,
-                           const PluginHandleMapper &mapper) {
+                           const PluginHandleMapper &pmapper) {
 
         std::string err;
 
@@ -799,15 +798,18 @@
         }
 
         Vamp::HostExt::ConfigurationRequest cr;
-        cr.plugin = mapper.handleToPlugin(j["pluginHandle"].int_value());
+        cr.plugin = pmapper.handleToPlugin(j["pluginHandle"].int_value());
         cr.configuration = toPluginConfiguration(j["configuration"]);
         return cr;
     }
 
     static json11::Json
-    fromConfigurationResponse(const Vamp::HostExt::ConfigurationResponse &cr) {
+    fromConfigurationResponse(const Vamp::HostExt::ConfigurationResponse &cr,
+                              const PluginHandleMapper &pmapper) {
 
         json11::Json::object jo;
+
+        jo["pluginHandle"] = pmapper.pluginToHandle(cr.plugin);
         
         json11::Json::array outs;
         for (auto &d: cr.outputs) {
@@ -819,10 +821,13 @@
     }
 
     static Vamp::HostExt::ConfigurationResponse
-    toConfigurationResponse(json11::Json j) {
+    toConfigurationResponse(json11::Json j,
+                            const PluginHandleMapper &pmapper) {
         
         Vamp::HostExt::ConfigurationResponse cr;
 
+        cr.plugin = pmapper.handleToPlugin(j["pluginHandle"].int_value());
+        
         if (!j["outputList"].is_array()) {
             throw Failure("array expected for output list");
         }
@@ -836,11 +841,11 @@
 
     static json11::Json
     fromProcessRequest(const Vamp::HostExt::ProcessRequest &r,
-                       const PluginHandleMapper &mapper,
+                       const PluginHandleMapper &pmapper,
                        BufferSerialisation serialisation) {
 
         json11::Json::object jo;
-        jo["pluginHandle"] = mapper.pluginToHandle(r.plugin);
+        jo["pluginHandle"] = pmapper.pluginToHandle(r.plugin);
 
         json11::Json::object io;
         io["timestamp"] = fromRealTime(r.timestamp);
@@ -865,7 +870,7 @@
 
     static Vamp::HostExt::ProcessRequest
     toProcessRequest(json11::Json j,
-                     const PluginHandleMapper &mapper,
+                     const PluginHandleMapper &pmapper,
                      BufferSerialisation &serialisation) {
 
         std::string err;
@@ -885,7 +890,7 @@
         }
 
         Vamp::HostExt::ProcessRequest r;
-        r.plugin = mapper.handleToPlugin(j["pluginHandle"].int_value());
+        r.plugin = pmapper.handleToPlugin(j["pluginHandle"].int_value());
 
         r.timestamp = toRealTime(input["timestamp"]);
 
@@ -922,7 +927,7 @@
 
     static json11::Json
     fromVampResponse_List(std::string errorText,
-                          const std::vector<Vamp::HostExt::PluginStaticData> &d) {
+                          const Vamp::HostExt::ListResponse &resp) {
 
         json11::Json::object jo;
         jo["type"] = "list";
@@ -930,7 +935,7 @@
         jo["errorText"] = errorText;
 
         json11::Json::array arr;
-        for (const auto &a: d) {
+        for (const auto &a: resp.pluginData) {
             arr.push_back(fromPluginStaticData(a));
         }
         json11::Json::object po;
@@ -951,86 +956,99 @@
 
     static json11::Json
     fromVampResponse_Load(const Vamp::HostExt::LoadResponse &resp,
-                          const PluginHandleMapper &mapper) {
+                          const PluginHandleMapper &pmapper) {
 
         json11::Json::object jo;
         jo["type"] = "load";
         jo["success"] = (resp.plugin != 0);
         jo["errorText"] = "";
-        jo["content"] = fromLoadResponse(resp, mapper);
+        jo["content"] = fromLoadResponse(resp, pmapper);
         return json11::Json(jo);
     }
 
     static json11::Json
     fromVampRequest_Configure(const Vamp::HostExt::ConfigurationRequest &req,
-                              const PluginHandleMapper &mapper) {
+                              const PluginHandleMapper &pmapper) {
 
         json11::Json::object jo;
         jo["type"] = "configure";
-        jo["content"] = fromConfigurationRequest(req, mapper);
+        jo["content"] = fromConfigurationRequest(req, pmapper);
         return json11::Json(jo);
     }    
 
     static json11::Json
-    fromVampResponse_Configure(const Vamp::HostExt::ConfigurationResponse &resp) {
+    fromVampResponse_Configure(const Vamp::HostExt::ConfigurationResponse &resp,
+                               const PluginHandleMapper &pmapper) {
         
         json11::Json::object jo;
         jo["type"] = "configure";
         jo["success"] = (!resp.outputs.empty());
         jo["errorText"] = "";
-        jo["content"] = fromConfigurationResponse(resp);
+        jo["content"] = fromConfigurationResponse(resp, pmapper);
         return json11::Json(jo);
     }
     
     static json11::Json
     fromVampRequest_Process(const Vamp::HostExt::ProcessRequest &req,
-                            const PluginHandleMapper &mapper,
+                            const PluginHandleMapper &pmapper,
                             BufferSerialisation serialisation) {
 
         json11::Json::object jo;
         jo["type"] = "process";
-        jo["content"] = fromProcessRequest(req, mapper, serialisation);
+        jo["content"] = fromProcessRequest(req, pmapper, serialisation);
         return json11::Json(jo);
     }    
 
     static json11::Json
     fromVampResponse_Process(const Vamp::HostExt::ProcessResponse &resp,
+                             const PluginHandleMapper &pmapper,
                              BufferSerialisation serialisation) {
         
         json11::Json::object jo;
         jo["type"] = "process";
         jo["success"] = true;
         jo["errorText"] = "";
-        jo["content"] = fromFeatureSet(resp.features, serialisation);
+        json11::Json::object po;
+        po["pluginHandle"] = pmapper.pluginToHandle(resp.plugin);
+        po["features"] = fromFeatureSet(resp.features,
+                                        *pmapper.pluginToOutputIdMapper(resp.plugin),
+                                        serialisation);
+        jo["content"] = po;
         return json11::Json(jo);
     }
     
     static json11::Json
-    fromVampRequest_Finish(Vamp::Plugin *p,
-                           const PluginHandleMapper &mapper) {
+    fromVampRequest_Finish(const Vamp::HostExt::FinishRequest &req,
+                           const PluginHandleMapper &pmapper) {
 
         json11::Json::object jo;
         jo["type"] = "finish";
         json11::Json::object fo;
-        fo["pluginHandle"] = mapper.pluginToHandle(p);
+        fo["pluginHandle"] = pmapper.pluginToHandle(req.plugin);
         jo["content"] = fo;
         return json11::Json(jo);
     }    
     
     static json11::Json
     fromVampResponse_Finish(const Vamp::HostExt::ProcessResponse &resp,
+                            const PluginHandleMapper &pmapper,
                             BufferSerialisation serialisation) {
 
         json11::Json::object jo;
         jo["type"] = "finish";
         jo["success"] = true;
         jo["errorText"] = "";
-        jo["content"] = fromFeatureSet(resp.features, serialisation);
+        json11::Json::object po;
+        po["pluginHandle"] = pmapper.pluginToHandle(resp.plugin);
+        po["features"] = fromFeatureSet(resp.features,
+                                        *pmapper.pluginToOutputIdMapper(resp.plugin),
+                                        serialisation);
+        jo["content"] = po;
         return json11::Json(jo);
     }
 
     static json11::Json
-    fromException(const std::exception &e, RRType responseType) {
+    fromError(std::string errorText, RRType responseType) {
 
         json11::Json::object jo;
         std::string type;
@@ -1044,10 +1062,15 @@
 
         jo["type"] = type;
         jo["success"] = false;
-        jo["errorText"] = std::string("exception caught: ") +
-            type + " request: " + e.what();
+        jo["errorText"] = std::string("error in ") + type + " request: " + errorText;
         return json11::Json(jo);
     }
+
+    static json11::Json
+    fromException(const std::exception &e, RRType responseType) {
+
+        return fromError(e.what(), responseType);
+    }
     
 private: // go private briefly for a couple of helper functions
     
@@ -1096,16 +1119,16 @@
         checkTypeField(j, "list");
     }
 
-    static std::vector<Vamp::HostExt::PluginStaticData>
+    static Vamp::HostExt::ListResponse
     toVampResponse_List(json11::Json j) {
 
-        std::vector<Vamp::HostExt::PluginStaticData> arr;
+        Vamp::HostExt::ListResponse resp;
         if (successful(j)) {
             for (const auto &a: j["content"]["plugins"].array_items()) {
-                arr.push_back(toPluginStaticData(a));
+                resp.pluginData.push_back(toPluginStaticData(a));
             }
         }
-        return arr;
+        return resp;
     }
 
     static Vamp::HostExt::LoadRequest
@@ -1116,63 +1139,80 @@
     }
     
     static Vamp::HostExt::LoadResponse
-    toVampResponse_Load(json11::Json j, const PluginHandleMapper &mapper) {
+    toVampResponse_Load(json11::Json j, const PluginHandleMapper &pmapper) {
         
         Vamp::HostExt::LoadResponse resp;
         if (successful(j)) {
-            resp = toLoadResponse(j["content"], mapper);
+            resp = toLoadResponse(j["content"], pmapper);
         }
         return resp;
     }
     
     static Vamp::HostExt::ConfigurationRequest
-    toVampRequest_Configure(json11::Json j, const PluginHandleMapper &mapper) {
+    toVampRequest_Configure(json11::Json j, const PluginHandleMapper &pmapper) {
         
         checkTypeField(j, "configure");
-        return toConfigurationRequest(j["content"], mapper);
+        return toConfigurationRequest(j["content"], pmapper);
     }
     
     static Vamp::HostExt::ConfigurationResponse
-    toVampResponse_Configure(json11::Json j) {
+    toVampResponse_Configure(json11::Json j, const PluginHandleMapper &pmapper) {
         
         Vamp::HostExt::ConfigurationResponse resp;
         if (successful(j)) {
-            resp = toConfigurationResponse(j["content"]);
+            resp = toConfigurationResponse(j["content"], pmapper);
         }
         return resp;
     }
     
     static Vamp::HostExt::ProcessRequest
-    toVampRequest_Process(json11::Json j, const PluginHandleMapper &mapper,
+    toVampRequest_Process(json11::Json j, const PluginHandleMapper &pmapper,
                           BufferSerialisation &serialisation) {
         
         checkTypeField(j, "process");
-        return toProcessRequest(j["content"], mapper, serialisation);
+        return toProcessRequest(j["content"], pmapper, serialisation);
     }
     
     static Vamp::HostExt::ProcessResponse
-    toVampResponse_Process(json11::Json j, BufferSerialisation &serialisation) {
+    toVampResponse_Process(json11::Json j,
+                           const PluginHandleMapper &pmapper,
+                           BufferSerialisation &serialisation) {
         
         Vamp::HostExt::ProcessResponse resp;
         if (successful(j)) {
-            resp.features = toFeatureSet(j["content"], serialisation);
+            auto jc = j["content"];
+            auto h = jc["pluginHandle"].int_value();
+            resp.plugin = pmapper.handleToPlugin(h);
+            resp.features = toFeatureSet(jc["features"],
+                                         *pmapper.handleToOutputIdMapper(h),
+                                         serialisation);
         }
         return resp;
     }
     
-    static Vamp::Plugin *
-    toVampRequest_Finish(json11::Json j, const PluginHandleMapper &mapper) {
+    static Vamp::HostExt::FinishRequest
+    toVampRequest_Finish(json11::Json j, const PluginHandleMapper &pmapper) {
         
         checkTypeField(j, "finish");
-        return mapper.handleToPlugin(j["content"]["pluginHandle"].int_value());
+        Vamp::HostExt::FinishRequest req;
+        req.plugin = pmapper.handleToPlugin
+            (j["content"]["pluginHandle"].int_value());
+        return req;
     }
     
     static Vamp::HostExt::ProcessResponse
-    toVampResponse_Finish(json11::Json j, BufferSerialisation &serialisation) {
+    toVampResponse_Finish(json11::Json j,
+                          const PluginHandleMapper &pmapper,
+                          BufferSerialisation &serialisation) {
         
         Vamp::HostExt::ProcessResponse resp;
         if (successful(j)) {
-            resp.features = toFeatureSet(j["content"], serialisation);
+            auto jc = j["content"];
+            auto h = jc["pluginHandle"].int_value();
+            resp.plugin = pmapper.handleToPlugin(h);
+            resp.features = toFeatureSet(jc["features"],
+                                         *pmapper.handleToOutputIdMapper(h),
+                                         serialisation);
         }
         return resp;
     }
--- a/test/test-vampipe-server.sh	Wed Sep 14 14:43:37 2016 +0100
+++ b/test/test-vampipe-server.sh	Mon Sep 19 14:48:43 2016 +0100
@@ -2,7 +2,11 @@
 
 ( bin/vampipe-convert request -i json -o capnp |
 	VAMP_PATH=./vamp-plugin-sdk/examples bin/vampipe-server |
-	bin/vampipe-convert response -i capnp -o json ) <<EOF
+
+#	capnp decode capnproto/vamp.capnp VampResponse
+
+	bin/vampipe-convert response -i capnp -o json
+) <<EOF
 {"type":"list"}
 {"type":"load","content": {"pluginKey":"vamp-example-plugins:percussiononsets","inputSampleRate":44100,"adapterFlags":["AdaptInputDomain","AdaptBufferSize"]}}
 {"type":"configure","content":{"pluginHandle":1,"configuration":{"blockSize": 8, "channelCount": 1, "parameterValues": {"sensitivity": 40, "threshold": 3}, "stepSize": 8}}}
--- a/utilities/json-cli.cpp	Wed Sep 14 14:43:37 2016 +0100
+++ b/utilities/json-cli.cpp	Mon Sep 19 14:48:43 2016 +0100
@@ -1,3 +1,6 @@
+
+//!!! This program was an early test -- it should still compile but
+//!!! it's incomplete. Remove it and use the server program instead.
 
 #include "VampJson.h"
 #include "bits/CountingPluginHandleMapper.h"
@@ -55,10 +58,10 @@
     }
     
     auto loader = PluginLoader::getInstance();
-    auto pluginData = loader->listPluginData();
+    auto resp = loader->listPluginData();
 
     Json::array j;
-    for (const auto &pd: pluginData) {
+    for (const auto &pd: resp.pluginData) {
 	j.push_back(VampJson::fromPluginStaticData(pd));
     }
     return Json(j);
@@ -105,7 +108,7 @@
 
     cerr << "Configured and initialised plugin " << handle << endl;
 
-    return VampJson::fromConfigurationResponse(response);
+    return VampJson::fromConfigurationResponse(response, mapper);
 }
 
 Json
--- a/utilities/json-to-capnp.cpp	Wed Sep 14 14:43:37 2016 +0100
+++ b/utilities/json-to-capnp.cpp	Mon Sep 19 14:48:43 2016 +0100
@@ -55,8 +55,9 @@
 
     } else if (type == "configurationresponse") {
 	auto resp = message.initRoot<ConfigurationResponse>();
+	PreservingPluginHandleMapper mapper;
 	VampnProto::buildConfigurationResponse
-	    (resp, VampJson::toConfigurationResponse(payload));
+	    (resp, VampJson::toConfigurationResponse(payload, mapper), mapper);
 
     } else if (type == "feature") {
 	auto f = message.initRoot<Feature>();
@@ -65,8 +66,9 @@
 
     } else if (type == "featureset") {
 	auto fs = message.initRoot<FeatureSet>();
+	PreservingPluginOutputIdMapper omapper;
 	VampnProto::buildFeatureSet
-	    (fs, VampJson::toFeatureSet(payload, serialisation));
+	    (fs, VampJson::toFeatureSet(payload, omapper, serialisation), omapper);
 
     } else if (type == "loadrequest") {
 	auto req = message.initRoot<LoadRequest>();
--- a/utilities/vampipe-convert.cpp	Wed Sep 14 14:43:37 2016 +0100
+++ b/utilities/vampipe-convert.cpp	Mon Sep 19 14:48:43 2016 +0100
@@ -113,7 +113,7 @@
 	rr.processRequest = VampJson::toVampRequest_Process(j, mapper, serialisation);
 	break;
     case RRType::Finish:
-	rr.finishPlugin = VampJson::toVampRequest_Finish(j, mapper);
+	rr.finishRequest = VampJson::toVampRequest_Finish(j, mapper);
 	break;
     case RRType::NotValid:
 	break;
@@ -127,6 +127,11 @@
 {
     Json j;
 
+    VampJson::BufferSerialisation serialisation =
+        (useBase64 ?
+         VampJson::BufferSerialisation::Base64 :
+         VampJson::BufferSerialisation::Text);
+
     switch (rr.type) {
 
     case RRType::List:
@@ -140,13 +145,10 @@
 	break;
     case RRType::Process:
 	j = VampJson::fromVampRequest_Process
-	    (rr.processRequest, mapper,
-	     useBase64 ?
-	     VampJson::BufferSerialisation::Base64 :
-	     VampJson::BufferSerialisation::Text);
+	    (rr.processRequest, mapper, serialisation);
 	break;
     case RRType::Finish:
-	j = VampJson::fromVampRequest_Finish(rr.finishPlugin, mapper);
+	j = VampJson::fromVampRequest_Finish(rr.finishRequest, mapper);
 	break;
     case RRType::NotValid:
 	break;
@@ -172,6 +174,9 @@
     rr.type = VampJson::getRequestResponseType(j);
     VampJson::BufferSerialisation serialisation = VampJson::BufferSerialisation::Text;
 
+    rr.success = j["success"].bool_value();
+    rr.errorText = j["errorText"].string_value();
+
     switch (rr.type) {
 
     case RRType::List:
@@ -181,13 +186,13 @@
 	rr.loadResponse = VampJson::toVampResponse_Load(j, mapper);
 	break;
     case RRType::Configure:
-	rr.configurationResponse = VampJson::toVampResponse_Configure(j);
+	rr.configurationResponse = VampJson::toVampResponse_Configure(j, mapper);
 	break;
     case RRType::Process: 
-	rr.processResponse = VampJson::toVampResponse_Process(j, serialisation);
+	rr.processResponse = VampJson::toVampResponse_Process(j, mapper, serialisation);
 	break;
     case RRType::Finish:
-	rr.finishResponse = VampJson::toVampResponse_Finish(j, serialisation);
+	rr.finishResponse = VampJson::toVampResponse_Finish(j, mapper, serialisation);
 	break;
     case RRType::NotValid:
 	break;
@@ -201,35 +206,42 @@
 {
     Json j;
 
-    switch (rr.type) {
+    VampJson::BufferSerialisation serialisation =
+        (useBase64 ?
+         VampJson::BufferSerialisation::Base64 :
+         VampJson::BufferSerialisation::Text);
 
-    case RRType::List:
-	j = VampJson::fromVampResponse_List("", rr.listResponse);
-	break;
-    case RRType::Load:
-	j = VampJson::fromVampResponse_Load(rr.loadResponse, mapper);
-	break;
-    case RRType::Configure:
-	j = VampJson::fromVampResponse_Configure(rr.configurationResponse);
-	break;
-    case RRType::Process:
-	j = VampJson::fromVampResponse_Process
-	    (rr.processResponse,
-	     useBase64 ?
-	     VampJson::BufferSerialisation::Base64 :
-	     VampJson::BufferSerialisation::Text);
-	break;
-    case RRType::Finish:
-	j = VampJson::fromVampResponse_Finish
-	    (rr.finishResponse,
-	     useBase64 ?
-	     VampJson::BufferSerialisation::Base64 :
-	     VampJson::BufferSerialisation::Text);
-	break;
-    case RRType::NotValid:
-	break;
+    if (!rr.success) {
+
+	j = VampJson::fromError(rr.errorText, rr.type);
+
+    } else {
+    
+	switch (rr.type) {
+
+	case RRType::List:
+	    j = VampJson::fromVampResponse_List("", rr.listResponse);
+	    break;
+	case RRType::Load:
+	    j = VampJson::fromVampResponse_Load(rr.loadResponse, mapper);
+	    break;
+	case RRType::Configure:
+	    j = VampJson::fromVampResponse_Configure(rr.configurationResponse,
+                                                     mapper);
+	    break;
+	case RRType::Process:
+	    j = VampJson::fromVampResponse_Process
+		(rr.processResponse, mapper, serialisation);
+	    break;
+	case RRType::Finish:
+	    j = VampJson::fromVampResponse_Finish
+		(rr.finishResponse, mapper, serialisation);
+	    break;
+	case RRType::NotValid:
+	    break;
+	}
     }
-
+    
     cout << j.dump() << endl;
 }
 
@@ -260,7 +272,7 @@
 	VampnProto::readVampRequest_Process(rr.processRequest, reader, mapper);
 	break;
     case RRType::Finish:
-	VampnProto::readVampRequest_Finish(rr.finishPlugin, reader, mapper);
+	VampnProto::readVampRequest_Finish(rr.finishRequest, reader, mapper);
 	break;
     case RRType::NotValid:
 	break;
@@ -291,7 +303,7 @@
 	VampnProto::buildVampRequest_Process(builder, rr.processRequest, mapper);
 	break;
     case RRType::Finish:
-	VampnProto::buildVampRequest_Finish(builder, rr.finishPlugin, mapper);
+	VampnProto::buildVampRequest_Finish(builder, rr.finishRequest, mapper);
 	break;
     case RRType::NotValid:
 	break;
@@ -310,6 +322,8 @@
     VampResponse::Reader reader = message.getRoot<VampResponse>();
     
     rr.type = VampnProto::getRequestResponseType(reader);
+    rr.success = reader.getSuccess();
+    rr.errorText = reader.getErrorText();
 
     switch (rr.type) {
 
@@ -321,13 +335,13 @@
 	break;
     case RRType::Configure:
 	VampnProto::readVampResponse_Configure(rr.configurationResponse,
-					       reader);
+					       reader, mapper);
 	break;
     case RRType::Process:
-	VampnProto::readVampResponse_Process(rr.processResponse, reader);
+	VampnProto::readVampResponse_Process(rr.processResponse, reader, mapper);
 	break;
     case RRType::Finish:
-	VampnProto::readVampResponse_Finish(rr.finishResponse, reader);
+	VampnProto::readVampResponse_Finish(rr.finishResponse, reader, mapper);
 	break;
     case RRType::NotValid:
 	break;
@@ -342,27 +356,34 @@
     ::capnp::MallocMessageBuilder message;
     VampResponse::Builder builder = message.initRoot<VampResponse>();
 
-    switch (rr.type) {
+    if (!rr.success) {
 
-    case RRType::List:
-	VampnProto::buildVampResponse_List(builder, "", rr.listResponse);
-	break;
-    case RRType::Load:
-	VampnProto::buildVampResponse_Load(builder, rr.loadResponse, mapper);
-	break;
-    case RRType::Configure:
-	VampnProto::buildVampResponse_Configure(builder, rr.configurationResponse);
-	break;
-    case RRType::Process:
-	VampnProto::buildVampResponse_Process(builder, rr.processResponse);
-	break;
-    case RRType::Finish:
-	VampnProto::buildVampResponse_Finish(builder, rr.finishResponse);
-	break;
-    case RRType::NotValid:
-	break;
+	VampnProto::buildVampResponse_Error(builder, rr.errorText, rr.type);
+
+    } else {
+	
+	switch (rr.type) {
+
+	case RRType::List:
+	    VampnProto::buildVampResponse_List(builder, rr.listResponse);
+	    break;
+	case RRType::Load:
+	    VampnProto::buildVampResponse_Load(builder, rr.loadResponse, mapper);
+	    break;
+	case RRType::Configure:
+	    VampnProto::buildVampResponse_Configure(builder, rr.configurationResponse, mapper);
+	    break;
+	case RRType::Process:
+	    VampnProto::buildVampResponse_Process(builder, rr.processResponse, mapper);
+	    break;
+	case RRType::Finish:
+	    VampnProto::buildVampResponse_Finish(builder, rr.finishResponse, mapper);
+	    break;
+	case RRType::NotValid:
+	    break;
+	}
     }
-
+    
     writeMessageToFd(1, message);
 }
 
@@ -483,6 +504,7 @@
 	    writeOutput(outformat, rr);
 	    
 	} catch (std::exception &e) {
+
 	    cerr << "Error: " << e.what() << endl;
 	    exit(1);
 	}
--- a/utilities/vampipe-server.cpp	Wed Sep 14 14:43:37 2016 +0100
+++ b/utilities/vampipe-server.cpp	Mon Sep 19 14:48:43 2016 +0100
@@ -65,7 +65,7 @@
 	VampnProto::readVampRequest_Process(rr.processRequest, reader, mapper);
 	break;
     case RRType::Finish:
-	VampnProto::readVampRequest_Finish(rr.finishPlugin, reader, mapper);
+	VampnProto::readVampRequest_Finish(rr.finishRequest, reader, mapper);
 	break;
     case RRType::NotValid:
 	break;
@@ -80,27 +80,44 @@
     ::capnp::MallocMessageBuilder message;
     VampResponse::Builder builder = message.initRoot<VampResponse>();
 
-    switch (rr.type) {
+    if (!rr.success) {
 
-    case RRType::List:
-	VampnProto::buildVampResponse_List(builder, "", rr.listResponse);
-	break;
-    case RRType::Load:
-	VampnProto::buildVampResponse_Load(builder, rr.loadResponse, mapper);
-	break;
-    case RRType::Configure:
-	VampnProto::buildVampResponse_Configure(builder, rr.configurationResponse);
-	break;
-    case RRType::Process:
-	VampnProto::buildVampResponse_Process(builder, rr.processResponse);
-	break;
-    case RRType::Finish:
-	VampnProto::buildVampResponse_Finish(builder, rr.finishResponse);
-	break;
-    case RRType::NotValid:
-	break;
+	VampnProto::buildVampResponse_Error(builder, rr.errorText, rr.type);
+
+    } else {
+	
+	switch (rr.type) {
+
+	case RRType::List:
+	    VampnProto::buildVampResponse_List(builder, rr.listResponse);
+	    break;
+	case RRType::Load:
+	    VampnProto::buildVampResponse_Load(builder, rr.loadResponse, mapper);
+	    break;
+	case RRType::Configure:
+	    VampnProto::buildVampResponse_Configure(builder, rr.configurationResponse, mapper);
+	    break;
+	case RRType::Process:
+	    VampnProto::buildVampResponse_Process(builder, rr.processResponse, mapper);
+	    break;
+	case RRType::Finish:
+	    VampnProto::buildVampResponse_Finish(builder, rr.finishResponse, mapper);
+	    break;
+	case RRType::NotValid:
+	    break;
+	}
     }
+    
+    writeMessageToFd(1, message);
+}
 
+void
+writeExceptionCapnp(const std::exception &e, RRType type)
+{
+    ::capnp::MallocMessageBuilder message;
+    VampResponse::Builder builder = message.initRoot<VampResponse>();
+    VampnProto::buildVampResponse_Exception(builder, e, type);
+    
     writeMessageToFd(1, message);
 }
 
@@ -168,6 +185,7 @@
 	    fbuffers[i] = preq.inputBuffers[i].data();
 	}
 
+	response.processResponse.plugin = preq.plugin;
 	response.processResponse.features =
 	    preq.plugin->process(fbuffers, preq.timestamp);
 	response.success = true;
@@ -178,13 +196,14 @@
 
     case RRType::Finish:
     {
-	auto h = mapper.pluginToHandle(request.finishPlugin);
+	response.finishResponse.plugin = request.finishRequest.plugin;
+	response.finishResponse.features =
+	    request.finishRequest.plugin->getRemainingFeatures();
 
-	response.finishResponse.features =
-	    request.finishPlugin->getRemainingFeatures();
-	    
-	mapper.removePlugin(h);
-	delete request.finishPlugin;
+	// We do not delete the plugin here -- we need it in the
+	// mapper when converting the features. It gets deleted by the
+	// caller.
+	
 	response.success = true;
 	break;
     }
@@ -204,9 +223,11 @@
 
     while (true) {
 
+	RequestOrResponse request;
+	
 	try {
 
-	    RequestOrResponse request = readRequestCapnp();
+	    request = readRequestCapnp();
 
 	    cerr << "vampipe-server: request received, of type "
 		 << int(request.type)
@@ -226,9 +247,19 @@
 	    writeResponseCapnp(response);
 
 	    cerr << "vampipe-server: response written" << endl;
+
+	    if (request.type == RRType::Finish) {
+		auto h = mapper.pluginToHandle(request.finishRequest.plugin);
+		mapper.removePlugin(h);
+		delete request.finishRequest.plugin;
+	    }
 	    
 	} catch (std::exception &e) {
+
 	    cerr << "vampipe-server: error: " << e.what() << endl;
+
+	    writeExceptionCapnp(e, request.type);
+	    
 	    exit(1);
 	}
     }