changeset 75:81e1c48e97f9

Rearrange and rename to Piper C++ structure
author Chris Cannam <c.cannam@qmul.ac.uk>
date Mon, 10 Oct 2016 16:31:09 +0100
parents d45cfa25aaad
children 5909d5d25733
files .hgignore Makefile base-n/LICENSE base-n/README.md base-n/example/basen_example.cpp base-n/include/basen.hpp bits/CountingPluginHandleMapper.h bits/DefaultPluginOutputIdMapper.h bits/PluginHandleMapper.h bits/PluginOutputIdMapper.h bits/PreservingPluginHandleMapper.h bits/PreservingPluginOutputIdMapper.h bits/RequestOrResponse.h bits/RequestResponseType.h capnproto/VampnProto.h json/VampJson.h json/base-n/LICENSE json/base-n/README.md json/base-n/example/basen_example.cpp json/base-n/include/basen.hpp json/json11/CMakeLists.txt json/json11/LICENSE.txt json/json11/Makefile json/json11/README.md json/json11/json11.cpp json/json11/json11.hpp json/json11/test.cpp json11/CMakeLists.txt json11/LICENSE.txt json11/Makefile json11/README.md json11/json11.cpp json11/json11.hpp json11/test.cpp test/test-server.sh test/test-vampipe-server.sh utilities/vampipe-convert.cpp utilities/vampipe-server.cpp vamp-capnp/VampnProto.h vamp-json/VampJson.h vamp-server/convert.cpp vamp-server/server.cpp vamp-support/CountingPluginHandleMapper.h vamp-support/DefaultPluginOutputIdMapper.h vamp-support/PluginHandleMapper.h vamp-support/PluginOutputIdMapper.h vamp-support/PreservingPluginHandleMapper.h vamp-support/PreservingPluginOutputIdMapper.h vamp-support/RequestOrResponse.h vamp-support/RequestResponseType.h
diffstat 50 files changed, 5833 insertions(+), 5823 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,5 @@
+syntax: glob
+bin
+o
+*.capnp.c++
+*.capnp.h
--- a/Makefile	Mon Oct 10 15:31:44 2016 +0100
+++ b/Makefile	Mon Oct 10 16:31:09 2016 +0100
@@ -1,39 +1,46 @@
+
+VAMPSDK_DIR	:= ../vamp-plugin-sdk
+PIPER_DIR	:= ../piper
 
 CXXFLAGS	:= -Wall -Werror -g3 -std=c++11
-INCFLAGS	:= -Ivamp-plugin-sdk -Ijson -I/usr/local/include -Icapnproto -I.
+INCFLAGS	:= -I$(VAMPSDK_DIR) -I. -I/usr/local/include
 
-LDFLAGS		:= vamp-plugin-sdk/libvamp-hostsdk.a -L/usr/local/lib -lcapnp -lkj -ldl
+LDFLAGS		:= $(VAMPSDK_DIR)/libvamp-hostsdk.a -L/usr/local/lib -lcapnp -lkj -ldl
 
-#!!! todo: proper dependencies
+all:	o bin bin/piper-convert bin/piper-vamp-server
 
-all:	bin/vampipe-convert bin/vampipe-server
+bin:
+	mkdir bin
 
-bin/vampipe-convert: o/vampipe-convert.o o/json11.o o/piper.capnp.o
+o:
+	mkdir o
+
+bin/piper-convert: o/convert.o o/json11.o o/piper.capnp.o
 	c++ $(CXXFLAGS) $^ -o $@ $(LDFLAGS)
 
-bin/vampipe-server: o/vampipe-server.o o/piper.capnp.o
+bin/piper-vamp-server: o/server.o o/piper.capnp.o
 	c++ $(CXXFLAGS) $^ -o $@ $(LDFLAGS)
 
-capnproto/piper.capnp.h:	capnproto/piper.capnp
-	capnp compile $< -oc++
+vamp-capnp/piper.capnp.h:	$(PIPER_DIR)/capnp/piper.capnp
+	capnp compile -oc++:vamp-capnp --src-prefix=$(PIPER_DIR)/capnp $<
 
-o/piper.capnp.o:	capnproto/piper.capnp.c++ capnproto/piper.capnp.h
+o/piper.capnp.o:	vamp-capnp/piper.capnp.c++ vamp-capnp/piper.capnp.h
 	c++ $(CXXFLAGS) $(INCFLAGS) -c $< -o $@
 
-o/json11.o:	json/json11/json11.cpp
+o/json11.o:	json11/json11.cpp
 	c++ $(CXXFLAGS) -c $< -o $@
 
-o/vampipe-convert.o:	utilities/vampipe-convert.cpp capnproto/piper.capnp.h capnproto/VampnProto.h json/VampJson.h
+o/convert.o:	vamp-server/convert.cpp vamp-capnp/piper.capnp.h vamp-capnp/VampnProto.h vamp-json/VampJson.h
 	c++ $(CXXFLAGS) $(INCFLAGS) -c $< -o $@
 
-o/vampipe-server.o:	utilities/vampipe-server.cpp capnproto/piper.capnp.h capnproto/VampnProto.h 
+o/server.o:	vamp-server/server.cpp vamp-capnp/piper.capnp.h vamp-capnp/VampnProto.h 
 	c++ $(CXXFLAGS) $(INCFLAGS) -c $< -o $@
 
 test:	all
-	test/test-vampipe-server.sh
+	test/test-server.sh
 
 clean:
-	rm -f */*.o capnproto/piper.capnp.h capnproto/piper.capnp.c++
+	rm -f */*.o vamp-capnp/piper.capnp.h vamp-capnp/piper.capnp.c++
 
 distclean:	clean
 	rm -f bin/*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base-n/LICENSE	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,19 @@
+Copyright (C) 2012 Andrzej Zawadzki (azawadzki@gmail.com)
+
+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 OR COPYRIGHT HOLDERS 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.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base-n/README.md	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,69 @@
+base-n provides encoding/decoding support for BaseX encoding schemes accessible through a standard STL-like iterator interface. Standard Base16, Base32, and Base64 are available out-of-the-box. Adding or modifying custom schemes is supported.
+
+# Usage overview #
+
+base-n is a small, single-header library which provides standard Base16, Base32, Base64, and custom Base-N encoding support.
+
+The main functionality is delivered by the following functions in `bn` namespace:
+```
+template<class Iter1, class Iter2>
+void encode_b16(Iter1 start, Iter1 end, Iter2 out);
+
+
+template<class Iter1, class Iter2>
+void encode_b32(Iter1 start, Iter1 end, Iter2 out);
+
+
+template<class Iter1, class Iter2>
+void encode_b64(Iter1 start, Iter1 end, Iter2 out);
+
+
+template<class Iter1, class Iter2>
+void decode_b16(Iter1 start, Iter1 end, Iter2 out);
+
+
+template<class Iter1, class Iter2>
+void decode_b32(Iter1 start, Iter1 end, Iter2 out);
+
+
+template<class Iter1, class Iter2>
+void decode_b64(Iter1 start, Iter1 end, Iter2 out);
+```
+
+In order to encode and decode data in `std::string` variable `in`, you can do the following:
+```
+bn::encode_b64(in.begin(), in.end(), back_inserter(encoded));
+bn::decode_b64(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, ""));
+```
+
+Should you find yourself lacking some encoding scheme or the default character mapping rules are not good for your use case, you can easily provide your own encoder. For that, you need to define a struct type which will describe the new encoding. Sample below:
+```
+struct b8_custom
+{
+        static size_t group_length()
+        {
+                return 3;
+        }
+
+        static char encode(int index)
+        {
+                const char* const dictionary = "01234567";
+                assert(index < strlen(dictionary));
+                return dictionary[index];
+        }
+
+        static char decode(char c)
+        {
+                if (c >= '0' && c <= '7') {
+                        return c - '0';
+                }
+                return -1;
+        }
+};
+...
+string encoded;
+bn::impl::encode<b8_custom>(in.begin(), in.end(), back_inserter(encoded));
+bn::impl::decode<b8_custom>(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, ""));
+```
+
+For a full working example, see `basen_example.cpp` file in `example` directory.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base-n/example/basen_example.cpp	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,94 @@
+/**
+ * base-n, 1.0
+ * Copyright (C) 2012 Andrzej Zawadzki (azawadzki@gmail.com)
+ * 
+ * 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 OR COPYRIGHT HOLDERS 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.
+**/
+#include <cassert>
+#include <cstring>
+#include <iostream>
+#include <iterator>
+#include <string>
+
+#include "basen.hpp"
+
+int main()
+{
+    using namespace std;
+    string in = "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.";
+    cout << in << endl;
+
+    {
+        string encoded;
+        bn::encode_b64(in.begin(), in.end(), back_inserter(encoded));
+        bn::decode_b64(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, ""));
+        cout << endl;
+    }
+    {
+        string encoded;
+        bn::encode_b32(in.begin(), in.end(), back_inserter(encoded));
+        bn::decode_b32(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, ""));
+        cout << endl;
+    }
+    {
+        string encoded;
+        bn::encode_b16(in.begin(), in.end(), back_inserter(encoded));
+        bn::decode_b16(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, ""));
+        cout << endl;
+    }
+    {
+        string encoded = "#TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz\n"
+            "IHNpbmd1bGFyIHBhc3Npb24gZnJvbS@BvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg\n"
+            " dGhlIG1(pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu\n"
+            "\rdWVkIGFuZCBpbmRlZmF0aWdhY*mxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo\n"
+            "ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4";
+        bn::decode_b64(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, ""));
+        cout << endl;
+    }
+    {
+        // move the struct definition outside of main() for non-C++11 compilers
+        struct b8_custom
+        {
+            static size_t group_length()
+            {
+               return 3;
+            }
+
+            static char encode(unsigned int index)
+            {
+                const char* const dictionary = "01234567";
+                assert(index < strlen(dictionary));
+                return dictionary[index];
+            }
+
+            static char decode(char c)
+            {
+                if (c >= '0' && c <= '7') {
+                    return c - '0';
+                }
+                return -1;
+            }
+        };
+        string encoded;
+        bn::impl::encode<b8_custom>(in.begin(), in.end(), back_inserter(encoded));
+        bn::impl::decode<b8_custom>(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, ""));
+        cout << endl;
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base-n/include/basen.hpp	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,290 @@
+/**
+ * base-n, 1.0
+ * Copyright (C) 2012 Andrzej Zawadzki (azawadzki@gmail.com)
+ * 
+ * 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 OR COPYRIGHT HOLDERS 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.
+**/
+#ifndef BASEN_HPP
+#define BASEN_HPP
+
+#include <algorithm>
+#include <cctype>
+#include <cassert>
+#include <cstring>
+
+namespace bn
+{
+
+template<class Iter1, class Iter2>
+void encode_b16(Iter1 start, Iter1 end, Iter2 out);
+
+template<class Iter1, class Iter2>
+void encode_b32(Iter1 start, Iter1 end, Iter2 out);
+
+template<class Iter1, class Iter2>
+void encode_b64(Iter1 start, Iter1 end, Iter2 out);
+
+template<class Iter1, class Iter2>
+void decode_b16(Iter1 start, Iter1 end, Iter2 out);
+
+template<class Iter1, class Iter2>
+void decode_b32(Iter1 start, Iter1 end, Iter2 out);
+
+template<class Iter1, class Iter2>
+void decode_b64(Iter1 start, Iter1 end, Iter2 out);
+
+namespace impl
+{
+
+const int ERROR = -1;
+
+namespace {
+
+char extract_partial_bits(char value, unsigned int start_bit, unsigned int bits_count)
+{
+    assert(start_bit + bits_count < 8);
+    // shift extracted bits to the beginning of the byte
+    char t1 = value >> (8 - bits_count - start_bit);
+    // mask out bits on the left
+    char t2 = t1 & ~(-1U << bits_count);
+    return t2;
+}
+
+char extract_overlapping_bits(char previous, char next, unsigned int start_bit, unsigned int bits_count)
+{
+    assert(start_bit + bits_count < 16);
+    int bits_count_in_previous = 8 - start_bit;
+    int bits_count_in_next = bits_count - bits_count_in_previous;
+    char t1 = previous << bits_count_in_next;
+    char t2 = next >> (8 - bits_count_in_next) & ~(-1U << bits_count_in_next) ;
+    return (t1 | t2) & ~(-1U << bits_count);
+}
+
+}
+
+struct b16_conversion_traits
+{
+    static size_t group_length()
+    {
+       return 4;
+    }
+
+    static char encode(unsigned int index)
+    {
+        const char* const dictionary = "0123456789ABCDEF";
+        assert(index < strlen(dictionary));
+        return dictionary[index];
+    }
+
+    static char decode(char c)
+    {
+        if (c >= '0' && c <= '9') {
+            return c - '0';
+        } else if (c >= 'A' && c <= 'F') {
+            return c - 'A' + 10;
+        }
+        return ERROR;
+    }
+};
+
+struct b32_conversion_traits
+{
+    static size_t group_length()
+    {
+       return 5;
+    }
+
+    static char encode(unsigned int index)
+    {
+        const char * dictionary = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+        assert(index < strlen(dictionary));
+        return dictionary[index];
+    }
+
+    static char decode(char c)
+    {
+        if (c >= 'A' && c <= 'Z') {
+            return c - 'A';
+        } else if (c >= '2' && c <= '7') {
+            return c - '2' + 26;
+        }
+        return ERROR;
+    }
+};
+
+struct b64_conversion_traits
+{
+    static size_t group_length()
+    {
+       return 6;
+    }
+
+    static char encode(unsigned int index)
+    {
+        const char* const dictionary = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+        assert(index < strlen(dictionary));
+        return dictionary[index];
+    }
+
+    static char decode(char c)
+    {
+        const int alph_len = 26;
+        if (c >= 'A' && c <= 'Z') {
+            return c - 'A';
+        } else if (c >= 'a' && c <= 'z') {
+            return c - 'a' + alph_len * 1;
+        } else if (c >= '0' && c <= '9') {
+            return c - '0' + alph_len * 2;
+        } else if (c == '+') {
+            return c - '+' + alph_len * 2 + 10;
+        } else if (c == '/') {
+            return c - '/' + alph_len * 2 + 11;
+        }
+        return ERROR;
+    }
+};
+
+template<class ConversionTraits, class Iter1, class Iter2>
+void decode(Iter1 start, Iter1 end, Iter2 out)
+{
+    Iter1 iter = start;
+    int output_current_bit = 0;
+    char buffer = 0;
+
+    while (iter != end) {
+        if (std::isspace(*iter)) {
+            ++iter;
+            continue;
+        }
+        char value = ConversionTraits::decode(*iter);
+        if (value == ERROR) {
+            // malformed data, but let's go on...
+            ++iter;
+            continue;
+        }
+        unsigned int bits_in_current_byte = std::min<int>(output_current_bit + ConversionTraits::group_length(), 8) - output_current_bit;
+        if (bits_in_current_byte == ConversionTraits::group_length()) {
+            // the value fits within current byte, so we can extract it directly
+            buffer |= value << (8 - output_current_bit - ConversionTraits::group_length());
+            output_current_bit += ConversionTraits::group_length();
+            // check if we filled up current byte completely; in such case we flush output and continue
+            if (output_current_bit == 8) {
+                *out++ = buffer;
+                buffer = 0;
+                output_current_bit = 0;
+            }
+        } else {
+            // the value spans across the current and the next byte
+            int bits_in_next_byte = ConversionTraits::group_length() - bits_in_current_byte;
+            // fill the current byte and flush it to our output
+            buffer |= value >> bits_in_next_byte;
+            *out++ = buffer;
+            buffer = 0;
+            // save the remainder of our value in the buffer; it will be flushed
+            // during next iterations
+            buffer |= value << (8 - bits_in_next_byte);
+            output_current_bit = bits_in_next_byte;
+        }
+        ++iter;
+    }
+}
+
+template<class ConversionTraits, class Iter1, class Iter2>
+void encode(Iter1 start, Iter1 end, Iter2 out)
+{
+    Iter1 iter = start;
+    int start_bit = 0;
+    bool has_backlog = false;
+    char backlog = 0;
+
+    while (has_backlog || iter != end) {
+        if (!has_backlog) {
+            if (start_bit + ConversionTraits::group_length() < 8) {
+                // the value fits within single byte, so we can extract it
+                // directly
+                char v = extract_partial_bits(*iter, start_bit, ConversionTraits::group_length());
+                *out++ = ConversionTraits::encode(v);
+                // since we know that start_bit + ConversionTraits::group_length() < 8 we don't need to go
+                // to the next byte
+                start_bit += ConversionTraits::group_length();
+            } else {
+                // our bits are spanning across byte border; we need to keep the
+                // starting point and move over to next byte.
+                backlog = *iter++;
+                has_backlog = true;
+            }
+        } else {
+            // encode value which is made from bits spanning across byte
+            // boundary
+            char v;
+            if (iter == end)
+                 v = extract_overlapping_bits(backlog, 0, start_bit, ConversionTraits::group_length());
+            else
+                 v = extract_overlapping_bits(backlog, *iter, start_bit, ConversionTraits::group_length());
+            *out++ = ConversionTraits::encode(v);
+            has_backlog = false;
+            start_bit = (start_bit + ConversionTraits::group_length()) % 8;
+        }
+    }
+}
+
+} // impl
+
+using namespace bn::impl;
+
+template<class Iter1, class Iter2>
+void encode_b16(Iter1 start, Iter1 end, Iter2 out)
+{
+    encode<b16_conversion_traits>(start, end, out);
+}
+
+template<class Iter1, class Iter2>
+void encode_b32(Iter1 start, Iter1 end, Iter2 out)
+{
+    encode<b32_conversion_traits>(start, end, out);
+}
+
+template<class Iter1, class Iter2>
+void encode_b64(Iter1 start, Iter1 end, Iter2 out)
+{
+    encode<b64_conversion_traits>(start, end, out);
+}
+
+template<class Iter1, class Iter2>
+void decode_b16(Iter1 start, Iter1 end, Iter2 out)
+{
+    decode<b16_conversion_traits>(start, end, out);
+}
+
+template<class Iter1, class Iter2>
+void decode_b32(Iter1 start, Iter1 end, Iter2 out)
+{
+    decode<b32_conversion_traits>(start, end, out);
+}
+
+template<class Iter1, class Iter2>
+void decode_b64(Iter1 start, Iter1 end, Iter2 out)
+{
+    decode<b64_conversion_traits>(start, end, out);
+}
+
+} // bn
+
+#endif // BASEN_HPP
+
--- a/bits/CountingPluginHandleMapper.h	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,142 +0,0 @@
-/* -*- 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_COUNTING_PLUGIN_HANDLE_MAPPER_H
-#define VAMPIPE_COUNTING_PLUGIN_HANDLE_MAPPER_H
-
-#include "PluginHandleMapper.h"
-#include "PluginOutputIdMapper.h"
-#include "DefaultPluginOutputIdMapper.h"
-
-#include <set>
-#include <map>
-
-namespace vampipe {
-
-class CountingPluginHandleMapper : public PluginHandleMapper
-{
-public:
-    CountingPluginHandleMapper() : m_nextHandle(1) { }
-
-    void addPlugin(Vamp::Plugin *p) {
-        if (!p) return;
-	if (m_rplugins.find(p) == m_rplugins.end()) {
-	    Handle h = m_nextHandle++;
-	    m_plugins[h] = p;
-	    m_rplugins[p] = h;
-            m_outputMappers[h] =
-                std::make_shared<DefaultPluginOutputIdMapper>(p);
-	}
-    }
-
-    void removePlugin(Handle h) {
-	if (m_plugins.find(h) == m_plugins.end()) return;
-	Vamp::Plugin *p = m_plugins[h];
-        m_outputMappers.erase(h);
-	m_plugins.erase(h);
-	if (isConfigured(h)) {
-	    m_configuredPlugins.erase(h);
-	    m_channelCounts.erase(h);
-	}
-	m_rplugins.erase(p);
-    }
-    
-    Handle pluginToHandle(Vamp::Plugin *p) const noexcept {
-	if (m_rplugins.find(p) == m_rplugins.end()) {
-            return INVALID_HANDLE;
-	}
-	return m_rplugins.at(p);
-    }
-    
-    Vamp::Plugin *handleToPlugin(Handle h) const noexcept {
-	if (m_plugins.find(h) == m_plugins.end()) {
-            return nullptr;
-	}
-	return m_plugins.at(h);
-    }
-
-    const std::shared_ptr<PluginOutputIdMapper> pluginToOutputIdMapper
-    (Vamp::Plugin *p) const noexcept {
-        return handleToOutputIdMapper(pluginToHandle(p));
-    }
-
-    const std::shared_ptr<PluginOutputIdMapper> handleToOutputIdMapper
-    (Handle h) const noexcept {
-	if (h != INVALID_HANDLE &&
-            m_outputMappers.find(h) != m_outputMappers.end()) {
-            return m_outputMappers.at(h);
-        } else {
-	    return {};
-	}
-    }
-
-    bool isConfigured(Handle h) const noexcept {
-        if (h == INVALID_HANDLE) return false;
-	return m_configuredPlugins.find(h) != m_configuredPlugins.end();
-    }
-
-    void markConfigured(Handle h, int channelCount, int blockSize) {
-        if (h == INVALID_HANDLE) return;
-	m_configuredPlugins.insert(h);
-	m_channelCounts[h] = channelCount;
-	m_blockSizes[h] = blockSize;
-    }
-
-    int getChannelCount(Handle h) const noexcept {
-	if (m_channelCounts.find(h) == m_channelCounts.end()) {
-	    return 0;
-	}
-	return m_channelCounts.at(h);
-    }
-
-    int getBlockSize(Handle h) const noexcept {
-	if (m_blockSizes.find(h) == m_blockSizes.end()) {
-            return 0;
-	}
-	return m_blockSizes.at(h);
-    }
-    
-private:
-    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;
-};
-
-}
-
-#endif
--- a/bits/DefaultPluginOutputIdMapper.h	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-/* -*- 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 noexcept {
-	int n = int(m_ids.size());
-	for (int i = 0; i < n; ++i) {
-	    if (outputId == m_ids[i]) {
-		return i;
-	    }
-	}
-	return -1;
-    }
-
-    virtual std::string indexToId(int index) const noexcept {
-        if (index < 0 || size_t(index) >= m_ids.size()) return "";
-	return m_ids[index];
-    }
-
-private:
-    std::vector<std::string> m_ids;
-};
-
-}
-
-#endif
--- a/bits/PluginHandleMapper.h	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-/* -*- 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_HANDLE_MAPPER_H
-#define VAMPIPE_PLUGIN_HANDLE_MAPPER_H
-
-#include "PluginOutputIdMapper.h"
-
-#include <vamp-hostsdk/Plugin.h>
-#include <memory>
-
-namespace vampipe {
-
-/**
- * Convert plugin pointers to handles within some scope defined by the
- * individual PluginHandleMapper implementation.
- *
- * The special handle 0 and the NULL plugin pointer are both used to
- * represent "not found" and will be returned in any case where an
- * unknown handle or plugin is requested.
- *
- * Note that the handle type must be representable as a JSON number,
- * hence the use of a 32-bit rather than 64-bit int.
- *
- * This interface also includes methods for obtaining a
- * PluginOutputIdMapper, \see PluginOutputIdMapper.
- */
-
-class PluginHandleMapper
-{
-public:
-    typedef int32_t Handle;
-    const Handle INVALID_HANDLE = 0;
-
-    virtual ~PluginHandleMapper() noexcept { }
-
-    /**
-     * Look up and return the handle for a given plugin pointer.
-     * If the given pointer is null or not known, return INVALID_HANDLE.
-     */
-    virtual Handle pluginToHandle(Vamp::Plugin *) const noexcept = 0;
-
-    /**
-     * Look up and return the plugin for a given handle.
-     * If the given handle is INVALID_HANDLE or not known, return nullptr.
-     */
-    virtual Vamp::Plugin *handleToPlugin(Handle)  const noexcept = 0;
-
-    /**
-     * Return a shared pointer to a PluginOutputIdMapper
-     * implementation for the given plugin pointer.  If the given
-     * pointer is null or not known, return the null shared_ptr.
-     */
-    virtual const std::shared_ptr<PluginOutputIdMapper> pluginToOutputIdMapper
-    (Vamp::Plugin *p) const noexcept = 0;
-
-    /**
-     * Return a shared pointer to a PluginOutputIdMapper
-     * implementation for the given plugin handle.  If the given
-     * handle is INVALID_HANDLE or not known, return the null shared_ptr.
-     */
-    virtual const std::shared_ptr<PluginOutputIdMapper> handleToOutputIdMapper
-    (Handle h) const noexcept = 0;
-};
-
-}
-
-#endif
-
--- a/bits/PluginOutputIdMapper.h	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
-/* -*- 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 {
-
-class PluginOutputIdMapper
-{
-public:
-    virtual ~PluginOutputIdMapper() { }
-
-    /**
-     * Return the index of the given output id in the plugin. The
-     * first output has index 0. If the given output id is unknown,
-     * return -1.
-     */
-    virtual int idToIndex(std::string outputId) const noexcept = 0;
-
-    /**
-     * Return the id of the output with the given index in the
-     * plugin. If the index is out of range, return the empty string.
-     */
-    virtual std::string indexToId(int index) const noexcept = 0;
-};
-
-}
-
-#endif
--- a/bits/PreservingPluginHandleMapper.h	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-/* -*- 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_HANDLE_MAPPER_H
-#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),
-        m_omapper(std::make_shared<PreservingPluginOutputIdMapper>()) { }
-
-    virtual Handle pluginToHandle(Vamp::Plugin *p) const noexcept {
-        if (!p) return INVALID_HANDLE;
-	if (p == m_plugin) return m_handle;
-	else {
-	    std::cerr << "PreservingPluginHandleMapper: p = " << p
-		      << " differs from saved m_plugin " << m_plugin
-		      << " (not returning saved handle " << m_handle << ")"
-		      << std::endl;
-            return INVALID_HANDLE;
-	}
-    }
-
-    virtual Vamp::Plugin *handleToPlugin(Handle h) const noexcept {
-        if (h == INVALID_HANDLE) return nullptr;
-	m_handle = h;
-	m_plugin = reinterpret_cast<Vamp::Plugin *>(h);
-	return m_plugin;
-    }
-
-    virtual const std::shared_ptr<PluginOutputIdMapper> pluginToOutputIdMapper
-    (Vamp::Plugin *p) const noexcept {
-        if (!p) return {};
-        return m_omapper;
-    }
-        
-    virtual const std::shared_ptr<PluginOutputIdMapper> handleToOutputIdMapper
-    (Handle h) const noexcept {
-        if (h == INVALID_HANDLE) return {};
-        return m_omapper;
-    }
-    
-private:
-    mutable Handle m_handle;
-    mutable Vamp::Plugin *m_plugin;
-    std::shared_ptr<PreservingPluginOutputIdMapper> m_omapper;
-};
-
-}
-
-#endif
--- a/bits/PreservingPluginOutputIdMapper.h	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/* -*- 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 noexcept {
-	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 noexcept {
-        if (index < 0 || size_t(index) >= m_ids.size()) return "";
-	return m_ids[index];
-    }
-
-private:
-    mutable std::vector<std::string> m_ids;
-};
-
-}
-
-#endif
--- a/bits/RequestOrResponse.h	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-/* -*- 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_REQUEST_OR_RESPONSE_H
-#define VAMPIPE_REQUEST_OR_RESPONSE_H
-
-#include "RequestResponseType.h"
-
-#include <vamp-hostsdk/PluginStaticData.h>
-#include <vamp-hostsdk/RequestResponse.h>
-
-#include <string>
-#include <vector>
-
-namespace vampipe {
-
-class RequestOrResponse
-{
-public:
-    enum Direction {
-	Request, Response
-    };
-
-    struct RpcId {
-        enum { Absent, Number, Tag } type;
-        int number;
-        std::string tag;
-    };
-    
-    RequestOrResponse() : // nothing by default
-	direction(Request),
-	type(RRType::NotValid),
-	success(false),
-        id({ RpcId::Absent, 0, "" })
-    { }
-
-    Direction direction;
-    RRType type;
-    bool success;
-    std::string errorText;
-    RpcId id;
-
-    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::HostExt::FinishRequest finishRequest;
-    Vamp::HostExt::ProcessResponse finishResponse;
-};
-
-}
-
-#endif
--- a/bits/RequestResponseType.h	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-/* -*- 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_REQUEST_RESPONSE_TYPE_H
-#define VAMPIPE_REQUEST_RESPONSE_TYPE_H
-
-namespace vampipe {
-
-enum class RRType {
-    List, Load, Configure, Process, Finish, NotValid
-};
-
-}
-
-#endif
-
--- a/capnproto/VampnProto.h	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1003 +0,0 @@
-/* -*- 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 2015-2016 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.
-*/
-
-#include "piper.capnp.h"
-
-#include <capnp/message.h>
-#include <capnp/serialize-packed.h>
-
-#include <vamp-hostsdk/Plugin.h>
-#include <vamp-hostsdk/PluginLoader.h>
-#include <vamp-hostsdk/PluginStaticData.h>
-
-#include "bits/PluginHandleMapper.h"
-#include "bits/PluginOutputIdMapper.h"
-#include "bits/RequestResponseType.h"
-
-namespace vampipe
-{
-
-using namespace piper;
-
-/**
- * Convert the structures laid out in the Vamp SDK classes into Cap'n
- * Proto structures (and back again).
- * 
- * At least some of this will be necessary for any implementation
- * using Cap'n Proto that uses the C++ Vamp SDK to provide its
- * reference structures. An implementation could alternatively use the
- * Cap'n Proto structures directly, and interact with Vamp plugins
- * using the Vamp C API, without using the C++ Vamp SDK classes at
- * all. That would avoid a lot of copying (in Cap'n Proto style).
- */
-class VampnProto
-{
-public:
-    typedef ::capnp::MallocMessageBuilder MsgBuilder;
-
-    template <typename T, typename B>
-    static void buildBasicDescriptor(B &basic, const T &t) {
-        basic.setIdentifier(t.identifier);
-        basic.setName(t.name);
-        basic.setDescription(t.description);
-    }
-
-    template <typename T, typename B>
-    static void readBasicDescriptor(T &t, const B &basic) {
-        t.identifier = basic.getIdentifier();
-        t.name = basic.getName();
-        t.description = basic.getDescription();
-    }
-
-    template <typename T, typename M>
-    static void buildValueExtents(M &m, const T &t) {
-        m.setMinValue(t.minValue);
-        m.setMaxValue(t.maxValue);
-    }
-
-    template <typename T, typename M>
-    static void readValueExtents(T &t, const M &m) {
-        t.minValue = m.getMinValue();
-        t.maxValue = m.getMaxValue();
-    }
-
-    static void buildRealTime(RealTime::Builder &b, const Vamp::RealTime &t) {
-        b.setSec(t.sec);
-        b.setNsec(t.nsec);
-    }
-
-    static void readRealTime(Vamp::RealTime &t, const RealTime::Reader &r) {
-        t.sec = r.getSec();
-        t.nsec = r.getNsec();
-    }
-
-    static SampleType
-    fromSampleType(Vamp::Plugin::OutputDescriptor::SampleType t) {
-        switch (t) {
-        case Vamp::Plugin::OutputDescriptor::OneSamplePerStep:
-            return SampleType::ONE_SAMPLE_PER_STEP;
-        case Vamp::Plugin::OutputDescriptor::FixedSampleRate:
-            return SampleType::FIXED_SAMPLE_RATE;
-        case Vamp::Plugin::OutputDescriptor::VariableSampleRate:
-            return SampleType::VARIABLE_SAMPLE_RATE;
-        }
-        throw std::logic_error("unexpected Vamp SampleType enum value");
-    }
-
-    static Vamp::Plugin::OutputDescriptor::SampleType
-    toSampleType(SampleType t) {
-        switch (t) {
-        case SampleType::ONE_SAMPLE_PER_STEP:
-            return Vamp::Plugin::OutputDescriptor::OneSamplePerStep;
-        case SampleType::FIXED_SAMPLE_RATE:
-            return Vamp::Plugin::OutputDescriptor::FixedSampleRate;
-        case SampleType::VARIABLE_SAMPLE_RATE:
-            return Vamp::Plugin::OutputDescriptor::VariableSampleRate;
-        }
-        throw std::logic_error("unexpected Capnp SampleType enum value");
-    }
-
-    static void
-    buildConfiguredOutputDescriptor(ConfiguredOutputDescriptor::Builder &b,
-                                    const Vamp::Plugin::OutputDescriptor &od) {
-
-        b.setUnit(od.unit);
-
-        b.setSampleType(fromSampleType(od.sampleType));
-        b.setSampleRate(od.sampleRate);
-        b.setHasDuration(od.hasDuration);
-
-        b.setHasFixedBinCount(od.hasFixedBinCount);
-        if (od.hasFixedBinCount) {
-            b.setBinCount(od.binCount);
-            if (od.binNames.size() > 0) {
-                auto binNames = b.initBinNames(od.binNames.size());
-                for (size_t i = 0; i < od.binNames.size(); ++i) {
-                    binNames.set(i, od.binNames[i]);
-                }
-            }
-        }
-
-        b.setHasKnownExtents(od.hasKnownExtents);
-        if (od.hasKnownExtents) {
-            buildValueExtents(b, od);
-        }
-
-        b.setIsQuantized(od.isQuantized);
-        if (od.isQuantized) {
-            b.setQuantizeStep(od.quantizeStep);
-        }
-    }
-
-    static void
-    buildOutputDescriptor(OutputDescriptor::Builder &b,
-                          const Vamp::Plugin::OutputDescriptor &od) {
-
-        auto basic = b.initBasic();
-        buildBasicDescriptor(basic, od);
-
-        auto configured = b.initConfigured();
-        buildConfiguredOutputDescriptor(configured, od);
-    }
-    
-    static void
-    readConfiguredOutputDescriptor(Vamp::Plugin::OutputDescriptor &od,
-                                   const ConfiguredOutputDescriptor::Reader &r) {
-
-        od.unit = r.getUnit();
-
-        od.sampleType = toSampleType(r.getSampleType());
-        od.sampleRate = r.getSampleRate();
-        od.hasDuration = r.getHasDuration();
-
-        od.hasFixedBinCount = r.getHasFixedBinCount();
-        if (od.hasFixedBinCount) {
-            od.binCount = r.getBinCount();
-            od.binNames.clear();
-            auto nn = r.getBinNames();
-            for (const auto &n: nn) {
-                od.binNames.push_back(n);
-            }
-        }
-
-        od.hasKnownExtents = r.getHasKnownExtents();
-        if (od.hasKnownExtents) {
-            readValueExtents(od, r);
-        }
-
-        od.isQuantized = r.getIsQuantized();
-        if (od.isQuantized) {
-            od.quantizeStep = r.getQuantizeStep();
-        }
-    }
-
-    static void
-    readOutputDescriptor(Vamp::Plugin::OutputDescriptor &od,
-                         const OutputDescriptor::Reader &r) {
-
-        readBasicDescriptor(od, r.getBasic());
-        readConfiguredOutputDescriptor(od, r.getConfigured());
-    }
-
-    static void
-    buildParameterDescriptor(ParameterDescriptor::Builder &b,
-                             const Vamp::Plugin::ParameterDescriptor &pd) {
-
-        auto basic = b.initBasic();
-        buildBasicDescriptor(basic, pd);
-
-        b.setUnit(pd.unit);
-
-        buildValueExtents(b, pd);
-
-        b.setDefaultValue(pd.defaultValue);
-
-        b.setIsQuantized(pd.isQuantized);
-        if (pd.isQuantized) {
-            b.setQuantizeStep(pd.quantizeStep);
-        }
-        
-        if (pd.valueNames.size() > 0) {
-            auto valueNames = b.initValueNames(pd.valueNames.size());
-            for (size_t i = 0; i < pd.valueNames.size(); ++i) {
-                valueNames.set(i, pd.valueNames[i]);
-            }
-        }
-    }
-
-    static void
-    readParameterDescriptor(Vamp::Plugin::ParameterDescriptor &pd,
-                            const ParameterDescriptor::Reader &r) {
-
-        readBasicDescriptor(pd, r.getBasic());
-
-        pd.unit = r.getUnit();
-
-        readValueExtents(pd, r);
-
-        pd.defaultValue = r.getDefaultValue();
-
-        pd.isQuantized = r.getIsQuantized();
-        if (pd.isQuantized) {
-            pd.quantizeStep = r.getQuantizeStep();
-        }
-
-        pd.valueNames.clear();
-        auto nn = r.getValueNames();
-        for (const auto &n: nn) {
-            pd.valueNames.push_back(n);
-        }
-    }
-    
-    static void
-    buildFeature(Feature::Builder &b,
-                 const Vamp::Plugin::Feature &f) {
-
-        b.setHasTimestamp(f.hasTimestamp);
-        if (f.hasTimestamp) {
-            auto timestamp = b.initTimestamp();
-            buildRealTime(timestamp, f.timestamp);
-        }
-
-        b.setHasDuration(f.hasDuration);
-        if (f.hasDuration) {
-            auto duration = b.initDuration();
-            buildRealTime(duration, f.duration);
-        }
-
-        b.setLabel(f.label);
-
-        if (f.values.size() > 0) {
-            auto values = b.initFeatureValues(f.values.size());
-            for (size_t i = 0; i < f.values.size(); ++i) {
-                values.set(i, f.values[i]);
-            }
-        }
-    }
-
-    static void
-    readFeature(Vamp::Plugin::Feature &f,
-                const Feature::Reader &r) {
-
-        f.hasTimestamp = r.getHasTimestamp();
-        if (f.hasTimestamp) {
-            readRealTime(f.timestamp, r.getTimestamp());
-        }
-
-        f.hasDuration = r.getHasDuration();
-        if (f.hasDuration) {
-            readRealTime(f.duration, r.getDuration());
-        }
-
-        f.label = r.getLabel();
-
-        f.values.clear();
-        auto vv = r.getFeatureValues();
-        for (auto v: vv) {
-            f.values.push_back(v);
-        }
-    }
-    
-    static void
-    buildFeatureSet(FeatureSet::Builder &b,
-                    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(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];
-                buildFeature(feature, fsi.second[j]);
-            }
-            ++ix;
-        }
-    }
-
-    static void
-    readFeatureSet(Vamp::Plugin::FeatureSet &fs,
-                   const FeatureSet::Reader &r,
-                   const PluginOutputIdMapper &omapper) {
-
-        fs.clear();
-        auto pp = r.getFeaturePairs();
-        for (const auto &p: pp) {
-            Vamp::Plugin::FeatureList vfl;
-            auto ff = p.getFeatures();
-            for (const auto &f: ff) {
-                Vamp::Plugin::Feature vf;
-                readFeature(vf, f);
-                vfl.push_back(vf);
-            }
-            fs[omapper.idToIndex(p.getOutput())] = vfl;
-        }
-    }
-    
-    static InputDomain
-    fromInputDomain(Vamp::Plugin::InputDomain d) {
-        switch(d) {
-        case Vamp::Plugin::TimeDomain:
-            return InputDomain::TIME_DOMAIN;
-        case Vamp::Plugin::FrequencyDomain:
-            return InputDomain::FREQUENCY_DOMAIN;
-        default:
-            throw std::logic_error("unexpected Vamp InputDomain enum value");
-        }
-    }
-
-    static Vamp::Plugin::InputDomain
-    toInputDomain(InputDomain d) {
-        switch(d) {
-        case InputDomain::TIME_DOMAIN:
-            return Vamp::Plugin::TimeDomain;
-        case InputDomain::FREQUENCY_DOMAIN:
-            return Vamp::Plugin::FrequencyDomain;
-        default:
-            throw std::logic_error("unexpected Capnp InputDomain enum value");
-        }
-    }
-    
-    static void
-    buildExtractorStaticData(ExtractorStaticData::Builder &b,
-                          const Vamp::HostExt::PluginStaticData &d) {
-
-        b.setKey(d.pluginKey);
-
-        auto basic = b.initBasic();
-        buildBasicDescriptor(basic, d.basic);
-
-        b.setMaker(d.maker);
-        b.setCopyright(d.copyright);
-        b.setVersion(d.pluginVersion);
-
-        auto clist = b.initCategory(d.category.size());
-        for (size_t i = 0; i < d.category.size(); ++i) {
-            clist.set(i, d.category[i]);
-        }
-
-        b.setMinChannelCount(d.minChannelCount);
-        b.setMaxChannelCount(d.maxChannelCount);
-
-        const auto &vparams = d.parameters;
-        auto plist = b.initParameters(vparams.size());
-        for (size_t i = 0; i < vparams.size(); ++i) {
-            auto pd = plist[i];
-            buildParameterDescriptor(pd, vparams[i]);
-        }
-        
-        const auto &vprogs = d.programs;
-        auto pglist = b.initPrograms(vprogs.size());
-        for (size_t i = 0; i < vprogs.size(); ++i) {
-            pglist.set(i, vprogs[i]);
-        }
-
-        b.setInputDomain(fromInputDomain(d.inputDomain));
-
-        const auto &vouts = d.basicOutputInfo;
-        auto olist = b.initBasicOutputInfo(vouts.size());
-        for (size_t i = 0; i < vouts.size(); ++i) {
-            auto od = olist[i];
-            buildBasicDescriptor(od, vouts[i]);
-        }
-    }
-
-    static void
-    readExtractorStaticData(Vamp::HostExt::PluginStaticData &d,
-                            const ExtractorStaticData::Reader &r) {
-        
-        d.pluginKey = r.getKey();
-
-        readBasicDescriptor(d.basic, r.getBasic());
-
-        d.maker = r.getMaker();
-        d.copyright = r.getCopyright();
-        d.pluginVersion = r.getVersion();
-
-        d.category.clear();
-        auto cc = r.getCategory();
-        for (auto c: cc) {
-            d.category.push_back(c);
-        }
-
-        d.minChannelCount = r.getMinChannelCount();
-        d.maxChannelCount = r.getMaxChannelCount();
-
-        d.parameters.clear();
-        auto pp = r.getParameters();
-        for (auto p: pp) {
-            Vamp::Plugin::ParameterDescriptor pd;
-            readParameterDescriptor(pd, p);
-            d.parameters.push_back(pd);
-        }
-
-        d.programs.clear();
-        auto prp = r.getPrograms();
-        for (auto p: prp) {
-            d.programs.push_back(p);
-        }
-
-        d.inputDomain = toInputDomain(r.getInputDomain());
-
-        d.basicOutputInfo.clear();
-        auto oo = r.getBasicOutputInfo();
-        for (auto o: oo) {
-            Vamp::HostExt::PluginStaticData::Basic b;
-            readBasicDescriptor(b, o);
-            d.basicOutputInfo.push_back(b);
-        }
-    }
-
-    static void
-    buildConfiguration(Configuration::Builder &b,
-                       const Vamp::HostExt::PluginConfiguration &c) {
-
-        const auto &vparams = c.parameterValues;
-        auto params = b.initParameterValues(vparams.size());
-        int i = 0;
-        for (const auto &pp : vparams) {
-            auto param = params[i++];
-            param.setParameter(pp.first);
-            param.setValue(pp.second);
-        }
-
-        b.setCurrentProgram(c.currentProgram);
-        b.setChannelCount(c.channelCount);
-        b.setStepSize(c.stepSize);
-        b.setBlockSize(c.blockSize);
-    }
-
-    static void
-    readConfiguration(Vamp::HostExt::PluginConfiguration &c,
-                      const Configuration::Reader &r) {
-
-        auto pp = r.getParameterValues();
-        for (const auto &p: pp) {
-            c.parameterValues[p.getParameter()] = p.getValue();
-        }
-
-        c.currentProgram = r.getCurrentProgram();
-        c.channelCount = r.getChannelCount();
-        c.stepSize = r.getStepSize();
-        c.blockSize = r.getBlockSize();
-    }
-
-    static void
-    buildLoadRequest(LoadRequest::Builder &r,
-                     const Vamp::HostExt::LoadRequest &req) {
-
-        r.setKey(req.pluginKey);
-        r.setInputSampleRate(req.inputSampleRate);
-
-        std::vector<AdapterFlag> flags;
-        if (req.adapterFlags & Vamp::HostExt::PluginLoader::ADAPT_INPUT_DOMAIN) {
-            flags.push_back(AdapterFlag::ADAPT_INPUT_DOMAIN);
-        }
-        if (req.adapterFlags & Vamp::HostExt::PluginLoader::ADAPT_CHANNEL_COUNT) {
-            flags.push_back(AdapterFlag::ADAPT_CHANNEL_COUNT);
-        }
-        if (req.adapterFlags & Vamp::HostExt::PluginLoader::ADAPT_BUFFER_SIZE) {
-            flags.push_back(AdapterFlag::ADAPT_BUFFER_SIZE);
-        }
-
-        auto f = r.initAdapterFlags(flags.size());
-        for (size_t i = 0; i < flags.size(); ++i) {
-            f.set(i, flags[i]);
-        }
-    }
-
-    static void
-    readLoadRequest(Vamp::HostExt::LoadRequest &req,
-                    const LoadRequest::Reader &r) {
-
-        req.pluginKey = r.getKey();
-        req.inputSampleRate = r.getInputSampleRate();
-
-        int flags = 0;
-        auto aa = r.getAdapterFlags();
-        for (auto a: aa) {
-            if (a == AdapterFlag::ADAPT_INPUT_DOMAIN) {
-                flags |= Vamp::HostExt::PluginLoader::ADAPT_INPUT_DOMAIN;
-            }
-            if (a == AdapterFlag::ADAPT_CHANNEL_COUNT) {
-                flags |= Vamp::HostExt::PluginLoader::ADAPT_CHANNEL_COUNT;
-            }
-            if (a == AdapterFlag::ADAPT_BUFFER_SIZE) {
-                flags |= Vamp::HostExt::PluginLoader::ADAPT_BUFFER_SIZE;
-            }
-        }
-        req.adapterFlags = flags;
-    }
-
-    static void
-    buildLoadResponse(LoadResponse::Builder &b,
-                      const Vamp::HostExt::LoadResponse &resp,
-                      const PluginHandleMapper &pmapper) {
-
-        b.setHandle(pmapper.pluginToHandle(resp.plugin));
-        auto sd = b.initStaticData();
-        buildExtractorStaticData(sd, resp.staticData);
-        auto conf = b.initDefaultConfiguration();
-        buildConfiguration(conf, resp.defaultConfiguration);
-    }
-
-    static void
-    readLoadResponse(Vamp::HostExt::LoadResponse &resp,
-                     const LoadResponse::Reader &r,
-                     const PluginHandleMapper &pmapper) {
-
-        resp.plugin = pmapper.handleToPlugin(r.getHandle());
-        readExtractorStaticData(resp.staticData, r.getStaticData());
-        readConfiguration(resp.defaultConfiguration,
-                                r.getDefaultConfiguration());
-    }
-
-    static void
-    buildConfigurationRequest(ConfigurationRequest::Builder &b,
-                              const Vamp::HostExt::ConfigurationRequest &cr,
-                              const PluginHandleMapper &pmapper) {
-
-        b.setHandle(pmapper.pluginToHandle(cr.plugin));
-        auto c = b.initConfiguration();
-        buildConfiguration(c, cr.configuration);
-    }
-
-    static void
-    readConfigurationRequest(Vamp::HostExt::ConfigurationRequest &cr,
-                             const ConfigurationRequest::Reader &r,
-                             const PluginHandleMapper &pmapper) {
-
-        auto h = r.getHandle();
-        cr.plugin = pmapper.handleToPlugin(h);
-        auto c = r.getConfiguration();
-        readConfiguration(cr.configuration, c);
-    }
-
-    static void
-    buildConfigurationResponse(ConfigurationResponse::Builder &b,
-                               const Vamp::HostExt::ConfigurationResponse &cr,
-                               const PluginHandleMapper &pmapper) {
-
-        b.setHandle(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];
-            buildOutputDescriptor(od, cr.outputs[i]);
-        }
-    }
-
-    static void
-    readConfigurationResponse(Vamp::HostExt::ConfigurationResponse &cr,
-                              const ConfigurationResponse::Reader &r,
-                              const PluginHandleMapper &pmapper) {
-
-        cr.plugin = pmapper.handleToPlugin(r.getHandle());
-        cr.outputs.clear();
-        auto oo = r.getOutputs();
-        for (const auto &o: oo) {
-            Vamp::Plugin::OutputDescriptor desc;
-            readOutputDescriptor(desc, o);
-            cr.outputs.push_back(desc);
-        }
-    }
-
-    static void
-    buildProcessInput(ProcessInput::Builder &b,
-                      Vamp::RealTime timestamp,
-                      const std::vector<std::vector<float> > &buffers) {
-
-        auto t = b.initTimestamp();
-        buildRealTime(t, timestamp);
-        auto vv = b.initInputBuffers(buffers.size());
-        for (size_t ch = 0; ch < buffers.size(); ++ch) {
-            const int n = int(buffers[ch].size());
-            vv.init(ch, n);
-            auto v = vv[ch];
-            for (int i = 0; i < n; ++i) {
-                v.set(i, buffers[ch][i]);
-            }
-        }
-    }
-    
-    static void
-    readProcessInput(Vamp::RealTime &timestamp,
-                     std::vector<std::vector<float> > &buffers,
-                     const ProcessInput::Reader &b) {
-
-        readRealTime(timestamp, b.getTimestamp());
-        buffers.clear();
-        auto vv = b.getInputBuffers();
-        for (const auto &v: vv) {
-            std::vector<float> buf;
-            for (auto x: v) {
-                buf.push_back(x);
-            }
-            buffers.push_back(buf);
-        }
-    }
-    
-    static void
-    buildProcessRequest(ProcessRequest::Builder &b,
-                        const Vamp::HostExt::ProcessRequest &pr,
-                        const PluginHandleMapper &pmapper) {
-
-        b.setHandle(pmapper.pluginToHandle(pr.plugin));
-        auto input = b.initProcessInput();
-        buildProcessInput(input, pr.timestamp, pr.inputBuffers);
-    }
-
-    static void
-    readProcessRequest(Vamp::HostExt::ProcessRequest &pr,
-                       const ProcessRequest::Reader &r,
-                       const PluginHandleMapper &pmapper) {
-
-        auto h = r.getHandle();
-        pr.plugin = pmapper.handleToPlugin(h);
-        readProcessInput(pr.timestamp, pr.inputBuffers, r.getProcessInput());
-    }
-
-    static void
-    buildProcessResponse(ProcessResponse::Builder &b,
-                         const Vamp::HostExt::ProcessResponse &pr,
-                         const PluginHandleMapper &pmapper) {
-
-        b.setHandle(pmapper.pluginToHandle(pr.plugin));
-        auto f = b.initFeatures();
-        buildFeatureSet(f, pr.features,
-                        *pmapper.pluginToOutputIdMapper(pr.plugin));
-    }
-    
-    static void
-    readProcessResponse(Vamp::HostExt::ProcessResponse &pr,
-                        const ProcessResponse::Reader &r,
-                        const PluginHandleMapper &pmapper) {
-
-        auto h = r.getHandle();
-        pr.plugin = pmapper.handleToPlugin(h);
-        readFeatureSet(pr.features, r.getFeatures(),
-                       *pmapper.handleToOutputIdMapper(r.getHandle()));
-    }
-
-    static void
-    buildFinishResponse(FinishResponse::Builder &b,
-                        const Vamp::HostExt::ProcessResponse &pr,
-                        const PluginHandleMapper &pmapper) {
-
-        b.setHandle(pmapper.pluginToHandle(pr.plugin));
-        auto f = b.initFeatures();
-        buildFeatureSet(f, pr.features,
-                        *pmapper.pluginToOutputIdMapper(pr.plugin));
-    }
-    
-    static void
-    readFinishResponse(Vamp::HostExt::ProcessResponse &pr,
-                       const FinishResponse::Reader &r,
-                       const PluginHandleMapper &pmapper) {
-
-        auto h = r.getHandle();
-        pr.plugin = pmapper.handleToPlugin(h);
-        readFeatureSet(pr.features, r.getFeatures(),
-                       *pmapper.handleToOutputIdMapper(r.getHandle()));
-    }
-
-    static void
-    buildRpcRequest_List(RpcRequest::Builder &b) {
-        b.getRequest().initList();
-    }
-
-    static void
-    buildRpcResponse_List(RpcResponse::Builder &b,
-                           const Vamp::HostExt::ListResponse &resp) {
-
-        auto r = b.getResponse().initList();
-        auto p = r.initAvailable(resp.plugins.size());
-        for (size_t i = 0; i < resp.plugins.size(); ++i) {
-            auto pd = p[i];
-            buildExtractorStaticData(pd, resp.plugins[i]);
-        }
-    }
-    
-    static void
-    buildRpcRequest_Load(RpcRequest::Builder &b,
-                          const Vamp::HostExt::LoadRequest &req) {
-        auto u = b.getRequest().initLoad();
-        buildLoadRequest(u, req);
-    }
-
-    static void
-    buildRpcResponse_Load(RpcResponse::Builder &b,
-                           const Vamp::HostExt::LoadResponse &resp,
-                           const PluginHandleMapper &pmapper) {
-
-        if (resp.plugin) {
-            auto u = b.getResponse().initLoad();
-            buildLoadResponse(u, resp, pmapper);
-        } else {
-            buildRpcResponse_Error(b, "Failed to load plugin", RRType::Load);
-        }
-    }
-
-    static void
-    buildRpcRequest_Configure(RpcRequest::Builder &b,
-                               const Vamp::HostExt::ConfigurationRequest &cr,
-                               const PluginHandleMapper &pmapper) {
-        auto u = b.getRequest().initConfigure();
-        buildConfigurationRequest(u, cr, pmapper);
-    }
-
-    static void
-    buildRpcResponse_Configure(RpcResponse::Builder &b,
-                                const Vamp::HostExt::ConfigurationResponse &cr,
-                                const PluginHandleMapper &pmapper) {
-
-        if (!cr.outputs.empty()) {
-            auto u = b.getResponse().initConfigure();
-            buildConfigurationResponse(u, cr, pmapper);
-        } else {
-            buildRpcResponse_Error(b, "Failed to configure plugin",
-                                   RRType::Configure);
-        }
-    }
-    
-    static void
-    buildRpcRequest_Process(RpcRequest::Builder &b,
-                             const Vamp::HostExt::ProcessRequest &pr,
-                             const PluginHandleMapper &pmapper) {
-        auto u = b.getRequest().initProcess();
-        buildProcessRequest(u, pr, pmapper);
-    }
-    
-    static void
-    buildRpcResponse_Process(RpcResponse::Builder &b,
-                              const Vamp::HostExt::ProcessResponse &pr,
-                              const PluginHandleMapper &pmapper) {
-
-        auto u = b.getResponse().initProcess();
-        buildProcessResponse(u, pr, pmapper);
-    }
-    
-    static void
-    buildRpcRequest_Finish(RpcRequest::Builder &b,
-                            const Vamp::HostExt::FinishRequest &req,
-                            const PluginHandleMapper &pmapper) {
-
-        auto u = b.getRequest().initFinish();
-        u.setHandle(pmapper.pluginToHandle(req.plugin));
-    }
-    
-    static void
-    buildRpcResponse_Finish(RpcResponse::Builder &b,
-                             const Vamp::HostExt::ProcessResponse &pr,
-                             const PluginHandleMapper &pmapper) {
-
-        auto u = b.getResponse().initFinish();
-        buildFinishResponse(u, pr, pmapper);
-    }
-
-    static void
-    buildRpcResponse_Error(RpcResponse::Builder &b,
-                            const std::string &errorText,
-                            RRType responseType)
-    {
-        std::string type;
-
-        auto e = b.getResponse().initError();
-
-        if (responseType == RRType::List) {
-            type = "list";
-        } else if (responseType == RRType::Load) {
-            type = "load";
-        } else if (responseType == RRType::Configure) {
-            type = "configure";
-        } else if (responseType == RRType::Process) {
-            type = "process";
-        } else if (responseType == RRType::Finish) {
-            type = "finish";
-        } else {
-            type = "invalid";
-        }
-
-        e.setCode(0);
-        e.setMessage(std::string("error in ") + type + " request: " + errorText);
-    }
-
-    static void
-    buildRpcResponse_Exception(RpcResponse::Builder &b,
-                                const std::exception &e,
-                                RRType responseType)
-    {
-        return buildRpcResponse_Error(b, e.what(), responseType);
-    }
-    
-    static RRType
-    getRequestResponseType(const RpcRequest::Reader &r) {
-        switch (r.getRequest().which()) {
-        case RpcRequest::Request::Which::LIST:
-            return RRType::List;
-        case RpcRequest::Request::Which::LOAD:
-            return RRType::Load;
-        case RpcRequest::Request::Which::CONFIGURE:
-            return RRType::Configure;
-        case RpcRequest::Request::Which::PROCESS:
-            return RRType::Process;
-        case RpcRequest::Request::Which::FINISH:
-            return RRType::Finish;
-        }
-        return RRType::NotValid;
-    }
-
-    static RRType
-    getRequestResponseType(const RpcResponse::Reader &r) {
-        switch (r.getResponse().which()) {
-        case RpcResponse::Response::Which::ERROR:
-            return RRType::NotValid; //!!! or error type? test this
-        case RpcResponse::Response::Which::LIST:
-            return RRType::List;
-        case RpcResponse::Response::Which::LOAD:
-            return RRType::Load;
-        case RpcResponse::Response::Which::CONFIGURE:
-            return RRType::Configure;
-        case RpcResponse::Response::Which::PROCESS:
-            return RRType::Process;
-        case RpcResponse::Response::Which::FINISH:
-            return RRType::Finish;
-        }
-        return RRType::NotValid;
-    }
-    
-    static void
-    readRpcResponse_Error(int &code,
-                          std::string &message,
-                          const RpcResponse::Reader &r) {
-        if (getRequestResponseType(r) != RRType::NotValid) {
-            throw std::logic_error("not an error response");
-        }
-        code = r.getResponse().getError().getCode();
-        message = r.getResponse().getError().getMessage();
-    }
-        
-    static void
-    readRpcRequest_List(const RpcRequest::Reader &r) {
-        if (getRequestResponseType(r) != RRType::List) {
-            throw std::logic_error("not a list request");
-        }
-    }
-
-    static void
-    readRpcResponse_List(Vamp::HostExt::ListResponse &resp,
-                          const RpcResponse::Reader &r) {
-        if (getRequestResponseType(r) != RRType::List) {
-            throw std::logic_error("not a list response");
-        }
-        resp.plugins.clear();
-        auto pp = r.getResponse().getList().getAvailable();
-        for (const auto &p: pp) {
-            Vamp::HostExt::PluginStaticData psd;
-            readExtractorStaticData(psd, p);
-            resp.plugins.push_back(psd);
-        }
-    }
-    
-    static void
-    readRpcRequest_Load(Vamp::HostExt::LoadRequest &req,
-                         const RpcRequest::Reader &r) {
-        if (getRequestResponseType(r) != RRType::Load) {
-            throw std::logic_error("not a load request");
-        }
-        readLoadRequest(req, r.getRequest().getLoad());
-    }
-
-    static void
-    readRpcResponse_Load(Vamp::HostExt::LoadResponse &resp,
-                         const RpcResponse::Reader &r,
-                         const PluginHandleMapper &pmapper) {
-        if (getRequestResponseType(r) != RRType::Load) {
-            throw std::logic_error("not a load response");
-        }
-        resp = {};
-        readLoadResponse(resp, r.getResponse().getLoad(), pmapper);
-    }
-    
-    static void
-    readRpcRequest_Configure(Vamp::HostExt::ConfigurationRequest &req,
-                              const RpcRequest::Reader &r,
-                              const PluginHandleMapper &pmapper) {
-        if (getRequestResponseType(r) != RRType::Configure) {
-            throw std::logic_error("not a configuration request");
-        }
-        readConfigurationRequest(req, r.getRequest().getConfigure(), pmapper);
-    }
-
-    static void
-    readRpcResponse_Configure(Vamp::HostExt::ConfigurationResponse &resp,
-                               const RpcResponse::Reader &r,
-                               const PluginHandleMapper &pmapper) {
-        if (getRequestResponseType(r) != RRType::Configure) {
-            throw std::logic_error("not a configuration response");
-        }
-        resp = {};
-        readConfigurationResponse(resp,
-                                  r.getResponse().getConfigure(),
-                                  pmapper);
-    }
-    
-    static void
-    readRpcRequest_Process(Vamp::HostExt::ProcessRequest &req,
-                            const RpcRequest::Reader &r,
-                            const PluginHandleMapper &pmapper) {
-        if (getRequestResponseType(r) != RRType::Process) {
-            throw std::logic_error("not a process request");
-        }
-        readProcessRequest(req, r.getRequest().getProcess(), pmapper);
-    }
-
-    static void
-    readRpcResponse_Process(Vamp::HostExt::ProcessResponse &resp,
-                             const RpcResponse::Reader &r,
-                             const PluginHandleMapper &pmapper) {
-        if (getRequestResponseType(r) != RRType::Process) {
-            throw std::logic_error("not a process response");
-        }
-        resp = {};
-        readProcessResponse(resp, r.getResponse().getProcess(), pmapper);
-    }
-    
-    static void
-    readRpcRequest_Finish(Vamp::HostExt::FinishRequest &req,
-                           const RpcRequest::Reader &r,
-                           const PluginHandleMapper &pmapper) {
-        if (getRequestResponseType(r) != RRType::Finish) {
-            throw std::logic_error("not a finish request");
-        }
-        req.plugin = pmapper.handleToPlugin
-            (r.getRequest().getFinish().getHandle());
-    }
-
-    static void
-    readRpcResponse_Finish(Vamp::HostExt::ProcessResponse &resp,
-                            const RpcResponse::Reader &r,
-                            const PluginHandleMapper &pmapper) {
-        if (getRequestResponseType(r) != RRType::Finish) {
-            throw std::logic_error("not a finish response");
-        }
-        resp = {};
-        readFinishResponse(resp, r.getResponse().getFinish(), pmapper);
-    }
-};
-
-}
-
-
--- a/json/VampJson.h	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1387 +0,0 @@
-/* -*- 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 2015-2016 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 VAMP_JSON_H
-#define VAMP_JSON_H
-
-#include <vector>
-#include <string>
-#include <sstream>
-
-#include <json11/json11.hpp>
-#include <base-n/include/basen.hpp>
-
-#include <vamp-hostsdk/Plugin.h>
-#include <vamp-hostsdk/PluginLoader.h>
-
-#include "bits/PluginHandleMapper.h"
-#include "bits/PluginOutputIdMapper.h"
-#include "bits/RequestResponseType.h"
-
-namespace vampipe {
-
-/**
- * Convert the structures laid out in the Vamp SDK classes into JSON
- * (and back again) following the schema in the vamp-json-schema
- * project repo.
- *
- * Functions with names starting "from" convert from a Vamp SDK object
- * to JSON output. Most of them return a json11::Json object, with a
- * few exceptions for low-level utilities that return a string. These
- * functions succeed all of the time.
- *
- * Functions with names starting "to" convert to a Vamp SDK object
- * from JSON input. These functions all accept a json11::Json object
- * as first argument, with a few exceptions for low-level utilities
- * that accept a string. These functions all accept a string reference
- * as a final argument and return an error string through it if the
- * conversion fails. If conversion fails the return value is
- * undefined, and any returned object may be incomplete or
- * invalid. Callers should check for an empty error string (indicating
- * success) before using the returned value.
- */
-
-class VampJson
-{
-public:
-    /** Serialisation format for arrays of floats (process input and
-     *  feature values). Wherever such an array appears, it may
-     *  alternatively be replaced by a single string containing a
-     *  base-64 encoding of the IEEE float buffer. When parsing, if a
-     *  string is found instead of an array in this case, it will be
-     *  interpreted as a base-64 encoded buffer. Only array or base-64
-     *  encoding may be provided, not both.
-     */
-    enum class BufferSerialisation {
-
-        /** Default JSON serialisation of values in array form. This
-         *  is relatively slow to parse and serialise, and can take a
-         *  lot of space.
-         */
-        Array,
-
-        /** Base64-encoded string of the raw data as packed
-         *  little-endian IEEE 32-bit floats. Faster and more compact
-         *  than the text encoding but more complicated to
-         *  provide. Note that Base64 serialisations produced by this
-         *  library do not including padding characters and so are not
-         *  necessarily multiples of 4 characters long. You will need
-         *  to pad them yourself if concatenating them or supplying to
-         *  a consumer that expects padding.
-         */
-        Base64
-    };
-    
-    static bool failed(const std::string &err) {
-        return err != "";
-    }
-    
-    template <typename T>
-    static json11::Json
-    fromBasicDescriptor(const T &t) {
-        return json11::Json::object { 
-            { "identifier", t.identifier },
-            { "name", t.name },
-            { "description", t.description }
-        };
-    }
-
-    template <typename T>
-    static void
-    toBasicDescriptor(json11::Json j, T &t, std::string &err) {
-        if (!j.is_object()) {
-            err = "object expected for basic descriptor content";
-            return;
-        }
-        if (!j["identifier"].is_string()) {
-            err = "string expected for identifier";
-            return;
-        }
-        t.identifier = j["identifier"].string_value();
-        t.name = j["name"].string_value();
-        t.description = j["description"].string_value();
-    }
-
-    template <typename T>
-    static json11::Json
-    fromValueExtents(const T &t) {
-        return json11::Json::object {
-            { "min", t.minValue },
-            { "max", t.maxValue }
-        };
-    }
-
-    template <typename T>
-    static bool
-    toValueExtents(json11::Json j, T &t, std::string &err) {
-        if (j["extents"].is_null()) {
-            return false;
-        } else if (j["extents"].is_object()) {
-            if (j["extents"]["min"].is_number() &&
-                j["extents"]["max"].is_number()) {
-                t.minValue = j["extents"]["min"].number_value();
-                t.maxValue = j["extents"]["max"].number_value();
-                return true;
-            } else {
-                err = "numbers expected for min and max";
-                return false;
-            }
-        } else {
-            err = "object expected for extents (if present)";
-            return false;
-        }
-    }
-
-    static json11::Json
-    fromRealTime(const Vamp::RealTime &r) {
-        return json11::Json::object {
-            { "s", r.sec },
-            { "n", r.nsec }
-        };
-    }
-
-    static Vamp::RealTime
-    toRealTime(json11::Json j, std::string &err) {
-        json11::Json sec = j["s"];
-        json11::Json nsec = j["n"];
-        if (!sec.is_number() || !nsec.is_number()) {
-            err = "invalid Vamp::RealTime object " + j.dump();
-            return {};
-        }
-        return Vamp::RealTime(sec.int_value(), nsec.int_value());
-    }
-
-    static std::string
-    fromSampleType(Vamp::Plugin::OutputDescriptor::SampleType type) {
-        switch (type) {
-        case Vamp::Plugin::OutputDescriptor::OneSamplePerStep:
-            return "OneSamplePerStep";
-        case Vamp::Plugin::OutputDescriptor::FixedSampleRate:
-            return "FixedSampleRate";
-        case Vamp::Plugin::OutputDescriptor::VariableSampleRate:
-            return "VariableSampleRate";
-        }
-        return "";
-    }
-
-    static Vamp::Plugin::OutputDescriptor::SampleType
-    toSampleType(std::string text, std::string &err) {
-        if (text == "OneSamplePerStep") {
-            return Vamp::Plugin::OutputDescriptor::OneSamplePerStep;
-        } else if (text == "FixedSampleRate") {
-            return Vamp::Plugin::OutputDescriptor::FixedSampleRate;
-        } else if (text == "VariableSampleRate") {
-            return Vamp::Plugin::OutputDescriptor::VariableSampleRate;
-        } else {
-            err = "invalid sample type string: " + text;
-            return Vamp::Plugin::OutputDescriptor::OneSamplePerStep;
-        }
-    }
-
-    static json11::Json
-    fromConfiguredOutputDescriptor(const Vamp::Plugin::OutputDescriptor &desc) {
-        json11::Json::object jo {
-            { "unit", desc.unit },
-            { "sampleType", fromSampleType(desc.sampleType) },
-            { "sampleRate", desc.sampleRate },
-            { "hasDuration", desc.hasDuration }
-        };
-        if (desc.hasFixedBinCount) {
-            jo["binCount"] = int(desc.binCount);
-            jo["binNames"] = json11::Json::array
-                (desc.binNames.begin(), desc.binNames.end());
-        }
-        if (desc.hasKnownExtents) {
-            jo["extents"] = fromValueExtents(desc);
-        }
-        if (desc.isQuantized) {
-            jo["quantizeStep"] = desc.quantizeStep;
-        }
-        return json11::Json(jo);
-    }
-    
-    static json11::Json
-    fromOutputDescriptor(const Vamp::Plugin::OutputDescriptor &desc) {
-        json11::Json::object jo {
-            { "basic", fromBasicDescriptor(desc) },
-            { "configured", fromConfiguredOutputDescriptor(desc) }
-        };
-        return json11::Json(jo);
-    }
-    
-    static Vamp::Plugin::OutputDescriptor
-    toConfiguredOutputDescriptor(json11::Json j, std::string &err) {
-
-        Vamp::Plugin::OutputDescriptor od;
-        if (!j.is_object()) {
-            err = "object expected for output descriptor";
-            return {};
-        }
-    
-        od.unit = j["unit"].string_value();
-
-        od.sampleType = toSampleType(j["sampleType"].string_value(), err);
-        if (failed(err)) return {};
-
-        if (!j["sampleRate"].is_number()) {
-            err = "number expected for sample rate";
-            return {};
-        }
-        od.sampleRate = j["sampleRate"].number_value();
-        od.hasDuration = j["hasDuration"].bool_value();
-
-        if (j["binCount"].is_number() && j["binCount"].int_value() > 0) {
-            od.hasFixedBinCount = true;
-            od.binCount = j["binCount"].int_value();
-            for (auto &n: j["binNames"].array_items()) {
-                if (!n.is_string()) {
-                    err = "string expected for bin name";
-                    return {};
-                }
-                od.binNames.push_back(n.string_value());
-            }
-        } else {
-            od.hasFixedBinCount = false;
-        }
-
-        bool extentsPresent = toValueExtents(j, od, err);
-        if (failed(err)) return {};
-        
-        od.hasKnownExtents = extentsPresent;
-
-        if (j["quantizeStep"].is_number()) {
-            od.isQuantized = true;
-            od.quantizeStep = j["quantizeStep"].number_value();
-        } else {
-            od.isQuantized = false;
-        }
-
-        return od;
-    }
-    
-    static Vamp::Plugin::OutputDescriptor
-    toOutputDescriptor(json11::Json j, std::string &err) {
-
-        Vamp::Plugin::OutputDescriptor od;
-        if (!j.is_object()) {
-            err = "object expected for output descriptor";
-            return {};
-        }
-
-        od = toConfiguredOutputDescriptor(j, err);
-        if (failed(err)) return {};
-    
-        toBasicDescriptor(j["basic"], od, err);
-        if (failed(err)) return {};
-
-        return od;
-    }
-
-    static json11::Json
-    fromParameterDescriptor(const Vamp::PluginBase::ParameterDescriptor &desc) {
-
-        json11::Json::object jo {
-            { "basic", fromBasicDescriptor(desc) },
-            { "unit", desc.unit },
-            { "extents", fromValueExtents(desc) },
-            { "defaultValue", desc.defaultValue },
-            { "valueNames", json11::Json::array
-                    (desc.valueNames.begin(), desc.valueNames.end()) }
-        };
-        if (desc.isQuantized) {
-            jo["quantizeStep"] = desc.quantizeStep;
-        }
-        return json11::Json(jo);
-    }
-
-    static Vamp::PluginBase::ParameterDescriptor
-    toParameterDescriptor(json11::Json j, std::string &err) {
-
-        Vamp::PluginBase::ParameterDescriptor pd;
-        if (!j.is_object()) {
-            err = "object expected for parameter descriptor";
-            return {};
-        }
-    
-        toBasicDescriptor(j["basic"], pd, err);
-        if (failed(err)) return {};
-
-        pd.unit = j["unit"].string_value();
-
-        bool extentsPresent = toValueExtents(j, pd, err);
-        if (failed(err)) return {};
-        if (!extentsPresent) {
-            err = "extents must be present in parameter descriptor";
-            return {};
-        }
-    
-        if (!j["defaultValue"].is_number()) {
-            err = "number expected for default value";
-            return {};
-        }
-    
-        pd.defaultValue = j["defaultValue"].number_value();
-
-        pd.valueNames.clear();
-        for (auto &n: j["valueNames"].array_items()) {
-            if (!n.is_string()) {
-                err = "string expected for value name";
-                return {};
-            }
-            pd.valueNames.push_back(n.string_value());
-        }
-
-        if (j["quantizeStep"].is_number()) {
-            pd.isQuantized = true;
-            pd.quantizeStep = j["quantizeStep"].number_value();
-        } else {
-            pd.isQuantized = false;
-        }
-
-        return pd;
-    }
-
-    static std::string
-    fromFloatBuffer(const float *buffer, size_t nfloats) {
-        // must use char pointers, otherwise the converter will only
-        // encode every 4th byte (as it will count up in float* steps)
-        const char *start = reinterpret_cast<const char *>(buffer);
-        const char *end = reinterpret_cast<const char *>(buffer + nfloats);
-        std::string encoded;
-        bn::encode_b64(start, end, back_inserter(encoded));
-        return encoded;
-    }
-
-    static std::vector<float>
-    toFloatBuffer(std::string encoded, std::string & /* err */) {
-        std::string decoded;
-        bn::decode_b64(encoded.begin(), encoded.end(), back_inserter(decoded));
-        const float *buffer = reinterpret_cast<const float *>(decoded.c_str());
-        size_t n = decoded.size() / sizeof(float);
-        return std::vector<float>(buffer, buffer + n);
-    }
-
-    static json11::Json
-    fromFeature(const Vamp::Plugin::Feature &f,
-                BufferSerialisation serialisation) {
-
-        json11::Json::object jo;
-        if (f.values.size() > 0) {
-            if (serialisation == BufferSerialisation::Array) {
-                jo["featureValues"] = json11::Json::array(f.values.begin(),
-                                                          f.values.end());
-            } else {
-                jo["featureValues"] = fromFloatBuffer(f.values.data(),
-                                                      f.values.size());
-            }
-        }
-        if (f.label != "") {
-            jo["label"] = f.label;
-        }
-        if (f.hasTimestamp) {
-            jo["timestamp"] = fromRealTime(f.timestamp);
-        }
-        if (f.hasDuration) {
-            jo["duration"] = fromRealTime(f.duration);
-        }
-        return json11::Json(jo);
-    }
-
-    static Vamp::Plugin::Feature
-    toFeature(json11::Json j, BufferSerialisation &serialisation, std::string &err) {
-
-        Vamp::Plugin::Feature f;
-        if (!j.is_object()) {
-            err = "object expected for feature";
-            return {};
-        }
-        if (j["timestamp"].is_object()) {
-            f.timestamp = toRealTime(j["timestamp"], err);
-            if (failed(err)) return {};
-            f.hasTimestamp = true;
-        }
-        if (j["duration"].is_object()) {
-            f.duration = toRealTime(j["duration"], err);
-            if (failed(err)) return {};
-            f.hasDuration = true;
-        }
-        if (j["featureValues"].is_string()) {
-            f.values = toFloatBuffer(j["featureValues"].string_value(), err);
-            if (failed(err)) return {};
-            serialisation = BufferSerialisation::Base64;
-        } else if (j["featureValues"].is_array()) {
-            for (auto v : j["featureValues"].array_items()) {
-                f.values.push_back(v.number_value());
-            }
-            serialisation = BufferSerialisation::Array;
-        }
-        f.label = j["label"].string_value();
-        return f;
-    }
-
-    static json11::Json
-    fromFeatureSet(const Vamp::Plugin::FeatureSet &fs,
-                   const PluginOutputIdMapper &omapper,
-                   BufferSerialisation serialisation) {
-
-        json11::Json::object jo;
-        for (const auto &fsi : fs) {
-            std::vector<json11::Json> fj;
-            for (const Vamp::Plugin::Feature &f: fsi.second) {
-                fj.push_back(fromFeature(f, serialisation));
-            }
-            jo[omapper.indexToId(fsi.first)] = fj;
-        }
-        return json11::Json(jo);
-    }
-
-    static Vamp::Plugin::FeatureList
-    toFeatureList(json11::Json j,
-                  BufferSerialisation &serialisation, std::string &err) {
-
-        Vamp::Plugin::FeatureList fl;
-        if (!j.is_array()) {
-            err = "array expected for feature list";
-            return {};
-        }
-        for (const json11::Json &fj : j.array_items()) {
-            fl.push_back(toFeature(fj, serialisation, err));
-            if (failed(err)) return {};
-        }
-        return fl;
-    }
-
-    static Vamp::Plugin::FeatureSet
-    toFeatureSet(json11::Json j,
-                 const PluginOutputIdMapper &omapper,
-                 BufferSerialisation &serialisation,
-                 std::string &err) {
-
-        Vamp::Plugin::FeatureSet fs;
-        if (!j.is_object()) {
-            err = "object expected for feature set";
-            return {};
-        }
-        for (auto &entry : j.object_items()) {
-            int n = omapper.idToIndex(entry.first);
-            if (fs.find(n) != fs.end()) {
-                err = "duplicate numerical index for output";
-                return {};
-            }
-            fs[n] = toFeatureList(entry.second, serialisation, err);
-            if (failed(err)) return {};
-        }
-        return fs;
-    }
-
-    static std::string
-    fromInputDomain(Vamp::Plugin::InputDomain domain) {
-
-        switch (domain) {
-        case Vamp::Plugin::TimeDomain:
-            return "TimeDomain";
-        case Vamp::Plugin::FrequencyDomain:
-            return "FrequencyDomain";
-        }
-        return "";
-    }
-
-    static Vamp::Plugin::InputDomain
-    toInputDomain(std::string text, std::string &err) {
-
-        if (text == "TimeDomain") {
-            return Vamp::Plugin::TimeDomain;
-        } else if (text == "FrequencyDomain") {
-            return Vamp::Plugin::FrequencyDomain;
-        } else {
-            err = "invalid input domain string: " + text;
-            return {};
-        }
-    }
-
-    static json11::Json
-    fromPluginStaticData(const Vamp::HostExt::PluginStaticData &d) {
-
-        json11::Json::object jo;
-        jo["key"] = d.pluginKey;
-        jo["basic"] = fromBasicDescriptor(d.basic);
-        jo["maker"] = d.maker;
-        jo["copyright"] = d.copyright;
-        jo["version"] = d.pluginVersion;
-
-        json11::Json::array cat;
-        for (const std::string &c: d.category) cat.push_back(c);
-        jo["category"] = cat;
-
-        jo["minChannelCount"] = d.minChannelCount;
-        jo["maxChannelCount"] = d.maxChannelCount;
-
-        json11::Json::array params;
-        Vamp::PluginBase::ParameterList vparams = d.parameters;
-        for (auto &p: vparams) params.push_back(fromParameterDescriptor(p));
-        jo["parameters"] = params;
-
-        json11::Json::array progs;
-        Vamp::PluginBase::ProgramList vprogs = d.programs;
-        for (auto &p: vprogs) progs.push_back(p);
-        jo["programs"] = progs;
-
-        jo["inputDomain"] = fromInputDomain(d.inputDomain);
-
-        json11::Json::array outinfo;
-        auto vouts = d.basicOutputInfo;
-        for (auto &o: vouts) outinfo.push_back(fromBasicDescriptor(o));
-        jo["basicOutputInfo"] = outinfo;
-    
-        return json11::Json(jo);
-    }
-
-    static Vamp::HostExt::PluginStaticData
-    toPluginStaticData(json11::Json j, std::string &err) {
-
-        if (!j.has_shape({
-                    { "key", json11::Json::STRING },
-                    { "version", json11::Json::NUMBER },
-                    { "minChannelCount", json11::Json::NUMBER },
-                    { "maxChannelCount", json11::Json::NUMBER },
-                    { "inputDomain", json11::Json::STRING }}, err)) {
-
-            err = "malformed plugin static data: " + err;
-
-        } else if (!j["basicOutputInfo"].is_array()) {
-
-            err = "array expected for basic output info";
-
-        } else if (!j["maker"].is_null() &&
-                   !j["maker"].is_string()) {
-
-            err = "string expected for maker";
-
-        } else if (!j["copyright"].is_null() &&
-                   !j["copyright"].is_string()) {
-            err = "string expected for copyright";
-
-        } else if (!j["category"].is_null() &&
-                   !j["category"].is_array()) {
-
-            err = "array expected for category";
-
-        } else if (!j["parameters"].is_null() &&
-                   !j["parameters"].is_array()) {
-
-            err = "array expected for parameters";
-
-        } else if (!j["programs"].is_null() &&
-                   !j["programs"].is_array()) {
-
-            err = "array expected for programs";
-
-        } else if (!j["inputDomain"].is_null() &&
-                   !j["inputDomain"].is_string()) {
-
-            err = "string expected for inputDomain";
-
-        } else if (!j["basicOutputInfo"].is_null() &&
-                   !j["basicOutputInfo"].is_array()) {
-            
-            err = "array expected for basicOutputInfo";
-
-        } else {
-
-            Vamp::HostExt::PluginStaticData psd;
-
-            psd.pluginKey = j["key"].string_value();
-
-            toBasicDescriptor(j["basic"], psd.basic, err);
-            if (failed(err)) return {};
-
-            psd.maker = j["maker"].string_value();
-            psd.copyright = j["copyright"].string_value();
-            psd.pluginVersion = j["version"].int_value();
-
-            for (const auto &c : j["category"].array_items()) {
-                if (!c.is_string()) {
-                    err = "strings expected in category array";
-                    return {};
-                }
-                psd.category.push_back(c.string_value());
-            }
-
-            psd.minChannelCount = j["minChannelCount"].int_value();
-            psd.maxChannelCount = j["maxChannelCount"].int_value();
-
-            for (const auto &p : j["parameters"].array_items()) {
-                auto pd = toParameterDescriptor(p, err);
-                if (failed(err)) return {};
-                psd.parameters.push_back(pd);
-            }
-
-            for (const auto &p : j["programs"].array_items()) {
-                if (!p.is_string()) {
-                    err = "strings expected in programs array";
-                    return {};
-                }
-                psd.programs.push_back(p.string_value());
-            }
-
-            psd.inputDomain = toInputDomain(j["inputDomain"].string_value(), err);
-            if (failed(err)) return {};
-
-            for (const auto &bo : j["basicOutputInfo"].array_items()) {
-                Vamp::HostExt::PluginStaticData::Basic b;
-                toBasicDescriptor(bo, b, err);
-                if (failed(err)) return {};
-                psd.basicOutputInfo.push_back(b);
-            }
-            
-            return psd;
-        }
-
-        // fallthrough error case
-        return {};
-    }
-
-    static json11::Json
-    fromPluginConfiguration(const Vamp::HostExt::PluginConfiguration &c) {
-
-        json11::Json::object jo;
-
-        json11::Json::object paramValues;
-        for (auto &vp: c.parameterValues) {
-            paramValues[vp.first] = vp.second;
-        }
-        jo["parameterValues"] = paramValues;
-
-        if (c.currentProgram != "") {
-            jo["currentProgram"] = c.currentProgram;
-        }
-
-        jo["channelCount"] = c.channelCount;
-        jo["stepSize"] = c.stepSize;
-        jo["blockSize"] = c.blockSize;
-    
-        return json11::Json(jo);
-    }
-
-    static Vamp::HostExt::PluginConfiguration
-    toPluginConfiguration(json11::Json j, std::string &err) {
-        
-        if (!j.has_shape({
-                    { "channelCount", json11::Json::NUMBER },
-                    { "stepSize", json11::Json::NUMBER },
-                    { "blockSize", json11::Json::NUMBER } }, err)) {
-            err = "malformed plugin configuration: " + err;
-            return {};
-        }
-
-        if (!j["parameterValues"].is_null() &&
-            !j["parameterValues"].is_object()) {
-            err = "object expected for parameter values";
-            return {};
-        }
-
-        for (auto &pv : j["parameterValues"].object_items()) {
-            if (!pv.second.is_number()) {
-                err = "number expected for parameter value";
-                return {};
-            }
-        }
-    
-        if (!j["currentProgram"].is_null() &&
-            !j["currentProgram"].is_string()) {
-            err = "string expected for program name";
-            return {};
-        }
-
-        Vamp::HostExt::PluginConfiguration config;
-
-        config.channelCount = j["channelCount"].number_value();
-        config.stepSize = j["stepSize"].number_value();
-        config.blockSize = j["blockSize"].number_value();
-        
-        for (auto &pv : j["parameterValues"].object_items()) {
-            config.parameterValues[pv.first] = pv.second.number_value();
-        }
-
-        if (j["currentProgram"].is_string()) {
-            config.currentProgram = j["currentProgram"].string_value();
-        }
-
-        return config;
-    }
-
-    static json11::Json
-    fromAdapterFlags(int flags) {
-
-        json11::Json::array arr;
-
-        if (flags & Vamp::HostExt::PluginLoader::ADAPT_INPUT_DOMAIN) {
-            arr.push_back("AdaptInputDomain");
-        }
-        if (flags & Vamp::HostExt::PluginLoader::ADAPT_CHANNEL_COUNT) {
-            arr.push_back("AdaptChannelCount");
-        }
-        if (flags & Vamp::HostExt::PluginLoader::ADAPT_BUFFER_SIZE) {
-            arr.push_back("AdaptBufferSize");
-        }
-
-        return json11::Json(arr);
-    }
-
-    static Vamp::HostExt::PluginLoader::AdapterFlags
-    toAdapterFlags(json11::Json j, std::string &err) {
-
-        int flags = 0x0;
-
-        if (!j.is_array()) {
-
-            err = "array expected for adapter flags";
-
-        } else {
-
-            for (auto &jj: j.array_items()) {
-                if (!jj.is_string()) {
-                    err = "string expected for adapter flag";
-                    break;
-                }
-                std::string text = jj.string_value();
-                if (text == "AdaptInputDomain") {
-                    flags |= Vamp::HostExt::PluginLoader::ADAPT_INPUT_DOMAIN;
-                } else if (text == "AdaptChannelCount") {
-                    flags |= Vamp::HostExt::PluginLoader::ADAPT_CHANNEL_COUNT;
-                } else if (text == "AdaptBufferSize") {
-                    flags |= Vamp::HostExt::PluginLoader::ADAPT_BUFFER_SIZE;
-                } else if (text == "AdaptAllSafe") {
-                    flags |= Vamp::HostExt::PluginLoader::ADAPT_ALL_SAFE;
-                } else if (text == "AdaptAll") {
-                    flags |= Vamp::HostExt::PluginLoader::ADAPT_ALL;
-                } else {
-                    err = "invalid adapter flag string: " + text;
-                    break;
-                }
-            }
-        }
-
-        return Vamp::HostExt::PluginLoader::AdapterFlags(flags);
-    }
-
-    static json11::Json
-    fromLoadRequest(const Vamp::HostExt::LoadRequest &req) {
-
-        json11::Json::object jo;
-        jo["key"] = req.pluginKey;
-        jo["inputSampleRate"] = req.inputSampleRate;
-        jo["adapterFlags"] = fromAdapterFlags(req.adapterFlags);
-        return json11::Json(jo);
-    }
-
-    static Vamp::HostExt::LoadRequest
-    toLoadRequest(json11::Json j, std::string &err) {
-        
-        if (!j.has_shape({
-                    { "key", json11::Json::STRING },
-                    { "inputSampleRate", json11::Json::NUMBER } }, err)) {
-            err = "malformed load request: " + err;
-            return {};
-        }
-    
-        Vamp::HostExt::LoadRequest req;
-        req.pluginKey = j["key"].string_value();
-        req.inputSampleRate = j["inputSampleRate"].number_value();
-        if (!j["adapterFlags"].is_null()) {
-            req.adapterFlags = toAdapterFlags(j["adapterFlags"], err);
-            if (failed(err)) return {};
-        }
-        return req;
-    }
-
-    static json11::Json
-    fromLoadResponse(const Vamp::HostExt::LoadResponse &resp,
-                     const PluginHandleMapper &pmapper) {
-
-        json11::Json::object jo;
-        jo["handle"] = double(pmapper.pluginToHandle(resp.plugin));
-        jo["staticData"] = fromPluginStaticData(resp.staticData);
-        jo["defaultConfiguration"] =
-            fromPluginConfiguration(resp.defaultConfiguration);
-        return json11::Json(jo);
-    }
-
-    static Vamp::HostExt::LoadResponse
-    toLoadResponse(json11::Json j,
-                   const PluginHandleMapper &pmapper, std::string &err) {
-
-        if (!j.has_shape({
-                    { "handle", json11::Json::NUMBER },
-                    { "staticData", json11::Json::OBJECT },
-                    { "defaultConfiguration", json11::Json::OBJECT } }, err)) {
-            err = "malformed load response: " + err;
-            return {};
-        }
-
-        Vamp::HostExt::LoadResponse resp;
-        resp.plugin = pmapper.handleToPlugin(j["handle"].int_value());
-        resp.staticData = toPluginStaticData(j["staticData"], err);
-        if (failed(err)) return {};
-        resp.defaultConfiguration = toPluginConfiguration(j["defaultConfiguration"],
-                                                          err);
-        if (failed(err)) return {};
-        return resp;
-    }
-
-    static json11::Json
-    fromConfigurationRequest(const Vamp::HostExt::ConfigurationRequest &cr,
-                             const PluginHandleMapper &pmapper) {
-
-        json11::Json::object jo;
-
-        jo["handle"] = pmapper.pluginToHandle(cr.plugin);
-        jo["configuration"] = fromPluginConfiguration(cr.configuration);
-        
-        return json11::Json(jo);
-    }
-
-    static Vamp::HostExt::ConfigurationRequest
-    toConfigurationRequest(json11::Json j,
-                           const PluginHandleMapper &pmapper, std::string &err) {
-
-        if (!j.has_shape({
-                    { "handle", json11::Json::NUMBER },
-                    { "configuration", json11::Json::OBJECT } }, err)) {
-            err = "malformed configuration request: " + err;
-            return {};
-        }
-
-        Vamp::HostExt::ConfigurationRequest cr;
-        cr.plugin = pmapper.handleToPlugin(j["handle"].int_value());
-        cr.configuration = toPluginConfiguration(j["configuration"], err);
-        if (failed(err)) return {};
-        return cr;
-    }
-
-    static json11::Json
-    fromConfigurationResponse(const Vamp::HostExt::ConfigurationResponse &cr,
-                              const PluginHandleMapper &pmapper) {
-
-        json11::Json::object jo;
-
-        jo["handle"] = pmapper.pluginToHandle(cr.plugin);
-        
-        json11::Json::array outs;
-        for (auto &d: cr.outputs) {
-            outs.push_back(fromOutputDescriptor(d));
-        }
-        jo["outputList"] = outs;
-        
-        return json11::Json(jo);
-    }
-
-    static Vamp::HostExt::ConfigurationResponse
-    toConfigurationResponse(json11::Json j,
-                            const PluginHandleMapper &pmapper, std::string &err) {
-        
-        Vamp::HostExt::ConfigurationResponse cr;
-
-        cr.plugin = pmapper.handleToPlugin(j["handle"].int_value());
-        
-        if (!j["outputList"].is_array()) {
-            err = "array expected for output list";
-            return {};
-        }
-
-        for (const auto &o: j["outputList"].array_items()) {
-            cr.outputs.push_back(toOutputDescriptor(o, err));
-            if (failed(err)) return {};
-        }
-
-        return cr;
-    }
-
-    static json11::Json
-    fromProcessRequest(const Vamp::HostExt::ProcessRequest &r,
-                       const PluginHandleMapper &pmapper,
-                       BufferSerialisation serialisation) {
-
-        json11::Json::object jo;
-        jo["handle"] = pmapper.pluginToHandle(r.plugin);
-
-        json11::Json::object io;
-        io["timestamp"] = fromRealTime(r.timestamp);
-
-        json11::Json::array chans;
-        for (size_t i = 0; i < r.inputBuffers.size(); ++i) {
-            if (serialisation == BufferSerialisation::Array) {
-                chans.push_back(json11::Json::array(r.inputBuffers[i].begin(),
-                                                    r.inputBuffers[i].end()));
-            } else {
-                chans.push_back(fromFloatBuffer(r.inputBuffers[i].data(),
-                                                r.inputBuffers[i].size()));
-            }
-        }
-        io["inputBuffers"] = chans;
-        
-        jo["processInput"] = io;
-        return json11::Json(jo);
-    }
-
-    static Vamp::HostExt::ProcessRequest
-    toProcessRequest(json11::Json j,
-                     const PluginHandleMapper &pmapper,
-                     BufferSerialisation &serialisation, std::string &err) {
-
-        if (!j.has_shape({
-                    { "handle", json11::Json::NUMBER },
-                    { "processInput", json11::Json::OBJECT } }, err)) {
-            err = "malformed process request: " + err;
-            return {};
-        }
-
-        auto input = j["processInput"];
-
-        if (!input.has_shape({
-                    { "timestamp", json11::Json::OBJECT },
-                    { "inputBuffers", json11::Json::ARRAY } }, err)) {
-            err = "malformed process request: " + err;
-            return {};
-        }
-
-        Vamp::HostExt::ProcessRequest r;
-        r.plugin = pmapper.handleToPlugin(j["handle"].int_value());
-
-        r.timestamp = toRealTime(input["timestamp"], err);
-        if (failed(err)) return {};
-
-        for (const auto &a: input["inputBuffers"].array_items()) {
-
-            if (a.is_string()) {
-                std::vector<float> buf = toFloatBuffer(a.string_value(),
-                                                       err);
-                if (failed(err)) return {};
-                r.inputBuffers.push_back(buf);
-                serialisation = BufferSerialisation::Base64;
-
-            } else if (a.is_array()) {
-                std::vector<float> buf;
-                for (auto v : a.array_items()) {
-                    buf.push_back(v.number_value());
-                }
-                r.inputBuffers.push_back(buf);
-                serialisation = BufferSerialisation::Array;
-
-            } else {
-                err = "expected arrays or strings in inputBuffers array";
-                return {};
-            }
-        }
-
-        return r;
-    }
-    
-private: // go private briefly for a couple of helper functions
-    
-    static void
-    checkTypeField(json11::Json j, std::string expected, std::string &err) {
-        if (!j["method"].is_string()) {
-            err = "string expected for method";
-            return;
-        }
-        if (j["method"].string_value() != expected) {
-            err = "expected value \"" + expected + "\" for type";
-            return;
-        }
-    }
-
-    static bool
-    successful(json11::Json j, std::string &err) {
-        if (!j["success"].is_bool()) {
-            err = "bool expected for success";
-            return false;
-        }
-        return j["success"].bool_value();
-    }
-
-    static void
-    markRPC(json11::Json::object &jo) {
-        jo["jsonrpc"] = "2.0";
-    }
-
-    static void
-    addId(json11::Json::object &jo, const json11::Json &id) {
-        if (!id.is_null()) {
-            jo["id"] = id;
-        }
-    }
-    
-public:
-
-    static json11::Json
-    fromRpcRequest_List(const json11::Json &id) {
-
-        json11::Json::object jo;
-        markRPC(jo);
-
-        jo["method"] = "list";
-        addId(jo, id);
-        return json11::Json(jo);
-    }
-
-    static json11::Json
-    fromRpcResponse_List(const Vamp::HostExt::ListResponse &resp,
-                         const json11::Json &id) {
-
-        json11::Json::object jo;
-        markRPC(jo);
-
-        json11::Json::array arr;
-        for (const auto &a: resp.plugins) {
-            arr.push_back(fromPluginStaticData(a));
-        }
-        json11::Json::object po;
-        po["available"] = arr;
-
-        jo["method"] = "list";
-        jo["result"] = po;
-        addId(jo, id);
-        return json11::Json(jo);
-    }
-    
-    static json11::Json
-    fromRpcRequest_Load(const Vamp::HostExt::LoadRequest &req,
-                        const json11::Json &id) {
-
-        json11::Json::object jo;
-        markRPC(jo);
-
-        jo["method"] = "load";
-        jo["params"] = fromLoadRequest(req);
-        addId(jo, id);
-        return json11::Json(jo);
-    }    
-
-    static json11::Json
-    fromRpcResponse_Load(const Vamp::HostExt::LoadResponse &resp,
-                         const PluginHandleMapper &pmapper,
-                         const json11::Json &id) {
-
-        if (resp.plugin) {
-
-            json11::Json::object jo;
-            markRPC(jo);
-
-            jo["method"] = "load";
-            jo["result"] = fromLoadResponse(resp, pmapper);
-            addId(jo, id);
-            return json11::Json(jo);
-            
-        } else {
-            return fromError("Failed to load plugin", RRType::Load, id);
-        }
-    }
-
-    static json11::Json
-    fromRpcRequest_Configure(const Vamp::HostExt::ConfigurationRequest &req,
-                             const PluginHandleMapper &pmapper,
-                             const json11::Json &id) {
-
-        json11::Json::object jo;
-        markRPC(jo);
-
-        jo["method"] = "configure";
-        jo["params"] = fromConfigurationRequest(req, pmapper);
-        addId(jo, id);
-        return json11::Json(jo);
-    }    
-
-    static json11::Json
-    fromRpcResponse_Configure(const Vamp::HostExt::ConfigurationResponse &resp,
-                              const PluginHandleMapper &pmapper,
-                              const json11::Json &id) {
-
-        if (!resp.outputs.empty()) {
-        
-            json11::Json::object jo;
-            markRPC(jo);
-
-            jo["method"] = "configure";
-            jo["result"] = fromConfigurationResponse(resp, pmapper);
-            addId(jo, id);
-            return json11::Json(jo);
-
-        } else {
-            return fromError("Failed to configure plugin", RRType::Configure, id);
-        }
-    }
-    
-    static json11::Json
-    fromRpcRequest_Process(const Vamp::HostExt::ProcessRequest &req,
-                           const PluginHandleMapper &pmapper,
-                           BufferSerialisation serialisation,
-                           const json11::Json &id) {
-
-        json11::Json::object jo;
-        markRPC(jo);
-
-        jo["method"] = "process";
-        jo["params"] = fromProcessRequest(req, pmapper, serialisation);
-        addId(jo, id);
-        return json11::Json(jo);
-    }    
-
-    static json11::Json
-    fromRpcResponse_Process(const Vamp::HostExt::ProcessResponse &resp,
-                            const PluginHandleMapper &pmapper,
-                            BufferSerialisation serialisation,
-                            const json11::Json &id) {
-        
-        json11::Json::object jo;
-        markRPC(jo);
-
-        json11::Json::object po;
-        po["handle"] = pmapper.pluginToHandle(resp.plugin);
-        po["features"] = fromFeatureSet(resp.features,
-                                        *pmapper.pluginToOutputIdMapper(resp.plugin),
-                                        serialisation);
-        jo["method"] = "process";
-        jo["result"] = po;
-        addId(jo, id);
-        return json11::Json(jo);
-    }
-    
-    static json11::Json
-    fromRpcRequest_Finish(const Vamp::HostExt::FinishRequest &req,
-                          const PluginHandleMapper &pmapper,
-                          const json11::Json &id) {
-
-        json11::Json::object jo;
-        markRPC(jo);
-
-        json11::Json::object fo;
-        fo["handle"] = pmapper.pluginToHandle(req.plugin);
-
-        jo["method"] = "finish";
-        jo["params"] = fo;
-        addId(jo, id);
-        return json11::Json(jo);
-    }    
-    
-    static json11::Json
-    fromRpcResponse_Finish(const Vamp::HostExt::ProcessResponse &resp,
-                           const PluginHandleMapper &pmapper,
-                           BufferSerialisation serialisation,
-                           const json11::Json &id) {
-
-        json11::Json::object jo;
-        markRPC(jo);
-
-        json11::Json::object po;
-        po["handle"] = pmapper.pluginToHandle(resp.plugin);
-        po["features"] = fromFeatureSet(resp.features,
-                                        *pmapper.pluginToOutputIdMapper(resp.plugin),
-                                        serialisation);
-        jo["method"] = "finish";
-        jo["result"] = po;
-        addId(jo, id);
-        return json11::Json(jo);
-    }
-
-    static json11::Json
-    fromError(std::string errorText,
-              RRType responseType,
-              const json11::Json &id) {
-
-        json11::Json::object jo;
-        markRPC(jo);
-
-        std::string type;
-
-        if (responseType == RRType::List) type = "list";
-        else if (responseType == RRType::Load) type = "load";
-        else if (responseType == RRType::Configure) type = "configure";
-        else if (responseType == RRType::Process) type = "process";
-        else if (responseType == RRType::Finish) type = "finish";
-        else type = "invalid";
-
-        json11::Json::object eo;
-        eo["code"] = 0;
-        eo["message"] = 
-            std::string("error in ") + type + " request: " + errorText;
-
-        jo["method"] = type;
-        jo["error"] = eo;
-        addId(jo, id);
-        return json11::Json(jo);
-    }
-
-    static RRType
-    getRequestResponseType(json11::Json j, std::string &err) {
-
-        if (!j["method"].is_string()) {
-            err = "string expected for method";
-            return RRType::NotValid;
-        }
-        
-        std::string type = j["method"].string_value();
-
-	if (type == "list") return RRType::List;
-	else if (type == "load") return RRType::Load;
-	else if (type == "configure") return RRType::Configure;
-	else if (type == "process") return RRType::Process;
-	else if (type == "finish") return RRType::Finish;
-        else if (type == "invalid") return RRType::NotValid;
-	else {
-	    err = "unknown or unexpected request/response type \"" + type + "\"";
-            return RRType::NotValid;
-	}
-    }
-
-    static void
-    toRpcRequest_List(json11::Json j, std::string &err) {
-        checkTypeField(j, "list", err);
-    }
-
-    static Vamp::HostExt::ListResponse
-    toRpcResponse_List(json11::Json j, std::string &err) {
-
-        Vamp::HostExt::ListResponse resp;
-        if (successful(j, err) && !failed(err)) {
-            for (const auto &a: j["result"]["available"].array_items()) {
-                resp.plugins.push_back(toPluginStaticData(a, err));
-                if (failed(err)) return {};
-            }
-        }
-        
-        return resp;
-    }
-
-    static Vamp::HostExt::LoadRequest
-    toRpcRequest_Load(json11::Json j, std::string &err) {
-        
-        checkTypeField(j, "load", err);
-        if (failed(err)) return {};
-        return toLoadRequest(j["params"], err);
-    }
-    
-    static Vamp::HostExt::LoadResponse
-    toRpcResponse_Load(json11::Json j,
-                        const PluginHandleMapper &pmapper,
-                        std::string &err) {
-        
-        Vamp::HostExt::LoadResponse resp;
-        if (successful(j, err) && !failed(err)) {
-            resp = toLoadResponse(j["result"], pmapper, err);
-        }
-        return resp;
-    }
-    
-    static Vamp::HostExt::ConfigurationRequest
-    toRpcRequest_Configure(json11::Json j,
-                            const PluginHandleMapper &pmapper,
-                            std::string &err) {
-        
-        checkTypeField(j, "configure", err);
-        if (failed(err)) return {};
-        return toConfigurationRequest(j["params"], pmapper, err);
-    }
-    
-    static Vamp::HostExt::ConfigurationResponse
-    toRpcResponse_Configure(json11::Json j,
-                             const PluginHandleMapper &pmapper,
-                             std::string &err) {
-        
-        Vamp::HostExt::ConfigurationResponse resp;
-        if (successful(j, err) && !failed(err)) {
-            resp = toConfigurationResponse(j["result"], pmapper, err);
-        }
-        return resp;
-    }
-    
-    static Vamp::HostExt::ProcessRequest
-    toRpcRequest_Process(json11::Json j, const PluginHandleMapper &pmapper,
-                          BufferSerialisation &serialisation, std::string &err) {
-        
-        checkTypeField(j, "process", err);
-        if (failed(err)) return {};
-        return toProcessRequest(j["params"], pmapper, serialisation, err);
-    }
-    
-    static Vamp::HostExt::ProcessResponse
-    toRpcResponse_Process(json11::Json j,
-                           const PluginHandleMapper &pmapper,
-                           BufferSerialisation &serialisation, std::string &err) {
-        
-        Vamp::HostExt::ProcessResponse resp;
-        if (successful(j, err) && !failed(err)) {
-            auto jc = j["result"];
-            auto h = jc["handle"].int_value();
-            resp.plugin = pmapper.handleToPlugin(h);
-            resp.features = toFeatureSet(jc["features"],
-                                         *pmapper.handleToOutputIdMapper(h),
-                                         serialisation, err);
-        }
-        return resp;
-    }
-    
-    static Vamp::HostExt::FinishRequest
-    toRpcRequest_Finish(json11::Json j, const PluginHandleMapper &pmapper,
-                         std::string &err) {
-        
-        checkTypeField(j, "finish", err);
-        if (failed(err)) return {};
-        Vamp::HostExt::FinishRequest req;
-        req.plugin = pmapper.handleToPlugin
-            (j["params"]["handle"].int_value());
-        return req;
-    }
-    
-    static Vamp::HostExt::ProcessResponse
-    toRpcResponse_Finish(json11::Json j,
-                          const PluginHandleMapper &pmapper,
-                          BufferSerialisation &serialisation, std::string &err) {
-        
-        Vamp::HostExt::ProcessResponse resp;
-        if (successful(j, err) && !failed(err)) {
-            auto jc = j["result"];
-            auto h = jc["handle"].int_value();
-            resp.plugin = pmapper.handleToPlugin(h);
-            resp.features = toFeatureSet(jc["features"],
-                                         *pmapper.handleToOutputIdMapper(h),
-                                         serialisation, err);
-        }
-        return resp;
-    }
-};
-
-}
-
-#endif
--- a/json/base-n/LICENSE	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-Copyright (C) 2012 Andrzej Zawadzki (azawadzki@gmail.com)
-
-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 OR COPYRIGHT HOLDERS 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.
--- a/json/base-n/README.md	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-base-n provides encoding/decoding support for BaseX encoding schemes accessible through a standard STL-like iterator interface. Standard Base16, Base32, and Base64 are available out-of-the-box. Adding or modifying custom schemes is supported.
-
-# Usage overview #
-
-base-n is a small, single-header library which provides standard Base16, Base32, Base64, and custom Base-N encoding support.
-
-The main functionality is delivered by the following functions in `bn` namespace:
-```
-template<class Iter1, class Iter2>
-void encode_b16(Iter1 start, Iter1 end, Iter2 out);
-
-
-template<class Iter1, class Iter2>
-void encode_b32(Iter1 start, Iter1 end, Iter2 out);
-
-
-template<class Iter1, class Iter2>
-void encode_b64(Iter1 start, Iter1 end, Iter2 out);
-
-
-template<class Iter1, class Iter2>
-void decode_b16(Iter1 start, Iter1 end, Iter2 out);
-
-
-template<class Iter1, class Iter2>
-void decode_b32(Iter1 start, Iter1 end, Iter2 out);
-
-
-template<class Iter1, class Iter2>
-void decode_b64(Iter1 start, Iter1 end, Iter2 out);
-```
-
-In order to encode and decode data in `std::string` variable `in`, you can do the following:
-```
-bn::encode_b64(in.begin(), in.end(), back_inserter(encoded));
-bn::decode_b64(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, ""));
-```
-
-Should you find yourself lacking some encoding scheme or the default character mapping rules are not good for your use case, you can easily provide your own encoder. For that, you need to define a struct type which will describe the new encoding. Sample below:
-```
-struct b8_custom
-{
-        static size_t group_length()
-        {
-                return 3;
-        }
-
-        static char encode(int index)
-        {
-                const char* const dictionary = "01234567";
-                assert(index < strlen(dictionary));
-                return dictionary[index];
-        }
-
-        static char decode(char c)
-        {
-                if (c >= '0' && c <= '7') {
-                        return c - '0';
-                }
-                return -1;
-        }
-};
-...
-string encoded;
-bn::impl::encode<b8_custom>(in.begin(), in.end(), back_inserter(encoded));
-bn::impl::decode<b8_custom>(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, ""));
-```
-
-For a full working example, see `basen_example.cpp` file in `example` directory.
--- a/json/base-n/example/basen_example.cpp	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-/**
- * base-n, 1.0
- * Copyright (C) 2012 Andrzej Zawadzki (azawadzki@gmail.com)
- * 
- * 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 OR COPYRIGHT HOLDERS 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.
-**/
-#include <cassert>
-#include <cstring>
-#include <iostream>
-#include <iterator>
-#include <string>
-
-#include "basen.hpp"
-
-int main()
-{
-    using namespace std;
-    string in = "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.";
-    cout << in << endl;
-
-    {
-        string encoded;
-        bn::encode_b64(in.begin(), in.end(), back_inserter(encoded));
-        bn::decode_b64(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, ""));
-        cout << endl;
-    }
-    {
-        string encoded;
-        bn::encode_b32(in.begin(), in.end(), back_inserter(encoded));
-        bn::decode_b32(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, ""));
-        cout << endl;
-    }
-    {
-        string encoded;
-        bn::encode_b16(in.begin(), in.end(), back_inserter(encoded));
-        bn::decode_b16(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, ""));
-        cout << endl;
-    }
-    {
-        string encoded = "#TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz\n"
-            "IHNpbmd1bGFyIHBhc3Npb24gZnJvbS@BvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg\n"
-            " dGhlIG1(pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu\n"
-            "\rdWVkIGFuZCBpbmRlZmF0aWdhY*mxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo\n"
-            "ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4";
-        bn::decode_b64(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, ""));
-        cout << endl;
-    }
-    {
-        // move the struct definition outside of main() for non-C++11 compilers
-        struct b8_custom
-        {
-            static size_t group_length()
-            {
-               return 3;
-            }
-
-            static char encode(unsigned int index)
-            {
-                const char* const dictionary = "01234567";
-                assert(index < strlen(dictionary));
-                return dictionary[index];
-            }
-
-            static char decode(char c)
-            {
-                if (c >= '0' && c <= '7') {
-                    return c - '0';
-                }
-                return -1;
-            }
-        };
-        string encoded;
-        bn::impl::encode<b8_custom>(in.begin(), in.end(), back_inserter(encoded));
-        bn::impl::decode<b8_custom>(encoded.begin(), encoded.end(), ostream_iterator<char>(cout, ""));
-        cout << endl;
-    }
-}
-
--- a/json/base-n/include/basen.hpp	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,290 +0,0 @@
-/**
- * base-n, 1.0
- * Copyright (C) 2012 Andrzej Zawadzki (azawadzki@gmail.com)
- * 
- * 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 OR COPYRIGHT HOLDERS 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.
-**/
-#ifndef BASEN_HPP
-#define BASEN_HPP
-
-#include <algorithm>
-#include <cctype>
-#include <cassert>
-#include <cstring>
-
-namespace bn
-{
-
-template<class Iter1, class Iter2>
-void encode_b16(Iter1 start, Iter1 end, Iter2 out);
-
-template<class Iter1, class Iter2>
-void encode_b32(Iter1 start, Iter1 end, Iter2 out);
-
-template<class Iter1, class Iter2>
-void encode_b64(Iter1 start, Iter1 end, Iter2 out);
-
-template<class Iter1, class Iter2>
-void decode_b16(Iter1 start, Iter1 end, Iter2 out);
-
-template<class Iter1, class Iter2>
-void decode_b32(Iter1 start, Iter1 end, Iter2 out);
-
-template<class Iter1, class Iter2>
-void decode_b64(Iter1 start, Iter1 end, Iter2 out);
-
-namespace impl
-{
-
-const int ERROR = -1;
-
-namespace {
-
-char extract_partial_bits(char value, unsigned int start_bit, unsigned int bits_count)
-{
-    assert(start_bit + bits_count < 8);
-    // shift extracted bits to the beginning of the byte
-    char t1 = value >> (8 - bits_count - start_bit);
-    // mask out bits on the left
-    char t2 = t1 & ~(-1U << bits_count);
-    return t2;
-}
-
-char extract_overlapping_bits(char previous, char next, unsigned int start_bit, unsigned int bits_count)
-{
-    assert(start_bit + bits_count < 16);
-    int bits_count_in_previous = 8 - start_bit;
-    int bits_count_in_next = bits_count - bits_count_in_previous;
-    char t1 = previous << bits_count_in_next;
-    char t2 = next >> (8 - bits_count_in_next) & ~(-1U << bits_count_in_next) ;
-    return (t1 | t2) & ~(-1U << bits_count);
-}
-
-}
-
-struct b16_conversion_traits
-{
-    static size_t group_length()
-    {
-       return 4;
-    }
-
-    static char encode(unsigned int index)
-    {
-        const char* const dictionary = "0123456789ABCDEF";
-        assert(index < strlen(dictionary));
-        return dictionary[index];
-    }
-
-    static char decode(char c)
-    {
-        if (c >= '0' && c <= '9') {
-            return c - '0';
-        } else if (c >= 'A' && c <= 'F') {
-            return c - 'A' + 10;
-        }
-        return ERROR;
-    }
-};
-
-struct b32_conversion_traits
-{
-    static size_t group_length()
-    {
-       return 5;
-    }
-
-    static char encode(unsigned int index)
-    {
-        const char * dictionary = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
-        assert(index < strlen(dictionary));
-        return dictionary[index];
-    }
-
-    static char decode(char c)
-    {
-        if (c >= 'A' && c <= 'Z') {
-            return c - 'A';
-        } else if (c >= '2' && c <= '7') {
-            return c - '2' + 26;
-        }
-        return ERROR;
-    }
-};
-
-struct b64_conversion_traits
-{
-    static size_t group_length()
-    {
-       return 6;
-    }
-
-    static char encode(unsigned int index)
-    {
-        const char* const dictionary = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-        assert(index < strlen(dictionary));
-        return dictionary[index];
-    }
-
-    static char decode(char c)
-    {
-        const int alph_len = 26;
-        if (c >= 'A' && c <= 'Z') {
-            return c - 'A';
-        } else if (c >= 'a' && c <= 'z') {
-            return c - 'a' + alph_len * 1;
-        } else if (c >= '0' && c <= '9') {
-            return c - '0' + alph_len * 2;
-        } else if (c == '+') {
-            return c - '+' + alph_len * 2 + 10;
-        } else if (c == '/') {
-            return c - '/' + alph_len * 2 + 11;
-        }
-        return ERROR;
-    }
-};
-
-template<class ConversionTraits, class Iter1, class Iter2>
-void decode(Iter1 start, Iter1 end, Iter2 out)
-{
-    Iter1 iter = start;
-    int output_current_bit = 0;
-    char buffer = 0;
-
-    while (iter != end) {
-        if (std::isspace(*iter)) {
-            ++iter;
-            continue;
-        }
-        char value = ConversionTraits::decode(*iter);
-        if (value == ERROR) {
-            // malformed data, but let's go on...
-            ++iter;
-            continue;
-        }
-        unsigned int bits_in_current_byte = std::min<int>(output_current_bit + ConversionTraits::group_length(), 8) - output_current_bit;
-        if (bits_in_current_byte == ConversionTraits::group_length()) {
-            // the value fits within current byte, so we can extract it directly
-            buffer |= value << (8 - output_current_bit - ConversionTraits::group_length());
-            output_current_bit += ConversionTraits::group_length();
-            // check if we filled up current byte completely; in such case we flush output and continue
-            if (output_current_bit == 8) {
-                *out++ = buffer;
-                buffer = 0;
-                output_current_bit = 0;
-            }
-        } else {
-            // the value spans across the current and the next byte
-            int bits_in_next_byte = ConversionTraits::group_length() - bits_in_current_byte;
-            // fill the current byte and flush it to our output
-            buffer |= value >> bits_in_next_byte;
-            *out++ = buffer;
-            buffer = 0;
-            // save the remainder of our value in the buffer; it will be flushed
-            // during next iterations
-            buffer |= value << (8 - bits_in_next_byte);
-            output_current_bit = bits_in_next_byte;
-        }
-        ++iter;
-    }
-}
-
-template<class ConversionTraits, class Iter1, class Iter2>
-void encode(Iter1 start, Iter1 end, Iter2 out)
-{
-    Iter1 iter = start;
-    int start_bit = 0;
-    bool has_backlog = false;
-    char backlog = 0;
-
-    while (has_backlog || iter != end) {
-        if (!has_backlog) {
-            if (start_bit + ConversionTraits::group_length() < 8) {
-                // the value fits within single byte, so we can extract it
-                // directly
-                char v = extract_partial_bits(*iter, start_bit, ConversionTraits::group_length());
-                *out++ = ConversionTraits::encode(v);
-                // since we know that start_bit + ConversionTraits::group_length() < 8 we don't need to go
-                // to the next byte
-                start_bit += ConversionTraits::group_length();
-            } else {
-                // our bits are spanning across byte border; we need to keep the
-                // starting point and move over to next byte.
-                backlog = *iter++;
-                has_backlog = true;
-            }
-        } else {
-            // encode value which is made from bits spanning across byte
-            // boundary
-            char v;
-            if (iter == end)
-                 v = extract_overlapping_bits(backlog, 0, start_bit, ConversionTraits::group_length());
-            else
-                 v = extract_overlapping_bits(backlog, *iter, start_bit, ConversionTraits::group_length());
-            *out++ = ConversionTraits::encode(v);
-            has_backlog = false;
-            start_bit = (start_bit + ConversionTraits::group_length()) % 8;
-        }
-    }
-}
-
-} // impl
-
-using namespace bn::impl;
-
-template<class Iter1, class Iter2>
-void encode_b16(Iter1 start, Iter1 end, Iter2 out)
-{
-    encode<b16_conversion_traits>(start, end, out);
-}
-
-template<class Iter1, class Iter2>
-void encode_b32(Iter1 start, Iter1 end, Iter2 out)
-{
-    encode<b32_conversion_traits>(start, end, out);
-}
-
-template<class Iter1, class Iter2>
-void encode_b64(Iter1 start, Iter1 end, Iter2 out)
-{
-    encode<b64_conversion_traits>(start, end, out);
-}
-
-template<class Iter1, class Iter2>
-void decode_b16(Iter1 start, Iter1 end, Iter2 out)
-{
-    decode<b16_conversion_traits>(start, end, out);
-}
-
-template<class Iter1, class Iter2>
-void decode_b32(Iter1 start, Iter1 end, Iter2 out)
-{
-    decode<b32_conversion_traits>(start, end, out);
-}
-
-template<class Iter1, class Iter2>
-void decode_b64(Iter1 start, Iter1 end, Iter2 out)
-{
-    decode<b64_conversion_traits>(start, end, out);
-}
-
-} // bn
-
-#endif // BASEN_HPP
-
--- a/json/json11/CMakeLists.txt	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-project(json11)
-
-cmake_minimum_required(VERSION 2.8)
-
-enable_testing()
-
-add_definitions(
-    -std=c++11
-    -fno-rtti
-    -fno-exceptions
-    -Wall
-    -Wextra
-    -Werror)
-
-set(json11_SRCS json11.cpp)
-
-add_library(json11 STATIC ${json11_SRCS})
-
-add_test(json11_test json11_test)
-
-add_executable(json11_test ${json11_SRCS} test.cpp)
--- a/json/json11/LICENSE.txt	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-Copyright (c) 2013 Dropbox, Inc.
-
-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 OR COPYRIGHT HOLDERS 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.
--- a/json/json11/Makefile	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-test: json11.cpp json11.hpp test.cpp
-	$(CXX) -O -std=c++11 json11.cpp test.cpp -o test -fno-rtti -fno-exceptions
-
-clean:
-	if [ -e test ]; then rm test; fi
-
-.PHONY: clean
--- a/json/json11/README.md	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-json11
-------
-
-json11 is a tiny JSON library for C++11, providing JSON parsing and serialization.
-
-The core object provided by the library is json11::Json. A Json object represents any JSON
-value: null, bool, number (int or double), string (std::string), array (std::vector), or
-object (std::map).
-
-Json objects act like values. They can be assigned, copied, moved, compared for equality or
-order, and so on. There are also helper methods Json::dump, to serialize a Json to a string, and
-Json::parse (static) to parse a std::string as a Json object.
-
-It's easy to make a JSON object with C++11's new initializer syntax:
-
-    Json my_json = Json::object {
-        { "key1", "value1" },
-        { "key2", false },
-        { "key3", Json::array { 1, 2, 3 } },
-    };
-    std::string json_str = my_json.dump();
-
-There are also implicit constructors that allow standard and user-defined types to be
-automatically converted to JSON. For example:
-
-    class Point {
-    public:
-        int x;
-        int y;
-        Point (int x, int y) : x(x), y(y) {}
-        Json to_json() const { return Json::array { x, y }; }
-    };
-
-    std::vector<Point> points = { { 1, 2 }, { 10, 20 }, { 100, 200 } };
-    std::string points_json = Json(points).dump();
-
-JSON values can have their values queried and inspected:
-
-    Json json = Json::array { Json::object { { "k", "v" } } };
-    std::string str = json[0]["k"].string_value();
-
-More documentation is still to come. For now, see json11.hpp.
--- a/json/json11/json11.cpp	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,769 +0,0 @@
-/* Copyright (c) 2013 Dropbox, Inc.
- *
- * 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 OR COPYRIGHT HOLDERS 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.
- */
-
-#include "json11.hpp"
-#include <cassert>
-#include <cmath>
-#include <cstdlib>
-#include <cstdio>
-#include <limits>
-
-namespace json11 {
-
-static const int max_depth = 200;
-
-using std::string;
-using std::vector;
-using std::map;
-using std::make_shared;
-using std::initializer_list;
-using std::move;
-
-/* * * * * * * * * * * * * * * * * * * *
- * Serialization
- */
-
-static void dump(std::nullptr_t, string &out) {
-    out += "null";
-}
-
-static void dump(double value, string &out) {
-    if (std::isfinite(value)) {
-        char buf[32];
-        snprintf(buf, sizeof buf, "%.17g", value);
-        out += buf;
-    } else {
-        out += "null";
-    }
-}
-
-static void dump(int value, string &out) {
-    char buf[32];
-    snprintf(buf, sizeof buf, "%d", value);
-    out += buf;
-}
-
-static void dump(bool value, string &out) {
-    out += value ? "true" : "false";
-}
-
-static void dump(const string &value, string &out) {
-    out += '"';
-    for (size_t i = 0; i < value.length(); i++) {
-        const char ch = value[i];
-        if (ch == '\\') {
-            out += "\\\\";
-        } else if (ch == '"') {
-            out += "\\\"";
-        } else if (ch == '\b') {
-            out += "\\b";
-        } else if (ch == '\f') {
-            out += "\\f";
-        } else if (ch == '\n') {
-            out += "\\n";
-        } else if (ch == '\r') {
-            out += "\\r";
-        } else if (ch == '\t') {
-            out += "\\t";
-        } else if (static_cast<uint8_t>(ch) <= 0x1f) {
-            char buf[8];
-            snprintf(buf, sizeof buf, "\\u%04x", ch);
-            out += buf;
-        } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80
-                   && static_cast<uint8_t>(value[i+2]) == 0xa8) {
-            out += "\\u2028";
-            i += 2;
-        } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80
-                   && static_cast<uint8_t>(value[i+2]) == 0xa9) {
-            out += "\\u2029";
-            i += 2;
-        } else {
-            out += ch;
-        }
-    }
-    out += '"';
-}
-
-static void dump(const Json::array &values, string &out) {
-    bool first = true;
-    out += "[";
-    for (const auto &value : values) {
-        if (!first)
-            out += ", ";
-        value.dump(out);
-        first = false;
-    }
-    out += "]";
-}
-
-static void dump(const Json::object &values, string &out) {
-    bool first = true;
-    out += "{";
-    for (const auto &kv : values) {
-        if (!first)
-            out += ", ";
-        dump(kv.first, out);
-        out += ": ";
-        kv.second.dump(out);
-        first = false;
-    }
-    out += "}";
-}
-
-void Json::dump(string &out) const {
-    m_ptr->dump(out);
-}
-
-/* * * * * * * * * * * * * * * * * * * *
- * Value wrappers
- */
-
-template <Json::Type tag, typename T>
-class Value : public JsonValue {
-protected:
-
-    // Constructors
-    explicit Value(const T &value) : m_value(value) {}
-    explicit Value(T &&value)      : m_value(move(value)) {}
-
-    // Get type tag
-    Json::Type type() const override {
-        return tag;
-    }
-
-    // Comparisons
-    bool equals(const JsonValue * other) const override {
-        return m_value == static_cast<const Value<tag, T> *>(other)->m_value;
-    }
-    bool less(const JsonValue * other) const override {
-        return m_value < static_cast<const Value<tag, T> *>(other)->m_value;
-    }
-
-    const T m_value;
-    void dump(string &out) const override { json11::dump(m_value, out); }
-};
-
-class JsonDouble final : public Value<Json::NUMBER, double> {
-    double number_value() const override { return m_value; }
-    int int_value() const override { return static_cast<int>(m_value); }
-    bool equals(const JsonValue * other) const override { return m_value == other->number_value(); }
-    bool less(const JsonValue * other)   const override { return m_value <  other->number_value(); }
-public:
-    explicit JsonDouble(double value) : Value(value) {}
-};
-
-class JsonInt final : public Value<Json::NUMBER, int> {
-    double number_value() const override { return m_value; }
-    int int_value() const override { return m_value; }
-    bool equals(const JsonValue * other) const override { return m_value == other->number_value(); }
-    bool less(const JsonValue * other)   const override { return m_value <  other->number_value(); }
-public:
-    explicit JsonInt(int value) : Value(value) {}
-};
-
-class JsonBoolean final : public Value<Json::BOOL, bool> {
-    bool bool_value() const override { return m_value; }
-public:
-    explicit JsonBoolean(bool value) : Value(value) {}
-};
-
-class JsonString final : public Value<Json::STRING, string> {
-    const string &string_value() const override { return m_value; }
-public:
-    explicit JsonString(const string &value) : Value(value) {}
-    explicit JsonString(string &&value)      : Value(move(value)) {}
-};
-
-class JsonArray final : public Value<Json::ARRAY, Json::array> {
-    const Json::array &array_items() const override { return m_value; }
-    const Json & operator[](size_t i) const override;
-public:
-    explicit JsonArray(const Json::array &value) : Value(value) {}
-    explicit JsonArray(Json::array &&value)      : Value(move(value)) {}
-};
-
-class JsonObject final : public Value<Json::OBJECT, Json::object> {
-    const Json::object &object_items() const override { return m_value; }
-    const Json & operator[](const string &key) const override;
-public:
-    explicit JsonObject(const Json::object &value) : Value(value) {}
-    explicit JsonObject(Json::object &&value)      : Value(move(value)) {}
-};
-
-class JsonNull final : public Value<Json::NUL, std::nullptr_t> {
-public:
-    JsonNull() : Value(nullptr) {}
-};
-
-/* * * * * * * * * * * * * * * * * * * *
- * Static globals - static-init-safe
- */
-struct Statics {
-    const std::shared_ptr<JsonValue> null = make_shared<JsonNull>();
-    const std::shared_ptr<JsonValue> t = make_shared<JsonBoolean>(true);
-    const std::shared_ptr<JsonValue> f = make_shared<JsonBoolean>(false);
-    const string empty_string;
-    const vector<Json> empty_vector;
-    const map<string, Json> empty_map;
-    Statics() {}
-};
-
-static const Statics & statics() {
-    static const Statics s {};
-    return s;
-}
-
-static const Json & static_null() {
-    // This has to be separate, not in Statics, because Json() accesses statics().null.
-    static const Json json_null;
-    return json_null;
-}
-
-/* * * * * * * * * * * * * * * * * * * *
- * Constructors
- */
-
-Json::Json() noexcept                  : m_ptr(statics().null) {}
-Json::Json(std::nullptr_t) noexcept    : m_ptr(statics().null) {}
-Json::Json(double value)               : m_ptr(make_shared<JsonDouble>(value)) {}
-Json::Json(int value)                  : m_ptr(make_shared<JsonInt>(value)) {}
-Json::Json(bool value)                 : m_ptr(value ? statics().t : statics().f) {}
-Json::Json(const string &value)        : m_ptr(make_shared<JsonString>(value)) {}
-Json::Json(string &&value)             : m_ptr(make_shared<JsonString>(move(value))) {}
-Json::Json(const char * value)         : m_ptr(make_shared<JsonString>(value)) {}
-Json::Json(const Json::array &values)  : m_ptr(make_shared<JsonArray>(values)) {}
-Json::Json(Json::array &&values)       : m_ptr(make_shared<JsonArray>(move(values))) {}
-Json::Json(const Json::object &values) : m_ptr(make_shared<JsonObject>(values)) {}
-Json::Json(Json::object &&values)      : m_ptr(make_shared<JsonObject>(move(values))) {}
-
-/* * * * * * * * * * * * * * * * * * * *
- * Accessors
- */
-
-Json::Type Json::type()                           const { return m_ptr->type();         }
-double Json::number_value()                       const { return m_ptr->number_value(); }
-int Json::int_value()                             const { return m_ptr->int_value();    }
-bool Json::bool_value()                           const { return m_ptr->bool_value();   }
-const string & Json::string_value()               const { return m_ptr->string_value(); }
-const vector<Json> & Json::array_items()          const { return m_ptr->array_items();  }
-const map<string, Json> & Json::object_items()    const { return m_ptr->object_items(); }
-const Json & Json::operator[] (size_t i)          const { return (*m_ptr)[i];           }
-const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key];         }
-
-double                    JsonValue::number_value()              const { return 0; }
-int                       JsonValue::int_value()                 const { return 0; }
-bool                      JsonValue::bool_value()                const { return false; }
-const string &            JsonValue::string_value()              const { return statics().empty_string; }
-const vector<Json> &      JsonValue::array_items()               const { return statics().empty_vector; }
-const map<string, Json> & JsonValue::object_items()              const { return statics().empty_map; }
-const Json &              JsonValue::operator[] (size_t)         const { return static_null(); }
-const Json &              JsonValue::operator[] (const string &) const { return static_null(); }
-
-const Json & JsonObject::operator[] (const string &key) const {
-    auto iter = m_value.find(key);
-    return (iter == m_value.end()) ? static_null() : iter->second;
-}
-const Json & JsonArray::operator[] (size_t i) const {
-    if (i >= m_value.size()) return static_null();
-    else return m_value[i];
-}
-
-/* * * * * * * * * * * * * * * * * * * *
- * Comparison
- */
-
-bool Json::operator== (const Json &other) const {
-    if (m_ptr->type() != other.m_ptr->type())
-        return false;
-
-    return m_ptr->equals(other.m_ptr.get());
-}
-
-bool Json::operator< (const Json &other) const {
-    if (m_ptr->type() != other.m_ptr->type())
-        return m_ptr->type() < other.m_ptr->type();
-
-    return m_ptr->less(other.m_ptr.get());
-}
-
-/* * * * * * * * * * * * * * * * * * * *
- * Parsing
- */
-
-/* esc(c)
- *
- * Format char c suitable for printing in an error message.
- */
-static inline string esc(char c) {
-    char buf[12];
-    if (static_cast<uint8_t>(c) >= 0x20 && static_cast<uint8_t>(c) <= 0x7f) {
-        snprintf(buf, sizeof buf, "'%c' (%d)", c, c);
-    } else {
-        snprintf(buf, sizeof buf, "(%d)", c);
-    }
-    return string(buf);
-}
-
-static inline bool in_range(long x, long lower, long upper) {
-    return (x >= lower && x <= upper);
-}
-
-/* JsonParser
- *
- * Object that tracks all state of an in-progress parse.
- */
-struct JsonParser {
-
-    /* State
-     */
-    const string &str;
-    size_t i;
-    string &err;
-    bool failed;
-    const JsonParse strategy;
-
-    /* fail(msg, err_ret = Json())
-     *
-     * Mark this parse as failed.
-     */
-    Json fail(string &&msg) {
-        return fail(move(msg), Json());
-    }
-
-    template <typename T>
-    T fail(string &&msg, const T err_ret) {
-        if (!failed)
-            err = std::move(msg);
-        failed = true;
-        return err_ret;
-    }
-
-    /* consume_whitespace()
-     *
-     * Advance until the current character is non-whitespace.
-     */
-    void consume_whitespace() {
-        while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t')
-            i++;
-    }
-
-    /* consume_comment()
-     *
-     * Advance comments (c-style inline and multiline).
-     */
-    bool consume_comment() {
-      bool comment_found = false;
-      if (str[i] == '/') {
-        i++;
-        if (i == str.size())
-          return fail("unexpected end of input inside comment", 0);
-        if (str[i] == '/') { // inline comment
-          i++;
-          if (i == str.size())
-            return fail("unexpected end of input inside inline comment", 0);
-          // advance until next line
-          while (str[i] != '\n') {
-            i++;
-            if (i == str.size())
-              return fail("unexpected end of input inside inline comment", 0);
-          }
-          comment_found = true;
-        }
-        else if (str[i] == '*') { // multiline comment
-          i++;
-          if (i > str.size()-2)
-            return fail("unexpected end of input inside multi-line comment", 0);
-          // advance until closing tokens
-          while (!(str[i] == '*' && str[i+1] == '/')) {
-            i++;
-            if (i > str.size()-2)
-              return fail(
-                "unexpected end of input inside multi-line comment", 0);
-          }
-          i += 2;
-          if (i == str.size())
-            return fail(
-              "unexpected end of input inside multi-line comment", 0);
-          comment_found = true;
-        }
-        else
-          return fail("malformed comment", 0);
-      }
-      return comment_found;
-    }
-
-    /* consume_garbage()
-     *
-     * Advance until the current character is non-whitespace and non-comment.
-     */
-    void consume_garbage() {
-      consume_whitespace();
-      if(strategy == JsonParse::COMMENTS) {
-        bool comment_found = false;
-        do {
-          comment_found = consume_comment();
-          consume_whitespace();
-        }
-        while(comment_found);
-      }
-    }
-
-    /* get_next_token()
-     *
-     * Return the next non-whitespace character. If the end of the input is reached,
-     * flag an error and return 0.
-     */
-    char get_next_token() {
-        consume_garbage();
-        if (i == str.size())
-            return fail("unexpected end of input", 0);
-
-        return str[i++];
-    }
-
-    /* encode_utf8(pt, out)
-     *
-     * Encode pt as UTF-8 and add it to out.
-     */
-    void encode_utf8(long pt, string & out) {
-        if (pt < 0)
-            return;
-
-        if (pt < 0x80) {
-            out += static_cast<char>(pt);
-        } else if (pt < 0x800) {
-            out += static_cast<char>((pt >> 6) | 0xC0);
-            out += static_cast<char>((pt & 0x3F) | 0x80);
-        } else if (pt < 0x10000) {
-            out += static_cast<char>((pt >> 12) | 0xE0);
-            out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80);
-            out += static_cast<char>((pt & 0x3F) | 0x80);
-        } else {
-            out += static_cast<char>((pt >> 18) | 0xF0);
-            out += static_cast<char>(((pt >> 12) & 0x3F) | 0x80);
-            out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80);
-            out += static_cast<char>((pt & 0x3F) | 0x80);
-        }
-    }
-
-    /* parse_string()
-     *
-     * Parse a string, starting at the current position.
-     */
-    string parse_string() {
-        string out;
-        long last_escaped_codepoint = -1;
-        while (true) {
-            if (i == str.size())
-                return fail("unexpected end of input in string", "");
-
-            char ch = str[i++];
-
-            if (ch == '"') {
-                encode_utf8(last_escaped_codepoint, out);
-                return out;
-            }
-
-            if (in_range(ch, 0, 0x1f))
-                return fail("unescaped " + esc(ch) + " in string", "");
-
-            // The usual case: non-escaped characters
-            if (ch != '\\') {
-                encode_utf8(last_escaped_codepoint, out);
-                last_escaped_codepoint = -1;
-                out += ch;
-                continue;
-            }
-
-            // Handle escapes
-            if (i == str.size())
-                return fail("unexpected end of input in string", "");
-
-            ch = str[i++];
-
-            if (ch == 'u') {
-                // Extract 4-byte escape sequence
-                string esc = str.substr(i, 4);
-                // Explicitly check length of the substring. The following loop
-                // relies on std::string returning the terminating NUL when
-                // accessing str[length]. Checking here reduces brittleness.
-                if (esc.length() < 4) {
-                    return fail("bad \\u escape: " + esc, "");
-                }
-                for (int j = 0; j < 4; j++) {
-                    if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F')
-                            && !in_range(esc[j], '0', '9'))
-                        return fail("bad \\u escape: " + esc, "");
-                }
-
-                long codepoint = strtol(esc.data(), nullptr, 16);
-
-                // JSON specifies that characters outside the BMP shall be encoded as a pair
-                // of 4-hex-digit \u escapes encoding their surrogate pair components. Check
-                // whether we're in the middle of such a beast: the previous codepoint was an
-                // escaped lead (high) surrogate, and this is a trail (low) surrogate.
-                if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF)
-                        && in_range(codepoint, 0xDC00, 0xDFFF)) {
-                    // Reassemble the two surrogate pairs into one astral-plane character, per
-                    // the UTF-16 algorithm.
-                    encode_utf8((((last_escaped_codepoint - 0xD800) << 10)
-                                 | (codepoint - 0xDC00)) + 0x10000, out);
-                    last_escaped_codepoint = -1;
-                } else {
-                    encode_utf8(last_escaped_codepoint, out);
-                    last_escaped_codepoint = codepoint;
-                }
-
-                i += 4;
-                continue;
-            }
-
-            encode_utf8(last_escaped_codepoint, out);
-            last_escaped_codepoint = -1;
-
-            if (ch == 'b') {
-                out += '\b';
-            } else if (ch == 'f') {
-                out += '\f';
-            } else if (ch == 'n') {
-                out += '\n';
-            } else if (ch == 'r') {
-                out += '\r';
-            } else if (ch == 't') {
-                out += '\t';
-            } else if (ch == '"' || ch == '\\' || ch == '/') {
-                out += ch;
-            } else {
-                return fail("invalid escape character " + esc(ch), "");
-            }
-        }
-    }
-
-    /* parse_number()
-     *
-     * Parse a double.
-     */
-    Json parse_number() {
-        size_t start_pos = i;
-
-        if (str[i] == '-')
-            i++;
-
-        // Integer part
-        if (str[i] == '0') {
-            i++;
-            if (in_range(str[i], '0', '9'))
-                return fail("leading 0s not permitted in numbers");
-        } else if (in_range(str[i], '1', '9')) {
-            i++;
-            while (in_range(str[i], '0', '9'))
-                i++;
-        } else {
-            return fail("invalid " + esc(str[i]) + " in number");
-        }
-
-        if (str[i] != '.' && str[i] != 'e' && str[i] != 'E'
-                && (i - start_pos) <= static_cast<size_t>(std::numeric_limits<int>::digits10)) {
-            return std::atoi(str.c_str() + start_pos);
-        }
-
-        // Decimal part
-        if (str[i] == '.') {
-            i++;
-            if (!in_range(str[i], '0', '9'))
-                return fail("at least one digit required in fractional part");
-
-            while (in_range(str[i], '0', '9'))
-                i++;
-        }
-
-        // Exponent part
-        if (str[i] == 'e' || str[i] == 'E') {
-            i++;
-
-            if (str[i] == '+' || str[i] == '-')
-                i++;
-
-            if (!in_range(str[i], '0', '9'))
-                return fail("at least one digit required in exponent");
-
-            while (in_range(str[i], '0', '9'))
-                i++;
-        }
-
-        return std::strtod(str.c_str() + start_pos, nullptr);
-    }
-
-    /* expect(str, res)
-     *
-     * Expect that 'str' starts at the character that was just read. If it does, advance
-     * the input and return res. If not, flag an error.
-     */
-    Json expect(const string &expected, Json res) {
-        assert(i != 0);
-        i--;
-        if (str.compare(i, expected.length(), expected) == 0) {
-            i += expected.length();
-            return res;
-        } else {
-            return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length()));
-        }
-    }
-
-    /* parse_json()
-     *
-     * Parse a JSON object.
-     */
-    Json parse_json(int depth) {
-        if (depth > max_depth) {
-            return fail("exceeded maximum nesting depth");
-        }
-
-        char ch = get_next_token();
-        if (failed)
-            return Json();
-
-        if (ch == '-' || (ch >= '0' && ch <= '9')) {
-            i--;
-            return parse_number();
-        }
-
-        if (ch == 't')
-            return expect("true", true);
-
-        if (ch == 'f')
-            return expect("false", false);
-
-        if (ch == 'n')
-            return expect("null", Json());
-
-        if (ch == '"')
-            return parse_string();
-
-        if (ch == '{') {
-            map<string, Json> data;
-            ch = get_next_token();
-            if (ch == '}')
-                return data;
-
-            while (1) {
-                if (ch != '"')
-                    return fail("expected '\"' in object, got " + esc(ch));
-
-                string key = parse_string();
-                if (failed)
-                    return Json();
-
-                ch = get_next_token();
-                if (ch != ':')
-                    return fail("expected ':' in object, got " + esc(ch));
-
-                data[std::move(key)] = parse_json(depth + 1);
-                if (failed)
-                    return Json();
-
-                ch = get_next_token();
-                if (ch == '}')
-                    break;
-                if (ch != ',')
-                    return fail("expected ',' in object, got " + esc(ch));
-
-                ch = get_next_token();
-            }
-            return data;
-        }
-
-        if (ch == '[') {
-            vector<Json> data;
-            ch = get_next_token();
-            if (ch == ']')
-                return data;
-
-            while (1) {
-                i--;
-                data.push_back(parse_json(depth + 1));
-                if (failed)
-                    return Json();
-
-                ch = get_next_token();
-                if (ch == ']')
-                    break;
-                if (ch != ',')
-                    return fail("expected ',' in list, got " + esc(ch));
-
-                ch = get_next_token();
-                (void)ch;
-            }
-            return data;
-        }
-
-        return fail("expected value, got " + esc(ch));
-    }
-};
-
-Json Json::parse(const string &in, string &err, JsonParse strategy) {
-    JsonParser parser { in, 0, err, false, strategy };
-    Json result = parser.parse_json(0);
-
-    // Check for any trailing garbage
-    parser.consume_garbage();
-    if (parser.i != in.size())
-        return parser.fail("unexpected trailing " + esc(in[parser.i]));
-
-    return result;
-}
-
-// Documented in json11.hpp
-vector<Json> Json::parse_multi(const string &in,
-                               string &err,
-                               JsonParse strategy) {
-    JsonParser parser { in, 0, err, false, strategy };
-
-    vector<Json> json_vec;
-    while (parser.i != in.size() && !parser.failed) {
-        json_vec.push_back(parser.parse_json(0));
-        // Check for another object
-        parser.consume_garbage();
-    }
-    return json_vec;
-}
-
-/* * * * * * * * * * * * * * * * * * * *
- * Shape-checking
- */
-
-bool Json::has_shape(const shape & types, string & err) const {
-    if (!is_object()) {
-        err = "expected JSON object, got " + dump();
-        return false;
-    }
-
-    for (auto & item : types) {
-        if ((*this)[item.first].type() != item.second) {
-            err = "bad type for " + item.first + " in " + dump();
-            return false;
-        }
-    }
-
-    return true;
-}
-
-} // namespace json11
--- a/json/json11/json11.hpp	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,211 +0,0 @@
-/* json11
- *
- * json11 is a tiny JSON library for C++11, providing JSON parsing and serialization.
- *
- * The core object provided by the library is json11::Json. A Json object represents any JSON
- * value: null, bool, number (int or double), string (std::string), array (std::vector), or
- * object (std::map).
- *
- * Json objects act like values: they can be assigned, copied, moved, compared for equality or
- * order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and
- * Json::parse (static) to parse a std::string as a Json object.
- *
- * Internally, the various types of Json object are represented by the JsonValue class
- * hierarchy.
- *
- * A note on numbers - JSON specifies the syntax of number formatting but not its semantics,
- * so some JSON implementations distinguish between integers and floating-point numbers, while
- * some don't. In json11, we choose the latter. Because some JSON implementations (namely
- * Javascript itself) treat all numbers as the same type, distinguishing the two leads
- * to JSON that will be *silently* changed by a round-trip through those implementations.
- * Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also
- * provides integer helpers.
- *
- * Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the
- * range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64
- * or long long to avoid the Y2038K problem; a double storing microseconds since some epoch
- * will be exact for +/- 275 years.)
- */
-
-/* Copyright (c) 2013 Dropbox, Inc.
- *
- * 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 OR COPYRIGHT HOLDERS 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.
- */
-
-#pragma once
-
-#include <string>
-#include <vector>
-#include <map>
-#include <memory>
-#include <initializer_list>
-
-namespace json11 {
-
-enum JsonParse {
-    STANDARD, COMMENTS
-};
-
-class JsonValue;
-
-class Json final {
-public:
-    // Types
-    enum Type {
-        NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT
-    };
-
-    // Array and object typedefs
-    typedef std::vector<Json> array;
-    typedef std::map<std::string, Json> object;
-
-    // Constructors for the various types of JSON value.
-    Json() noexcept;                // NUL
-    Json(std::nullptr_t) noexcept;  // NUL
-    Json(double value);             // NUMBER
-    Json(int value);                // NUMBER
-    Json(bool value);               // BOOL
-    Json(const std::string &value); // STRING
-    Json(std::string &&value);      // STRING
-    Json(const char * value);       // STRING
-    Json(const array &values);      // ARRAY
-    Json(array &&values);           // ARRAY
-    Json(const object &values);     // OBJECT
-    Json(object &&values);          // OBJECT
-
-    // Implicit constructor: anything with a to_json() function.
-    template <class T, class = decltype(&T::to_json)>
-    Json(const T & t) : Json(t.to_json()) {}
-
-    // Implicit constructor: map-like objects (std::map, std::unordered_map, etc)
-    template <class M, typename std::enable_if<
-        std::is_constructible<std::string, typename M::key_type>::value
-        && std::is_constructible<Json, typename M::mapped_type>::value,
-            int>::type = 0>
-    Json(const M & m) : Json(object(m.begin(), m.end())) {}
-
-    // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc)
-    template <class V, typename std::enable_if<
-        std::is_constructible<Json, typename V::value_type>::value,
-            int>::type = 0>
-    Json(const V & v) : Json(array(v.begin(), v.end())) {}
-
-    // This prevents Json(some_pointer) from accidentally producing a bool. Use
-    // Json(bool(some_pointer)) if that behavior is desired.
-    Json(void *) = delete;
-
-    // Accessors
-    Type type() const;
-
-    bool is_null()   const { return type() == NUL; }
-    bool is_number() const { return type() == NUMBER; }
-    bool is_bool()   const { return type() == BOOL; }
-    bool is_string() const { return type() == STRING; }
-    bool is_array()  const { return type() == ARRAY; }
-    bool is_object() const { return type() == OBJECT; }
-
-    // Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not
-    // distinguish between integer and non-integer numbers - number_value() and int_value()
-    // can both be applied to a NUMBER-typed object.
-    double number_value() const;
-    int int_value() const;
-
-    // Return the enclosed value if this is a boolean, false otherwise.
-    bool bool_value() const;
-    // Return the enclosed string if this is a string, "" otherwise.
-    const std::string &string_value() const;
-    // Return the enclosed std::vector if this is an array, or an empty vector otherwise.
-    const array &array_items() const;
-    // Return the enclosed std::map if this is an object, or an empty map otherwise.
-    const object &object_items() const;
-
-    // Return a reference to arr[i] if this is an array, Json() otherwise.
-    const Json & operator[](size_t i) const;
-    // Return a reference to obj[key] if this is an object, Json() otherwise.
-    const Json & operator[](const std::string &key) const;
-
-    // Serialize.
-    void dump(std::string &out) const;
-    std::string dump() const {
-        std::string out;
-        dump(out);
-        return out;
-    }
-
-    // Parse. If parse fails, return Json() and assign an error message to err.
-    static Json parse(const std::string & in,
-                      std::string & err,
-                      JsonParse strategy = JsonParse::STANDARD);
-    static Json parse(const char * in,
-                      std::string & err,
-                      JsonParse strategy = JsonParse::STANDARD) {
-        if (in) {
-            return parse(std::string(in), err, strategy);
-        } else {
-            err = "null input";
-            return nullptr;
-        }
-    }
-    // Parse multiple objects, concatenated or separated by whitespace
-    static std::vector<Json> parse_multi(
-        const std::string & in,
-        std::string & err,
-        JsonParse strategy = JsonParse::STANDARD);
-
-    bool operator== (const Json &rhs) const;
-    bool operator<  (const Json &rhs) const;
-    bool operator!= (const Json &rhs) const { return !(*this == rhs); }
-    bool operator<= (const Json &rhs) const { return !(rhs < *this); }
-    bool operator>  (const Json &rhs) const { return  (rhs < *this); }
-    bool operator>= (const Json &rhs) const { return !(*this < rhs); }
-
-    /* has_shape(types, err)
-     *
-     * Return true if this is a JSON object and, for each item in types, has a field of
-     * the given type. If not, return false and set err to a descriptive message.
-     */
-    typedef std::initializer_list<std::pair<std::string, Type>> shape;
-    bool has_shape(const shape & types, std::string & err) const;
-
-private:
-    std::shared_ptr<JsonValue> m_ptr;
-};
-
-// Internal class hierarchy - JsonValue objects are not exposed to users of this API.
-class JsonValue {
-protected:
-    friend class Json;
-    friend class JsonInt;
-    friend class JsonDouble;
-    virtual Json::Type type() const = 0;
-    virtual bool equals(const JsonValue * other) const = 0;
-    virtual bool less(const JsonValue * other) const = 0;
-    virtual void dump(std::string &out) const = 0;
-    virtual double number_value() const;
-    virtual int int_value() const;
-    virtual bool bool_value() const;
-    virtual const std::string &string_value() const;
-    virtual const Json::array &array_items() const;
-    virtual const Json &operator[](size_t i) const;
-    virtual const Json::object &object_items() const;
-    virtual const Json &operator[](const std::string &key) const;
-    virtual ~JsonValue() {}
-};
-
-} // namespace json11
--- a/json/json11/test.cpp	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,205 +0,0 @@
-#include <string>
-#include <cstdio>
-#include <cstring>
-#include <iostream>
-#include <sstream>
-#include "json11.hpp"
-#include <cassert>
-#include <list>
-#include <set>
-#include <unordered_map>
-
-using namespace json11;
-using std::string;
-
-// Check that Json has the properties we want.
-#include <type_traits>
-#define CHECK_TRAIT(x) static_assert(std::x::value, #x)
-CHECK_TRAIT(is_nothrow_constructible<Json>);
-CHECK_TRAIT(is_nothrow_default_constructible<Json>);
-CHECK_TRAIT(is_copy_constructible<Json>);
-CHECK_TRAIT(is_nothrow_move_constructible<Json>);
-CHECK_TRAIT(is_copy_assignable<Json>);
-CHECK_TRAIT(is_nothrow_move_assignable<Json>);
-CHECK_TRAIT(is_nothrow_destructible<Json>);
-
-void parse_from_stdin() {
-    string buf;
-    string line;
-    while (std::getline(std::cin, line)) {
-        buf += line + "\n";
-    }
-
-    string err;
-    auto json = Json::parse(buf, err);
-    if (!err.empty()) {
-        printf("Failed: %s\n", err.c_str());
-    } else {
-        printf("Result: %s\n", json.dump().c_str());
-    }
-}
-
-int main(int argc, char **argv) {
-    if (argc == 2 && argv[1] == string("--stdin")) {
-        parse_from_stdin();
-        return 0;
-    }
-
-    const string simple_test =
-        R"({"k1":"v1", "k2":42, "k3":["a",123,true,false,null]})";
-
-    string err;
-    auto json = Json::parse(simple_test, err);
-
-    std::cout << "k1: " << json["k1"].string_value() << "\n";
-    std::cout << "k3: " << json["k3"].dump() << "\n";
-
-    for (auto &k : json["k3"].array_items()) {
-        std::cout << "    - " << k.dump() << "\n";
-    }
-
-    const string comment_test = R"({
-      // comment /* with nested comment */
-      "a": 1,
-      // comment
-      // continued
-      "b": "text",
-      /* multi
-         line
-         comment */
-      // and single-line comment
-      "c": [1, 2, 3]
-    })";
-
-    string err_comment;
-    auto json_comment = Json::parse(
-      comment_test, err_comment, JsonParse::COMMENTS);
-    if (!err_comment.empty()) {
-        printf("Failed: %s\n", err_comment.c_str());
-    } else {
-        printf("Result: %s\n", json_comment.dump().c_str());
-    }
-
-    string failing_comment_test = R"({
-      /* bad comment
-      "a": 1,
-    })";
-
-    string err_failing_comment;
-    auto json_failing_comment = Json::parse(
-      failing_comment_test, err_failing_comment, JsonParse::COMMENTS);
-    if (!err_failing_comment.empty()) {
-        printf("Failed: %s\n", err_failing_comment.c_str());
-    } else {
-        printf("Result: %s\n", json_failing_comment.dump().c_str());
-    }
-
-    failing_comment_test = R"({
-      / / bad comment })";
-
-    json_failing_comment = Json::parse(
-      failing_comment_test, err_failing_comment, JsonParse::COMMENTS);
-    if (!err_failing_comment.empty()) {
-        printf("Failed: %s\n", err_failing_comment.c_str());
-    } else {
-        printf("Result: %s\n", json_failing_comment.dump().c_str());
-    }
-
-    failing_comment_test = R"({// bad comment })";
-
-    json_failing_comment = Json::parse(
-      failing_comment_test, err_failing_comment, JsonParse::COMMENTS);
-    if (!err_failing_comment.empty()) {
-        printf("Failed: %s\n", err_failing_comment.c_str());
-    } else {
-        printf("Result: %s\n", json_failing_comment.dump().c_str());
-    }
-
-    failing_comment_test = R"({
-          "a": 1
-        }/)";
-
-    json_failing_comment = Json::parse(
-      failing_comment_test, err_failing_comment, JsonParse::COMMENTS);
-    if (!err_failing_comment.empty()) {
-        printf("Failed: %s\n", err_failing_comment.c_str());
-    } else {
-        printf("Result: %s\n", json_failing_comment.dump().c_str());
-    }
-
-    failing_comment_test = R"({/* bad
-                                  comment *})";
-
-    json_failing_comment = Json::parse(
-      failing_comment_test, err_failing_comment, JsonParse::COMMENTS);
-    if (!err_failing_comment.empty()) {
-        printf("Failed: %s\n", err_failing_comment.c_str());
-    } else {
-        printf("Result: %s\n", json_failing_comment.dump().c_str());
-    }
-
-    std::list<int> l1 { 1, 2, 3 };
-    std::vector<int> l2 { 1, 2, 3 };
-    std::set<int> l3 { 1, 2, 3 };
-    assert(Json(l1) == Json(l2));
-    assert(Json(l2) == Json(l3));
-
-    std::map<string, string> m1 { { "k1", "v1" }, { "k2", "v2" } };
-    std::unordered_map<string, string> m2 { { "k1", "v1" }, { "k2", "v2" } };
-    assert(Json(m1) == Json(m2));
-
-    // Json literals
-    Json obj = Json::object({
-        { "k1", "v1" },
-        { "k2", 42.0 },
-        { "k3", Json::array({ "a", 123.0, true, false, nullptr }) },
-    });
-
-    std::cout << "obj: " << obj.dump() << "\n";
-
-    assert(Json("a").number_value() == 0);
-    assert(Json("a").string_value() == "a");
-    assert(Json().number_value() == 0);
-
-    assert(obj == json);
-    assert(Json(42) == Json(42.0));
-    assert(Json(42) != Json(42.1));
-
-    const string unicode_escape_test =
-        R"([ "blah\ud83d\udca9blah\ud83dblah\udca9blah\u0000blah\u1234" ])";
-
-    const char utf8[] = "blah" "\xf0\x9f\x92\xa9" "blah" "\xed\xa0\xbd" "blah"
-                        "\xed\xb2\xa9" "blah" "\0" "blah" "\xe1\x88\xb4";
-
-    Json uni = Json::parse(unicode_escape_test, err);
-    assert(uni[0].string_value().size() == (sizeof utf8) - 1);
-    assert(std::memcmp(uni[0].string_value().data(), utf8, sizeof utf8) == 0);
-
-    // Demonstrates the behavior change in Xcode 7 / Clang 3.7 described
-    // here: https://llvm.org/bugs/show_bug.cgi?id=23812
-    Json nested_array = Json::array { Json::array { 1, 2, 3 } };
-    assert(nested_array.is_array());
-    assert(nested_array.array_items().size() == 1);
-    assert(nested_array.array_items()[0].is_array());
-    assert(nested_array.array_items()[0].array_items().size() == 3);
-
-    Json my_json = Json::object {
-        { "key1", "value1" },
-        { "key2", false },
-        { "key3", Json::array { 1, 2, 3 } },
-    };
-    std::string json_str = my_json.dump();
-    printf("%s\n", json_str.c_str());
-
-    class Point {
-    public:
-        int x;
-        int y;
-        Point (int x, int y) : x(x), y(y) {}
-        Json to_json() const { return Json::array { x, y }; }
-    };
-
-    std::vector<Point> points = { { 1, 2 }, { 10, 20 }, { 100, 200 } };
-    std::string points_json = Json(points).dump();
-    printf("%s\n", points_json.c_str());
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/json11/CMakeLists.txt	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,21 @@
+project(json11)
+
+cmake_minimum_required(VERSION 2.8)
+
+enable_testing()
+
+add_definitions(
+    -std=c++11
+    -fno-rtti
+    -fno-exceptions
+    -Wall
+    -Wextra
+    -Werror)
+
+set(json11_SRCS json11.cpp)
+
+add_library(json11 STATIC ${json11_SRCS})
+
+add_test(json11_test json11_test)
+
+add_executable(json11_test ${json11_SRCS} test.cpp)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/json11/LICENSE.txt	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,19 @@
+Copyright (c) 2013 Dropbox, Inc.
+
+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 OR COPYRIGHT HOLDERS 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.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/json11/Makefile	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,7 @@
+test: json11.cpp json11.hpp test.cpp
+	$(CXX) -O -std=c++11 json11.cpp test.cpp -o test -fno-rtti -fno-exceptions
+
+clean:
+	if [ -e test ]; then rm test; fi
+
+.PHONY: clean
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/json11/README.md	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,42 @@
+json11
+------
+
+json11 is a tiny JSON library for C++11, providing JSON parsing and serialization.
+
+The core object provided by the library is json11::Json. A Json object represents any JSON
+value: null, bool, number (int or double), string (std::string), array (std::vector), or
+object (std::map).
+
+Json objects act like values. They can be assigned, copied, moved, compared for equality or
+order, and so on. There are also helper methods Json::dump, to serialize a Json to a string, and
+Json::parse (static) to parse a std::string as a Json object.
+
+It's easy to make a JSON object with C++11's new initializer syntax:
+
+    Json my_json = Json::object {
+        { "key1", "value1" },
+        { "key2", false },
+        { "key3", Json::array { 1, 2, 3 } },
+    };
+    std::string json_str = my_json.dump();
+
+There are also implicit constructors that allow standard and user-defined types to be
+automatically converted to JSON. For example:
+
+    class Point {
+    public:
+        int x;
+        int y;
+        Point (int x, int y) : x(x), y(y) {}
+        Json to_json() const { return Json::array { x, y }; }
+    };
+
+    std::vector<Point> points = { { 1, 2 }, { 10, 20 }, { 100, 200 } };
+    std::string points_json = Json(points).dump();
+
+JSON values can have their values queried and inspected:
+
+    Json json = Json::array { Json::object { { "k", "v" } } };
+    std::string str = json[0]["k"].string_value();
+
+More documentation is still to come. For now, see json11.hpp.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/json11/json11.cpp	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,769 @@
+/* Copyright (c) 2013 Dropbox, Inc.
+ *
+ * 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 OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "json11.hpp"
+#include <cassert>
+#include <cmath>
+#include <cstdlib>
+#include <cstdio>
+#include <limits>
+
+namespace json11 {
+
+static const int max_depth = 200;
+
+using std::string;
+using std::vector;
+using std::map;
+using std::make_shared;
+using std::initializer_list;
+using std::move;
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Serialization
+ */
+
+static void dump(std::nullptr_t, string &out) {
+    out += "null";
+}
+
+static void dump(double value, string &out) {
+    if (std::isfinite(value)) {
+        char buf[32];
+        snprintf(buf, sizeof buf, "%.17g", value);
+        out += buf;
+    } else {
+        out += "null";
+    }
+}
+
+static void dump(int value, string &out) {
+    char buf[32];
+    snprintf(buf, sizeof buf, "%d", value);
+    out += buf;
+}
+
+static void dump(bool value, string &out) {
+    out += value ? "true" : "false";
+}
+
+static void dump(const string &value, string &out) {
+    out += '"';
+    for (size_t i = 0; i < value.length(); i++) {
+        const char ch = value[i];
+        if (ch == '\\') {
+            out += "\\\\";
+        } else if (ch == '"') {
+            out += "\\\"";
+        } else if (ch == '\b') {
+            out += "\\b";
+        } else if (ch == '\f') {
+            out += "\\f";
+        } else if (ch == '\n') {
+            out += "\\n";
+        } else if (ch == '\r') {
+            out += "\\r";
+        } else if (ch == '\t') {
+            out += "\\t";
+        } else if (static_cast<uint8_t>(ch) <= 0x1f) {
+            char buf[8];
+            snprintf(buf, sizeof buf, "\\u%04x", ch);
+            out += buf;
+        } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80
+                   && static_cast<uint8_t>(value[i+2]) == 0xa8) {
+            out += "\\u2028";
+            i += 2;
+        } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80
+                   && static_cast<uint8_t>(value[i+2]) == 0xa9) {
+            out += "\\u2029";
+            i += 2;
+        } else {
+            out += ch;
+        }
+    }
+    out += '"';
+}
+
+static void dump(const Json::array &values, string &out) {
+    bool first = true;
+    out += "[";
+    for (const auto &value : values) {
+        if (!first)
+            out += ", ";
+        value.dump(out);
+        first = false;
+    }
+    out += "]";
+}
+
+static void dump(const Json::object &values, string &out) {
+    bool first = true;
+    out += "{";
+    for (const auto &kv : values) {
+        if (!first)
+            out += ", ";
+        dump(kv.first, out);
+        out += ": ";
+        kv.second.dump(out);
+        first = false;
+    }
+    out += "}";
+}
+
+void Json::dump(string &out) const {
+    m_ptr->dump(out);
+}
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Value wrappers
+ */
+
+template <Json::Type tag, typename T>
+class Value : public JsonValue {
+protected:
+
+    // Constructors
+    explicit Value(const T &value) : m_value(value) {}
+    explicit Value(T &&value)      : m_value(move(value)) {}
+
+    // Get type tag
+    Json::Type type() const override {
+        return tag;
+    }
+
+    // Comparisons
+    bool equals(const JsonValue * other) const override {
+        return m_value == static_cast<const Value<tag, T> *>(other)->m_value;
+    }
+    bool less(const JsonValue * other) const override {
+        return m_value < static_cast<const Value<tag, T> *>(other)->m_value;
+    }
+
+    const T m_value;
+    void dump(string &out) const override { json11::dump(m_value, out); }
+};
+
+class JsonDouble final : public Value<Json::NUMBER, double> {
+    double number_value() const override { return m_value; }
+    int int_value() const override { return static_cast<int>(m_value); }
+    bool equals(const JsonValue * other) const override { return m_value == other->number_value(); }
+    bool less(const JsonValue * other)   const override { return m_value <  other->number_value(); }
+public:
+    explicit JsonDouble(double value) : Value(value) {}
+};
+
+class JsonInt final : public Value<Json::NUMBER, int> {
+    double number_value() const override { return m_value; }
+    int int_value() const override { return m_value; }
+    bool equals(const JsonValue * other) const override { return m_value == other->number_value(); }
+    bool less(const JsonValue * other)   const override { return m_value <  other->number_value(); }
+public:
+    explicit JsonInt(int value) : Value(value) {}
+};
+
+class JsonBoolean final : public Value<Json::BOOL, bool> {
+    bool bool_value() const override { return m_value; }
+public:
+    explicit JsonBoolean(bool value) : Value(value) {}
+};
+
+class JsonString final : public Value<Json::STRING, string> {
+    const string &string_value() const override { return m_value; }
+public:
+    explicit JsonString(const string &value) : Value(value) {}
+    explicit JsonString(string &&value)      : Value(move(value)) {}
+};
+
+class JsonArray final : public Value<Json::ARRAY, Json::array> {
+    const Json::array &array_items() const override { return m_value; }
+    const Json & operator[](size_t i) const override;
+public:
+    explicit JsonArray(const Json::array &value) : Value(value) {}
+    explicit JsonArray(Json::array &&value)      : Value(move(value)) {}
+};
+
+class JsonObject final : public Value<Json::OBJECT, Json::object> {
+    const Json::object &object_items() const override { return m_value; }
+    const Json & operator[](const string &key) const override;
+public:
+    explicit JsonObject(const Json::object &value) : Value(value) {}
+    explicit JsonObject(Json::object &&value)      : Value(move(value)) {}
+};
+
+class JsonNull final : public Value<Json::NUL, std::nullptr_t> {
+public:
+    JsonNull() : Value(nullptr) {}
+};
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Static globals - static-init-safe
+ */
+struct Statics {
+    const std::shared_ptr<JsonValue> null = make_shared<JsonNull>();
+    const std::shared_ptr<JsonValue> t = make_shared<JsonBoolean>(true);
+    const std::shared_ptr<JsonValue> f = make_shared<JsonBoolean>(false);
+    const string empty_string;
+    const vector<Json> empty_vector;
+    const map<string, Json> empty_map;
+    Statics() {}
+};
+
+static const Statics & statics() {
+    static const Statics s {};
+    return s;
+}
+
+static const Json & static_null() {
+    // This has to be separate, not in Statics, because Json() accesses statics().null.
+    static const Json json_null;
+    return json_null;
+}
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Constructors
+ */
+
+Json::Json() noexcept                  : m_ptr(statics().null) {}
+Json::Json(std::nullptr_t) noexcept    : m_ptr(statics().null) {}
+Json::Json(double value)               : m_ptr(make_shared<JsonDouble>(value)) {}
+Json::Json(int value)                  : m_ptr(make_shared<JsonInt>(value)) {}
+Json::Json(bool value)                 : m_ptr(value ? statics().t : statics().f) {}
+Json::Json(const string &value)        : m_ptr(make_shared<JsonString>(value)) {}
+Json::Json(string &&value)             : m_ptr(make_shared<JsonString>(move(value))) {}
+Json::Json(const char * value)         : m_ptr(make_shared<JsonString>(value)) {}
+Json::Json(const Json::array &values)  : m_ptr(make_shared<JsonArray>(values)) {}
+Json::Json(Json::array &&values)       : m_ptr(make_shared<JsonArray>(move(values))) {}
+Json::Json(const Json::object &values) : m_ptr(make_shared<JsonObject>(values)) {}
+Json::Json(Json::object &&values)      : m_ptr(make_shared<JsonObject>(move(values))) {}
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Accessors
+ */
+
+Json::Type Json::type()                           const { return m_ptr->type();         }
+double Json::number_value()                       const { return m_ptr->number_value(); }
+int Json::int_value()                             const { return m_ptr->int_value();    }
+bool Json::bool_value()                           const { return m_ptr->bool_value();   }
+const string & Json::string_value()               const { return m_ptr->string_value(); }
+const vector<Json> & Json::array_items()          const { return m_ptr->array_items();  }
+const map<string, Json> & Json::object_items()    const { return m_ptr->object_items(); }
+const Json & Json::operator[] (size_t i)          const { return (*m_ptr)[i];           }
+const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key];         }
+
+double                    JsonValue::number_value()              const { return 0; }
+int                       JsonValue::int_value()                 const { return 0; }
+bool                      JsonValue::bool_value()                const { return false; }
+const string &            JsonValue::string_value()              const { return statics().empty_string; }
+const vector<Json> &      JsonValue::array_items()               const { return statics().empty_vector; }
+const map<string, Json> & JsonValue::object_items()              const { return statics().empty_map; }
+const Json &              JsonValue::operator[] (size_t)         const { return static_null(); }
+const Json &              JsonValue::operator[] (const string &) const { return static_null(); }
+
+const Json & JsonObject::operator[] (const string &key) const {
+    auto iter = m_value.find(key);
+    return (iter == m_value.end()) ? static_null() : iter->second;
+}
+const Json & JsonArray::operator[] (size_t i) const {
+    if (i >= m_value.size()) return static_null();
+    else return m_value[i];
+}
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Comparison
+ */
+
+bool Json::operator== (const Json &other) const {
+    if (m_ptr->type() != other.m_ptr->type())
+        return false;
+
+    return m_ptr->equals(other.m_ptr.get());
+}
+
+bool Json::operator< (const Json &other) const {
+    if (m_ptr->type() != other.m_ptr->type())
+        return m_ptr->type() < other.m_ptr->type();
+
+    return m_ptr->less(other.m_ptr.get());
+}
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Parsing
+ */
+
+/* esc(c)
+ *
+ * Format char c suitable for printing in an error message.
+ */
+static inline string esc(char c) {
+    char buf[12];
+    if (static_cast<uint8_t>(c) >= 0x20 && static_cast<uint8_t>(c) <= 0x7f) {
+        snprintf(buf, sizeof buf, "'%c' (%d)", c, c);
+    } else {
+        snprintf(buf, sizeof buf, "(%d)", c);
+    }
+    return string(buf);
+}
+
+static inline bool in_range(long x, long lower, long upper) {
+    return (x >= lower && x <= upper);
+}
+
+/* JsonParser
+ *
+ * Object that tracks all state of an in-progress parse.
+ */
+struct JsonParser {
+
+    /* State
+     */
+    const string &str;
+    size_t i;
+    string &err;
+    bool failed;
+    const JsonParse strategy;
+
+    /* fail(msg, err_ret = Json())
+     *
+     * Mark this parse as failed.
+     */
+    Json fail(string &&msg) {
+        return fail(move(msg), Json());
+    }
+
+    template <typename T>
+    T fail(string &&msg, const T err_ret) {
+        if (!failed)
+            err = std::move(msg);
+        failed = true;
+        return err_ret;
+    }
+
+    /* consume_whitespace()
+     *
+     * Advance until the current character is non-whitespace.
+     */
+    void consume_whitespace() {
+        while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t')
+            i++;
+    }
+
+    /* consume_comment()
+     *
+     * Advance comments (c-style inline and multiline).
+     */
+    bool consume_comment() {
+      bool comment_found = false;
+      if (str[i] == '/') {
+        i++;
+        if (i == str.size())
+          return fail("unexpected end of input inside comment", 0);
+        if (str[i] == '/') { // inline comment
+          i++;
+          if (i == str.size())
+            return fail("unexpected end of input inside inline comment", 0);
+          // advance until next line
+          while (str[i] != '\n') {
+            i++;
+            if (i == str.size())
+              return fail("unexpected end of input inside inline comment", 0);
+          }
+          comment_found = true;
+        }
+        else if (str[i] == '*') { // multiline comment
+          i++;
+          if (i > str.size()-2)
+            return fail("unexpected end of input inside multi-line comment", 0);
+          // advance until closing tokens
+          while (!(str[i] == '*' && str[i+1] == '/')) {
+            i++;
+            if (i > str.size()-2)
+              return fail(
+                "unexpected end of input inside multi-line comment", 0);
+          }
+          i += 2;
+          if (i == str.size())
+            return fail(
+              "unexpected end of input inside multi-line comment", 0);
+          comment_found = true;
+        }
+        else
+          return fail("malformed comment", 0);
+      }
+      return comment_found;
+    }
+
+    /* consume_garbage()
+     *
+     * Advance until the current character is non-whitespace and non-comment.
+     */
+    void consume_garbage() {
+      consume_whitespace();
+      if(strategy == JsonParse::COMMENTS) {
+        bool comment_found = false;
+        do {
+          comment_found = consume_comment();
+          consume_whitespace();
+        }
+        while(comment_found);
+      }
+    }
+
+    /* get_next_token()
+     *
+     * Return the next non-whitespace character. If the end of the input is reached,
+     * flag an error and return 0.
+     */
+    char get_next_token() {
+        consume_garbage();
+        if (i == str.size())
+            return fail("unexpected end of input", 0);
+
+        return str[i++];
+    }
+
+    /* encode_utf8(pt, out)
+     *
+     * Encode pt as UTF-8 and add it to out.
+     */
+    void encode_utf8(long pt, string & out) {
+        if (pt < 0)
+            return;
+
+        if (pt < 0x80) {
+            out += static_cast<char>(pt);
+        } else if (pt < 0x800) {
+            out += static_cast<char>((pt >> 6) | 0xC0);
+            out += static_cast<char>((pt & 0x3F) | 0x80);
+        } else if (pt < 0x10000) {
+            out += static_cast<char>((pt >> 12) | 0xE0);
+            out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80);
+            out += static_cast<char>((pt & 0x3F) | 0x80);
+        } else {
+            out += static_cast<char>((pt >> 18) | 0xF0);
+            out += static_cast<char>(((pt >> 12) & 0x3F) | 0x80);
+            out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80);
+            out += static_cast<char>((pt & 0x3F) | 0x80);
+        }
+    }
+
+    /* parse_string()
+     *
+     * Parse a string, starting at the current position.
+     */
+    string parse_string() {
+        string out;
+        long last_escaped_codepoint = -1;
+        while (true) {
+            if (i == str.size())
+                return fail("unexpected end of input in string", "");
+
+            char ch = str[i++];
+
+            if (ch == '"') {
+                encode_utf8(last_escaped_codepoint, out);
+                return out;
+            }
+
+            if (in_range(ch, 0, 0x1f))
+                return fail("unescaped " + esc(ch) + " in string", "");
+
+            // The usual case: non-escaped characters
+            if (ch != '\\') {
+                encode_utf8(last_escaped_codepoint, out);
+                last_escaped_codepoint = -1;
+                out += ch;
+                continue;
+            }
+
+            // Handle escapes
+            if (i == str.size())
+                return fail("unexpected end of input in string", "");
+
+            ch = str[i++];
+
+            if (ch == 'u') {
+                // Extract 4-byte escape sequence
+                string esc = str.substr(i, 4);
+                // Explicitly check length of the substring. The following loop
+                // relies on std::string returning the terminating NUL when
+                // accessing str[length]. Checking here reduces brittleness.
+                if (esc.length() < 4) {
+                    return fail("bad \\u escape: " + esc, "");
+                }
+                for (int j = 0; j < 4; j++) {
+                    if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F')
+                            && !in_range(esc[j], '0', '9'))
+                        return fail("bad \\u escape: " + esc, "");
+                }
+
+                long codepoint = strtol(esc.data(), nullptr, 16);
+
+                // JSON specifies that characters outside the BMP shall be encoded as a pair
+                // of 4-hex-digit \u escapes encoding their surrogate pair components. Check
+                // whether we're in the middle of such a beast: the previous codepoint was an
+                // escaped lead (high) surrogate, and this is a trail (low) surrogate.
+                if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF)
+                        && in_range(codepoint, 0xDC00, 0xDFFF)) {
+                    // Reassemble the two surrogate pairs into one astral-plane character, per
+                    // the UTF-16 algorithm.
+                    encode_utf8((((last_escaped_codepoint - 0xD800) << 10)
+                                 | (codepoint - 0xDC00)) + 0x10000, out);
+                    last_escaped_codepoint = -1;
+                } else {
+                    encode_utf8(last_escaped_codepoint, out);
+                    last_escaped_codepoint = codepoint;
+                }
+
+                i += 4;
+                continue;
+            }
+
+            encode_utf8(last_escaped_codepoint, out);
+            last_escaped_codepoint = -1;
+
+            if (ch == 'b') {
+                out += '\b';
+            } else if (ch == 'f') {
+                out += '\f';
+            } else if (ch == 'n') {
+                out += '\n';
+            } else if (ch == 'r') {
+                out += '\r';
+            } else if (ch == 't') {
+                out += '\t';
+            } else if (ch == '"' || ch == '\\' || ch == '/') {
+                out += ch;
+            } else {
+                return fail("invalid escape character " + esc(ch), "");
+            }
+        }
+    }
+
+    /* parse_number()
+     *
+     * Parse a double.
+     */
+    Json parse_number() {
+        size_t start_pos = i;
+
+        if (str[i] == '-')
+            i++;
+
+        // Integer part
+        if (str[i] == '0') {
+            i++;
+            if (in_range(str[i], '0', '9'))
+                return fail("leading 0s not permitted in numbers");
+        } else if (in_range(str[i], '1', '9')) {
+            i++;
+            while (in_range(str[i], '0', '9'))
+                i++;
+        } else {
+            return fail("invalid " + esc(str[i]) + " in number");
+        }
+
+        if (str[i] != '.' && str[i] != 'e' && str[i] != 'E'
+                && (i - start_pos) <= static_cast<size_t>(std::numeric_limits<int>::digits10)) {
+            return std::atoi(str.c_str() + start_pos);
+        }
+
+        // Decimal part
+        if (str[i] == '.') {
+            i++;
+            if (!in_range(str[i], '0', '9'))
+                return fail("at least one digit required in fractional part");
+
+            while (in_range(str[i], '0', '9'))
+                i++;
+        }
+
+        // Exponent part
+        if (str[i] == 'e' || str[i] == 'E') {
+            i++;
+
+            if (str[i] == '+' || str[i] == '-')
+                i++;
+
+            if (!in_range(str[i], '0', '9'))
+                return fail("at least one digit required in exponent");
+
+            while (in_range(str[i], '0', '9'))
+                i++;
+        }
+
+        return std::strtod(str.c_str() + start_pos, nullptr);
+    }
+
+    /* expect(str, res)
+     *
+     * Expect that 'str' starts at the character that was just read. If it does, advance
+     * the input and return res. If not, flag an error.
+     */
+    Json expect(const string &expected, Json res) {
+        assert(i != 0);
+        i--;
+        if (str.compare(i, expected.length(), expected) == 0) {
+            i += expected.length();
+            return res;
+        } else {
+            return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length()));
+        }
+    }
+
+    /* parse_json()
+     *
+     * Parse a JSON object.
+     */
+    Json parse_json(int depth) {
+        if (depth > max_depth) {
+            return fail("exceeded maximum nesting depth");
+        }
+
+        char ch = get_next_token();
+        if (failed)
+            return Json();
+
+        if (ch == '-' || (ch >= '0' && ch <= '9')) {
+            i--;
+            return parse_number();
+        }
+
+        if (ch == 't')
+            return expect("true", true);
+
+        if (ch == 'f')
+            return expect("false", false);
+
+        if (ch == 'n')
+            return expect("null", Json());
+
+        if (ch == '"')
+            return parse_string();
+
+        if (ch == '{') {
+            map<string, Json> data;
+            ch = get_next_token();
+            if (ch == '}')
+                return data;
+
+            while (1) {
+                if (ch != '"')
+                    return fail("expected '\"' in object, got " + esc(ch));
+
+                string key = parse_string();
+                if (failed)
+                    return Json();
+
+                ch = get_next_token();
+                if (ch != ':')
+                    return fail("expected ':' in object, got " + esc(ch));
+
+                data[std::move(key)] = parse_json(depth + 1);
+                if (failed)
+                    return Json();
+
+                ch = get_next_token();
+                if (ch == '}')
+                    break;
+                if (ch != ',')
+                    return fail("expected ',' in object, got " + esc(ch));
+
+                ch = get_next_token();
+            }
+            return data;
+        }
+
+        if (ch == '[') {
+            vector<Json> data;
+            ch = get_next_token();
+            if (ch == ']')
+                return data;
+
+            while (1) {
+                i--;
+                data.push_back(parse_json(depth + 1));
+                if (failed)
+                    return Json();
+
+                ch = get_next_token();
+                if (ch == ']')
+                    break;
+                if (ch != ',')
+                    return fail("expected ',' in list, got " + esc(ch));
+
+                ch = get_next_token();
+                (void)ch;
+            }
+            return data;
+        }
+
+        return fail("expected value, got " + esc(ch));
+    }
+};
+
+Json Json::parse(const string &in, string &err, JsonParse strategy) {
+    JsonParser parser { in, 0, err, false, strategy };
+    Json result = parser.parse_json(0);
+
+    // Check for any trailing garbage
+    parser.consume_garbage();
+    if (parser.i != in.size())
+        return parser.fail("unexpected trailing " + esc(in[parser.i]));
+
+    return result;
+}
+
+// Documented in json11.hpp
+vector<Json> Json::parse_multi(const string &in,
+                               string &err,
+                               JsonParse strategy) {
+    JsonParser parser { in, 0, err, false, strategy };
+
+    vector<Json> json_vec;
+    while (parser.i != in.size() && !parser.failed) {
+        json_vec.push_back(parser.parse_json(0));
+        // Check for another object
+        parser.consume_garbage();
+    }
+    return json_vec;
+}
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Shape-checking
+ */
+
+bool Json::has_shape(const shape & types, string & err) const {
+    if (!is_object()) {
+        err = "expected JSON object, got " + dump();
+        return false;
+    }
+
+    for (auto & item : types) {
+        if ((*this)[item.first].type() != item.second) {
+            err = "bad type for " + item.first + " in " + dump();
+            return false;
+        }
+    }
+
+    return true;
+}
+
+} // namespace json11
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/json11/json11.hpp	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,211 @@
+/* json11
+ *
+ * json11 is a tiny JSON library for C++11, providing JSON parsing and serialization.
+ *
+ * The core object provided by the library is json11::Json. A Json object represents any JSON
+ * value: null, bool, number (int or double), string (std::string), array (std::vector), or
+ * object (std::map).
+ *
+ * Json objects act like values: they can be assigned, copied, moved, compared for equality or
+ * order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and
+ * Json::parse (static) to parse a std::string as a Json object.
+ *
+ * Internally, the various types of Json object are represented by the JsonValue class
+ * hierarchy.
+ *
+ * A note on numbers - JSON specifies the syntax of number formatting but not its semantics,
+ * so some JSON implementations distinguish between integers and floating-point numbers, while
+ * some don't. In json11, we choose the latter. Because some JSON implementations (namely
+ * Javascript itself) treat all numbers as the same type, distinguishing the two leads
+ * to JSON that will be *silently* changed by a round-trip through those implementations.
+ * Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also
+ * provides integer helpers.
+ *
+ * Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the
+ * range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64
+ * or long long to avoid the Y2038K problem; a double storing microseconds since some epoch
+ * will be exact for +/- 275 years.)
+ */
+
+/* Copyright (c) 2013 Dropbox, Inc.
+ *
+ * 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 OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include <map>
+#include <memory>
+#include <initializer_list>
+
+namespace json11 {
+
+enum JsonParse {
+    STANDARD, COMMENTS
+};
+
+class JsonValue;
+
+class Json final {
+public:
+    // Types
+    enum Type {
+        NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT
+    };
+
+    // Array and object typedefs
+    typedef std::vector<Json> array;
+    typedef std::map<std::string, Json> object;
+
+    // Constructors for the various types of JSON value.
+    Json() noexcept;                // NUL
+    Json(std::nullptr_t) noexcept;  // NUL
+    Json(double value);             // NUMBER
+    Json(int value);                // NUMBER
+    Json(bool value);               // BOOL
+    Json(const std::string &value); // STRING
+    Json(std::string &&value);      // STRING
+    Json(const char * value);       // STRING
+    Json(const array &values);      // ARRAY
+    Json(array &&values);           // ARRAY
+    Json(const object &values);     // OBJECT
+    Json(object &&values);          // OBJECT
+
+    // Implicit constructor: anything with a to_json() function.
+    template <class T, class = decltype(&T::to_json)>
+    Json(const T & t) : Json(t.to_json()) {}
+
+    // Implicit constructor: map-like objects (std::map, std::unordered_map, etc)
+    template <class M, typename std::enable_if<
+        std::is_constructible<std::string, typename M::key_type>::value
+        && std::is_constructible<Json, typename M::mapped_type>::value,
+            int>::type = 0>
+    Json(const M & m) : Json(object(m.begin(), m.end())) {}
+
+    // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc)
+    template <class V, typename std::enable_if<
+        std::is_constructible<Json, typename V::value_type>::value,
+            int>::type = 0>
+    Json(const V & v) : Json(array(v.begin(), v.end())) {}
+
+    // This prevents Json(some_pointer) from accidentally producing a bool. Use
+    // Json(bool(some_pointer)) if that behavior is desired.
+    Json(void *) = delete;
+
+    // Accessors
+    Type type() const;
+
+    bool is_null()   const { return type() == NUL; }
+    bool is_number() const { return type() == NUMBER; }
+    bool is_bool()   const { return type() == BOOL; }
+    bool is_string() const { return type() == STRING; }
+    bool is_array()  const { return type() == ARRAY; }
+    bool is_object() const { return type() == OBJECT; }
+
+    // Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not
+    // distinguish between integer and non-integer numbers - number_value() and int_value()
+    // can both be applied to a NUMBER-typed object.
+    double number_value() const;
+    int int_value() const;
+
+    // Return the enclosed value if this is a boolean, false otherwise.
+    bool bool_value() const;
+    // Return the enclosed string if this is a string, "" otherwise.
+    const std::string &string_value() const;
+    // Return the enclosed std::vector if this is an array, or an empty vector otherwise.
+    const array &array_items() const;
+    // Return the enclosed std::map if this is an object, or an empty map otherwise.
+    const object &object_items() const;
+
+    // Return a reference to arr[i] if this is an array, Json() otherwise.
+    const Json & operator[](size_t i) const;
+    // Return a reference to obj[key] if this is an object, Json() otherwise.
+    const Json & operator[](const std::string &key) const;
+
+    // Serialize.
+    void dump(std::string &out) const;
+    std::string dump() const {
+        std::string out;
+        dump(out);
+        return out;
+    }
+
+    // Parse. If parse fails, return Json() and assign an error message to err.
+    static Json parse(const std::string & in,
+                      std::string & err,
+                      JsonParse strategy = JsonParse::STANDARD);
+    static Json parse(const char * in,
+                      std::string & err,
+                      JsonParse strategy = JsonParse::STANDARD) {
+        if (in) {
+            return parse(std::string(in), err, strategy);
+        } else {
+            err = "null input";
+            return nullptr;
+        }
+    }
+    // Parse multiple objects, concatenated or separated by whitespace
+    static std::vector<Json> parse_multi(
+        const std::string & in,
+        std::string & err,
+        JsonParse strategy = JsonParse::STANDARD);
+
+    bool operator== (const Json &rhs) const;
+    bool operator<  (const Json &rhs) const;
+    bool operator!= (const Json &rhs) const { return !(*this == rhs); }
+    bool operator<= (const Json &rhs) const { return !(rhs < *this); }
+    bool operator>  (const Json &rhs) const { return  (rhs < *this); }
+    bool operator>= (const Json &rhs) const { return !(*this < rhs); }
+
+    /* has_shape(types, err)
+     *
+     * Return true if this is a JSON object and, for each item in types, has a field of
+     * the given type. If not, return false and set err to a descriptive message.
+     */
+    typedef std::initializer_list<std::pair<std::string, Type>> shape;
+    bool has_shape(const shape & types, std::string & err) const;
+
+private:
+    std::shared_ptr<JsonValue> m_ptr;
+};
+
+// Internal class hierarchy - JsonValue objects are not exposed to users of this API.
+class JsonValue {
+protected:
+    friend class Json;
+    friend class JsonInt;
+    friend class JsonDouble;
+    virtual Json::Type type() const = 0;
+    virtual bool equals(const JsonValue * other) const = 0;
+    virtual bool less(const JsonValue * other) const = 0;
+    virtual void dump(std::string &out) const = 0;
+    virtual double number_value() const;
+    virtual int int_value() const;
+    virtual bool bool_value() const;
+    virtual const std::string &string_value() const;
+    virtual const Json::array &array_items() const;
+    virtual const Json &operator[](size_t i) const;
+    virtual const Json::object &object_items() const;
+    virtual const Json &operator[](const std::string &key) const;
+    virtual ~JsonValue() {}
+};
+
+} // namespace json11
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/json11/test.cpp	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,205 @@
+#include <string>
+#include <cstdio>
+#include <cstring>
+#include <iostream>
+#include <sstream>
+#include "json11.hpp"
+#include <cassert>
+#include <list>
+#include <set>
+#include <unordered_map>
+
+using namespace json11;
+using std::string;
+
+// Check that Json has the properties we want.
+#include <type_traits>
+#define CHECK_TRAIT(x) static_assert(std::x::value, #x)
+CHECK_TRAIT(is_nothrow_constructible<Json>);
+CHECK_TRAIT(is_nothrow_default_constructible<Json>);
+CHECK_TRAIT(is_copy_constructible<Json>);
+CHECK_TRAIT(is_nothrow_move_constructible<Json>);
+CHECK_TRAIT(is_copy_assignable<Json>);
+CHECK_TRAIT(is_nothrow_move_assignable<Json>);
+CHECK_TRAIT(is_nothrow_destructible<Json>);
+
+void parse_from_stdin() {
+    string buf;
+    string line;
+    while (std::getline(std::cin, line)) {
+        buf += line + "\n";
+    }
+
+    string err;
+    auto json = Json::parse(buf, err);
+    if (!err.empty()) {
+        printf("Failed: %s\n", err.c_str());
+    } else {
+        printf("Result: %s\n", json.dump().c_str());
+    }
+}
+
+int main(int argc, char **argv) {
+    if (argc == 2 && argv[1] == string("--stdin")) {
+        parse_from_stdin();
+        return 0;
+    }
+
+    const string simple_test =
+        R"({"k1":"v1", "k2":42, "k3":["a",123,true,false,null]})";
+
+    string err;
+    auto json = Json::parse(simple_test, err);
+
+    std::cout << "k1: " << json["k1"].string_value() << "\n";
+    std::cout << "k3: " << json["k3"].dump() << "\n";
+
+    for (auto &k : json["k3"].array_items()) {
+        std::cout << "    - " << k.dump() << "\n";
+    }
+
+    const string comment_test = R"({
+      // comment /* with nested comment */
+      "a": 1,
+      // comment
+      // continued
+      "b": "text",
+      /* multi
+         line
+         comment */
+      // and single-line comment
+      "c": [1, 2, 3]
+    })";
+
+    string err_comment;
+    auto json_comment = Json::parse(
+      comment_test, err_comment, JsonParse::COMMENTS);
+    if (!err_comment.empty()) {
+        printf("Failed: %s\n", err_comment.c_str());
+    } else {
+        printf("Result: %s\n", json_comment.dump().c_str());
+    }
+
+    string failing_comment_test = R"({
+      /* bad comment
+      "a": 1,
+    })";
+
+    string err_failing_comment;
+    auto json_failing_comment = Json::parse(
+      failing_comment_test, err_failing_comment, JsonParse::COMMENTS);
+    if (!err_failing_comment.empty()) {
+        printf("Failed: %s\n", err_failing_comment.c_str());
+    } else {
+        printf("Result: %s\n", json_failing_comment.dump().c_str());
+    }
+
+    failing_comment_test = R"({
+      / / bad comment })";
+
+    json_failing_comment = Json::parse(
+      failing_comment_test, err_failing_comment, JsonParse::COMMENTS);
+    if (!err_failing_comment.empty()) {
+        printf("Failed: %s\n", err_failing_comment.c_str());
+    } else {
+        printf("Result: %s\n", json_failing_comment.dump().c_str());
+    }
+
+    failing_comment_test = R"({// bad comment })";
+
+    json_failing_comment = Json::parse(
+      failing_comment_test, err_failing_comment, JsonParse::COMMENTS);
+    if (!err_failing_comment.empty()) {
+        printf("Failed: %s\n", err_failing_comment.c_str());
+    } else {
+        printf("Result: %s\n", json_failing_comment.dump().c_str());
+    }
+
+    failing_comment_test = R"({
+          "a": 1
+        }/)";
+
+    json_failing_comment = Json::parse(
+      failing_comment_test, err_failing_comment, JsonParse::COMMENTS);
+    if (!err_failing_comment.empty()) {
+        printf("Failed: %s\n", err_failing_comment.c_str());
+    } else {
+        printf("Result: %s\n", json_failing_comment.dump().c_str());
+    }
+
+    failing_comment_test = R"({/* bad
+                                  comment *})";
+
+    json_failing_comment = Json::parse(
+      failing_comment_test, err_failing_comment, JsonParse::COMMENTS);
+    if (!err_failing_comment.empty()) {
+        printf("Failed: %s\n", err_failing_comment.c_str());
+    } else {
+        printf("Result: %s\n", json_failing_comment.dump().c_str());
+    }
+
+    std::list<int> l1 { 1, 2, 3 };
+    std::vector<int> l2 { 1, 2, 3 };
+    std::set<int> l3 { 1, 2, 3 };
+    assert(Json(l1) == Json(l2));
+    assert(Json(l2) == Json(l3));
+
+    std::map<string, string> m1 { { "k1", "v1" }, { "k2", "v2" } };
+    std::unordered_map<string, string> m2 { { "k1", "v1" }, { "k2", "v2" } };
+    assert(Json(m1) == Json(m2));
+
+    // Json literals
+    Json obj = Json::object({
+        { "k1", "v1" },
+        { "k2", 42.0 },
+        { "k3", Json::array({ "a", 123.0, true, false, nullptr }) },
+    });
+
+    std::cout << "obj: " << obj.dump() << "\n";
+
+    assert(Json("a").number_value() == 0);
+    assert(Json("a").string_value() == "a");
+    assert(Json().number_value() == 0);
+
+    assert(obj == json);
+    assert(Json(42) == Json(42.0));
+    assert(Json(42) != Json(42.1));
+
+    const string unicode_escape_test =
+        R"([ "blah\ud83d\udca9blah\ud83dblah\udca9blah\u0000blah\u1234" ])";
+
+    const char utf8[] = "blah" "\xf0\x9f\x92\xa9" "blah" "\xed\xa0\xbd" "blah"
+                        "\xed\xb2\xa9" "blah" "\0" "blah" "\xe1\x88\xb4";
+
+    Json uni = Json::parse(unicode_escape_test, err);
+    assert(uni[0].string_value().size() == (sizeof utf8) - 1);
+    assert(std::memcmp(uni[0].string_value().data(), utf8, sizeof utf8) == 0);
+
+    // Demonstrates the behavior change in Xcode 7 / Clang 3.7 described
+    // here: https://llvm.org/bugs/show_bug.cgi?id=23812
+    Json nested_array = Json::array { Json::array { 1, 2, 3 } };
+    assert(nested_array.is_array());
+    assert(nested_array.array_items().size() == 1);
+    assert(nested_array.array_items()[0].is_array());
+    assert(nested_array.array_items()[0].array_items().size() == 3);
+
+    Json my_json = Json::object {
+        { "key1", "value1" },
+        { "key2", false },
+        { "key3", Json::array { 1, 2, 3 } },
+    };
+    std::string json_str = my_json.dump();
+    printf("%s\n", json_str.c_str());
+
+    class Point {
+    public:
+        int x;
+        int y;
+        Point (int x, int y) : x(x), y(y) {}
+        Json to_json() const { return Json::array { x, y }; }
+    };
+
+    std::vector<Point> points = { { 1, 2 }, { 10, 20 }, { 100, 200 } };
+    std::string points_json = Json(points).dump();
+    printf("%s\n", points_json.c_str());
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/test-server.sh	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,73 @@
+#!/bin/bash 
+
+set -eu
+
+piperdir=../piper
+vampsdkdir=../vamp-plugin-sdk
+schemadir="$piperdir"/json/schema
+
+reqfile="/tmp/$$.req.json"
+respfile="/tmp/$$.resp.json"
+allrespfile="/tmp/$$.resp.all"
+expected="/tmp/$$.expected"
+obtained="/tmp/$$.obtained"
+trap "rm -f $reqfile $respfile $allrespfile $obtained $expected" 0
+
+validate() {
+    local file="$1"
+    local schemaname="$2"
+    jsonschema -i "$file" "$schemadir/$schemaname.json" 1>&2 && \
+        echo "validated $schemaname" 1>&2 || \
+        echo "failed to validate $schemaname" 1>&2
+}
+
+validate_request() {
+    local json="$1"
+    echo "$json" > "$reqfile"
+    validate "$reqfile" "rpcrequest"
+}
+
+validate_response() {
+    local json="$1"
+    echo "$json" > "$respfile"
+    validate "$respfile" "rpcresponse"
+}
+
+( while read request ; do
+      validate_request "$request"
+      echo "$request"
+  done |
+      bin/piper-convert request -i json -o capnp |
+      VAMP_PATH="$vampsdkdir"/examples bin/piper-vamp-server |
+      bin/piper-convert response -i capnp -o json |
+      while read response ; do
+          echo "$response" >> "$allrespfile"
+          validate_response "$response"
+      done
+) <<EOF
+{"method":"list"}
+{"method":"load","id":6,"params": {"key":"vamp-example-plugins:percussiononsets","inputSampleRate":44100,"adapterFlags":["AdaptInputDomain","AdaptBufferSize"]}}
+{"method":"configure","id":"weevil","params":{"handle":1,"configuration":{"blockSize": 8, "channelCount": 1, "parameterValues": {"sensitivity": 40, "threshold": 3}, "stepSize": 8}}}
+{"method":"process","params": {"handle": 1, "processInput": { "timestamp": {"s": 0, "n": 0}, "inputBuffers": [ [1,2,3,4,5,6,7,8] ]}}}
+{"method":"finish","params": {"handle": 1}}
+EOF
+
+# Expected output, apart from the plugin list which seems a bit
+# fragile to check here
+cat > "$expected" <<EOF
+{"id": 6, "jsonrpc": "2.0", "method": "load", "result": {"defaultConfiguration": {"blockSize": 1024, "channelCount": 1, "parameterValues": {"sensitivity": 40, "threshold": 3}, "stepSize": 1024}, "handle": 1, "staticData": {"basic": {"description": "Detect percussive note onsets by identifying broadband energy rises", "identifier": "percussiononsets", "name": "Simple Percussion Onset Detector"}, "basicOutputInfo": [{"description": "Percussive note onset locations", "identifier": "onsets", "name": "Onsets"}, {"description": "Broadband energy rise detection function", "identifier": "detectionfunction", "name": "Detection Function"}], "category": ["Time", "Onsets"], "copyright": "Code copyright 2006 Queen Mary, University of London, after Dan Barry et al 2005.  Freely redistributable (BSD license)", "inputDomain": "TimeDomain", "key": "vamp-example-plugins:percussiononsets", "maker": "Vamp SDK Example Plugins", "maxChannelCount": 1, "minChannelCount": 1, "parameters": [{"basic": {"description": "Energy rise within a frequency bin necessary to count toward broadband total", "identifier": "threshold", "name": "Energy rise threshold"}, "defaultValue": 3, "extents": {"max": 20, "min": 0}, "unit": "dB", "valueNames": []}, {"basic": {"description": "Sensitivity of peak detector applied to broadband detection function", "identifier": "sensitivity", "name": "Sensitivity"}, "defaultValue": 40, "extents": {"max": 100, "min": 0}, "unit": "%", "valueNames": []}], "programs": [], "version": 2}}}
+{"id": "weevil", "jsonrpc": "2.0", "method": "configure", "result": {"handle": 1, "outputList": [{"basic": {"description": "Percussive note onset locations", "identifier": "onsets", "name": "Onsets"}, "configured": {"binCount": 0, "binNames": [], "hasDuration": false, "sampleRate": 44100, "sampleType": "VariableSampleRate", "unit": ""}}, {"basic": {"description": "Broadband energy rise detection function", "identifier": "detectionfunction", "name": "Detection Function"}, "configured": {"binCount": 1, "binNames": [""], "hasDuration": false, "quantizeStep": 1, "sampleRate": 86.1328125, "sampleType": "FixedSampleRate", "unit": ""}}]}}
+{"jsonrpc": "2.0", "method": "process", "result": {"features": {}, "handle": 1}}
+{"jsonrpc": "2.0", "method": "finish", "result": {"features": {"detectionfunction": [{"featureValues": [0], "timestamp": {"n": 11609977, "s": 0}}]}, "handle": 1}}
+EOF
+
+# Skip plugin list
+tail -n +2 "$allrespfile" > "$obtained"
+
+echo "Checking response contents against expected contents..."
+if ! cmp "$obtained" "$expected"; then
+    diff -u1 "$obtained" "$expected"
+else
+    echo "OK"
+fi
+
--- a/test/test-vampipe-server.sh	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-#!/bin/bash 
-
-set -eu
-
-reqfile="/tmp/$$.req.json"
-respfile="/tmp/$$.resp.json"
-allrespfile="/tmp/$$.resp.all"
-expected="/tmp/$$.expected"
-obtained="/tmp/$$.obtained"
-trap "rm -f $reqfile $respfile $allrespfile $obtained $expected" 0
-
-schema=vamp-json-schema/schema
-
-validate() {
-    local file="$1"
-    local schemaname="$2"
-    jsonschema -i "$file" "$schema/$schemaname.json" 1>&2 && \
-        echo "validated $schemaname" 1>&2 || \
-        echo "failed to validate $schemaname" 1>&2
-}
-
-validate_request() {
-    local json="$1"
-    echo "$json" > "$reqfile"
-    validate "$reqfile" "rpcrequest"
-}
-
-validate_response() {
-    local json="$1"
-    echo "$json" > "$respfile"
-    validate "$respfile" "rpcresponse"
-}
-
-( while read request ; do
-      validate_request "$request"
-      echo "$request"
-  done |
-      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 |
-      while read response ; do
-          echo "$response" >> "$allrespfile"
-          validate_response "$response"
-      done
-) <<EOF
-{"method":"list"}
-{"method":"load","id":6,"params": {"key":"vamp-example-plugins:percussiononsets","inputSampleRate":44100,"adapterFlags":["AdaptInputDomain","AdaptBufferSize"]}}
-{"method":"configure","id":"weevil","params":{"handle":1,"configuration":{"blockSize": 8, "channelCount": 1, "parameterValues": {"sensitivity": 40, "threshold": 3}, "stepSize": 8}}}
-{"method":"process","params": {"handle": 1, "processInput": { "timestamp": {"s": 0, "n": 0}, "inputBuffers": [ [1,2,3,4,5,6,7,8] ]}}}
-{"method":"finish","params": {"handle": 1}}
-EOF
-
-# Expected output, apart from the plugin list which seems a bit
-# fragile to check here
-cat > "$expected" <<EOF
-{"id": 6, "jsonrpc": "2.0", "method": "load", "result": {"defaultConfiguration": {"blockSize": 1024, "channelCount": 1, "parameterValues": {"sensitivity": 40, "threshold": 3}, "stepSize": 1024}, "handle": 1, "staticData": {"basic": {"description": "Detect percussive note onsets by identifying broadband energy rises", "identifier": "percussiononsets", "name": "Simple Percussion Onset Detector"}, "basicOutputInfo": [{"description": "Percussive note onset locations", "identifier": "onsets", "name": "Onsets"}, {"description": "Broadband energy rise detection function", "identifier": "detectionfunction", "name": "Detection Function"}], "category": ["Time", "Onsets"], "copyright": "Code copyright 2006 Queen Mary, University of London, after Dan Barry et al 2005.  Freely redistributable (BSD license)", "inputDomain": "TimeDomain", "key": "vamp-example-plugins:percussiononsets", "maker": "Vamp SDK Example Plugins", "maxChannelCount": 1, "minChannelCount": 1, "parameters": [{"basic": {"description": "Energy rise within a frequency bin necessary to count toward broadband total", "identifier": "threshold", "name": "Energy rise threshold"}, "defaultValue": 3, "extents": {"max": 20, "min": 0}, "unit": "dB", "valueNames": []}, {"basic": {"description": "Sensitivity of peak detector applied to broadband detection function", "identifier": "sensitivity", "name": "Sensitivity"}, "defaultValue": 40, "extents": {"max": 100, "min": 0}, "unit": "%", "valueNames": []}], "programs": [], "version": 2}}}
-{"id": "weevil", "jsonrpc": "2.0", "method": "configure", "result": {"handle": 1, "outputList": [{"basic": {"description": "Percussive note onset locations", "identifier": "onsets", "name": "Onsets"}, "configured": {"binCount": 0, "binNames": [], "hasDuration": false, "sampleRate": 44100, "sampleType": "VariableSampleRate", "unit": ""}}, {"basic": {"description": "Broadband energy rise detection function", "identifier": "detectionfunction", "name": "Detection Function"}, "configured": {"binCount": 1, "binNames": [""], "hasDuration": false, "quantizeStep": 1, "sampleRate": 86.1328125, "sampleType": "FixedSampleRate", "unit": ""}}]}}
-{"jsonrpc": "2.0", "method": "process", "result": {"features": {}, "handle": 1}}
-{"jsonrpc": "2.0", "method": "finish", "result": {"features": {"detectionfunction": [{"featureValues": [0], "timestamp": {"n": 11609977, "s": 0}}]}, "handle": 1}}
-EOF
-
-# Skip plugin list
-tail -n +2 "$allrespfile" > "$obtained"
-
-echo "Checking response contents against expected contents..."
-if ! cmp "$obtained" "$expected"; then
-    diff -u1 "$obtained" "$expected"
-else
-    echo "OK"
-fi
-
--- a/utilities/vampipe-convert.cpp	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,611 +0,0 @@
-
-#include "VampJson.h"
-#include "VampnProto.h"
-
-#include "bits/RequestOrResponse.h"
-#include "bits/PreservingPluginHandleMapper.h"
-
-#include <iostream>
-#include <sstream>
-#include <stdexcept>
-
-using namespace std;
-using namespace json11;
-using namespace vampipe;
-
-void usage()
-{
-    string myname = "vampipe-convert";
-    cerr << "\n" << myname <<
-	": Validate and convert Vamp request and response messages\n\n"
-	"    Usage: " << myname << " [-i <informat>] [-o <outformat>] request\n"
-	"           " << myname << " [-i <informat>] [-o <outformat>] response\n\n"
-	"    where\n"
-	"       <informat>: the format to read from stdin\n"
-	"           (\"json\" or \"capnp\", default is \"json\")\n"
-	"       <outformat>: the format to convert to and write to stdout\n"
-	"           (\"json\", \"json-b64\" or \"capnp\", default is \"json\")\n"
-	"       request|response: whether messages are Vamp request or response type\n\n"
-	"If <informat> and <outformat> differ, convert from <informat> to <outformat>.\n"
-	"If <informat> and <outformat> are the same, just check validity of incoming\n"
-	"messages and pass them to output.\n\n"
-	"Specifying \"json-b64\" as output format forces base64 encoding for process and\n"
-	"feature blocks, unlike the \"json\" output format which uses text encoding.\n"
-	"The \"json\" input format accepts either.\n\n";
-
-    exit(2);
-}
-
-Json
-convertRequestJson(string input, string &err)
-{
-    Json j = Json::parse(input, err);
-    if (err != "") {
-	err = "invalid json: " + err;
-	return {};
-    }
-    if (!j.is_object()) {
-	err = "object expected at top level";
-    } else if (!j["method"].is_string()) {
-	err = "string expected for method field";
-    } else if (!j["params"].is_null() && !j["params"].is_object()) {
-	err = "object expected for params field";
-    }
-    return j;
-}
-
-Json
-convertResponseJson(string input, string &err)
-{
-    Json j = Json::parse(input, err);
-    if (err != "") {
-	err = "invalid json: " + err;
-	return {};
-    }
-    if (!j.is_object()) {
-	err = "object expected at top level";
-    } else {
-        if (!j["result"].is_object()) {
-            if (!j["error"].is_object()) {
-                err = "expected either result or error object";
-            }
-        }
-    }
-    return j;
-}
-
-//!!! Lots of potential for refactoring the conversion classes based
-//!!! on the common matter in the following eight functions...
-
-PreservingPluginHandleMapper mapper;
-
-static RequestOrResponse::RpcId
-readJsonId(const Json &j)
-{
-    RequestOrResponse::RpcId id;
-
-    if (j["id"].is_number()) {
-        id.type = RequestOrResponse::RpcId::Number;
-        id.number = j["id"].number_value();
-    } else if (j["id"].is_string()) {
-        id.type = RequestOrResponse::RpcId::Tag;
-        id.tag = j["id"].string_value();
-    } else {
-        id.type = RequestOrResponse::RpcId::Absent;
-    }
-
-    return id;
-}
-
-static Json
-writeJsonId(const RequestOrResponse::RpcId &id)
-{
-    if (id.type == RequestOrResponse::RpcId::Number) {
-        return id.number;
-    } else if (id.type == RequestOrResponse::RpcId::Tag) {
-        return id.tag;
-    } else {
-        return Json();
-    }
-}
-
-template <typename Reader>
-static RequestOrResponse::RpcId
-readCapnpId(const Reader &r)
-{
-    int number;
-    string tag;
-    switch (r.getId().which()) {
-    case RpcRequest::Id::Which::NUMBER:
-        number = r.getId().getNumber();
-        return { RequestOrResponse::RpcId::Number, number, "" };
-    case RpcRequest::Id::Which::TAG:
-        tag = r.getId().getTag();
-        return { RequestOrResponse::RpcId::Tag, 0, tag };
-    case RpcRequest::Id::Which::NONE:
-        return { RequestOrResponse::RpcId::Absent, 0, "" };
-    }
-    return {};
-}
-
-template <typename Builder>
-static void
-buildCapnpId(Builder &b, const RequestOrResponse::RpcId &id)
-{
-    switch (id.type) {
-    case RequestOrResponse::RpcId::Number:
-        b.getId().setNumber(id.number);
-        break;
-    case RequestOrResponse::RpcId::Tag:
-        b.getId().setTag(id.tag);
-        break;
-    case RequestOrResponse::RpcId::Absent:
-        b.getId().setNone();
-        break;
-    }
-}
-
-RequestOrResponse
-readRequestJson(string &err)
-{
-    RequestOrResponse rr;
-    rr.direction = RequestOrResponse::Request;
-
-    string input;
-    if (!getline(cin, input)) {
-	// the EOF case, not actually an error
-	rr.type = RRType::NotValid;
-	return rr;
-    }
-    
-    Json j = convertRequestJson(input, err);
-    if (err != "") return {};
-
-    rr.type = VampJson::getRequestResponseType(j, err);
-    if (err != "") return {};
-
-    rr.id = readJsonId(j);
-
-    VampJson::BufferSerialisation serialisation =
-        VampJson::BufferSerialisation::Array;
-
-    switch (rr.type) {
-
-    case RRType::List:
-	VampJson::toRpcRequest_List(j, err); // type check only
-	break;
-    case RRType::Load:
-	rr.loadRequest = VampJson::toRpcRequest_Load(j, err);
-	break;
-    case RRType::Configure:
-	rr.configurationRequest = VampJson::toRpcRequest_Configure(j, mapper, err);
-	break;
-    case RRType::Process:
-	rr.processRequest = VampJson::toRpcRequest_Process(j, mapper, serialisation, err);
-	break;
-    case RRType::Finish:
-	rr.finishRequest = VampJson::toRpcRequest_Finish(j, mapper, err);
-	break;
-    case RRType::NotValid:
-	break;
-    }
-
-    return rr;
-}
-
-void
-writeRequestJson(RequestOrResponse &rr, bool useBase64)
-{
-    Json j;
-
-    VampJson::BufferSerialisation serialisation =
-        (useBase64 ?
-         VampJson::BufferSerialisation::Base64 :
-         VampJson::BufferSerialisation::Array);
-
-    Json id = writeJsonId(rr.id);
-    
-    switch (rr.type) {
-
-    case RRType::List:
-	j = VampJson::fromRpcRequest_List(id);
-	break;
-    case RRType::Load:
-	j = VampJson::fromRpcRequest_Load(rr.loadRequest, id);
-	break;
-    case RRType::Configure:
-	j = VampJson::fromRpcRequest_Configure(rr.configurationRequest, mapper, id);
-	break;
-    case RRType::Process:
-	j = VampJson::fromRpcRequest_Process
-	    (rr.processRequest, mapper, serialisation, id);
-	break;
-    case RRType::Finish:
-	j = VampJson::fromRpcRequest_Finish(rr.finishRequest, mapper, id);
-	break;
-    case RRType::NotValid:
-	break;
-    }
-
-    cout << j.dump() << endl;
-}
-
-RequestOrResponse
-readResponseJson(string &err)
-{
-    RequestOrResponse rr;
-    rr.direction = RequestOrResponse::Response;
-
-    string input;
-    if (!getline(cin, input)) {
-	// the EOF case, not actually an error
-	rr.type = RRType::NotValid;
-	return rr;
-    }
-
-    Json j = convertResponseJson(input, err);
-    if (err != "") return {};
-
-    rr.type = VampJson::getRequestResponseType(j, err);
-    if (err != "") return {};
-
-    rr.id = readJsonId(j);
-    
-    VampJson::BufferSerialisation serialisation =
-        VampJson::BufferSerialisation::Array;
-
-    rr.success = j["success"].bool_value();
-    rr.errorText = j["errorText"].string_value();
-
-    switch (rr.type) {
-
-    case RRType::List:
-	rr.listResponse = VampJson::toRpcResponse_List(j, err);
-	break;
-    case RRType::Load:
-	rr.loadResponse = VampJson::toRpcResponse_Load(j, mapper, err);
-	break;
-    case RRType::Configure:
-	rr.configurationResponse = VampJson::toRpcResponse_Configure(j, mapper, err);
-	break;
-    case RRType::Process: 
-	rr.processResponse = VampJson::toRpcResponse_Process(j, mapper, serialisation, err);
-	break;
-    case RRType::Finish:
-	rr.finishResponse = VampJson::toRpcResponse_Finish(j, mapper, serialisation, err);
-	break;
-    case RRType::NotValid:
-	break;
-    }
-
-    return rr;
-}
-
-void
-writeResponseJson(RequestOrResponse &rr, bool useBase64)
-{
-    Json j;
-
-    VampJson::BufferSerialisation serialisation =
-        (useBase64 ?
-         VampJson::BufferSerialisation::Base64 :
-         VampJson::BufferSerialisation::Array);
-
-    Json id = writeJsonId(rr.id);
-
-    if (!rr.success) {
-
-	j = VampJson::fromError(rr.errorText, rr.type, id);
-
-    } else {
-    
-	switch (rr.type) {
-
-	case RRType::List:
-	    j = VampJson::fromRpcResponse_List(rr.listResponse, id);
-	    break;
-	case RRType::Load:
-	    j = VampJson::fromRpcResponse_Load(rr.loadResponse, mapper, id);
-	    break;
-	case RRType::Configure:
-	    j = VampJson::fromRpcResponse_Configure(rr.configurationResponse,
-                                                    mapper, id);
-	    break;
-	case RRType::Process:
-	    j = VampJson::fromRpcResponse_Process
-		(rr.processResponse, mapper, serialisation, id);
-	    break;
-	case RRType::Finish:
-	    j = VampJson::fromRpcResponse_Finish
-		(rr.finishResponse, mapper, serialisation, id);
-	    break;
-	case RRType::NotValid:
-	    break;
-	}
-    }
-    
-    cout << j.dump() << endl;
-}
-
-RequestOrResponse
-readRequestCapnp(kj::BufferedInputStreamWrapper &buffered)
-{
-    RequestOrResponse rr;
-    rr.direction = RequestOrResponse::Request;
-
-    ::capnp::InputStreamMessageReader message(buffered);
-    RpcRequest::Reader reader = message.getRoot<RpcRequest>();
-    
-    rr.type = VampnProto::getRequestResponseType(reader);
-    rr.id = readCapnpId(reader);
-
-    switch (rr.type) {
-
-    case RRType::List:
-	VampnProto::readRpcRequest_List(reader); // type check only
-	break;
-    case RRType::Load:
-	VampnProto::readRpcRequest_Load(rr.loadRequest, reader);
-	break;
-    case RRType::Configure:
-	VampnProto::readRpcRequest_Configure(rr.configurationRequest,
-					      reader, mapper);
-	break;
-    case RRType::Process:
-	VampnProto::readRpcRequest_Process(rr.processRequest, reader, mapper);
-	break;
-    case RRType::Finish:
-	VampnProto::readRpcRequest_Finish(rr.finishRequest, reader, mapper);
-	break;
-    case RRType::NotValid:
-	break;
-    }
-
-    return rr;
-}
-
-void
-writeRequestCapnp(RequestOrResponse &rr)
-{
-    ::capnp::MallocMessageBuilder message;
-    RpcRequest::Builder builder = message.initRoot<RpcRequest>();
-
-    buildCapnpId(builder, rr.id);
-    
-    switch (rr.type) {
-
-    case RRType::List:
-	VampnProto::buildRpcRequest_List(builder);
-	break;
-    case RRType::Load:
-	VampnProto::buildRpcRequest_Load(builder, rr.loadRequest);
-	break;
-    case RRType::Configure:
-	VampnProto::buildRpcRequest_Configure(builder,
-                                              rr.configurationRequest, mapper);
-	break;
-    case RRType::Process:
-	VampnProto::buildRpcRequest_Process(builder, rr.processRequest, mapper);
-	break;
-    case RRType::Finish:
-	VampnProto::buildRpcRequest_Finish(builder, rr.finishRequest, mapper);
-	break;
-    case RRType::NotValid:
-	break;
-    }
-
-    writeMessageToFd(1, message);
-}
-
-RequestOrResponse
-readResponseCapnp(kj::BufferedInputStreamWrapper &buffered)
-{
-    RequestOrResponse rr;
-    rr.direction = RequestOrResponse::Response;
-
-    ::capnp::InputStreamMessageReader message(buffered);
-    RpcResponse::Reader reader = message.getRoot<RpcResponse>();
-    
-    rr.type = VampnProto::getRequestResponseType(reader);
-    rr.success = true;
-    rr.errorText = "";
-    rr.id = readCapnpId(reader);
-    int errorCode = 0;
-
-    switch (rr.type) {
-
-    case RRType::List:
-	VampnProto::readRpcResponse_List(rr.listResponse, reader);
-	break;
-    case RRType::Load:
-	VampnProto::readRpcResponse_Load(rr.loadResponse, reader, mapper);
-	break;
-    case RRType::Configure:
-	VampnProto::readRpcResponse_Configure(rr.configurationResponse,
-					       reader, mapper);
-	break;
-    case RRType::Process:
-	VampnProto::readRpcResponse_Process(rr.processResponse, reader, mapper);
-	break;
-    case RRType::Finish:
-	VampnProto::readRpcResponse_Finish(rr.finishResponse, reader, mapper);
-	break;
-    case RRType::NotValid:
-        // error
-        rr.success = false;
-        VampnProto::readRpcResponse_Error(errorCode, rr.errorText, reader);
-	break;
-    }
-
-    return rr;
-}
-
-void
-writeResponseCapnp(RequestOrResponse &rr)
-{
-    ::capnp::MallocMessageBuilder message;
-    RpcResponse::Builder builder = message.initRoot<RpcResponse>();
-
-    buildCapnpId(builder, rr.id);
-
-    if (!rr.success) {
-
-	VampnProto::buildRpcResponse_Error(builder, rr.errorText, rr.type);
-
-    } else {
-	
-	switch (rr.type) {
-
-	case RRType::List:
-	    VampnProto::buildRpcResponse_List(builder, rr.listResponse);
-	    break;
-	case RRType::Load:
-	    VampnProto::buildRpcResponse_Load(builder, rr.loadResponse, mapper);
-	    break;
-	case RRType::Configure:
-	    VampnProto::buildRpcResponse_Configure(builder, rr.configurationResponse, mapper);
-	    break;
-	case RRType::Process:
-	    VampnProto::buildRpcResponse_Process(builder, rr.processResponse, mapper);
-	    break;
-	case RRType::Finish:
-	    VampnProto::buildRpcResponse_Finish(builder, rr.finishResponse, mapper);
-	    break;
-	case RRType::NotValid:
-	    break;
-	}
-    }
-    
-    writeMessageToFd(1, message);
-}
-
-RequestOrResponse
-readInputJson(RequestOrResponse::Direction direction, string &err)
-{
-    if (direction == RequestOrResponse::Request) {
-	return readRequestJson(err);
-    } else {
-	return readResponseJson(err);
-    }
-}
-
-RequestOrResponse
-readInputCapnp(RequestOrResponse::Direction direction)
-{
-    static kj::FdInputStream stream(0); // stdin
-    static kj::BufferedInputStreamWrapper buffered(stream);
-
-    if (buffered.tryGetReadBuffer() == nullptr) {
-	return {};
-    }
-    
-    if (direction == RequestOrResponse::Request) {
-	return readRequestCapnp(buffered);
-    } else {
-	return readResponseCapnp(buffered);
-    }
-}
-
-RequestOrResponse
-readInput(string format, RequestOrResponse::Direction direction)
-{
-    if (format == "json") {
-	string err;
-	auto result = readInputJson(direction, err);
-	if (err != "") throw runtime_error(err);
-	else return result;
-    } else if (format == "capnp") {
-	return readInputCapnp(direction);
-    } else {
-	throw runtime_error("unknown input format \"" + format + "\"");
-    }
-}
-
-void
-writeOutput(string format, RequestOrResponse &rr)
-{
-    if (format == "json") {
-	if (rr.direction == RequestOrResponse::Request) {
-	    writeRequestJson(rr, false);
-	} else {
-	    writeResponseJson(rr, false);
-	}
-    } else if (format == "json-b64") {
-	if (rr.direction == RequestOrResponse::Request) {
-	    writeRequestJson(rr, true);
-	} else {
-	    writeResponseJson(rr, true);
-	}
-    } else if (format == "capnp") {
-	if (rr.direction == RequestOrResponse::Request) {
-	    writeRequestCapnp(rr);
-	} else {
-	    writeResponseCapnp(rr);
-	}
-    } else {
-	throw runtime_error("unknown output format \"" + format + "\"");
-    }
-}
-
-int main(int argc, char **argv)
-{
-    if (argc < 2) {
-	usage();
-    }
-
-    string informat = "json", outformat = "json";
-    RequestOrResponse::Direction direction = RequestOrResponse::Request;
-    bool haveDirection = false;
-    
-    for (int i = 1; i < argc; ++i) {
-
-	string arg = argv[i];
-	bool final = (i + 1 == argc);
-	
-	if (arg == "-i") {
-	    if (final) usage();
-	    else informat = argv[++i];
-
-	} else if (arg == "-o") {
-	    if (final) usage();
-	    else outformat = argv[++i];
-
-	} else if (arg == "request") {
-	    direction = RequestOrResponse::Request;
-	    haveDirection = true;
-
-	} else if (arg == "response") {
-	    direction = RequestOrResponse::Response;
-	    haveDirection = true;
-	    
-	} else {
-	    usage();
-	}
-    }
-
-    if (informat == "" || outformat == "" || !haveDirection) {
-	usage();
-    }
-
-    while (true) {
-
-	try {
-
-	    RequestOrResponse rr = readInput(informat, direction);
-
-	    // NotValid without an exception indicates EOF:
-	    if (rr.type == RRType::NotValid) break;
-
-	    writeOutput(outformat, rr);
-	    
-	} catch (std::exception &e) {
-
-	    cerr << "Error: " << e.what() << endl;
-	    exit(1);
-	}
-    }
-
-    exit(0);
-}
-
-
--- a/utilities/vampipe-server.cpp	Mon Oct 10 15:31:44 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,304 +0,0 @@
-
-#include "VampnProto.h"
-
-#include "bits/RequestOrResponse.h"
-#include "bits/CountingPluginHandleMapper.h"
-
-#include <iostream>
-#include <sstream>
-#include <stdexcept>
-
-#include <map>
-#include <set>
-
-using namespace std;
-using namespace vampipe;
-using namespace Vamp;
-using namespace Vamp::HostExt;
-
-void usage()
-{
-    string myname = "vampipe-server";
-    cerr << "\n" << myname <<
-	": Load and run Vamp plugins in response to messages from stdin\n\n"
-	"    Usage: " << myname << "\n\n"
-	"Expects Vamp request messages in Cap'n Proto packed format on stdin,\n"
-	"and writes Vamp response messages in the same format to stdout.\n\n";
-
-    exit(2);
-}
-
-static CountingPluginHandleMapper mapper;
-
-static RequestOrResponse::RpcId readId(const RpcRequest::Reader &r)
-{
-    int number;
-    string tag;
-    switch (r.getId().which()) {
-    case RpcRequest::Id::Which::NUMBER:
-        number = r.getId().getNumber();
-        return { RequestOrResponse::RpcId::Number, number, "" };
-    case RpcRequest::Id::Which::TAG:
-        tag = r.getId().getTag();
-        return { RequestOrResponse::RpcId::Tag, 0, tag };
-    case RpcRequest::Id::Which::NONE:
-        return { RequestOrResponse::RpcId::Absent, 0, "" };
-    }
-    return {};
-}
-
-static void buildId(RpcResponse::Builder &b, const RequestOrResponse::RpcId &id)
-{
-    switch (id.type) {
-    case RequestOrResponse::RpcId::Number:
-        b.getId().setNumber(id.number);
-        break;
-    case RequestOrResponse::RpcId::Tag:
-        b.getId().setTag(id.tag);
-        break;
-    case RequestOrResponse::RpcId::Absent:
-        b.getId().setNone();
-        break;
-    }
-}
-
-RequestOrResponse
-readRequestCapnp()
-{
-    RequestOrResponse rr;
-    rr.direction = RequestOrResponse::Request;
-
-    static kj::FdInputStream stream(0); // stdin
-    static kj::BufferedInputStreamWrapper buffered(stream);
-
-    if (buffered.tryGetReadBuffer() == nullptr) {
-	rr.type = RRType::NotValid;
-	return rr;
-    }
-
-    ::capnp::InputStreamMessageReader message(buffered);
-    RpcRequest::Reader reader = message.getRoot<RpcRequest>();
-    
-    rr.type = VampnProto::getRequestResponseType(reader);
-    rr.id = readId(reader);
-
-    switch (rr.type) {
-
-    case RRType::List:
-	VampnProto::readRpcRequest_List(reader); // type check only
-	break;
-    case RRType::Load:
-	VampnProto::readRpcRequest_Load(rr.loadRequest, reader);
-	break;
-    case RRType::Configure:
-	VampnProto::readRpcRequest_Configure(rr.configurationRequest,
-					      reader, mapper);
-	break;
-    case RRType::Process:
-	VampnProto::readRpcRequest_Process(rr.processRequest, reader, mapper);
-	break;
-    case RRType::Finish:
-	VampnProto::readRpcRequest_Finish(rr.finishRequest, reader, mapper);
-	break;
-    case RRType::NotValid:
-	break;
-    }
-
-    return rr;
-}
-
-void
-writeResponseCapnp(RequestOrResponse &rr)
-{
-    ::capnp::MallocMessageBuilder message;
-    RpcResponse::Builder builder = message.initRoot<RpcResponse>();
-
-    buildId(builder, rr.id);
-    
-    if (!rr.success) {
-
-	VampnProto::buildRpcResponse_Error(builder, rr.errorText, rr.type);
-
-    } else {
-	
-	switch (rr.type) {
-
-	case RRType::List:
-	    VampnProto::buildRpcResponse_List(builder, rr.listResponse);
-	    break;
-	case RRType::Load:
-	    VampnProto::buildRpcResponse_Load(builder, rr.loadResponse, mapper);
-	    break;
-	case RRType::Configure:
-	    VampnProto::buildRpcResponse_Configure(builder, rr.configurationResponse, mapper);
-	    break;
-	case RRType::Process:
-	    VampnProto::buildRpcResponse_Process(builder, rr.processResponse, mapper);
-	    break;
-	case RRType::Finish:
-	    VampnProto::buildRpcResponse_Finish(builder, rr.finishResponse, mapper);
-	    break;
-	case RRType::NotValid:
-	    break;
-	}
-    }
-    
-    writeMessageToFd(1, message);
-}
-
-void
-writeExceptionCapnp(const std::exception &e, RRType type)
-{
-    ::capnp::MallocMessageBuilder message;
-    RpcResponse::Builder builder = message.initRoot<RpcResponse>();
-    VampnProto::buildRpcResponse_Exception(builder, e, type);
-    
-    writeMessageToFd(1, message);
-}
-
-RequestOrResponse
-handleRequest(const RequestOrResponse &request)
-{
-    RequestOrResponse response;
-    response.direction = RequestOrResponse::Response;
-    response.type = request.type;
-
-    auto loader = PluginLoader::getInstance();
-
-    switch (request.type) {
-
-    case RRType::List:
-	response.listResponse = loader->listPluginData();
-	response.success = true;
-	break;
-
-    case RRType::Load:
-	response.loadResponse = loader->loadPlugin(request.loadRequest);
-	if (response.loadResponse.plugin != nullptr) {
-	    mapper.addPlugin(response.loadResponse.plugin);
-	    response.success = true;
-	}
-	break;
-	
-    case RRType::Configure:
-    {
-	auto &creq = request.configurationRequest;
-	auto h = mapper.pluginToHandle(creq.plugin);
-	if (mapper.isConfigured(h)) {
-	    throw runtime_error("plugin has already been configured");
-	}
-
-	response.configurationResponse = loader->configurePlugin(creq);
-	
-	if (!response.configurationResponse.outputs.empty()) {
-	    mapper.markConfigured
-		(h, creq.configuration.channelCount, creq.configuration.blockSize);
-	    response.success = true;
-	}
-	break;
-    }
-
-    case RRType::Process:
-    {
-	auto &preq = request.processRequest;
-	auto h = mapper.pluginToHandle(preq.plugin);
-	if (!mapper.isConfigured(h)) {
-	    throw runtime_error("plugin has not been configured");
-	}
-
-	int channels = int(preq.inputBuffers.size());
-	if (channels != mapper.getChannelCount(h)) {
-	    throw runtime_error("wrong number of channels supplied to process");
-	}
-		
-	const float **fbuffers = new const float *[channels];
-	for (int i = 0; i < channels; ++i) {
-	    if (int(preq.inputBuffers[i].size()) != mapper.getBlockSize(h)) {
-		delete[] fbuffers;
-		throw runtime_error("wrong block size supplied to process");
-	    }
-	    fbuffers[i] = preq.inputBuffers[i].data();
-	}
-
-	response.processResponse.plugin = preq.plugin;
-	response.processResponse.features =
-	    preq.plugin->process(fbuffers, preq.timestamp);
-	response.success = true;
-
-	delete[] fbuffers;
-	break;
-    }
-
-    case RRType::Finish:
-    {
-	response.finishResponse.plugin = request.finishRequest.plugin;
-	response.finishResponse.features =
-	    request.finishRequest.plugin->getRemainingFeatures();
-
-	// 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;
-    }
-
-    case RRType::NotValid:
-	break;
-    }
-    
-    return response;
-}
-
-int main(int argc, char **argv)
-{
-    if (argc != 1) {
-	usage();
-    }
-
-    while (true) {
-
-	RequestOrResponse request;
-	
-	try {
-
-	    request = readRequestCapnp();
-
-	    cerr << "vampipe-server: request received, of type "
-		 << int(request.type)
-		 << endl;
-	    
-	    // NotValid without an exception indicates EOF:
-	    if (request.type == RRType::NotValid) {
-		cerr << "vampipe-server: eof reached" << endl;
-		break;
-	    }
-
-	    RequestOrResponse response = handleRequest(request);
-            response.id = request.id;
-
-	    cerr << "vampipe-server: request handled, writing response"
-		 << endl;
-	    
-	    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);
-	}
-    }
-
-    exit(0);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-capnp/VampnProto.h	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,1001 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Piper C++
+
+    Centre for Digital Music, Queen Mary, University of London.
+    Copyright 2015-2016 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.
+*/
+
+#include "piper.capnp.h"
+
+#include <capnp/message.h>
+#include <capnp/serialize-packed.h>
+
+#include <vamp-hostsdk/Plugin.h>
+#include <vamp-hostsdk/PluginLoader.h>
+#include <vamp-hostsdk/PluginStaticData.h>
+
+#include "vamp-support/PluginHandleMapper.h"
+#include "vamp-support/PluginOutputIdMapper.h"
+#include "vamp-support/RequestResponseType.h"
+
+namespace piper
+{
+
+/**
+ * Convert the structures laid out in the Vamp SDK classes into Cap'n
+ * Proto structures (and back again).
+ * 
+ * At least some of this will be necessary for any implementation
+ * using Cap'n Proto that uses the C++ Vamp SDK to provide its
+ * reference structures. An implementation could alternatively use the
+ * Cap'n Proto structures directly, and interact with Vamp plugins
+ * using the Vamp C API, without using the C++ Vamp SDK classes at
+ * all. That would avoid a lot of copying (in Cap'n Proto style).
+ */
+class VampnProto
+{
+public:
+    typedef ::capnp::MallocMessageBuilder MsgBuilder;
+
+    template <typename T, typename B>
+    static void buildBasicDescriptor(B &basic, const T &t) {
+        basic.setIdentifier(t.identifier);
+        basic.setName(t.name);
+        basic.setDescription(t.description);
+    }
+
+    template <typename T, typename B>
+    static void readBasicDescriptor(T &t, const B &basic) {
+        t.identifier = basic.getIdentifier();
+        t.name = basic.getName();
+        t.description = basic.getDescription();
+    }
+
+    template <typename T, typename M>
+    static void buildValueExtents(M &m, const T &t) {
+        m.setMinValue(t.minValue);
+        m.setMaxValue(t.maxValue);
+    }
+
+    template <typename T, typename M>
+    static void readValueExtents(T &t, const M &m) {
+        t.minValue = m.getMinValue();
+        t.maxValue = m.getMaxValue();
+    }
+
+    static void buildRealTime(RealTime::Builder &b, const Vamp::RealTime &t) {
+        b.setSec(t.sec);
+        b.setNsec(t.nsec);
+    }
+
+    static void readRealTime(Vamp::RealTime &t, const RealTime::Reader &r) {
+        t.sec = r.getSec();
+        t.nsec = r.getNsec();
+    }
+
+    static SampleType
+    fromSampleType(Vamp::Plugin::OutputDescriptor::SampleType t) {
+        switch (t) {
+        case Vamp::Plugin::OutputDescriptor::OneSamplePerStep:
+            return SampleType::ONE_SAMPLE_PER_STEP;
+        case Vamp::Plugin::OutputDescriptor::FixedSampleRate:
+            return SampleType::FIXED_SAMPLE_RATE;
+        case Vamp::Plugin::OutputDescriptor::VariableSampleRate:
+            return SampleType::VARIABLE_SAMPLE_RATE;
+        }
+        throw std::logic_error("unexpected Vamp SampleType enum value");
+    }
+
+    static Vamp::Plugin::OutputDescriptor::SampleType
+    toSampleType(SampleType t) {
+        switch (t) {
+        case SampleType::ONE_SAMPLE_PER_STEP:
+            return Vamp::Plugin::OutputDescriptor::OneSamplePerStep;
+        case SampleType::FIXED_SAMPLE_RATE:
+            return Vamp::Plugin::OutputDescriptor::FixedSampleRate;
+        case SampleType::VARIABLE_SAMPLE_RATE:
+            return Vamp::Plugin::OutputDescriptor::VariableSampleRate;
+        }
+        throw std::logic_error("unexpected Capnp SampleType enum value");
+    }
+
+    static void
+    buildConfiguredOutputDescriptor(ConfiguredOutputDescriptor::Builder &b,
+                                    const Vamp::Plugin::OutputDescriptor &od) {
+
+        b.setUnit(od.unit);
+
+        b.setSampleType(fromSampleType(od.sampleType));
+        b.setSampleRate(od.sampleRate);
+        b.setHasDuration(od.hasDuration);
+
+        b.setHasFixedBinCount(od.hasFixedBinCount);
+        if (od.hasFixedBinCount) {
+            b.setBinCount(od.binCount);
+            if (od.binNames.size() > 0) {
+                auto binNames = b.initBinNames(od.binNames.size());
+                for (size_t i = 0; i < od.binNames.size(); ++i) {
+                    binNames.set(i, od.binNames[i]);
+                }
+            }
+        }
+
+        b.setHasKnownExtents(od.hasKnownExtents);
+        if (od.hasKnownExtents) {
+            buildValueExtents(b, od);
+        }
+
+        b.setIsQuantized(od.isQuantized);
+        if (od.isQuantized) {
+            b.setQuantizeStep(od.quantizeStep);
+        }
+    }
+
+    static void
+    buildOutputDescriptor(OutputDescriptor::Builder &b,
+                          const Vamp::Plugin::OutputDescriptor &od) {
+
+        auto basic = b.initBasic();
+        buildBasicDescriptor(basic, od);
+
+        auto configured = b.initConfigured();
+        buildConfiguredOutputDescriptor(configured, od);
+    }
+    
+    static void
+    readConfiguredOutputDescriptor(Vamp::Plugin::OutputDescriptor &od,
+                                   const ConfiguredOutputDescriptor::Reader &r) {
+
+        od.unit = r.getUnit();
+
+        od.sampleType = toSampleType(r.getSampleType());
+        od.sampleRate = r.getSampleRate();
+        od.hasDuration = r.getHasDuration();
+
+        od.hasFixedBinCount = r.getHasFixedBinCount();
+        if (od.hasFixedBinCount) {
+            od.binCount = r.getBinCount();
+            od.binNames.clear();
+            auto nn = r.getBinNames();
+            for (const auto &n: nn) {
+                od.binNames.push_back(n);
+            }
+        }
+
+        od.hasKnownExtents = r.getHasKnownExtents();
+        if (od.hasKnownExtents) {
+            readValueExtents(od, r);
+        }
+
+        od.isQuantized = r.getIsQuantized();
+        if (od.isQuantized) {
+            od.quantizeStep = r.getQuantizeStep();
+        }
+    }
+
+    static void
+    readOutputDescriptor(Vamp::Plugin::OutputDescriptor &od,
+                         const OutputDescriptor::Reader &r) {
+
+        readBasicDescriptor(od, r.getBasic());
+        readConfiguredOutputDescriptor(od, r.getConfigured());
+    }
+
+    static void
+    buildParameterDescriptor(ParameterDescriptor::Builder &b,
+                             const Vamp::Plugin::ParameterDescriptor &pd) {
+
+        auto basic = b.initBasic();
+        buildBasicDescriptor(basic, pd);
+
+        b.setUnit(pd.unit);
+
+        buildValueExtents(b, pd);
+
+        b.setDefaultValue(pd.defaultValue);
+
+        b.setIsQuantized(pd.isQuantized);
+        if (pd.isQuantized) {
+            b.setQuantizeStep(pd.quantizeStep);
+        }
+        
+        if (pd.valueNames.size() > 0) {
+            auto valueNames = b.initValueNames(pd.valueNames.size());
+            for (size_t i = 0; i < pd.valueNames.size(); ++i) {
+                valueNames.set(i, pd.valueNames[i]);
+            }
+        }
+    }
+
+    static void
+    readParameterDescriptor(Vamp::Plugin::ParameterDescriptor &pd,
+                            const ParameterDescriptor::Reader &r) {
+
+        readBasicDescriptor(pd, r.getBasic());
+
+        pd.unit = r.getUnit();
+
+        readValueExtents(pd, r);
+
+        pd.defaultValue = r.getDefaultValue();
+
+        pd.isQuantized = r.getIsQuantized();
+        if (pd.isQuantized) {
+            pd.quantizeStep = r.getQuantizeStep();
+        }
+
+        pd.valueNames.clear();
+        auto nn = r.getValueNames();
+        for (const auto &n: nn) {
+            pd.valueNames.push_back(n);
+        }
+    }
+    
+    static void
+    buildFeature(Feature::Builder &b,
+                 const Vamp::Plugin::Feature &f) {
+
+        b.setHasTimestamp(f.hasTimestamp);
+        if (f.hasTimestamp) {
+            auto timestamp = b.initTimestamp();
+            buildRealTime(timestamp, f.timestamp);
+        }
+
+        b.setHasDuration(f.hasDuration);
+        if (f.hasDuration) {
+            auto duration = b.initDuration();
+            buildRealTime(duration, f.duration);
+        }
+
+        b.setLabel(f.label);
+
+        if (f.values.size() > 0) {
+            auto values = b.initFeatureValues(f.values.size());
+            for (size_t i = 0; i < f.values.size(); ++i) {
+                values.set(i, f.values[i]);
+            }
+        }
+    }
+
+    static void
+    readFeature(Vamp::Plugin::Feature &f,
+                const Feature::Reader &r) {
+
+        f.hasTimestamp = r.getHasTimestamp();
+        if (f.hasTimestamp) {
+            readRealTime(f.timestamp, r.getTimestamp());
+        }
+
+        f.hasDuration = r.getHasDuration();
+        if (f.hasDuration) {
+            readRealTime(f.duration, r.getDuration());
+        }
+
+        f.label = r.getLabel();
+
+        f.values.clear();
+        auto vv = r.getFeatureValues();
+        for (auto v: vv) {
+            f.values.push_back(v);
+        }
+    }
+    
+    static void
+    buildFeatureSet(FeatureSet::Builder &b,
+                    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(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];
+                buildFeature(feature, fsi.second[j]);
+            }
+            ++ix;
+        }
+    }
+
+    static void
+    readFeatureSet(Vamp::Plugin::FeatureSet &fs,
+                   const FeatureSet::Reader &r,
+                   const PluginOutputIdMapper &omapper) {
+
+        fs.clear();
+        auto pp = r.getFeaturePairs();
+        for (const auto &p: pp) {
+            Vamp::Plugin::FeatureList vfl;
+            auto ff = p.getFeatures();
+            for (const auto &f: ff) {
+                Vamp::Plugin::Feature vf;
+                readFeature(vf, f);
+                vfl.push_back(vf);
+            }
+            fs[omapper.idToIndex(p.getOutput())] = vfl;
+        }
+    }
+    
+    static InputDomain
+    fromInputDomain(Vamp::Plugin::InputDomain d) {
+        switch(d) {
+        case Vamp::Plugin::TimeDomain:
+            return InputDomain::TIME_DOMAIN;
+        case Vamp::Plugin::FrequencyDomain:
+            return InputDomain::FREQUENCY_DOMAIN;
+        default:
+            throw std::logic_error("unexpected Vamp InputDomain enum value");
+        }
+    }
+
+    static Vamp::Plugin::InputDomain
+    toInputDomain(InputDomain d) {
+        switch(d) {
+        case InputDomain::TIME_DOMAIN:
+            return Vamp::Plugin::TimeDomain;
+        case InputDomain::FREQUENCY_DOMAIN:
+            return Vamp::Plugin::FrequencyDomain;
+        default:
+            throw std::logic_error("unexpected Capnp InputDomain enum value");
+        }
+    }
+    
+    static void
+    buildExtractorStaticData(ExtractorStaticData::Builder &b,
+                          const Vamp::HostExt::PluginStaticData &d) {
+
+        b.setKey(d.pluginKey);
+
+        auto basic = b.initBasic();
+        buildBasicDescriptor(basic, d.basic);
+
+        b.setMaker(d.maker);
+        b.setCopyright(d.copyright);
+        b.setVersion(d.pluginVersion);
+
+        auto clist = b.initCategory(d.category.size());
+        for (size_t i = 0; i < d.category.size(); ++i) {
+            clist.set(i, d.category[i]);
+        }
+
+        b.setMinChannelCount(d.minChannelCount);
+        b.setMaxChannelCount(d.maxChannelCount);
+
+        const auto &vparams = d.parameters;
+        auto plist = b.initParameters(vparams.size());
+        for (size_t i = 0; i < vparams.size(); ++i) {
+            auto pd = plist[i];
+            buildParameterDescriptor(pd, vparams[i]);
+        }
+        
+        const auto &vprogs = d.programs;
+        auto pglist = b.initPrograms(vprogs.size());
+        for (size_t i = 0; i < vprogs.size(); ++i) {
+            pglist.set(i, vprogs[i]);
+        }
+
+        b.setInputDomain(fromInputDomain(d.inputDomain));
+
+        const auto &vouts = d.basicOutputInfo;
+        auto olist = b.initBasicOutputInfo(vouts.size());
+        for (size_t i = 0; i < vouts.size(); ++i) {
+            auto od = olist[i];
+            buildBasicDescriptor(od, vouts[i]);
+        }
+    }
+
+    static void
+    readExtractorStaticData(Vamp::HostExt::PluginStaticData &d,
+                            const ExtractorStaticData::Reader &r) {
+        
+        d.pluginKey = r.getKey();
+
+        readBasicDescriptor(d.basic, r.getBasic());
+
+        d.maker = r.getMaker();
+        d.copyright = r.getCopyright();
+        d.pluginVersion = r.getVersion();
+
+        d.category.clear();
+        auto cc = r.getCategory();
+        for (auto c: cc) {
+            d.category.push_back(c);
+        }
+
+        d.minChannelCount = r.getMinChannelCount();
+        d.maxChannelCount = r.getMaxChannelCount();
+
+        d.parameters.clear();
+        auto pp = r.getParameters();
+        for (auto p: pp) {
+            Vamp::Plugin::ParameterDescriptor pd;
+            readParameterDescriptor(pd, p);
+            d.parameters.push_back(pd);
+        }
+
+        d.programs.clear();
+        auto prp = r.getPrograms();
+        for (auto p: prp) {
+            d.programs.push_back(p);
+        }
+
+        d.inputDomain = toInputDomain(r.getInputDomain());
+
+        d.basicOutputInfo.clear();
+        auto oo = r.getBasicOutputInfo();
+        for (auto o: oo) {
+            Vamp::HostExt::PluginStaticData::Basic b;
+            readBasicDescriptor(b, o);
+            d.basicOutputInfo.push_back(b);
+        }
+    }
+
+    static void
+    buildConfiguration(Configuration::Builder &b,
+                       const Vamp::HostExt::PluginConfiguration &c) {
+
+        const auto &vparams = c.parameterValues;
+        auto params = b.initParameterValues(vparams.size());
+        int i = 0;
+        for (const auto &pp : vparams) {
+            auto param = params[i++];
+            param.setParameter(pp.first);
+            param.setValue(pp.second);
+        }
+
+        b.setCurrentProgram(c.currentProgram);
+        b.setChannelCount(c.channelCount);
+        b.setStepSize(c.stepSize);
+        b.setBlockSize(c.blockSize);
+    }
+
+    static void
+    readConfiguration(Vamp::HostExt::PluginConfiguration &c,
+                      const Configuration::Reader &r) {
+
+        auto pp = r.getParameterValues();
+        for (const auto &p: pp) {
+            c.parameterValues[p.getParameter()] = p.getValue();
+        }
+
+        c.currentProgram = r.getCurrentProgram();
+        c.channelCount = r.getChannelCount();
+        c.stepSize = r.getStepSize();
+        c.blockSize = r.getBlockSize();
+    }
+
+    static void
+    buildLoadRequest(LoadRequest::Builder &r,
+                     const Vamp::HostExt::LoadRequest &req) {
+
+        r.setKey(req.pluginKey);
+        r.setInputSampleRate(req.inputSampleRate);
+
+        std::vector<AdapterFlag> flags;
+        if (req.adapterFlags & Vamp::HostExt::PluginLoader::ADAPT_INPUT_DOMAIN) {
+            flags.push_back(AdapterFlag::ADAPT_INPUT_DOMAIN);
+        }
+        if (req.adapterFlags & Vamp::HostExt::PluginLoader::ADAPT_CHANNEL_COUNT) {
+            flags.push_back(AdapterFlag::ADAPT_CHANNEL_COUNT);
+        }
+        if (req.adapterFlags & Vamp::HostExt::PluginLoader::ADAPT_BUFFER_SIZE) {
+            flags.push_back(AdapterFlag::ADAPT_BUFFER_SIZE);
+        }
+
+        auto f = r.initAdapterFlags(flags.size());
+        for (size_t i = 0; i < flags.size(); ++i) {
+            f.set(i, flags[i]);
+        }
+    }
+
+    static void
+    readLoadRequest(Vamp::HostExt::LoadRequest &req,
+                    const LoadRequest::Reader &r) {
+
+        req.pluginKey = r.getKey();
+        req.inputSampleRate = r.getInputSampleRate();
+
+        int flags = 0;
+        auto aa = r.getAdapterFlags();
+        for (auto a: aa) {
+            if (a == AdapterFlag::ADAPT_INPUT_DOMAIN) {
+                flags |= Vamp::HostExt::PluginLoader::ADAPT_INPUT_DOMAIN;
+            }
+            if (a == AdapterFlag::ADAPT_CHANNEL_COUNT) {
+                flags |= Vamp::HostExt::PluginLoader::ADAPT_CHANNEL_COUNT;
+            }
+            if (a == AdapterFlag::ADAPT_BUFFER_SIZE) {
+                flags |= Vamp::HostExt::PluginLoader::ADAPT_BUFFER_SIZE;
+            }
+        }
+        req.adapterFlags = flags;
+    }
+
+    static void
+    buildLoadResponse(LoadResponse::Builder &b,
+                      const Vamp::HostExt::LoadResponse &resp,
+                      const PluginHandleMapper &pmapper) {
+
+        b.setHandle(pmapper.pluginToHandle(resp.plugin));
+        auto sd = b.initStaticData();
+        buildExtractorStaticData(sd, resp.staticData);
+        auto conf = b.initDefaultConfiguration();
+        buildConfiguration(conf, resp.defaultConfiguration);
+    }
+
+    static void
+    readLoadResponse(Vamp::HostExt::LoadResponse &resp,
+                     const LoadResponse::Reader &r,
+                     const PluginHandleMapper &pmapper) {
+
+        resp.plugin = pmapper.handleToPlugin(r.getHandle());
+        readExtractorStaticData(resp.staticData, r.getStaticData());
+        readConfiguration(resp.defaultConfiguration,
+                                r.getDefaultConfiguration());
+    }
+
+    static void
+    buildConfigurationRequest(ConfigurationRequest::Builder &b,
+                              const Vamp::HostExt::ConfigurationRequest &cr,
+                              const PluginHandleMapper &pmapper) {
+
+        b.setHandle(pmapper.pluginToHandle(cr.plugin));
+        auto c = b.initConfiguration();
+        buildConfiguration(c, cr.configuration);
+    }
+
+    static void
+    readConfigurationRequest(Vamp::HostExt::ConfigurationRequest &cr,
+                             const ConfigurationRequest::Reader &r,
+                             const PluginHandleMapper &pmapper) {
+
+        auto h = r.getHandle();
+        cr.plugin = pmapper.handleToPlugin(h);
+        auto c = r.getConfiguration();
+        readConfiguration(cr.configuration, c);
+    }
+
+    static void
+    buildConfigurationResponse(ConfigurationResponse::Builder &b,
+                               const Vamp::HostExt::ConfigurationResponse &cr,
+                               const PluginHandleMapper &pmapper) {
+
+        b.setHandle(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];
+            buildOutputDescriptor(od, cr.outputs[i]);
+        }
+    }
+
+    static void
+    readConfigurationResponse(Vamp::HostExt::ConfigurationResponse &cr,
+                              const ConfigurationResponse::Reader &r,
+                              const PluginHandleMapper &pmapper) {
+
+        cr.plugin = pmapper.handleToPlugin(r.getHandle());
+        cr.outputs.clear();
+        auto oo = r.getOutputs();
+        for (const auto &o: oo) {
+            Vamp::Plugin::OutputDescriptor desc;
+            readOutputDescriptor(desc, o);
+            cr.outputs.push_back(desc);
+        }
+    }
+
+    static void
+    buildProcessInput(ProcessInput::Builder &b,
+                      Vamp::RealTime timestamp,
+                      const std::vector<std::vector<float> > &buffers) {
+
+        auto t = b.initTimestamp();
+        buildRealTime(t, timestamp);
+        auto vv = b.initInputBuffers(buffers.size());
+        for (size_t ch = 0; ch < buffers.size(); ++ch) {
+            const int n = int(buffers[ch].size());
+            vv.init(ch, n);
+            auto v = vv[ch];
+            for (int i = 0; i < n; ++i) {
+                v.set(i, buffers[ch][i]);
+            }
+        }
+    }
+    
+    static void
+    readProcessInput(Vamp::RealTime &timestamp,
+                     std::vector<std::vector<float> > &buffers,
+                     const ProcessInput::Reader &b) {
+
+        readRealTime(timestamp, b.getTimestamp());
+        buffers.clear();
+        auto vv = b.getInputBuffers();
+        for (const auto &v: vv) {
+            std::vector<float> buf;
+            for (auto x: v) {
+                buf.push_back(x);
+            }
+            buffers.push_back(buf);
+        }
+    }
+    
+    static void
+    buildProcessRequest(ProcessRequest::Builder &b,
+                        const Vamp::HostExt::ProcessRequest &pr,
+                        const PluginHandleMapper &pmapper) {
+
+        b.setHandle(pmapper.pluginToHandle(pr.plugin));
+        auto input = b.initProcessInput();
+        buildProcessInput(input, pr.timestamp, pr.inputBuffers);
+    }
+
+    static void
+    readProcessRequest(Vamp::HostExt::ProcessRequest &pr,
+                       const ProcessRequest::Reader &r,
+                       const PluginHandleMapper &pmapper) {
+
+        auto h = r.getHandle();
+        pr.plugin = pmapper.handleToPlugin(h);
+        readProcessInput(pr.timestamp, pr.inputBuffers, r.getProcessInput());
+    }
+
+    static void
+    buildProcessResponse(ProcessResponse::Builder &b,
+                         const Vamp::HostExt::ProcessResponse &pr,
+                         const PluginHandleMapper &pmapper) {
+
+        b.setHandle(pmapper.pluginToHandle(pr.plugin));
+        auto f = b.initFeatures();
+        buildFeatureSet(f, pr.features,
+                        *pmapper.pluginToOutputIdMapper(pr.plugin));
+    }
+    
+    static void
+    readProcessResponse(Vamp::HostExt::ProcessResponse &pr,
+                        const ProcessResponse::Reader &r,
+                        const PluginHandleMapper &pmapper) {
+
+        auto h = r.getHandle();
+        pr.plugin = pmapper.handleToPlugin(h);
+        readFeatureSet(pr.features, r.getFeatures(),
+                       *pmapper.handleToOutputIdMapper(r.getHandle()));
+    }
+
+    static void
+    buildFinishResponse(FinishResponse::Builder &b,
+                        const Vamp::HostExt::ProcessResponse &pr,
+                        const PluginHandleMapper &pmapper) {
+
+        b.setHandle(pmapper.pluginToHandle(pr.plugin));
+        auto f = b.initFeatures();
+        buildFeatureSet(f, pr.features,
+                        *pmapper.pluginToOutputIdMapper(pr.plugin));
+    }
+    
+    static void
+    readFinishResponse(Vamp::HostExt::ProcessResponse &pr,
+                       const FinishResponse::Reader &r,
+                       const PluginHandleMapper &pmapper) {
+
+        auto h = r.getHandle();
+        pr.plugin = pmapper.handleToPlugin(h);
+        readFeatureSet(pr.features, r.getFeatures(),
+                       *pmapper.handleToOutputIdMapper(r.getHandle()));
+    }
+
+    static void
+    buildRpcRequest_List(RpcRequest::Builder &b) {
+        b.getRequest().initList();
+    }
+
+    static void
+    buildRpcResponse_List(RpcResponse::Builder &b,
+                           const Vamp::HostExt::ListResponse &resp) {
+
+        auto r = b.getResponse().initList();
+        auto p = r.initAvailable(resp.available.size());
+        for (size_t i = 0; i < resp.available.size(); ++i) {
+            auto pd = p[i];
+            buildExtractorStaticData(pd, resp.available[i]);
+        }
+    }
+    
+    static void
+    buildRpcRequest_Load(RpcRequest::Builder &b,
+                          const Vamp::HostExt::LoadRequest &req) {
+        auto u = b.getRequest().initLoad();
+        buildLoadRequest(u, req);
+    }
+
+    static void
+    buildRpcResponse_Load(RpcResponse::Builder &b,
+                           const Vamp::HostExt::LoadResponse &resp,
+                           const PluginHandleMapper &pmapper) {
+
+        if (resp.plugin) {
+            auto u = b.getResponse().initLoad();
+            buildLoadResponse(u, resp, pmapper);
+        } else {
+            buildRpcResponse_Error(b, "Failed to load plugin", RRType::Load);
+        }
+    }
+
+    static void
+    buildRpcRequest_Configure(RpcRequest::Builder &b,
+                               const Vamp::HostExt::ConfigurationRequest &cr,
+                               const PluginHandleMapper &pmapper) {
+        auto u = b.getRequest().initConfigure();
+        buildConfigurationRequest(u, cr, pmapper);
+    }
+
+    static void
+    buildRpcResponse_Configure(RpcResponse::Builder &b,
+                                const Vamp::HostExt::ConfigurationResponse &cr,
+                                const PluginHandleMapper &pmapper) {
+
+        if (!cr.outputs.empty()) {
+            auto u = b.getResponse().initConfigure();
+            buildConfigurationResponse(u, cr, pmapper);
+        } else {
+            buildRpcResponse_Error(b, "Failed to configure plugin",
+                                   RRType::Configure);
+        }
+    }
+    
+    static void
+    buildRpcRequest_Process(RpcRequest::Builder &b,
+                             const Vamp::HostExt::ProcessRequest &pr,
+                             const PluginHandleMapper &pmapper) {
+        auto u = b.getRequest().initProcess();
+        buildProcessRequest(u, pr, pmapper);
+    }
+    
+    static void
+    buildRpcResponse_Process(RpcResponse::Builder &b,
+                              const Vamp::HostExt::ProcessResponse &pr,
+                              const PluginHandleMapper &pmapper) {
+
+        auto u = b.getResponse().initProcess();
+        buildProcessResponse(u, pr, pmapper);
+    }
+    
+    static void
+    buildRpcRequest_Finish(RpcRequest::Builder &b,
+                            const Vamp::HostExt::FinishRequest &req,
+                            const PluginHandleMapper &pmapper) {
+
+        auto u = b.getRequest().initFinish();
+        u.setHandle(pmapper.pluginToHandle(req.plugin));
+    }
+    
+    static void
+    buildRpcResponse_Finish(RpcResponse::Builder &b,
+                             const Vamp::HostExt::ProcessResponse &pr,
+                             const PluginHandleMapper &pmapper) {
+
+        auto u = b.getResponse().initFinish();
+        buildFinishResponse(u, pr, pmapper);
+    }
+
+    static void
+    buildRpcResponse_Error(RpcResponse::Builder &b,
+                            const std::string &errorText,
+                            RRType responseType)
+    {
+        std::string type;
+
+        auto e = b.getResponse().initError();
+
+        if (responseType == RRType::List) {
+            type = "list";
+        } else if (responseType == RRType::Load) {
+            type = "load";
+        } else if (responseType == RRType::Configure) {
+            type = "configure";
+        } else if (responseType == RRType::Process) {
+            type = "process";
+        } else if (responseType == RRType::Finish) {
+            type = "finish";
+        } else {
+            type = "invalid";
+        }
+
+        e.setCode(0);
+        e.setMessage(std::string("error in ") + type + " request: " + errorText);
+    }
+
+    static void
+    buildRpcResponse_Exception(RpcResponse::Builder &b,
+                                const std::exception &e,
+                                RRType responseType)
+    {
+        return buildRpcResponse_Error(b, e.what(), responseType);
+    }
+    
+    static RRType
+    getRequestResponseType(const RpcRequest::Reader &r) {
+        switch (r.getRequest().which()) {
+        case RpcRequest::Request::Which::LIST:
+            return RRType::List;
+        case RpcRequest::Request::Which::LOAD:
+            return RRType::Load;
+        case RpcRequest::Request::Which::CONFIGURE:
+            return RRType::Configure;
+        case RpcRequest::Request::Which::PROCESS:
+            return RRType::Process;
+        case RpcRequest::Request::Which::FINISH:
+            return RRType::Finish;
+        }
+        return RRType::NotValid;
+    }
+
+    static RRType
+    getRequestResponseType(const RpcResponse::Reader &r) {
+        switch (r.getResponse().which()) {
+        case RpcResponse::Response::Which::ERROR:
+            return RRType::NotValid; //!!! or error type? test this
+        case RpcResponse::Response::Which::LIST:
+            return RRType::List;
+        case RpcResponse::Response::Which::LOAD:
+            return RRType::Load;
+        case RpcResponse::Response::Which::CONFIGURE:
+            return RRType::Configure;
+        case RpcResponse::Response::Which::PROCESS:
+            return RRType::Process;
+        case RpcResponse::Response::Which::FINISH:
+            return RRType::Finish;
+        }
+        return RRType::NotValid;
+    }
+    
+    static void
+    readRpcResponse_Error(int &code,
+                          std::string &message,
+                          const RpcResponse::Reader &r) {
+        if (getRequestResponseType(r) != RRType::NotValid) {
+            throw std::logic_error("not an error response");
+        }
+        code = r.getResponse().getError().getCode();
+        message = r.getResponse().getError().getMessage();
+    }
+        
+    static void
+    readRpcRequest_List(const RpcRequest::Reader &r) {
+        if (getRequestResponseType(r) != RRType::List) {
+            throw std::logic_error("not a list request");
+        }
+    }
+
+    static void
+    readRpcResponse_List(Vamp::HostExt::ListResponse &resp,
+                          const RpcResponse::Reader &r) {
+        if (getRequestResponseType(r) != RRType::List) {
+            throw std::logic_error("not a list response");
+        }
+        resp.available.clear();
+        auto pp = r.getResponse().getList().getAvailable();
+        for (const auto &p: pp) {
+            Vamp::HostExt::PluginStaticData psd;
+            readExtractorStaticData(psd, p);
+            resp.available.push_back(psd);
+        }
+    }
+    
+    static void
+    readRpcRequest_Load(Vamp::HostExt::LoadRequest &req,
+                         const RpcRequest::Reader &r) {
+        if (getRequestResponseType(r) != RRType::Load) {
+            throw std::logic_error("not a load request");
+        }
+        readLoadRequest(req, r.getRequest().getLoad());
+    }
+
+    static void
+    readRpcResponse_Load(Vamp::HostExt::LoadResponse &resp,
+                         const RpcResponse::Reader &r,
+                         const PluginHandleMapper &pmapper) {
+        if (getRequestResponseType(r) != RRType::Load) {
+            throw std::logic_error("not a load response");
+        }
+        resp = {};
+        readLoadResponse(resp, r.getResponse().getLoad(), pmapper);
+    }
+    
+    static void
+    readRpcRequest_Configure(Vamp::HostExt::ConfigurationRequest &req,
+                              const RpcRequest::Reader &r,
+                              const PluginHandleMapper &pmapper) {
+        if (getRequestResponseType(r) != RRType::Configure) {
+            throw std::logic_error("not a configuration request");
+        }
+        readConfigurationRequest(req, r.getRequest().getConfigure(), pmapper);
+    }
+
+    static void
+    readRpcResponse_Configure(Vamp::HostExt::ConfigurationResponse &resp,
+                               const RpcResponse::Reader &r,
+                               const PluginHandleMapper &pmapper) {
+        if (getRequestResponseType(r) != RRType::Configure) {
+            throw std::logic_error("not a configuration response");
+        }
+        resp = {};
+        readConfigurationResponse(resp,
+                                  r.getResponse().getConfigure(),
+                                  pmapper);
+    }
+    
+    static void
+    readRpcRequest_Process(Vamp::HostExt::ProcessRequest &req,
+                            const RpcRequest::Reader &r,
+                            const PluginHandleMapper &pmapper) {
+        if (getRequestResponseType(r) != RRType::Process) {
+            throw std::logic_error("not a process request");
+        }
+        readProcessRequest(req, r.getRequest().getProcess(), pmapper);
+    }
+
+    static void
+    readRpcResponse_Process(Vamp::HostExt::ProcessResponse &resp,
+                             const RpcResponse::Reader &r,
+                             const PluginHandleMapper &pmapper) {
+        if (getRequestResponseType(r) != RRType::Process) {
+            throw std::logic_error("not a process response");
+        }
+        resp = {};
+        readProcessResponse(resp, r.getResponse().getProcess(), pmapper);
+    }
+    
+    static void
+    readRpcRequest_Finish(Vamp::HostExt::FinishRequest &req,
+                           const RpcRequest::Reader &r,
+                           const PluginHandleMapper &pmapper) {
+        if (getRequestResponseType(r) != RRType::Finish) {
+            throw std::logic_error("not a finish request");
+        }
+        req.plugin = pmapper.handleToPlugin
+            (r.getRequest().getFinish().getHandle());
+    }
+
+    static void
+    readRpcResponse_Finish(Vamp::HostExt::ProcessResponse &resp,
+                            const RpcResponse::Reader &r,
+                            const PluginHandleMapper &pmapper) {
+        if (getRequestResponseType(r) != RRType::Finish) {
+            throw std::logic_error("not a finish response");
+        }
+        resp = {};
+        readFinishResponse(resp, r.getResponse().getFinish(), pmapper);
+    }
+};
+
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-json/VampJson.h	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,1387 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Piper C++
+
+    Centre for Digital Music, Queen Mary, University of London.
+    Copyright 2015-2016 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 PIPER_VAMP_JSON_H
+#define PIPER_VAMP_JSON_H
+
+#include <vector>
+#include <string>
+#include <sstream>
+
+#include <json11/json11.hpp>
+#include <base-n/include/basen.hpp>
+
+#include <vamp-hostsdk/Plugin.h>
+#include <vamp-hostsdk/PluginLoader.h>
+
+#include "vamp-support/PluginHandleMapper.h"
+#include "vamp-support/PluginOutputIdMapper.h"
+#include "vamp-support/RequestResponseType.h"
+
+namespace piper {
+
+/**
+ * Convert the structures laid out in the Vamp SDK classes into JSON
+ * (and back again) following the schema in the vamp-json-schema
+ * project repo.
+ *
+ * Functions with names starting "from" convert from a Vamp SDK object
+ * to JSON output. Most of them return a json11::Json object, with a
+ * few exceptions for low-level utilities that return a string. These
+ * functions succeed all of the time.
+ *
+ * Functions with names starting "to" convert to a Vamp SDK object
+ * from JSON input. These functions all accept a json11::Json object
+ * as first argument, with a few exceptions for low-level utilities
+ * that accept a string. These functions all accept a string reference
+ * as a final argument and return an error string through it if the
+ * conversion fails. If conversion fails the return value is
+ * undefined, and any returned object may be incomplete or
+ * invalid. Callers should check for an empty error string (indicating
+ * success) before using the returned value.
+ */
+
+class VampJson
+{
+public:
+    /** Serialisation format for arrays of floats (process input and
+     *  feature values). Wherever such an array appears, it may
+     *  alternatively be replaced by a single string containing a
+     *  base-64 encoding of the IEEE float buffer. When parsing, if a
+     *  string is found instead of an array in this case, it will be
+     *  interpreted as a base-64 encoded buffer. Only array or base-64
+     *  encoding may be provided, not both.
+     */
+    enum class BufferSerialisation {
+
+        /** Default JSON serialisation of values in array form. This
+         *  is relatively slow to parse and serialise, and can take a
+         *  lot of space.
+         */
+        Array,
+
+        /** Base64-encoded string of the raw data as packed
+         *  little-endian IEEE 32-bit floats. Faster and more compact
+         *  than the text encoding but more complicated to
+         *  provide. Note that Base64 serialisations produced by this
+         *  library do not including padding characters and so are not
+         *  necessarily multiples of 4 characters long. You will need
+         *  to pad them yourself if concatenating them or supplying to
+         *  a consumer that expects padding.
+         */
+        Base64
+    };
+    
+    static bool failed(const std::string &err) {
+        return err != "";
+    }
+    
+    template <typename T>
+    static json11::Json
+    fromBasicDescriptor(const T &t) {
+        return json11::Json::object { 
+            { "identifier", t.identifier },
+            { "name", t.name },
+            { "description", t.description }
+        };
+    }
+
+    template <typename T>
+    static void
+    toBasicDescriptor(json11::Json j, T &t, std::string &err) {
+        if (!j.is_object()) {
+            err = "object expected for basic descriptor content";
+            return;
+        }
+        if (!j["identifier"].is_string()) {
+            err = "string expected for identifier";
+            return;
+        }
+        t.identifier = j["identifier"].string_value();
+        t.name = j["name"].string_value();
+        t.description = j["description"].string_value();
+    }
+
+    template <typename T>
+    static json11::Json
+    fromValueExtents(const T &t) {
+        return json11::Json::object {
+            { "min", t.minValue },
+            { "max", t.maxValue }
+        };
+    }
+
+    template <typename T>
+    static bool
+    toValueExtents(json11::Json j, T &t, std::string &err) {
+        if (j["extents"].is_null()) {
+            return false;
+        } else if (j["extents"].is_object()) {
+            if (j["extents"]["min"].is_number() &&
+                j["extents"]["max"].is_number()) {
+                t.minValue = j["extents"]["min"].number_value();
+                t.maxValue = j["extents"]["max"].number_value();
+                return true;
+            } else {
+                err = "numbers expected for min and max";
+                return false;
+            }
+        } else {
+            err = "object expected for extents (if present)";
+            return false;
+        }
+    }
+
+    static json11::Json
+    fromRealTime(const Vamp::RealTime &r) {
+        return json11::Json::object {
+            { "s", r.sec },
+            { "n", r.nsec }
+        };
+    }
+
+    static Vamp::RealTime
+    toRealTime(json11::Json j, std::string &err) {
+        json11::Json sec = j["s"];
+        json11::Json nsec = j["n"];
+        if (!sec.is_number() || !nsec.is_number()) {
+            err = "invalid Vamp::RealTime object " + j.dump();
+            return {};
+        }
+        return Vamp::RealTime(sec.int_value(), nsec.int_value());
+    }
+
+    static std::string
+    fromSampleType(Vamp::Plugin::OutputDescriptor::SampleType type) {
+        switch (type) {
+        case Vamp::Plugin::OutputDescriptor::OneSamplePerStep:
+            return "OneSamplePerStep";
+        case Vamp::Plugin::OutputDescriptor::FixedSampleRate:
+            return "FixedSampleRate";
+        case Vamp::Plugin::OutputDescriptor::VariableSampleRate:
+            return "VariableSampleRate";
+        }
+        return "";
+    }
+
+    static Vamp::Plugin::OutputDescriptor::SampleType
+    toSampleType(std::string text, std::string &err) {
+        if (text == "OneSamplePerStep") {
+            return Vamp::Plugin::OutputDescriptor::OneSamplePerStep;
+        } else if (text == "FixedSampleRate") {
+            return Vamp::Plugin::OutputDescriptor::FixedSampleRate;
+        } else if (text == "VariableSampleRate") {
+            return Vamp::Plugin::OutputDescriptor::VariableSampleRate;
+        } else {
+            err = "invalid sample type string: " + text;
+            return Vamp::Plugin::OutputDescriptor::OneSamplePerStep;
+        }
+    }
+
+    static json11::Json
+    fromConfiguredOutputDescriptor(const Vamp::Plugin::OutputDescriptor &desc) {
+        json11::Json::object jo {
+            { "unit", desc.unit },
+            { "sampleType", fromSampleType(desc.sampleType) },
+            { "sampleRate", desc.sampleRate },
+            { "hasDuration", desc.hasDuration }
+        };
+        if (desc.hasFixedBinCount) {
+            jo["binCount"] = int(desc.binCount);
+            jo["binNames"] = json11::Json::array
+                (desc.binNames.begin(), desc.binNames.end());
+        }
+        if (desc.hasKnownExtents) {
+            jo["extents"] = fromValueExtents(desc);
+        }
+        if (desc.isQuantized) {
+            jo["quantizeStep"] = desc.quantizeStep;
+        }
+        return json11::Json(jo);
+    }
+    
+    static json11::Json
+    fromOutputDescriptor(const Vamp::Plugin::OutputDescriptor &desc) {
+        json11::Json::object jo {
+            { "basic", fromBasicDescriptor(desc) },
+            { "configured", fromConfiguredOutputDescriptor(desc) }
+        };
+        return json11::Json(jo);
+    }
+    
+    static Vamp::Plugin::OutputDescriptor
+    toConfiguredOutputDescriptor(json11::Json j, std::string &err) {
+
+        Vamp::Plugin::OutputDescriptor od;
+        if (!j.is_object()) {
+            err = "object expected for output descriptor";
+            return {};
+        }
+    
+        od.unit = j["unit"].string_value();
+
+        od.sampleType = toSampleType(j["sampleType"].string_value(), err);
+        if (failed(err)) return {};
+
+        if (!j["sampleRate"].is_number()) {
+            err = "number expected for sample rate";
+            return {};
+        }
+        od.sampleRate = j["sampleRate"].number_value();
+        od.hasDuration = j["hasDuration"].bool_value();
+
+        if (j["binCount"].is_number() && j["binCount"].int_value() > 0) {
+            od.hasFixedBinCount = true;
+            od.binCount = j["binCount"].int_value();
+            for (auto &n: j["binNames"].array_items()) {
+                if (!n.is_string()) {
+                    err = "string expected for bin name";
+                    return {};
+                }
+                od.binNames.push_back(n.string_value());
+            }
+        } else {
+            od.hasFixedBinCount = false;
+        }
+
+        bool extentsPresent = toValueExtents(j, od, err);
+        if (failed(err)) return {};
+        
+        od.hasKnownExtents = extentsPresent;
+
+        if (j["quantizeStep"].is_number()) {
+            od.isQuantized = true;
+            od.quantizeStep = j["quantizeStep"].number_value();
+        } else {
+            od.isQuantized = false;
+        }
+
+        return od;
+    }
+    
+    static Vamp::Plugin::OutputDescriptor
+    toOutputDescriptor(json11::Json j, std::string &err) {
+
+        Vamp::Plugin::OutputDescriptor od;
+        if (!j.is_object()) {
+            err = "object expected for output descriptor";
+            return {};
+        }
+
+        od = toConfiguredOutputDescriptor(j, err);
+        if (failed(err)) return {};
+    
+        toBasicDescriptor(j["basic"], od, err);
+        if (failed(err)) return {};
+
+        return od;
+    }
+
+    static json11::Json
+    fromParameterDescriptor(const Vamp::PluginBase::ParameterDescriptor &desc) {
+
+        json11::Json::object jo {
+            { "basic", fromBasicDescriptor(desc) },
+            { "unit", desc.unit },
+            { "extents", fromValueExtents(desc) },
+            { "defaultValue", desc.defaultValue },
+            { "valueNames", json11::Json::array
+                    (desc.valueNames.begin(), desc.valueNames.end()) }
+        };
+        if (desc.isQuantized) {
+            jo["quantizeStep"] = desc.quantizeStep;
+        }
+        return json11::Json(jo);
+    }
+
+    static Vamp::PluginBase::ParameterDescriptor
+    toParameterDescriptor(json11::Json j, std::string &err) {
+
+        Vamp::PluginBase::ParameterDescriptor pd;
+        if (!j.is_object()) {
+            err = "object expected for parameter descriptor";
+            return {};
+        }
+    
+        toBasicDescriptor(j["basic"], pd, err);
+        if (failed(err)) return {};
+
+        pd.unit = j["unit"].string_value();
+
+        bool extentsPresent = toValueExtents(j, pd, err);
+        if (failed(err)) return {};
+        if (!extentsPresent) {
+            err = "extents must be present in parameter descriptor";
+            return {};
+        }
+    
+        if (!j["defaultValue"].is_number()) {
+            err = "number expected for default value";
+            return {};
+        }
+    
+        pd.defaultValue = j["defaultValue"].number_value();
+
+        pd.valueNames.clear();
+        for (auto &n: j["valueNames"].array_items()) {
+            if (!n.is_string()) {
+                err = "string expected for value name";
+                return {};
+            }
+            pd.valueNames.push_back(n.string_value());
+        }
+
+        if (j["quantizeStep"].is_number()) {
+            pd.isQuantized = true;
+            pd.quantizeStep = j["quantizeStep"].number_value();
+        } else {
+            pd.isQuantized = false;
+        }
+
+        return pd;
+    }
+
+    static std::string
+    fromFloatBuffer(const float *buffer, size_t nfloats) {
+        // must use char pointers, otherwise the converter will only
+        // encode every 4th byte (as it will count up in float* steps)
+        const char *start = reinterpret_cast<const char *>(buffer);
+        const char *end = reinterpret_cast<const char *>(buffer + nfloats);
+        std::string encoded;
+        bn::encode_b64(start, end, back_inserter(encoded));
+        return encoded;
+    }
+
+    static std::vector<float>
+    toFloatBuffer(std::string encoded, std::string & /* err */) {
+        std::string decoded;
+        bn::decode_b64(encoded.begin(), encoded.end(), back_inserter(decoded));
+        const float *buffer = reinterpret_cast<const float *>(decoded.c_str());
+        size_t n = decoded.size() / sizeof(float);
+        return std::vector<float>(buffer, buffer + n);
+    }
+
+    static json11::Json
+    fromFeature(const Vamp::Plugin::Feature &f,
+                BufferSerialisation serialisation) {
+
+        json11::Json::object jo;
+        if (f.values.size() > 0) {
+            if (serialisation == BufferSerialisation::Array) {
+                jo["featureValues"] = json11::Json::array(f.values.begin(),
+                                                          f.values.end());
+            } else {
+                jo["featureValues"] = fromFloatBuffer(f.values.data(),
+                                                      f.values.size());
+            }
+        }
+        if (f.label != "") {
+            jo["label"] = f.label;
+        }
+        if (f.hasTimestamp) {
+            jo["timestamp"] = fromRealTime(f.timestamp);
+        }
+        if (f.hasDuration) {
+            jo["duration"] = fromRealTime(f.duration);
+        }
+        return json11::Json(jo);
+    }
+
+    static Vamp::Plugin::Feature
+    toFeature(json11::Json j, BufferSerialisation &serialisation, std::string &err) {
+
+        Vamp::Plugin::Feature f;
+        if (!j.is_object()) {
+            err = "object expected for feature";
+            return {};
+        }
+        if (j["timestamp"].is_object()) {
+            f.timestamp = toRealTime(j["timestamp"], err);
+            if (failed(err)) return {};
+            f.hasTimestamp = true;
+        }
+        if (j["duration"].is_object()) {
+            f.duration = toRealTime(j["duration"], err);
+            if (failed(err)) return {};
+            f.hasDuration = true;
+        }
+        if (j["featureValues"].is_string()) {
+            f.values = toFloatBuffer(j["featureValues"].string_value(), err);
+            if (failed(err)) return {};
+            serialisation = BufferSerialisation::Base64;
+        } else if (j["featureValues"].is_array()) {
+            for (auto v : j["featureValues"].array_items()) {
+                f.values.push_back(v.number_value());
+            }
+            serialisation = BufferSerialisation::Array;
+        }
+        f.label = j["label"].string_value();
+        return f;
+    }
+
+    static json11::Json
+    fromFeatureSet(const Vamp::Plugin::FeatureSet &fs,
+                   const PluginOutputIdMapper &omapper,
+                   BufferSerialisation serialisation) {
+
+        json11::Json::object jo;
+        for (const auto &fsi : fs) {
+            std::vector<json11::Json> fj;
+            for (const Vamp::Plugin::Feature &f: fsi.second) {
+                fj.push_back(fromFeature(f, serialisation));
+            }
+            jo[omapper.indexToId(fsi.first)] = fj;
+        }
+        return json11::Json(jo);
+    }
+
+    static Vamp::Plugin::FeatureList
+    toFeatureList(json11::Json j,
+                  BufferSerialisation &serialisation, std::string &err) {
+
+        Vamp::Plugin::FeatureList fl;
+        if (!j.is_array()) {
+            err = "array expected for feature list";
+            return {};
+        }
+        for (const json11::Json &fj : j.array_items()) {
+            fl.push_back(toFeature(fj, serialisation, err));
+            if (failed(err)) return {};
+        }
+        return fl;
+    }
+
+    static Vamp::Plugin::FeatureSet
+    toFeatureSet(json11::Json j,
+                 const PluginOutputIdMapper &omapper,
+                 BufferSerialisation &serialisation,
+                 std::string &err) {
+
+        Vamp::Plugin::FeatureSet fs;
+        if (!j.is_object()) {
+            err = "object expected for feature set";
+            return {};
+        }
+        for (auto &entry : j.object_items()) {
+            int n = omapper.idToIndex(entry.first);
+            if (fs.find(n) != fs.end()) {
+                err = "duplicate numerical index for output";
+                return {};
+            }
+            fs[n] = toFeatureList(entry.second, serialisation, err);
+            if (failed(err)) return {};
+        }
+        return fs;
+    }
+
+    static std::string
+    fromInputDomain(Vamp::Plugin::InputDomain domain) {
+
+        switch (domain) {
+        case Vamp::Plugin::TimeDomain:
+            return "TimeDomain";
+        case Vamp::Plugin::FrequencyDomain:
+            return "FrequencyDomain";
+        }
+        return "";
+    }
+
+    static Vamp::Plugin::InputDomain
+    toInputDomain(std::string text, std::string &err) {
+
+        if (text == "TimeDomain") {
+            return Vamp::Plugin::TimeDomain;
+        } else if (text == "FrequencyDomain") {
+            return Vamp::Plugin::FrequencyDomain;
+        } else {
+            err = "invalid input domain string: " + text;
+            return {};
+        }
+    }
+
+    static json11::Json
+    fromPluginStaticData(const Vamp::HostExt::PluginStaticData &d) {
+
+        json11::Json::object jo;
+        jo["key"] = d.pluginKey;
+        jo["basic"] = fromBasicDescriptor(d.basic);
+        jo["maker"] = d.maker;
+        jo["copyright"] = d.copyright;
+        jo["version"] = d.pluginVersion;
+
+        json11::Json::array cat;
+        for (const std::string &c: d.category) cat.push_back(c);
+        jo["category"] = cat;
+
+        jo["minChannelCount"] = d.minChannelCount;
+        jo["maxChannelCount"] = d.maxChannelCount;
+
+        json11::Json::array params;
+        Vamp::PluginBase::ParameterList vparams = d.parameters;
+        for (auto &p: vparams) params.push_back(fromParameterDescriptor(p));
+        jo["parameters"] = params;
+
+        json11::Json::array progs;
+        Vamp::PluginBase::ProgramList vprogs = d.programs;
+        for (auto &p: vprogs) progs.push_back(p);
+        jo["programs"] = progs;
+
+        jo["inputDomain"] = fromInputDomain(d.inputDomain);
+
+        json11::Json::array outinfo;
+        auto vouts = d.basicOutputInfo;
+        for (auto &o: vouts) outinfo.push_back(fromBasicDescriptor(o));
+        jo["basicOutputInfo"] = outinfo;
+    
+        return json11::Json(jo);
+    }
+
+    static Vamp::HostExt::PluginStaticData
+    toPluginStaticData(json11::Json j, std::string &err) {
+
+        if (!j.has_shape({
+                    { "key", json11::Json::STRING },
+                    { "version", json11::Json::NUMBER },
+                    { "minChannelCount", json11::Json::NUMBER },
+                    { "maxChannelCount", json11::Json::NUMBER },
+                    { "inputDomain", json11::Json::STRING }}, err)) {
+
+            err = "malformed plugin static data: " + err;
+
+        } else if (!j["basicOutputInfo"].is_array()) {
+
+            err = "array expected for basic output info";
+
+        } else if (!j["maker"].is_null() &&
+                   !j["maker"].is_string()) {
+
+            err = "string expected for maker";
+
+        } else if (!j["copyright"].is_null() &&
+                   !j["copyright"].is_string()) {
+            err = "string expected for copyright";
+
+        } else if (!j["category"].is_null() &&
+                   !j["category"].is_array()) {
+
+            err = "array expected for category";
+
+        } else if (!j["parameters"].is_null() &&
+                   !j["parameters"].is_array()) {
+
+            err = "array expected for parameters";
+
+        } else if (!j["programs"].is_null() &&
+                   !j["programs"].is_array()) {
+
+            err = "array expected for programs";
+
+        } else if (!j["inputDomain"].is_null() &&
+                   !j["inputDomain"].is_string()) {
+
+            err = "string expected for inputDomain";
+
+        } else if (!j["basicOutputInfo"].is_null() &&
+                   !j["basicOutputInfo"].is_array()) {
+            
+            err = "array expected for basicOutputInfo";
+
+        } else {
+
+            Vamp::HostExt::PluginStaticData psd;
+
+            psd.pluginKey = j["key"].string_value();
+
+            toBasicDescriptor(j["basic"], psd.basic, err);
+            if (failed(err)) return {};
+
+            psd.maker = j["maker"].string_value();
+            psd.copyright = j["copyright"].string_value();
+            psd.pluginVersion = j["version"].int_value();
+
+            for (const auto &c : j["category"].array_items()) {
+                if (!c.is_string()) {
+                    err = "strings expected in category array";
+                    return {};
+                }
+                psd.category.push_back(c.string_value());
+            }
+
+            psd.minChannelCount = j["minChannelCount"].int_value();
+            psd.maxChannelCount = j["maxChannelCount"].int_value();
+
+            for (const auto &p : j["parameters"].array_items()) {
+                auto pd = toParameterDescriptor(p, err);
+                if (failed(err)) return {};
+                psd.parameters.push_back(pd);
+            }
+
+            for (const auto &p : j["programs"].array_items()) {
+                if (!p.is_string()) {
+                    err = "strings expected in programs array";
+                    return {};
+                }
+                psd.programs.push_back(p.string_value());
+            }
+
+            psd.inputDomain = toInputDomain(j["inputDomain"].string_value(), err);
+            if (failed(err)) return {};
+
+            for (const auto &bo : j["basicOutputInfo"].array_items()) {
+                Vamp::HostExt::PluginStaticData::Basic b;
+                toBasicDescriptor(bo, b, err);
+                if (failed(err)) return {};
+                psd.basicOutputInfo.push_back(b);
+            }
+            
+            return psd;
+        }
+
+        // fallthrough error case
+        return {};
+    }
+
+    static json11::Json
+    fromPluginConfiguration(const Vamp::HostExt::PluginConfiguration &c) {
+
+        json11::Json::object jo;
+
+        json11::Json::object paramValues;
+        for (auto &vp: c.parameterValues) {
+            paramValues[vp.first] = vp.second;
+        }
+        jo["parameterValues"] = paramValues;
+
+        if (c.currentProgram != "") {
+            jo["currentProgram"] = c.currentProgram;
+        }
+
+        jo["channelCount"] = c.channelCount;
+        jo["stepSize"] = c.stepSize;
+        jo["blockSize"] = c.blockSize;
+    
+        return json11::Json(jo);
+    }
+
+    static Vamp::HostExt::PluginConfiguration
+    toPluginConfiguration(json11::Json j, std::string &err) {
+        
+        if (!j.has_shape({
+                    { "channelCount", json11::Json::NUMBER },
+                    { "stepSize", json11::Json::NUMBER },
+                    { "blockSize", json11::Json::NUMBER } }, err)) {
+            err = "malformed plugin configuration: " + err;
+            return {};
+        }
+
+        if (!j["parameterValues"].is_null() &&
+            !j["parameterValues"].is_object()) {
+            err = "object expected for parameter values";
+            return {};
+        }
+
+        for (auto &pv : j["parameterValues"].object_items()) {
+            if (!pv.second.is_number()) {
+                err = "number expected for parameter value";
+                return {};
+            }
+        }
+    
+        if (!j["currentProgram"].is_null() &&
+            !j["currentProgram"].is_string()) {
+            err = "string expected for program name";
+            return {};
+        }
+
+        Vamp::HostExt::PluginConfiguration config;
+
+        config.channelCount = j["channelCount"].number_value();
+        config.stepSize = j["stepSize"].number_value();
+        config.blockSize = j["blockSize"].number_value();
+        
+        for (auto &pv : j["parameterValues"].object_items()) {
+            config.parameterValues[pv.first] = pv.second.number_value();
+        }
+
+        if (j["currentProgram"].is_string()) {
+            config.currentProgram = j["currentProgram"].string_value();
+        }
+
+        return config;
+    }
+
+    static json11::Json
+    fromAdapterFlags(int flags) {
+
+        json11::Json::array arr;
+
+        if (flags & Vamp::HostExt::PluginLoader::ADAPT_INPUT_DOMAIN) {
+            arr.push_back("AdaptInputDomain");
+        }
+        if (flags & Vamp::HostExt::PluginLoader::ADAPT_CHANNEL_COUNT) {
+            arr.push_back("AdaptChannelCount");
+        }
+        if (flags & Vamp::HostExt::PluginLoader::ADAPT_BUFFER_SIZE) {
+            arr.push_back("AdaptBufferSize");
+        }
+
+        return json11::Json(arr);
+    }
+
+    static Vamp::HostExt::PluginLoader::AdapterFlags
+    toAdapterFlags(json11::Json j, std::string &err) {
+
+        int flags = 0x0;
+
+        if (!j.is_array()) {
+
+            err = "array expected for adapter flags";
+
+        } else {
+
+            for (auto &jj: j.array_items()) {
+                if (!jj.is_string()) {
+                    err = "string expected for adapter flag";
+                    break;
+                }
+                std::string text = jj.string_value();
+                if (text == "AdaptInputDomain") {
+                    flags |= Vamp::HostExt::PluginLoader::ADAPT_INPUT_DOMAIN;
+                } else if (text == "AdaptChannelCount") {
+                    flags |= Vamp::HostExt::PluginLoader::ADAPT_CHANNEL_COUNT;
+                } else if (text == "AdaptBufferSize") {
+                    flags |= Vamp::HostExt::PluginLoader::ADAPT_BUFFER_SIZE;
+                } else if (text == "AdaptAllSafe") {
+                    flags |= Vamp::HostExt::PluginLoader::ADAPT_ALL_SAFE;
+                } else if (text == "AdaptAll") {
+                    flags |= Vamp::HostExt::PluginLoader::ADAPT_ALL;
+                } else {
+                    err = "invalid adapter flag string: " + text;
+                    break;
+                }
+            }
+        }
+
+        return Vamp::HostExt::PluginLoader::AdapterFlags(flags);
+    }
+
+    static json11::Json
+    fromLoadRequest(const Vamp::HostExt::LoadRequest &req) {
+
+        json11::Json::object jo;
+        jo["key"] = req.pluginKey;
+        jo["inputSampleRate"] = req.inputSampleRate;
+        jo["adapterFlags"] = fromAdapterFlags(req.adapterFlags);
+        return json11::Json(jo);
+    }
+
+    static Vamp::HostExt::LoadRequest
+    toLoadRequest(json11::Json j, std::string &err) {
+        
+        if (!j.has_shape({
+                    { "key", json11::Json::STRING },
+                    { "inputSampleRate", json11::Json::NUMBER } }, err)) {
+            err = "malformed load request: " + err;
+            return {};
+        }
+    
+        Vamp::HostExt::LoadRequest req;
+        req.pluginKey = j["key"].string_value();
+        req.inputSampleRate = j["inputSampleRate"].number_value();
+        if (!j["adapterFlags"].is_null()) {
+            req.adapterFlags = toAdapterFlags(j["adapterFlags"], err);
+            if (failed(err)) return {};
+        }
+        return req;
+    }
+
+    static json11::Json
+    fromLoadResponse(const Vamp::HostExt::LoadResponse &resp,
+                     const PluginHandleMapper &pmapper) {
+
+        json11::Json::object jo;
+        jo["handle"] = double(pmapper.pluginToHandle(resp.plugin));
+        jo["staticData"] = fromPluginStaticData(resp.staticData);
+        jo["defaultConfiguration"] =
+            fromPluginConfiguration(resp.defaultConfiguration);
+        return json11::Json(jo);
+    }
+
+    static Vamp::HostExt::LoadResponse
+    toLoadResponse(json11::Json j,
+                   const PluginHandleMapper &pmapper, std::string &err) {
+
+        if (!j.has_shape({
+                    { "handle", json11::Json::NUMBER },
+                    { "staticData", json11::Json::OBJECT },
+                    { "defaultConfiguration", json11::Json::OBJECT } }, err)) {
+            err = "malformed load response: " + err;
+            return {};
+        }
+
+        Vamp::HostExt::LoadResponse resp;
+        resp.plugin = pmapper.handleToPlugin(j["handle"].int_value());
+        resp.staticData = toPluginStaticData(j["staticData"], err);
+        if (failed(err)) return {};
+        resp.defaultConfiguration = toPluginConfiguration(j["defaultConfiguration"],
+                                                          err);
+        if (failed(err)) return {};
+        return resp;
+    }
+
+    static json11::Json
+    fromConfigurationRequest(const Vamp::HostExt::ConfigurationRequest &cr,
+                             const PluginHandleMapper &pmapper) {
+
+        json11::Json::object jo;
+
+        jo["handle"] = pmapper.pluginToHandle(cr.plugin);
+        jo["configuration"] = fromPluginConfiguration(cr.configuration);
+        
+        return json11::Json(jo);
+    }
+
+    static Vamp::HostExt::ConfigurationRequest
+    toConfigurationRequest(json11::Json j,
+                           const PluginHandleMapper &pmapper, std::string &err) {
+
+        if (!j.has_shape({
+                    { "handle", json11::Json::NUMBER },
+                    { "configuration", json11::Json::OBJECT } }, err)) {
+            err = "malformed configuration request: " + err;
+            return {};
+        }
+
+        Vamp::HostExt::ConfigurationRequest cr;
+        cr.plugin = pmapper.handleToPlugin(j["handle"].int_value());
+        cr.configuration = toPluginConfiguration(j["configuration"], err);
+        if (failed(err)) return {};
+        return cr;
+    }
+
+    static json11::Json
+    fromConfigurationResponse(const Vamp::HostExt::ConfigurationResponse &cr,
+                              const PluginHandleMapper &pmapper) {
+
+        json11::Json::object jo;
+
+        jo["handle"] = pmapper.pluginToHandle(cr.plugin);
+        
+        json11::Json::array outs;
+        for (auto &d: cr.outputs) {
+            outs.push_back(fromOutputDescriptor(d));
+        }
+        jo["outputList"] = outs;
+        
+        return json11::Json(jo);
+    }
+
+    static Vamp::HostExt::ConfigurationResponse
+    toConfigurationResponse(json11::Json j,
+                            const PluginHandleMapper &pmapper, std::string &err) {
+        
+        Vamp::HostExt::ConfigurationResponse cr;
+
+        cr.plugin = pmapper.handleToPlugin(j["handle"].int_value());
+        
+        if (!j["outputList"].is_array()) {
+            err = "array expected for output list";
+            return {};
+        }
+
+        for (const auto &o: j["outputList"].array_items()) {
+            cr.outputs.push_back(toOutputDescriptor(o, err));
+            if (failed(err)) return {};
+        }
+
+        return cr;
+    }
+
+    static json11::Json
+    fromProcessRequest(const Vamp::HostExt::ProcessRequest &r,
+                       const PluginHandleMapper &pmapper,
+                       BufferSerialisation serialisation) {
+
+        json11::Json::object jo;
+        jo["handle"] = pmapper.pluginToHandle(r.plugin);
+
+        json11::Json::object io;
+        io["timestamp"] = fromRealTime(r.timestamp);
+
+        json11::Json::array chans;
+        for (size_t i = 0; i < r.inputBuffers.size(); ++i) {
+            if (serialisation == BufferSerialisation::Array) {
+                chans.push_back(json11::Json::array(r.inputBuffers[i].begin(),
+                                                    r.inputBuffers[i].end()));
+            } else {
+                chans.push_back(fromFloatBuffer(r.inputBuffers[i].data(),
+                                                r.inputBuffers[i].size()));
+            }
+        }
+        io["inputBuffers"] = chans;
+        
+        jo["processInput"] = io;
+        return json11::Json(jo);
+    }
+
+    static Vamp::HostExt::ProcessRequest
+    toProcessRequest(json11::Json j,
+                     const PluginHandleMapper &pmapper,
+                     BufferSerialisation &serialisation, std::string &err) {
+
+        if (!j.has_shape({
+                    { "handle", json11::Json::NUMBER },
+                    { "processInput", json11::Json::OBJECT } }, err)) {
+            err = "malformed process request: " + err;
+            return {};
+        }
+
+        auto input = j["processInput"];
+
+        if (!input.has_shape({
+                    { "timestamp", json11::Json::OBJECT },
+                    { "inputBuffers", json11::Json::ARRAY } }, err)) {
+            err = "malformed process request: " + err;
+            return {};
+        }
+
+        Vamp::HostExt::ProcessRequest r;
+        r.plugin = pmapper.handleToPlugin(j["handle"].int_value());
+
+        r.timestamp = toRealTime(input["timestamp"], err);
+        if (failed(err)) return {};
+
+        for (const auto &a: input["inputBuffers"].array_items()) {
+
+            if (a.is_string()) {
+                std::vector<float> buf = toFloatBuffer(a.string_value(),
+                                                       err);
+                if (failed(err)) return {};
+                r.inputBuffers.push_back(buf);
+                serialisation = BufferSerialisation::Base64;
+
+            } else if (a.is_array()) {
+                std::vector<float> buf;
+                for (auto v : a.array_items()) {
+                    buf.push_back(v.number_value());
+                }
+                r.inputBuffers.push_back(buf);
+                serialisation = BufferSerialisation::Array;
+
+            } else {
+                err = "expected arrays or strings in inputBuffers array";
+                return {};
+            }
+        }
+
+        return r;
+    }
+    
+private: // go private briefly for a couple of helper functions
+    
+    static void
+    checkTypeField(json11::Json j, std::string expected, std::string &err) {
+        if (!j["method"].is_string()) {
+            err = "string expected for method";
+            return;
+        }
+        if (j["method"].string_value() != expected) {
+            err = "expected value \"" + expected + "\" for type";
+            return;
+        }
+    }
+
+    static bool
+    successful(json11::Json j, std::string &err) {
+        if (!j["success"].is_bool()) {
+            err = "bool expected for success";
+            return false;
+        }
+        return j["success"].bool_value();
+    }
+
+    static void
+    markRPC(json11::Json::object &jo) {
+        jo["jsonrpc"] = "2.0";
+    }
+
+    static void
+    addId(json11::Json::object &jo, const json11::Json &id) {
+        if (!id.is_null()) {
+            jo["id"] = id;
+        }
+    }
+    
+public:
+
+    static json11::Json
+    fromRpcRequest_List(const json11::Json &id) {
+
+        json11::Json::object jo;
+        markRPC(jo);
+
+        jo["method"] = "list";
+        addId(jo, id);
+        return json11::Json(jo);
+    }
+
+    static json11::Json
+    fromRpcResponse_List(const Vamp::HostExt::ListResponse &resp,
+                         const json11::Json &id) {
+
+        json11::Json::object jo;
+        markRPC(jo);
+
+        json11::Json::array arr;
+        for (const auto &a: resp.available) {
+            arr.push_back(fromPluginStaticData(a));
+        }
+        json11::Json::object po;
+        po["available"] = arr;
+
+        jo["method"] = "list";
+        jo["result"] = po;
+        addId(jo, id);
+        return json11::Json(jo);
+    }
+    
+    static json11::Json
+    fromRpcRequest_Load(const Vamp::HostExt::LoadRequest &req,
+                        const json11::Json &id) {
+
+        json11::Json::object jo;
+        markRPC(jo);
+
+        jo["method"] = "load";
+        jo["params"] = fromLoadRequest(req);
+        addId(jo, id);
+        return json11::Json(jo);
+    }    
+
+    static json11::Json
+    fromRpcResponse_Load(const Vamp::HostExt::LoadResponse &resp,
+                         const PluginHandleMapper &pmapper,
+                         const json11::Json &id) {
+
+        if (resp.plugin) {
+
+            json11::Json::object jo;
+            markRPC(jo);
+
+            jo["method"] = "load";
+            jo["result"] = fromLoadResponse(resp, pmapper);
+            addId(jo, id);
+            return json11::Json(jo);
+            
+        } else {
+            return fromError("Failed to load plugin", RRType::Load, id);
+        }
+    }
+
+    static json11::Json
+    fromRpcRequest_Configure(const Vamp::HostExt::ConfigurationRequest &req,
+                             const PluginHandleMapper &pmapper,
+                             const json11::Json &id) {
+
+        json11::Json::object jo;
+        markRPC(jo);
+
+        jo["method"] = "configure";
+        jo["params"] = fromConfigurationRequest(req, pmapper);
+        addId(jo, id);
+        return json11::Json(jo);
+    }    
+
+    static json11::Json
+    fromRpcResponse_Configure(const Vamp::HostExt::ConfigurationResponse &resp,
+                              const PluginHandleMapper &pmapper,
+                              const json11::Json &id) {
+
+        if (!resp.outputs.empty()) {
+        
+            json11::Json::object jo;
+            markRPC(jo);
+
+            jo["method"] = "configure";
+            jo["result"] = fromConfigurationResponse(resp, pmapper);
+            addId(jo, id);
+            return json11::Json(jo);
+
+        } else {
+            return fromError("Failed to configure plugin", RRType::Configure, id);
+        }
+    }
+    
+    static json11::Json
+    fromRpcRequest_Process(const Vamp::HostExt::ProcessRequest &req,
+                           const PluginHandleMapper &pmapper,
+                           BufferSerialisation serialisation,
+                           const json11::Json &id) {
+
+        json11::Json::object jo;
+        markRPC(jo);
+
+        jo["method"] = "process";
+        jo["params"] = fromProcessRequest(req, pmapper, serialisation);
+        addId(jo, id);
+        return json11::Json(jo);
+    }    
+
+    static json11::Json
+    fromRpcResponse_Process(const Vamp::HostExt::ProcessResponse &resp,
+                            const PluginHandleMapper &pmapper,
+                            BufferSerialisation serialisation,
+                            const json11::Json &id) {
+        
+        json11::Json::object jo;
+        markRPC(jo);
+
+        json11::Json::object po;
+        po["handle"] = pmapper.pluginToHandle(resp.plugin);
+        po["features"] = fromFeatureSet(resp.features,
+                                        *pmapper.pluginToOutputIdMapper(resp.plugin),
+                                        serialisation);
+        jo["method"] = "process";
+        jo["result"] = po;
+        addId(jo, id);
+        return json11::Json(jo);
+    }
+    
+    static json11::Json
+    fromRpcRequest_Finish(const Vamp::HostExt::FinishRequest &req,
+                          const PluginHandleMapper &pmapper,
+                          const json11::Json &id) {
+
+        json11::Json::object jo;
+        markRPC(jo);
+
+        json11::Json::object fo;
+        fo["handle"] = pmapper.pluginToHandle(req.plugin);
+
+        jo["method"] = "finish";
+        jo["params"] = fo;
+        addId(jo, id);
+        return json11::Json(jo);
+    }    
+    
+    static json11::Json
+    fromRpcResponse_Finish(const Vamp::HostExt::ProcessResponse &resp,
+                           const PluginHandleMapper &pmapper,
+                           BufferSerialisation serialisation,
+                           const json11::Json &id) {
+
+        json11::Json::object jo;
+        markRPC(jo);
+
+        json11::Json::object po;
+        po["handle"] = pmapper.pluginToHandle(resp.plugin);
+        po["features"] = fromFeatureSet(resp.features,
+                                        *pmapper.pluginToOutputIdMapper(resp.plugin),
+                                        serialisation);
+        jo["method"] = "finish";
+        jo["result"] = po;
+        addId(jo, id);
+        return json11::Json(jo);
+    }
+
+    static json11::Json
+    fromError(std::string errorText,
+              RRType responseType,
+              const json11::Json &id) {
+
+        json11::Json::object jo;
+        markRPC(jo);
+
+        std::string type;
+
+        if (responseType == RRType::List) type = "list";
+        else if (responseType == RRType::Load) type = "load";
+        else if (responseType == RRType::Configure) type = "configure";
+        else if (responseType == RRType::Process) type = "process";
+        else if (responseType == RRType::Finish) type = "finish";
+        else type = "invalid";
+
+        json11::Json::object eo;
+        eo["code"] = 0;
+        eo["message"] = 
+            std::string("error in ") + type + " request: " + errorText;
+
+        jo["method"] = type;
+        jo["error"] = eo;
+        addId(jo, id);
+        return json11::Json(jo);
+    }
+
+    static RRType
+    getRequestResponseType(json11::Json j, std::string &err) {
+
+        if (!j["method"].is_string()) {
+            err = "string expected for method";
+            return RRType::NotValid;
+        }
+        
+        std::string type = j["method"].string_value();
+
+	if (type == "list") return RRType::List;
+	else if (type == "load") return RRType::Load;
+	else if (type == "configure") return RRType::Configure;
+	else if (type == "process") return RRType::Process;
+	else if (type == "finish") return RRType::Finish;
+        else if (type == "invalid") return RRType::NotValid;
+	else {
+	    err = "unknown or unexpected request/response type \"" + type + "\"";
+            return RRType::NotValid;
+	}
+    }
+
+    static void
+    toRpcRequest_List(json11::Json j, std::string &err) {
+        checkTypeField(j, "list", err);
+    }
+
+    static Vamp::HostExt::ListResponse
+    toRpcResponse_List(json11::Json j, std::string &err) {
+
+        Vamp::HostExt::ListResponse resp;
+        if (successful(j, err) && !failed(err)) {
+            for (const auto &a: j["result"]["available"].array_items()) {
+                resp.available.push_back(toPluginStaticData(a, err));
+                if (failed(err)) return {};
+            }
+        }
+        
+        return resp;
+    }
+
+    static Vamp::HostExt::LoadRequest
+    toRpcRequest_Load(json11::Json j, std::string &err) {
+        
+        checkTypeField(j, "load", err);
+        if (failed(err)) return {};
+        return toLoadRequest(j["params"], err);
+    }
+    
+    static Vamp::HostExt::LoadResponse
+    toRpcResponse_Load(json11::Json j,
+                        const PluginHandleMapper &pmapper,
+                        std::string &err) {
+        
+        Vamp::HostExt::LoadResponse resp;
+        if (successful(j, err) && !failed(err)) {
+            resp = toLoadResponse(j["result"], pmapper, err);
+        }
+        return resp;
+    }
+    
+    static Vamp::HostExt::ConfigurationRequest
+    toRpcRequest_Configure(json11::Json j,
+                            const PluginHandleMapper &pmapper,
+                            std::string &err) {
+        
+        checkTypeField(j, "configure", err);
+        if (failed(err)) return {};
+        return toConfigurationRequest(j["params"], pmapper, err);
+    }
+    
+    static Vamp::HostExt::ConfigurationResponse
+    toRpcResponse_Configure(json11::Json j,
+                             const PluginHandleMapper &pmapper,
+                             std::string &err) {
+        
+        Vamp::HostExt::ConfigurationResponse resp;
+        if (successful(j, err) && !failed(err)) {
+            resp = toConfigurationResponse(j["result"], pmapper, err);
+        }
+        return resp;
+    }
+    
+    static Vamp::HostExt::ProcessRequest
+    toRpcRequest_Process(json11::Json j, const PluginHandleMapper &pmapper,
+                          BufferSerialisation &serialisation, std::string &err) {
+        
+        checkTypeField(j, "process", err);
+        if (failed(err)) return {};
+        return toProcessRequest(j["params"], pmapper, serialisation, err);
+    }
+    
+    static Vamp::HostExt::ProcessResponse
+    toRpcResponse_Process(json11::Json j,
+                           const PluginHandleMapper &pmapper,
+                           BufferSerialisation &serialisation, std::string &err) {
+        
+        Vamp::HostExt::ProcessResponse resp;
+        if (successful(j, err) && !failed(err)) {
+            auto jc = j["result"];
+            auto h = jc["handle"].int_value();
+            resp.plugin = pmapper.handleToPlugin(h);
+            resp.features = toFeatureSet(jc["features"],
+                                         *pmapper.handleToOutputIdMapper(h),
+                                         serialisation, err);
+        }
+        return resp;
+    }
+    
+    static Vamp::HostExt::FinishRequest
+    toRpcRequest_Finish(json11::Json j, const PluginHandleMapper &pmapper,
+                         std::string &err) {
+        
+        checkTypeField(j, "finish", err);
+        if (failed(err)) return {};
+        Vamp::HostExt::FinishRequest req;
+        req.plugin = pmapper.handleToPlugin
+            (j["params"]["handle"].int_value());
+        return req;
+    }
+    
+    static Vamp::HostExt::ProcessResponse
+    toRpcResponse_Finish(json11::Json j,
+                          const PluginHandleMapper &pmapper,
+                          BufferSerialisation &serialisation, std::string &err) {
+        
+        Vamp::HostExt::ProcessResponse resp;
+        if (successful(j, err) && !failed(err)) {
+            auto jc = j["result"];
+            auto h = jc["handle"].int_value();
+            resp.plugin = pmapper.handleToPlugin(h);
+            resp.features = toFeatureSet(jc["features"],
+                                         *pmapper.handleToOutputIdMapper(h),
+                                         serialisation, err);
+        }
+        return resp;
+    }
+};
+
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-server/convert.cpp	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,610 @@
+
+#include "vamp-json/VampJson.h"
+#include "vamp-capnp/VampnProto.h"
+#include "vamp-support/RequestOrResponse.h"
+#include "vamp-support/PreservingPluginHandleMapper.h"
+
+#include <iostream>
+#include <sstream>
+#include <stdexcept>
+
+using namespace std;
+using namespace json11;
+using namespace piper;
+
+void usage()
+{
+    string myname = "piper-convert";
+    cerr << "\n" << myname <<
+	": Validate and convert Piper request and response messages\n\n"
+	"    Usage: " << myname << " [-i <informat>] [-o <outformat>] request\n"
+	"           " << myname << " [-i <informat>] [-o <outformat>] response\n\n"
+	"    where\n"
+	"       <informat>: the format to read from stdin\n"
+	"           (\"json\" or \"capnp\", default is \"json\")\n"
+	"       <outformat>: the format to convert to and write to stdout\n"
+	"           (\"json\", \"json-b64\" or \"capnp\", default is \"json\")\n"
+	"       request|response: whether messages are Vamp request or response type\n\n"
+	"If <informat> and <outformat> differ, convert from <informat> to <outformat>.\n"
+	"If <informat> and <outformat> are the same, just check validity of incoming\n"
+	"messages and pass them to output.\n\n"
+	"Specifying \"json-b64\" as output format forces base64 encoding for process and\n"
+	"feature blocks, unlike the \"json\" output format which uses text encoding.\n"
+	"The \"json\" input format accepts either.\n\n";
+
+    exit(2);
+}
+
+Json
+convertRequestJson(string input, string &err)
+{
+    Json j = Json::parse(input, err);
+    if (err != "") {
+	err = "invalid json: " + err;
+	return {};
+    }
+    if (!j.is_object()) {
+	err = "object expected at top level";
+    } else if (!j["method"].is_string()) {
+	err = "string expected for method field";
+    } else if (!j["params"].is_null() && !j["params"].is_object()) {
+	err = "object expected for params field";
+    }
+    return j;
+}
+
+Json
+convertResponseJson(string input, string &err)
+{
+    Json j = Json::parse(input, err);
+    if (err != "") {
+	err = "invalid json: " + err;
+	return {};
+    }
+    if (!j.is_object()) {
+	err = "object expected at top level";
+    } else {
+        if (!j["result"].is_object()) {
+            if (!j["error"].is_object()) {
+                err = "expected either result or error object";
+            }
+        }
+    }
+    return j;
+}
+
+//!!! Lots of potential for refactoring the conversion classes based
+//!!! on the common matter in the following eight functions...
+
+PreservingPluginHandleMapper mapper;
+
+static RequestOrResponse::RpcId
+readJsonId(const Json &j)
+{
+    RequestOrResponse::RpcId id;
+
+    if (j["id"].is_number()) {
+        id.type = RequestOrResponse::RpcId::Number;
+        id.number = j["id"].number_value();
+    } else if (j["id"].is_string()) {
+        id.type = RequestOrResponse::RpcId::Tag;
+        id.tag = j["id"].string_value();
+    } else {
+        id.type = RequestOrResponse::RpcId::Absent;
+    }
+
+    return id;
+}
+
+static Json
+writeJsonId(const RequestOrResponse::RpcId &id)
+{
+    if (id.type == RequestOrResponse::RpcId::Number) {
+        return id.number;
+    } else if (id.type == RequestOrResponse::RpcId::Tag) {
+        return id.tag;
+    } else {
+        return Json();
+    }
+}
+
+template <typename Reader>
+static RequestOrResponse::RpcId
+readCapnpId(const Reader &r)
+{
+    int number;
+    string tag;
+    switch (r.getId().which()) {
+    case RpcRequest::Id::Which::NUMBER:
+        number = r.getId().getNumber();
+        return { RequestOrResponse::RpcId::Number, number, "" };
+    case RpcRequest::Id::Which::TAG:
+        tag = r.getId().getTag();
+        return { RequestOrResponse::RpcId::Tag, 0, tag };
+    case RpcRequest::Id::Which::NONE:
+        return { RequestOrResponse::RpcId::Absent, 0, "" };
+    }
+    return {};
+}
+
+template <typename Builder>
+static void
+buildCapnpId(Builder &b, const RequestOrResponse::RpcId &id)
+{
+    switch (id.type) {
+    case RequestOrResponse::RpcId::Number:
+        b.getId().setNumber(id.number);
+        break;
+    case RequestOrResponse::RpcId::Tag:
+        b.getId().setTag(id.tag);
+        break;
+    case RequestOrResponse::RpcId::Absent:
+        b.getId().setNone();
+        break;
+    }
+}
+
+RequestOrResponse
+readRequestJson(string &err)
+{
+    RequestOrResponse rr;
+    rr.direction = RequestOrResponse::Request;
+
+    string input;
+    if (!getline(cin, input)) {
+	// the EOF case, not actually an error
+	rr.type = RRType::NotValid;
+	return rr;
+    }
+    
+    Json j = convertRequestJson(input, err);
+    if (err != "") return {};
+
+    rr.type = VampJson::getRequestResponseType(j, err);
+    if (err != "") return {};
+
+    rr.id = readJsonId(j);
+
+    VampJson::BufferSerialisation serialisation =
+        VampJson::BufferSerialisation::Array;
+
+    switch (rr.type) {
+
+    case RRType::List:
+	VampJson::toRpcRequest_List(j, err); // type check only
+	break;
+    case RRType::Load:
+	rr.loadRequest = VampJson::toRpcRequest_Load(j, err);
+	break;
+    case RRType::Configure:
+	rr.configurationRequest = VampJson::toRpcRequest_Configure(j, mapper, err);
+	break;
+    case RRType::Process:
+	rr.processRequest = VampJson::toRpcRequest_Process(j, mapper, serialisation, err);
+	break;
+    case RRType::Finish:
+	rr.finishRequest = VampJson::toRpcRequest_Finish(j, mapper, err);
+	break;
+    case RRType::NotValid:
+	break;
+    }
+
+    return rr;
+}
+
+void
+writeRequestJson(RequestOrResponse &rr, bool useBase64)
+{
+    Json j;
+
+    VampJson::BufferSerialisation serialisation =
+        (useBase64 ?
+         VampJson::BufferSerialisation::Base64 :
+         VampJson::BufferSerialisation::Array);
+
+    Json id = writeJsonId(rr.id);
+    
+    switch (rr.type) {
+
+    case RRType::List:
+	j = VampJson::fromRpcRequest_List(id);
+	break;
+    case RRType::Load:
+	j = VampJson::fromRpcRequest_Load(rr.loadRequest, id);
+	break;
+    case RRType::Configure:
+	j = VampJson::fromRpcRequest_Configure(rr.configurationRequest, mapper, id);
+	break;
+    case RRType::Process:
+	j = VampJson::fromRpcRequest_Process
+	    (rr.processRequest, mapper, serialisation, id);
+	break;
+    case RRType::Finish:
+	j = VampJson::fromRpcRequest_Finish(rr.finishRequest, mapper, id);
+	break;
+    case RRType::NotValid:
+	break;
+    }
+
+    cout << j.dump() << endl;
+}
+
+RequestOrResponse
+readResponseJson(string &err)
+{
+    RequestOrResponse rr;
+    rr.direction = RequestOrResponse::Response;
+
+    string input;
+    if (!getline(cin, input)) {
+	// the EOF case, not actually an error
+	rr.type = RRType::NotValid;
+	return rr;
+    }
+
+    Json j = convertResponseJson(input, err);
+    if (err != "") return {};
+
+    rr.type = VampJson::getRequestResponseType(j, err);
+    if (err != "") return {};
+
+    rr.id = readJsonId(j);
+    
+    VampJson::BufferSerialisation serialisation =
+        VampJson::BufferSerialisation::Array;
+
+    rr.success = j["success"].bool_value();
+    rr.errorText = j["errorText"].string_value();
+
+    switch (rr.type) {
+
+    case RRType::List:
+	rr.listResponse = VampJson::toRpcResponse_List(j, err);
+	break;
+    case RRType::Load:
+	rr.loadResponse = VampJson::toRpcResponse_Load(j, mapper, err);
+	break;
+    case RRType::Configure:
+	rr.configurationResponse = VampJson::toRpcResponse_Configure(j, mapper, err);
+	break;
+    case RRType::Process: 
+	rr.processResponse = VampJson::toRpcResponse_Process(j, mapper, serialisation, err);
+	break;
+    case RRType::Finish:
+	rr.finishResponse = VampJson::toRpcResponse_Finish(j, mapper, serialisation, err);
+	break;
+    case RRType::NotValid:
+	break;
+    }
+
+    return rr;
+}
+
+void
+writeResponseJson(RequestOrResponse &rr, bool useBase64)
+{
+    Json j;
+
+    VampJson::BufferSerialisation serialisation =
+        (useBase64 ?
+         VampJson::BufferSerialisation::Base64 :
+         VampJson::BufferSerialisation::Array);
+
+    Json id = writeJsonId(rr.id);
+
+    if (!rr.success) {
+
+	j = VampJson::fromError(rr.errorText, rr.type, id);
+
+    } else {
+    
+	switch (rr.type) {
+
+	case RRType::List:
+	    j = VampJson::fromRpcResponse_List(rr.listResponse, id);
+	    break;
+	case RRType::Load:
+	    j = VampJson::fromRpcResponse_Load(rr.loadResponse, mapper, id);
+	    break;
+	case RRType::Configure:
+	    j = VampJson::fromRpcResponse_Configure(rr.configurationResponse,
+                                                    mapper, id);
+	    break;
+	case RRType::Process:
+	    j = VampJson::fromRpcResponse_Process
+		(rr.processResponse, mapper, serialisation, id);
+	    break;
+	case RRType::Finish:
+	    j = VampJson::fromRpcResponse_Finish
+		(rr.finishResponse, mapper, serialisation, id);
+	    break;
+	case RRType::NotValid:
+	    break;
+	}
+    }
+    
+    cout << j.dump() << endl;
+}
+
+RequestOrResponse
+readRequestCapnp(kj::BufferedInputStreamWrapper &buffered)
+{
+    RequestOrResponse rr;
+    rr.direction = RequestOrResponse::Request;
+
+    ::capnp::InputStreamMessageReader message(buffered);
+    RpcRequest::Reader reader = message.getRoot<RpcRequest>();
+    
+    rr.type = VampnProto::getRequestResponseType(reader);
+    rr.id = readCapnpId(reader);
+
+    switch (rr.type) {
+
+    case RRType::List:
+	VampnProto::readRpcRequest_List(reader); // type check only
+	break;
+    case RRType::Load:
+	VampnProto::readRpcRequest_Load(rr.loadRequest, reader);
+	break;
+    case RRType::Configure:
+	VampnProto::readRpcRequest_Configure(rr.configurationRequest,
+					      reader, mapper);
+	break;
+    case RRType::Process:
+	VampnProto::readRpcRequest_Process(rr.processRequest, reader, mapper);
+	break;
+    case RRType::Finish:
+	VampnProto::readRpcRequest_Finish(rr.finishRequest, reader, mapper);
+	break;
+    case RRType::NotValid:
+	break;
+    }
+
+    return rr;
+}
+
+void
+writeRequestCapnp(RequestOrResponse &rr)
+{
+    ::capnp::MallocMessageBuilder message;
+    RpcRequest::Builder builder = message.initRoot<RpcRequest>();
+
+    buildCapnpId(builder, rr.id);
+    
+    switch (rr.type) {
+
+    case RRType::List:
+	VampnProto::buildRpcRequest_List(builder);
+	break;
+    case RRType::Load:
+	VampnProto::buildRpcRequest_Load(builder, rr.loadRequest);
+	break;
+    case RRType::Configure:
+	VampnProto::buildRpcRequest_Configure(builder,
+                                              rr.configurationRequest, mapper);
+	break;
+    case RRType::Process:
+	VampnProto::buildRpcRequest_Process(builder, rr.processRequest, mapper);
+	break;
+    case RRType::Finish:
+	VampnProto::buildRpcRequest_Finish(builder, rr.finishRequest, mapper);
+	break;
+    case RRType::NotValid:
+	break;
+    }
+
+    writeMessageToFd(1, message);
+}
+
+RequestOrResponse
+readResponseCapnp(kj::BufferedInputStreamWrapper &buffered)
+{
+    RequestOrResponse rr;
+    rr.direction = RequestOrResponse::Response;
+
+    ::capnp::InputStreamMessageReader message(buffered);
+    RpcResponse::Reader reader = message.getRoot<RpcResponse>();
+    
+    rr.type = VampnProto::getRequestResponseType(reader);
+    rr.success = true;
+    rr.errorText = "";
+    rr.id = readCapnpId(reader);
+    int errorCode = 0;
+
+    switch (rr.type) {
+
+    case RRType::List:
+	VampnProto::readRpcResponse_List(rr.listResponse, reader);
+	break;
+    case RRType::Load:
+	VampnProto::readRpcResponse_Load(rr.loadResponse, reader, mapper);
+	break;
+    case RRType::Configure:
+	VampnProto::readRpcResponse_Configure(rr.configurationResponse,
+					       reader, mapper);
+	break;
+    case RRType::Process:
+	VampnProto::readRpcResponse_Process(rr.processResponse, reader, mapper);
+	break;
+    case RRType::Finish:
+	VampnProto::readRpcResponse_Finish(rr.finishResponse, reader, mapper);
+	break;
+    case RRType::NotValid:
+        // error
+        rr.success = false;
+        VampnProto::readRpcResponse_Error(errorCode, rr.errorText, reader);
+	break;
+    }
+
+    return rr;
+}
+
+void
+writeResponseCapnp(RequestOrResponse &rr)
+{
+    ::capnp::MallocMessageBuilder message;
+    RpcResponse::Builder builder = message.initRoot<RpcResponse>();
+
+    buildCapnpId(builder, rr.id);
+
+    if (!rr.success) {
+
+	VampnProto::buildRpcResponse_Error(builder, rr.errorText, rr.type);
+
+    } else {
+	
+	switch (rr.type) {
+
+	case RRType::List:
+	    VampnProto::buildRpcResponse_List(builder, rr.listResponse);
+	    break;
+	case RRType::Load:
+	    VampnProto::buildRpcResponse_Load(builder, rr.loadResponse, mapper);
+	    break;
+	case RRType::Configure:
+	    VampnProto::buildRpcResponse_Configure(builder, rr.configurationResponse, mapper);
+	    break;
+	case RRType::Process:
+	    VampnProto::buildRpcResponse_Process(builder, rr.processResponse, mapper);
+	    break;
+	case RRType::Finish:
+	    VampnProto::buildRpcResponse_Finish(builder, rr.finishResponse, mapper);
+	    break;
+	case RRType::NotValid:
+	    break;
+	}
+    }
+    
+    writeMessageToFd(1, message);
+}
+
+RequestOrResponse
+readInputJson(RequestOrResponse::Direction direction, string &err)
+{
+    if (direction == RequestOrResponse::Request) {
+	return readRequestJson(err);
+    } else {
+	return readResponseJson(err);
+    }
+}
+
+RequestOrResponse
+readInputCapnp(RequestOrResponse::Direction direction)
+{
+    static kj::FdInputStream stream(0); // stdin
+    static kj::BufferedInputStreamWrapper buffered(stream);
+
+    if (buffered.tryGetReadBuffer() == nullptr) {
+	return {};
+    }
+    
+    if (direction == RequestOrResponse::Request) {
+	return readRequestCapnp(buffered);
+    } else {
+	return readResponseCapnp(buffered);
+    }
+}
+
+RequestOrResponse
+readInput(string format, RequestOrResponse::Direction direction)
+{
+    if (format == "json") {
+	string err;
+	auto result = readInputJson(direction, err);
+	if (err != "") throw runtime_error(err);
+	else return result;
+    } else if (format == "capnp") {
+	return readInputCapnp(direction);
+    } else {
+	throw runtime_error("unknown input format \"" + format + "\"");
+    }
+}
+
+void
+writeOutput(string format, RequestOrResponse &rr)
+{
+    if (format == "json") {
+	if (rr.direction == RequestOrResponse::Request) {
+	    writeRequestJson(rr, false);
+	} else {
+	    writeResponseJson(rr, false);
+	}
+    } else if (format == "json-b64") {
+	if (rr.direction == RequestOrResponse::Request) {
+	    writeRequestJson(rr, true);
+	} else {
+	    writeResponseJson(rr, true);
+	}
+    } else if (format == "capnp") {
+	if (rr.direction == RequestOrResponse::Request) {
+	    writeRequestCapnp(rr);
+	} else {
+	    writeResponseCapnp(rr);
+	}
+    } else {
+	throw runtime_error("unknown output format \"" + format + "\"");
+    }
+}
+
+int main(int argc, char **argv)
+{
+    if (argc < 2) {
+	usage();
+    }
+
+    string informat = "json", outformat = "json";
+    RequestOrResponse::Direction direction = RequestOrResponse::Request;
+    bool haveDirection = false;
+    
+    for (int i = 1; i < argc; ++i) {
+
+	string arg = argv[i];
+	bool final = (i + 1 == argc);
+	
+	if (arg == "-i") {
+	    if (final) usage();
+	    else informat = argv[++i];
+
+	} else if (arg == "-o") {
+	    if (final) usage();
+	    else outformat = argv[++i];
+
+	} else if (arg == "request") {
+	    direction = RequestOrResponse::Request;
+	    haveDirection = true;
+
+	} else if (arg == "response") {
+	    direction = RequestOrResponse::Response;
+	    haveDirection = true;
+	    
+	} else {
+	    usage();
+	}
+    }
+
+    if (informat == "" || outformat == "" || !haveDirection) {
+	usage();
+    }
+
+    while (true) {
+
+	try {
+
+	    RequestOrResponse rr = readInput(informat, direction);
+
+	    // NotValid without an exception indicates EOF:
+	    if (rr.type == RRType::NotValid) break;
+
+	    writeOutput(outformat, rr);
+	    
+	} catch (std::exception &e) {
+
+	    cerr << "Error: " << e.what() << endl;
+	    exit(1);
+	}
+    }
+
+    exit(0);
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-server/server.cpp	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,303 @@
+
+#include "vamp-capnp/VampnProto.h"
+#include "vamp-support/RequestOrResponse.h"
+#include "vamp-support/CountingPluginHandleMapper.h"
+
+#include <iostream>
+#include <sstream>
+#include <stdexcept>
+
+#include <map>
+#include <set>
+
+using namespace std;
+using namespace piper;
+using namespace Vamp;
+using namespace Vamp::HostExt;
+
+void usage()
+{
+    string myname = "piper-vamp-server";
+    cerr << "\n" << myname <<
+	": Load and run Vamp plugins in response to messages from stdin\n\n"
+	"    Usage: " << myname << "\n\n"
+	"Expects Piper request messages in Cap'n Proto packed format on stdin,\n"
+	"and writes Piper response messages in the same format to stdout.\n\n";
+
+    exit(2);
+}
+
+static CountingPluginHandleMapper mapper;
+
+static RequestOrResponse::RpcId readId(const RpcRequest::Reader &r)
+{
+    int number;
+    string tag;
+    switch (r.getId().which()) {
+    case RpcRequest::Id::Which::NUMBER:
+        number = r.getId().getNumber();
+        return { RequestOrResponse::RpcId::Number, number, "" };
+    case RpcRequest::Id::Which::TAG:
+        tag = r.getId().getTag();
+        return { RequestOrResponse::RpcId::Tag, 0, tag };
+    case RpcRequest::Id::Which::NONE:
+        return { RequestOrResponse::RpcId::Absent, 0, "" };
+    }
+    return {};
+}
+
+static void buildId(RpcResponse::Builder &b, const RequestOrResponse::RpcId &id)
+{
+    switch (id.type) {
+    case RequestOrResponse::RpcId::Number:
+        b.getId().setNumber(id.number);
+        break;
+    case RequestOrResponse::RpcId::Tag:
+        b.getId().setTag(id.tag);
+        break;
+    case RequestOrResponse::RpcId::Absent:
+        b.getId().setNone();
+        break;
+    }
+}
+
+RequestOrResponse
+readRequestCapnp()
+{
+    RequestOrResponse rr;
+    rr.direction = RequestOrResponse::Request;
+
+    static kj::FdInputStream stream(0); // stdin
+    static kj::BufferedInputStreamWrapper buffered(stream);
+
+    if (buffered.tryGetReadBuffer() == nullptr) {
+	rr.type = RRType::NotValid;
+	return rr;
+    }
+
+    ::capnp::InputStreamMessageReader message(buffered);
+    RpcRequest::Reader reader = message.getRoot<RpcRequest>();
+    
+    rr.type = VampnProto::getRequestResponseType(reader);
+    rr.id = readId(reader);
+
+    switch (rr.type) {
+
+    case RRType::List:
+	VampnProto::readRpcRequest_List(reader); // type check only
+	break;
+    case RRType::Load:
+	VampnProto::readRpcRequest_Load(rr.loadRequest, reader);
+	break;
+    case RRType::Configure:
+	VampnProto::readRpcRequest_Configure(rr.configurationRequest,
+					      reader, mapper);
+	break;
+    case RRType::Process:
+	VampnProto::readRpcRequest_Process(rr.processRequest, reader, mapper);
+	break;
+    case RRType::Finish:
+	VampnProto::readRpcRequest_Finish(rr.finishRequest, reader, mapper);
+	break;
+    case RRType::NotValid:
+	break;
+    }
+
+    return rr;
+}
+
+void
+writeResponseCapnp(RequestOrResponse &rr)
+{
+    ::capnp::MallocMessageBuilder message;
+    RpcResponse::Builder builder = message.initRoot<RpcResponse>();
+
+    buildId(builder, rr.id);
+    
+    if (!rr.success) {
+
+	VampnProto::buildRpcResponse_Error(builder, rr.errorText, rr.type);
+
+    } else {
+	
+	switch (rr.type) {
+
+	case RRType::List:
+	    VampnProto::buildRpcResponse_List(builder, rr.listResponse);
+	    break;
+	case RRType::Load:
+	    VampnProto::buildRpcResponse_Load(builder, rr.loadResponse, mapper);
+	    break;
+	case RRType::Configure:
+	    VampnProto::buildRpcResponse_Configure(builder, rr.configurationResponse, mapper);
+	    break;
+	case RRType::Process:
+	    VampnProto::buildRpcResponse_Process(builder, rr.processResponse, mapper);
+	    break;
+	case RRType::Finish:
+	    VampnProto::buildRpcResponse_Finish(builder, rr.finishResponse, mapper);
+	    break;
+	case RRType::NotValid:
+	    break;
+	}
+    }
+    
+    writeMessageToFd(1, message);
+}
+
+void
+writeExceptionCapnp(const std::exception &e, RRType type)
+{
+    ::capnp::MallocMessageBuilder message;
+    RpcResponse::Builder builder = message.initRoot<RpcResponse>();
+    VampnProto::buildRpcResponse_Exception(builder, e, type);
+    
+    writeMessageToFd(1, message);
+}
+
+RequestOrResponse
+handleRequest(const RequestOrResponse &request)
+{
+    RequestOrResponse response;
+    response.direction = RequestOrResponse::Response;
+    response.type = request.type;
+
+    auto loader = PluginLoader::getInstance();
+
+    switch (request.type) {
+
+    case RRType::List:
+	response.listResponse = loader->listPluginData();
+	response.success = true;
+	break;
+
+    case RRType::Load:
+	response.loadResponse = loader->loadPlugin(request.loadRequest);
+	if (response.loadResponse.plugin != nullptr) {
+	    mapper.addPlugin(response.loadResponse.plugin);
+	    response.success = true;
+	}
+	break;
+	
+    case RRType::Configure:
+    {
+	auto &creq = request.configurationRequest;
+	auto h = mapper.pluginToHandle(creq.plugin);
+	if (mapper.isConfigured(h)) {
+	    throw runtime_error("plugin has already been configured");
+	}
+
+	response.configurationResponse = loader->configurePlugin(creq);
+	
+	if (!response.configurationResponse.outputs.empty()) {
+	    mapper.markConfigured
+		(h, creq.configuration.channelCount, creq.configuration.blockSize);
+	    response.success = true;
+	}
+	break;
+    }
+
+    case RRType::Process:
+    {
+	auto &preq = request.processRequest;
+	auto h = mapper.pluginToHandle(preq.plugin);
+	if (!mapper.isConfigured(h)) {
+	    throw runtime_error("plugin has not been configured");
+	}
+
+	int channels = int(preq.inputBuffers.size());
+	if (channels != mapper.getChannelCount(h)) {
+	    throw runtime_error("wrong number of channels supplied to process");
+	}
+		
+	const float **fbuffers = new const float *[channels];
+	for (int i = 0; i < channels; ++i) {
+	    if (int(preq.inputBuffers[i].size()) != mapper.getBlockSize(h)) {
+		delete[] fbuffers;
+		throw runtime_error("wrong block size supplied to process");
+	    }
+	    fbuffers[i] = preq.inputBuffers[i].data();
+	}
+
+	response.processResponse.plugin = preq.plugin;
+	response.processResponse.features =
+	    preq.plugin->process(fbuffers, preq.timestamp);
+	response.success = true;
+
+	delete[] fbuffers;
+	break;
+    }
+
+    case RRType::Finish:
+    {
+	response.finishResponse.plugin = request.finishRequest.plugin;
+	response.finishResponse.features =
+	    request.finishRequest.plugin->getRemainingFeatures();
+
+	// 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;
+    }
+
+    case RRType::NotValid:
+	break;
+    }
+    
+    return response;
+}
+
+int main(int argc, char **argv)
+{
+    if (argc != 1) {
+	usage();
+    }
+
+    while (true) {
+
+	RequestOrResponse request;
+	
+	try {
+
+	    request = readRequestCapnp();
+
+	    cerr << "piper-vamp-server: request received, of type "
+		 << int(request.type)
+		 << endl;
+	    
+	    // NotValid without an exception indicates EOF:
+	    if (request.type == RRType::NotValid) {
+		cerr << "piper-vamp-server: eof reached" << endl;
+		break;
+	    }
+
+	    RequestOrResponse response = handleRequest(request);
+            response.id = request.id;
+
+	    cerr << "piper-vamp-server: request handled, writing response"
+		 << endl;
+	    
+	    writeResponseCapnp(response);
+
+	    cerr << "piper-vamp-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 << "piper-vamp-server: error: " << e.what() << endl;
+
+	    writeExceptionCapnp(e, request.type);
+	    
+	    exit(1);
+	}
+    }
+
+    exit(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-support/CountingPluginHandleMapper.h	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,142 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Piper C++
+
+    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 PIPER_COUNTING_PLUGIN_HANDLE_MAPPER_H
+#define PIPER_COUNTING_PLUGIN_HANDLE_MAPPER_H
+
+#include "PluginHandleMapper.h"
+#include "PluginOutputIdMapper.h"
+#include "DefaultPluginOutputIdMapper.h"
+
+#include <set>
+#include <map>
+
+namespace piper {
+
+class CountingPluginHandleMapper : public PluginHandleMapper
+{
+public:
+    CountingPluginHandleMapper() : m_nextHandle(1) { }
+
+    void addPlugin(Vamp::Plugin *p) {
+        if (!p) return;
+	if (m_rplugins.find(p) == m_rplugins.end()) {
+	    Handle h = m_nextHandle++;
+	    m_plugins[h] = p;
+	    m_rplugins[p] = h;
+            m_outputMappers[h] =
+                std::make_shared<DefaultPluginOutputIdMapper>(p);
+	}
+    }
+
+    void removePlugin(Handle h) {
+	if (m_plugins.find(h) == m_plugins.end()) return;
+	Vamp::Plugin *p = m_plugins[h];
+        m_outputMappers.erase(h);
+	m_plugins.erase(h);
+	if (isConfigured(h)) {
+	    m_configuredPlugins.erase(h);
+	    m_channelCounts.erase(h);
+	}
+	m_rplugins.erase(p);
+    }
+    
+    Handle pluginToHandle(Vamp::Plugin *p) const noexcept {
+	if (m_rplugins.find(p) == m_rplugins.end()) {
+            return INVALID_HANDLE;
+	}
+	return m_rplugins.at(p);
+    }
+    
+    Vamp::Plugin *handleToPlugin(Handle h) const noexcept {
+	if (m_plugins.find(h) == m_plugins.end()) {
+            return nullptr;
+	}
+	return m_plugins.at(h);
+    }
+
+    const std::shared_ptr<PluginOutputIdMapper> pluginToOutputIdMapper
+    (Vamp::Plugin *p) const noexcept {
+        return handleToOutputIdMapper(pluginToHandle(p));
+    }
+
+    const std::shared_ptr<PluginOutputIdMapper> handleToOutputIdMapper
+    (Handle h) const noexcept {
+	if (h != INVALID_HANDLE &&
+            m_outputMappers.find(h) != m_outputMappers.end()) {
+            return m_outputMappers.at(h);
+        } else {
+	    return {};
+	}
+    }
+
+    bool isConfigured(Handle h) const noexcept {
+        if (h == INVALID_HANDLE) return false;
+	return m_configuredPlugins.find(h) != m_configuredPlugins.end();
+    }
+
+    void markConfigured(Handle h, int channelCount, int blockSize) {
+        if (h == INVALID_HANDLE) return;
+	m_configuredPlugins.insert(h);
+	m_channelCounts[h] = channelCount;
+	m_blockSizes[h] = blockSize;
+    }
+
+    int getChannelCount(Handle h) const noexcept {
+	if (m_channelCounts.find(h) == m_channelCounts.end()) {
+	    return 0;
+	}
+	return m_channelCounts.at(h);
+    }
+
+    int getBlockSize(Handle h) const noexcept {
+	if (m_blockSizes.find(h) == m_blockSizes.end()) {
+            return 0;
+	}
+	return m_blockSizes.at(h);
+    }
+    
+private:
+    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;
+};
+
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-support/DefaultPluginOutputIdMapper.h	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,73 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Piper C++
+
+    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 PIPER_DEFAULT_PLUGIN_ID_MAPPER_H
+#define PIPER_DEFAULT_PLUGIN_ID_MAPPER_H
+
+#include <vamp-hostsdk/Plugin.h>
+
+namespace piper {
+
+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 noexcept {
+	int n = int(m_ids.size());
+	for (int i = 0; i < n; ++i) {
+	    if (outputId == m_ids[i]) {
+		return i;
+	    }
+	}
+	return -1;
+    }
+
+    virtual std::string indexToId(int index) const noexcept {
+        if (index < 0 || size_t(index) >= m_ids.size()) return "";
+	return m_ids[index];
+    }
+
+private:
+    std::vector<std::string> m_ids;
+};
+
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-support/PluginHandleMapper.h	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,100 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Piper C++
+
+    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 PIPER_PLUGIN_HANDLE_MAPPER_H
+#define PIPER_PLUGIN_HANDLE_MAPPER_H
+
+#include "PluginOutputIdMapper.h"
+
+#include <vamp-hostsdk/Plugin.h>
+#include <memory>
+
+namespace piper {
+
+/**
+ * Convert plugin pointers to handles within some scope defined by the
+ * individual PluginHandleMapper implementation.
+ *
+ * The special handle 0 and the NULL plugin pointer are both used to
+ * represent "not found" and will be returned in any case where an
+ * unknown handle or plugin is requested.
+ *
+ * Note that the handle type must be representable as a JSON number,
+ * hence the use of a 32-bit rather than 64-bit int.
+ *
+ * This interface also includes methods for obtaining a
+ * PluginOutputIdMapper, \see PluginOutputIdMapper.
+ */
+
+class PluginHandleMapper
+{
+public:
+    typedef int32_t Handle;
+    const Handle INVALID_HANDLE = 0;
+
+    virtual ~PluginHandleMapper() noexcept { }
+
+    /**
+     * Look up and return the handle for a given plugin pointer.
+     * If the given pointer is null or not known, return INVALID_HANDLE.
+     */
+    virtual Handle pluginToHandle(Vamp::Plugin *) const noexcept = 0;
+
+    /**
+     * Look up and return the plugin for a given handle.
+     * If the given handle is INVALID_HANDLE or not known, return nullptr.
+     */
+    virtual Vamp::Plugin *handleToPlugin(Handle)  const noexcept = 0;
+
+    /**
+     * Return a shared pointer to a PluginOutputIdMapper
+     * implementation for the given plugin pointer.  If the given
+     * pointer is null or not known, return the null shared_ptr.
+     */
+    virtual const std::shared_ptr<PluginOutputIdMapper> pluginToOutputIdMapper
+    (Vamp::Plugin *p) const noexcept = 0;
+
+    /**
+     * Return a shared pointer to a PluginOutputIdMapper
+     * implementation for the given plugin handle.  If the given
+     * handle is INVALID_HANDLE or not known, return the null shared_ptr.
+     */
+    virtual const std::shared_ptr<PluginOutputIdMapper> handleToOutputIdMapper
+    (Handle h) const noexcept = 0;
+};
+
+}
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-support/PluginOutputIdMapper.h	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,66 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Piper C++
+
+    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 PIPER_PLUGIN_ID_MAPPER_H
+#define PIPER_PLUGIN_ID_MAPPER_H
+
+#include <vamp-hostsdk/Plugin.h>
+
+#include <map>
+#include <string>
+
+namespace piper {
+
+class PluginOutputIdMapper
+{
+public:
+    virtual ~PluginOutputIdMapper() { }
+
+    /**
+     * Return the index of the given output id in the plugin. The
+     * first output has index 0. If the given output id is unknown,
+     * return -1.
+     */
+    virtual int idToIndex(std::string outputId) const noexcept = 0;
+
+    /**
+     * Return the id of the output with the given index in the
+     * plugin. If the index is out of range, return the empty string.
+     */
+    virtual std::string indexToId(int index) const noexcept = 0;
+};
+
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-support/PreservingPluginHandleMapper.h	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,96 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Piper C++
+
+    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 PIPER_PRESERVING_PLUGIN_HANDLE_MAPPER_H
+#define PIPER_PRESERVING_PLUGIN_HANDLE_MAPPER_H
+
+#include "PluginHandleMapper.h"
+#include "PreservingPluginOutputIdMapper.h"
+
+#include <iostream>
+
+namespace piper {
+
+//!!! 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),
+        m_omapper(std::make_shared<PreservingPluginOutputIdMapper>()) { }
+
+    virtual Handle pluginToHandle(Vamp::Plugin *p) const noexcept {
+        if (!p) return INVALID_HANDLE;
+	if (p == m_plugin) return m_handle;
+	else {
+	    std::cerr << "PreservingPluginHandleMapper: p = " << p
+		      << " differs from saved m_plugin " << m_plugin
+		      << " (not returning saved handle " << m_handle << ")"
+		      << std::endl;
+            return INVALID_HANDLE;
+	}
+    }
+
+    virtual Vamp::Plugin *handleToPlugin(Handle h) const noexcept {
+        if (h == INVALID_HANDLE) return nullptr;
+	m_handle = h;
+	m_plugin = reinterpret_cast<Vamp::Plugin *>(h);
+	return m_plugin;
+    }
+
+    virtual const std::shared_ptr<PluginOutputIdMapper> pluginToOutputIdMapper
+    (Vamp::Plugin *p) const noexcept {
+        if (!p) return {};
+        return m_omapper;
+    }
+        
+    virtual const std::shared_ptr<PluginOutputIdMapper> handleToOutputIdMapper
+    (Handle h) const noexcept {
+        if (h == INVALID_HANDLE) return {};
+        return m_omapper;
+    }
+    
+private:
+    mutable Handle m_handle;
+    mutable Vamp::Plugin *m_plugin;
+    std::shared_ptr<PreservingPluginOutputIdMapper> m_omapper;
+};
+
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-support/PreservingPluginOutputIdMapper.h	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,76 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Piper C++
+
+    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 PIPER_PRESERVING_PLUGIN_OUTPUT_ID_MAPPER_H
+#define PIPER_PRESERVING_PLUGIN_OUTPUT_ID_MAPPER_H
+
+#include "PluginOutputIdMapper.h"
+
+#include <iostream>
+
+namespace piper {
+
+//!!! 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 noexcept {
+	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 noexcept {
+        if (index < 0 || size_t(index) >= m_ids.size()) return "";
+	return m_ids[index];
+    }
+
+private:
+    mutable std::vector<std::string> m_ids;
+};
+
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-support/RequestOrResponse.h	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,87 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Piper C++
+
+    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 PIPER_REQUEST_OR_RESPONSE_H
+#define PIPER_REQUEST_OR_RESPONSE_H
+
+#include "RequestResponseType.h"
+
+#include <vamp-hostsdk/PluginStaticData.h>
+#include <vamp-hostsdk/RequestResponse.h>
+
+#include <string>
+#include <vector>
+
+namespace piper {
+
+class RequestOrResponse
+{
+public:
+    enum Direction {
+	Request, Response
+    };
+
+    struct RpcId {
+        enum { Absent, Number, Tag } type;
+        int number;
+        std::string tag;
+    };
+    
+    RequestOrResponse() : // nothing by default
+	direction(Request),
+	type(RRType::NotValid),
+	success(false),
+        id({ RpcId::Absent, 0, "" })
+    { }
+
+    Direction direction;
+    RRType type;
+    bool success;
+    std::string errorText;
+    RpcId id;
+
+    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::HostExt::FinishRequest finishRequest;
+    Vamp::HostExt::ProcessResponse finishResponse;
+};
+
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-support/RequestResponseType.h	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,47 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Piper C++
+
+    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 PIPER_REQUEST_RESPONSE_TYPE_H
+#define PIPER_REQUEST_RESPONSE_TYPE_H
+
+namespace piper {
+
+enum class RRType {
+    List, Load, Configure, Process, Finish, NotValid
+};
+
+}
+
+#endif
+