changeset 51:f4244a2d55ac

Introduce and use output id mappers
author Chris Cannam <c.cannam@qmul.ac.uk>
date Fri, 16 Sep 2016 15:10:57 +0100
parents 12e3b396544c
children e90fd30990eb
files bits/CountingPluginHandleMapper.h bits/DefaultPluginOutputIdMapper.h bits/PluginHandleMapper.h bits/PluginOutputIdMapper.h bits/PreservingPluginHandleMapper.h bits/PreservingPluginOutputIdMapper.h capnproto/VampnProto.h json/VampJson.h utilities/json-to-capnp.cpp utilities/vampipe-convert.cpp utilities/vampipe-server.cpp
diffstat 11 files changed, 280 insertions(+), 81 deletions(-) [+]
line wrap: on
line diff
--- a/bits/CountingPluginHandleMapper.h	Fri Sep 16 14:15:41 2016 +0100
+++ b/bits/CountingPluginHandleMapper.h	Fri Sep 16 15:10:57 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>
@@ -48,19 +50,28 @@
 public:
     CountingPluginHandleMapper() : m_nextHandle(1) { }
 
+    ~CountingPluginHandleMapper() {
+        for (auto &o: m_outputMappers) {
+            delete o.second;
+        }
+    }
+
     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] = new 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];
+        delete m_outputMappers.at(h);
+        m_outputMappers.erase(h);
 	m_plugins.erase(h);
 	if (isConfigured(h)) {
 	    m_configuredPlugins.erase(h);
@@ -69,38 +80,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 {
+    //!!! iffy: mapper will move when another plugin is added. return by value?
+    const PluginOutputIdMapper &pluginToOutputIdMapper(Vamp::Plugin *p) const {
+        // pluginToHandle checks the plugin has been registered with us
+        return *m_outputMappers.at(pluginToHandle(p));
+    }
+
+    //!!! iffy: mapper will move when another plugin is added. return by value?
+    const 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 +133,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, DefaultPluginOutputIdMapper *> m_outputMappers;
 };
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bits/DefaultPluginOutputIdMapper.h	Fri Sep 16 15:10:57 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	Fri Sep 16 14:15:41 2016 +0100
+++ b/bits/PluginHandleMapper.h	Fri Sep 16 15:10:57 2016 +0100
@@ -37,6 +37,8 @@
 
 #include <vamp-hostsdk/Plugin.h>
 
+#include "PluginOutputIdMapper.h"
+
 namespace vampipe {
 
 class PluginHandleMapper
@@ -44,13 +46,20 @@
     // 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 PluginOutputIdMapper &pluginToOutputIdMapper(Vamp::Plugin *p) const = 0; // may throw NotFound
+    virtual const PluginOutputIdMapper &handleToOutputIdMapper(Handle h) const = 0; // may throw NotFound
 };
 
 }
--- a/bits/PluginOutputIdMapper.h	Fri Sep 16 14:15:41 2016 +0100
+++ b/bits/PluginOutputIdMapper.h	Fri Sep 16 15:10:57 2016 +0100
@@ -42,31 +42,14 @@
 
 namespace vampipe {
 
+//!!! doc interface
 class PluginOutputIdMapper
 {
 public:
-    PluginOutputIdMapper(Vamp::Plugin *p) {
-	Vamp::Plugin::OutputList outputs = p->getOutputDescriptors();
-	for (const auto &d: outputs) {
-	    m_ids.push_back(d.identifier);
-	}
-    }
-
-    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;
-	    }
-	}
-    }
-
-    std::string indexToId(int index) const {
-	return m_ids[index];
-    }
-
-private:
-    std::vector<std::string> m_ids;
+    virtual ~PluginOutputIdMapper() { }
+    
+    virtual int idToIndex(std::string outputId) const = 0;
+    virtual std::string indexToId(int index) const = 0;
 };
 
 }
--- a/bits/PreservingPluginHandleMapper.h	Fri Sep 16 14:15:41 2016 +0100
+++ b/bits/PreservingPluginHandleMapper.h	Fri Sep 16 15:10:57 2016 +0100
@@ -36,11 +36,16 @@
 #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:
@@ -63,9 +68,18 @@
 	return m_plugin;
     }
 
+    virtual const PluginOutputIdMapper &pluginToOutputIdMapper(Vamp::Plugin *) const {
+        return m_omapper;
+    }
+        
+    virtual const PluginOutputIdMapper &handleToOutputIdMapper(int32_t h) const {
+        return m_omapper;
+    }
+    
 private:
     mutable int32_t m_handle;
     mutable Vamp::Plugin *m_plugin;
+    PreservingPluginOutputIdMapper m_omapper;
 };
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bits/PreservingPluginOutputIdMapper.h	Fri Sep 16 15:10:57 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/capnproto/VampnProto.h	Fri Sep 16 14:15:41 2016 +0100
+++ b/capnproto/VampnProto.h	Fri Sep 16 15:10:57 2016 +0100
@@ -529,7 +529,7 @@
     static void
     buildLoadResponse(LoadResponse::Builder &b,
                       const Vamp::HostExt::LoadResponse &resp,
-                      PluginHandleMapper &pmapper) {
+                      const PluginHandleMapper &pmapper) {
 
         b.setPluginHandle(pmapper.pluginToHandle(resp.plugin));
         auto sd = b.initStaticData();
@@ -541,7 +541,7 @@
     static void
     readLoadResponse(Vamp::HostExt::LoadResponse &resp,
                      const LoadResponse::Reader &r,
-                     PluginHandleMapper &pmapper) {
+                     const PluginHandleMapper &pmapper) {
 
         resp.plugin = pmapper.handleToPlugin(r.getPluginHandle());
         readPluginStaticData(resp.staticData, r.getStaticData());
@@ -552,7 +552,7 @@
     static void
     buildConfigurationRequest(ConfigurationRequest::Builder &b,
                               const Vamp::HostExt::ConfigurationRequest &cr,
-                              PluginHandleMapper &pmapper) {
+                              const PluginHandleMapper &pmapper) {
 
         b.setPluginHandle(pmapper.pluginToHandle(cr.plugin));
         auto c = b.initConfiguration();
@@ -562,7 +562,7 @@
     static void
     readConfigurationRequest(Vamp::HostExt::ConfigurationRequest &cr,
                              const ConfigurationRequest::Reader &r,
-                             PluginHandleMapper &pmapper) {
+                             const PluginHandleMapper &pmapper) {
 
         auto h = r.getPluginHandle();
         cr.plugin = pmapper.handleToPlugin(h);
@@ -632,7 +632,7 @@
     static void
     buildProcessRequest(ProcessRequest::Builder &b,
                         const Vamp::HostExt::ProcessRequest &pr,
-                        PluginHandleMapper &pmapper) {
+                        const PluginHandleMapper &pmapper) {
 
         b.setPluginHandle(pmapper.pluginToHandle(pr.plugin));
         auto input = b.initInput();
@@ -642,7 +642,7 @@
     static void
     readProcessRequest(Vamp::HostExt::ProcessRequest &pr,
                        const ProcessRequest::Reader &r,
-                       PluginHandleMapper &pmapper) {
+                       const PluginHandleMapper &pmapper) {
 
         auto h = r.getPluginHandle();
         pr.plugin = pmapper.handleToPlugin(h);
@@ -652,18 +652,20 @@
     static void
     buildProcessResponse(ProcessResponse::Builder &b,
                          const Vamp::HostExt::ProcessResponse &pr,
-                         const PluginOutputIdMapper &omapper) {
+                         const PluginHandleMapper &pmapper) {
 
         auto f = b.initFeatures();
-        buildFeatureSet(f, pr.features, omapper);
+        buildFeatureSet(f, pr.features,
+                        pmapper.pluginToOutputIdMapper(pr.plugin));
     }
     
     static void
     readProcessResponse(Vamp::HostExt::ProcessResponse &pr,
                         const ProcessResponse::Reader &r,
-                        const PluginOutputIdMapper &omapper) {
+                        const PluginHandleMapper &pmapper) {
 
-        readFeatureSet(pr.features, r.getFeatures(), omapper);
+        readFeatureSet(pr.features, r.getFeatures(),
+                       pmapper.handleToOutputIdMapper(r.getPluginHandle()));
     }
 
     static void
@@ -695,7 +697,7 @@
     static void
     buildVampResponse_Load(VampResponse::Builder &b,
                            const Vamp::HostExt::LoadResponse &resp,
-                           PluginHandleMapper &pmapper) {
+                           const PluginHandleMapper &pmapper) {
         b.setSuccess(resp.plugin != 0);
         b.setErrorText("");
         auto u = b.getResponse().initLoad();
@@ -705,7 +707,7 @@
     static void
     buildVampRequest_Configure(VampRequest::Builder &b,
                                const Vamp::HostExt::ConfigurationRequest &cr,
-                               PluginHandleMapper &pmapper) {
+                               const PluginHandleMapper &pmapper) {
         auto u = b.getRequest().initConfigure();
         buildConfigurationRequest(u, cr, pmapper);
     }
@@ -722,7 +724,7 @@
     static void
     buildVampRequest_Process(VampRequest::Builder &b,
                              const Vamp::HostExt::ProcessRequest &pr,
-                             PluginHandleMapper &pmapper) {
+                             const PluginHandleMapper &pmapper) {
         auto u = b.getRequest().initProcess();
         buildProcessRequest(u, pr, pmapper);
     }
@@ -730,17 +732,17 @@
     static void
     buildVampResponse_Process(VampResponse::Builder &b,
                               const Vamp::HostExt::ProcessResponse &pr,
-                              const PluginOutputIdMapper &omapper) {
+                              const PluginHandleMapper &pmapper) {
         b.setSuccess(true);
         b.setErrorText("");
         auto u = b.getResponse().initProcess();
-        buildProcessResponse(u, pr, omapper);
+        buildProcessResponse(u, pr, pmapper);
     }
     
     static void
     buildVampRequest_Finish(VampRequest::Builder &b,
                             Vamp::Plugin *p,
-                            PluginHandleMapper &pmapper) {
+                            const PluginHandleMapper &pmapper) {
 
         auto u = b.getRequest().initFinish();
         u.setPluginHandle(pmapper.pluginToHandle(p));
@@ -749,9 +751,9 @@
     static void
     buildVampResponse_Finish(VampResponse::Builder &b,
                              const Vamp::HostExt::ProcessResponse &pr,
-                             const PluginOutputIdMapper &omapper) {
+                             const PluginHandleMapper &pmapper) {
 
-        buildVampResponse_Process(b, pr, omapper);
+        buildVampResponse_Process(b, pr, pmapper);
     }
 
     static RRType
@@ -824,7 +826,7 @@
     static void
     readVampResponse_Load(Vamp::HostExt::LoadResponse &resp,
                           const VampResponse::Reader &r,
-                          PluginHandleMapper &pmapper) {
+                          const PluginHandleMapper &pmapper) {
         if (getRequestResponseType(r) != RRType::Load) {
             throw std::logic_error("not a load response");
         }
@@ -837,7 +839,7 @@
     static void
     readVampRequest_Configure(Vamp::HostExt::ConfigurationRequest &req,
                               const VampRequest::Reader &r,
-                              PluginHandleMapper &pmapper) {
+                              const PluginHandleMapper &pmapper) {
         if (getRequestResponseType(r) != RRType::Configure) {
             throw std::logic_error("not a configuration request");
         }
@@ -859,7 +861,7 @@
     static void
     readVampRequest_Process(Vamp::HostExt::ProcessRequest &req,
                             const VampRequest::Reader &r,
-                            PluginHandleMapper &pmapper) {
+                            const PluginHandleMapper &pmapper) {
         if (getRequestResponseType(r) != RRType::Process) {
             throw std::logic_error("not a process request");
         }
@@ -869,20 +871,20 @@
     static void
     readVampResponse_Process(Vamp::HostExt::ProcessResponse &resp,
                              const VampResponse::Reader &r,
-                             const PluginOutputIdMapper &omapper) {
+                             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(), omapper);
+            readProcessResponse(resp, r.getResponse().getProcess(), pmapper);
         }
     }
     
     static void
     readVampRequest_Finish(Vamp::Plugin *&finishPlugin,
                            const VampRequest::Reader &r,
-                           PluginHandleMapper &pmapper) {
+                           const PluginHandleMapper &pmapper) {
         if (getRequestResponseType(r) != RRType::Finish) {
             throw std::logic_error("not a finish request");
         }
@@ -893,13 +895,13 @@
     static void
     readVampResponse_Finish(Vamp::HostExt::ProcessResponse &resp,
                             const VampResponse::Reader &r,
-                            const PluginOutputIdMapper &omapper) {
+                            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(), omapper);
+            readProcessResponse(resp, r.getResponse().getFinish(), pmapper);
         }
     }
 };
--- a/json/VampJson.h	Fri Sep 16 14:15:41 2016 +0100
+++ b/json/VampJson.h	Fri Sep 16 15:10:57 2016 +0100
@@ -994,14 +994,16 @@
 
     static json11::Json
     fromVampResponse_Process(const Vamp::HostExt::ProcessResponse &resp,
-                             const PluginOutputIdMapper &omapper,
+                             const PluginHandleMapper &pmapper,
                              BufferSerialisation serialisation) {
         
         json11::Json::object jo;
         jo["type"] = "process";
         jo["success"] = true;
         jo["errorText"] = "";
-        jo["content"] = fromFeatureSet(resp.features, omapper, serialisation);
+        jo["content"] = fromFeatureSet(resp.features,
+                                       pmapper.pluginToOutputIdMapper(resp.plugin),
+                                       serialisation);
         return json11::Json(jo);
     }
     
@@ -1019,14 +1021,16 @@
     
     static json11::Json
     fromVampResponse_Finish(const Vamp::HostExt::ProcessResponse &resp,
-                            const PluginOutputIdMapper &omapper,
+                            const PluginHandleMapper &pmapper,
                             BufferSerialisation serialisation) {
 
         json11::Json::object jo;
         jo["type"] = "finish";
         jo["success"] = true;
         jo["errorText"] = "";
-        jo["content"] = fromFeatureSet(resp.features, omapper, serialisation);
+        jo["content"] = fromFeatureSet(resp.features,
+                                       pmapper.pluginToOutputIdMapper(resp.plugin),
+                                       serialisation);
         return json11::Json(jo);
     }
 
@@ -1153,12 +1157,16 @@
     
     static Vamp::HostExt::ProcessResponse
     toVampResponse_Process(json11::Json j,
-                           const PluginOutputIdMapper &omapper,
+                           const PluginHandleMapper &pmapper,
                            BufferSerialisation &serialisation) {
         
         Vamp::HostExt::ProcessResponse resp;
         if (successful(j)) {
-            resp.features = toFeatureSet(j["content"], omapper, serialisation);
+            auto jc = j["content"];
+            resp.features = toFeatureSet
+                (jc["features"],
+                 pmapper.handleToOutputIdMapper(jc["pluginHandle"].int_value()),
+                 serialisation);
         }
         return resp;
     }
@@ -1172,12 +1180,16 @@
     
     static Vamp::HostExt::ProcessResponse
     toVampResponse_Finish(json11::Json j,
-                          const PluginOutputIdMapper &omapper,
+                          const PluginHandleMapper &pmapper,
                           BufferSerialisation &serialisation) {
         
         Vamp::HostExt::ProcessResponse resp;
         if (successful(j)) {
-            resp.features = toFeatureSet(j["content"], omapper, serialisation);
+            auto jc = j["content"];
+            resp.features = toFeatureSet
+                (jc["features"],
+                 pmapper.handleToOutputIdMapper(jc["pluginHandle"].int_value()),
+                 serialisation);
         }
         return resp;
     }
--- a/utilities/json-to-capnp.cpp	Fri Sep 16 14:15:41 2016 +0100
+++ b/utilities/json-to-capnp.cpp	Fri Sep 16 15:10:57 2016 +0100
@@ -65,8 +65,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	Fri Sep 16 14:15:41 2016 +0100
+++ b/utilities/vampipe-convert.cpp	Fri Sep 16 15:10:57 2016 +0100
@@ -184,10 +184,10 @@
 	rr.configurationResponse = VampJson::toVampResponse_Configure(j);
 	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;
@@ -215,6 +215,7 @@
     case RRType::Process:
 	j = VampJson::fromVampResponse_Process
 	    (rr.processResponse,
+	     mapper,
 	     useBase64 ?
 	     VampJson::BufferSerialisation::Base64 :
 	     VampJson::BufferSerialisation::Text);
@@ -222,6 +223,7 @@
     case RRType::Finish:
 	j = VampJson::fromVampResponse_Finish
 	    (rr.finishResponse,
+	     mapper,
 	     useBase64 ?
 	     VampJson::BufferSerialisation::Base64 :
 	     VampJson::BufferSerialisation::Text);
@@ -324,10 +326,10 @@
 					       reader);
 	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;
@@ -354,10 +356,10 @@
 	VampnProto::buildVampResponse_Configure(builder, rr.configurationResponse);
 	break;
     case RRType::Process:
-	VampnProto::buildVampResponse_Process(builder, rr.processResponse);
+	VampnProto::buildVampResponse_Process(builder, rr.processResponse, mapper);
 	break;
     case RRType::Finish:
-	VampnProto::buildVampResponse_Finish(builder, rr.finishResponse);
+	VampnProto::buildVampResponse_Finish(builder, rr.finishResponse, mapper);
 	break;
     case RRType::NotValid:
 	break;
--- a/utilities/vampipe-server.cpp	Fri Sep 16 14:15:41 2016 +0100
+++ b/utilities/vampipe-server.cpp	Fri Sep 16 15:10:57 2016 +0100
@@ -92,10 +92,10 @@
 	VampnProto::buildVampResponse_Configure(builder, rr.configurationResponse);
 	break;
     case RRType::Process:
-	VampnProto::buildVampResponse_Process(builder, rr.processResponse);
+	VampnProto::buildVampResponse_Process(builder, rr.processResponse, mapper);
 	break;
     case RRType::Finish:
-	VampnProto::buildVampResponse_Finish(builder, rr.finishResponse);
+	VampnProto::buildVampResponse_Finish(builder, rr.finishResponse, mapper);
 	break;
     case RRType::NotValid:
 	break;