changeset 48:1ed825597b6f

Move some things around (have not yet updated builds)
author Chris Cannam
date Tue, 08 Nov 2016 12:02:57 +0000
parents cedcaa258e22
children 93513dba1354
files PiperAdapter.h PiperPluginLibrary.cpp PiperPluginLibrary.h base64.js example.cpp examples/qm-vamp-plugins.cpp examples/silvet.cpp examples/vamp-example-plugins.cpp examples/vamp-test-plugin.cpp perf-test-node.js perf-test.html perf-test.js piper.map qm-vamp-plugins.cpp quick-test.cpp quick-test.html quick-test.js silvet.cpp src/PiperAdapter.h src/PiperPluginLibrary.cpp src/PiperPluginLibrary.h src/piper.map test/base64.js test/perf-test-node.js test/perf-test.html test/perf-test.js test/quick-test.cpp test/quick-test.html test/quick-test.js vamp-test-plugin.cpp
diffstat 30 files changed, 1638 insertions(+), 1638 deletions(-) [+]
line wrap: on
line diff
--- a/PiperAdapter.h	Mon Nov 07 14:49:05 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,147 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Piper Vamp JSON Adapter
-
-    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_ADAPTER_H
-#define PIPER_ADAPTER_H
-
-#include "vamp-support/PluginStaticData.h"
-#include "vamp-support/PluginConfiguration.h"
-#include "vamp-support/RequestResponse.h"
-
-#include <vamp-hostsdk/PluginInputDomainAdapter.h>
-#include <vamp-hostsdk/PluginBufferingAdapter.h>
-#include <vamp-hostsdk/PluginChannelAdapter.h>
-
-namespace piper_vamp_js { //!!! not a good name for this namespace
-
-class PiperAdapterInterface
-{
-public:
-    virtual std::string getLibraryName() const = 0;
-    virtual piper_vamp::PluginStaticData getStaticData() const = 0;
-    virtual piper_vamp::LoadResponse loadPlugin(piper_vamp::LoadRequest r) const = 0;
-    virtual Vamp::Plugin *createPlugin(float inputSampleRate) const = 0;
-};
-
-template <typename P>
-class PiperAdapterBase : public PiperAdapterInterface
-{
-    const int adaptInputDomain = 0x01;
-    const int adaptChannelCount = 0x02;
-    const int adaptBufferSize = 0x04;
-
-protected:
-    PiperAdapterBase(std::string libname) : m_soname(libname) { }
-    
-public:
-    virtual Vamp::Plugin *createPlugin(float inputSampleRate) const = 0;
-
-    virtual std::string getLibraryName() const override {
-        return m_soname;
-    }
-    
-    virtual piper_vamp::PluginStaticData getStaticData() const override {
-        Vamp::Plugin *p = createPlugin(44100.f);
-	auto data = piper_vamp::PluginStaticData::fromPlugin
-	    (m_soname + ":" + p->getIdentifier(),
-	     {}, //!!! todo: category - tricky one that
-	     p);
-        delete p;
-        return data;
-    }
-
-    virtual piper_vamp::LoadResponse loadPlugin(piper_vamp::LoadRequest r)
-        const override {
-	
-	// We assume the caller has guaranteed that the request is for
-	// the correct plugin (so we don't have to check the plugin
-	// key field here)
-
-	Vamp::Plugin *p = createPlugin(r.inputSampleRate);
-	
-	if (r.adapterFlags & adaptInputDomain) {
-	    if (p->getInputDomain() == Vamp::Plugin::FrequencyDomain) {
-		p = new Vamp::HostExt::PluginInputDomainAdapter(p);
-	    }
-	}
-
-	if (r.adapterFlags & adaptBufferSize) {
-	    p = new Vamp::HostExt::PluginBufferingAdapter(p);
-	}
-
-	if (r.adapterFlags & adaptChannelCount) {
-	    p = new Vamp::HostExt::PluginChannelAdapter(p);
-	}
-
-	piper_vamp::LoadResponse response;
-	response.plugin = p;
-
-	response.staticData = piper_vamp::PluginStaticData::fromPlugin
-	    (m_soname + ":" + p->getIdentifier(),
-	     {}, //!!! todo: category - tricky one that
-	     p);
-
-	int defaultChannels = 0;
-	if (p->getMinChannelCount() == p->getMaxChannelCount()) {
-	    defaultChannels = p->getMinChannelCount();
-	}
-    
-	response.defaultConfiguration = piper_vamp::PluginConfiguration::fromPlugin
-	    (p,
-	     defaultChannels,
-	     p->getPreferredStepSize(),
-	     p->getPreferredBlockSize());
-    
-	return response;
-    }
-    
-private:
-    std::string m_soname;
-};
-
-template <typename P>
-class PiperAdapter : public PiperAdapterBase<P>
-{
-public:
-    PiperAdapter(std::string libname) : PiperAdapterBase<P>(libname) { }
-    
-    virtual Vamp::Plugin *createPlugin(float inputSampleRate) const override {
-        return new P(inputSampleRate);
-    }
-};
-
-}
-
-#endif
-
--- a/PiperPluginLibrary.cpp	Mon Nov 07 14:49:05 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,349 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Piper Vamp JSON Adapter
-
-    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 "PiperPluginLibrary.h"
-#include "PiperAdapter.h"
-
-#include "vamp-json/VampJson.h"
-
-using namespace std;
-using namespace json11;
-using namespace piper_vamp;
-
-namespace piper_vamp_js { //!!! not good
-
-//!!! too many explicit namespaces here
-
-//!!! dup with piper-convert
-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";
-    }
-    return j;
-}
-
-PiperPluginLibrary::PiperPluginLibrary(vector<PiperAdapterInterface *> pp) :
-    m_useBase64(false)
-{
-    for (PiperAdapterInterface *p: pp) {
-	string key = p->getStaticData().pluginKey;
-	m_adapters[key] = p;
-    }
-}
-
-ListResponse
-PiperPluginLibrary::listPluginData(ListRequest req) const
-{
-    bool filtered = !req.from.empty();
-    ListResponse resp;
-    for (auto a: m_adapters) {
-        if (filtered) {
-            auto n = a.second->getLibraryName();
-            bool found = false;
-            for (const auto &f: req.from) {
-                if (f == n) {
-                    found = true;
-                    break;
-                }
-            }
-            if (!found) {
-                continue;
-            }
-        }
-	resp.available.push_back(a.second->getStaticData());
-    }
-    return resp;
-}
-
-LoadResponse
-PiperPluginLibrary::loadPlugin(LoadRequest req, string &err) const
-{
-    string key = req.pluginKey;
-    if (m_adapters.find(key) != m_adapters.end()) {
-        auto resp = m_adapters.at(key)->loadPlugin(req);
-        if (!resp.plugin) {
-            // This should not actually happen -- the load call here
-            // is just an object construction, not a dynamic load. But
-            // report it if it does...
-            err = "failed to construct plugin with key " + key;
-        }
-        return resp;
-    } else {
-	err = "no adapter for plugin key " + key;
-        return {};
-    }
-}
-
-ConfigurationResponse
-PiperPluginLibrary::configurePlugin(ConfigurationRequest req,
-                                      string &err) const
-{
-    for (PluginConfiguration::ParameterMap::const_iterator i =
-             req.configuration.parameterValues.begin();
-         i != req.configuration.parameterValues.end(); ++i) {
-        req.plugin->setParameter(i->first, i->second);
-    }
-
-    if (req.configuration.currentProgram != "") {
-        req.plugin->selectProgram(req.configuration.currentProgram);
-    }
-
-    ConfigurationResponse response;
-
-    response.plugin = req.plugin;
-
-    if (req.plugin->initialise(req.configuration.channelCount,
-                               req.configuration.stepSize,
-                               req.configuration.blockSize)) {
-        response.outputs = req.plugin->getOutputDescriptors();
-    } else {
-        err = "configuration failed (wrong channel count, step size, block size?)";
-    }
-
-    return response;
-}
-
-string
-PiperPluginLibrary::processRawImpl(int handle,
-                                     const float *const *inputBuffers,
-                                     int sec,
-                                     int nsec)
-{
-    Vamp::Plugin *plugin = m_mapper.handleToPlugin(handle);
-    if (!plugin) {
-        return VampJson::fromError("unknown plugin handle",
-                                   RRType::Process, Json())
-            .dump();
-    }
-    
-    if (!m_mapper.isConfigured(handle)) {
-        return VampJson::fromError("plugin has not been configured",
-                                   RRType::Process, Json())
-            .dump();
-    }
-
-    Vamp::RealTime timestamp(sec, nsec);
-
-    ProcessResponse resp;
-    resp.plugin = plugin;
-    resp.features = plugin->process(inputBuffers, timestamp);
-    
-    m_useBase64 = true;
-    
-    return VampJson::fromRpcResponse_Process
-        (resp, m_mapper,
-         VampJson::BufferSerialisation::Base64,
-         Json())
-        .dump();
-}
-
-string
-PiperPluginLibrary::requestJsonImpl(string req)
-{
-    string err;
-    
-    Json j = convertRequestJson(req, err);
-
-    // we don't care what this is, only that it is retained in the response:
-    auto id = j["id"];
-
-    Json rj;
-    if (err != "") {
-        return VampJson::fromError(err, RRType::NotValid, id).dump();
-    }
-    
-    RRType type = VampJson::getRequestResponseType(j, err);
-    if (err != "") {
-        return VampJson::fromError(err, RRType::NotValid, id).dump();
-    }
-
-    VampJson::BufferSerialisation serialisation =
-        (m_useBase64 ?
-         VampJson::BufferSerialisation::Base64 :
-         VampJson::BufferSerialisation::Array);
-    
-    switch (type) {
-
-    case RRType::List:
-    {
-        auto req = VampJson::toRpcRequest_List(j, err);
-        if (err != "") {
-            rj = VampJson::fromError(err, type, id);
-        } else {
-            rj = VampJson::fromRpcResponse_List(listPluginData(req), id);
-        }
-        break;
-    }
-
-    case RRType::Load:
-    {
-        auto req = VampJson::toRpcRequest_Load(j, err);
-        if (err != "") {
-            rj = VampJson::fromError(err, type, id);
-        } else {
-            auto resp = loadPlugin(req, err);
-            if (err != "") {
-                rj = VampJson::fromError(err, type, id);
-            } else {
-                m_mapper.addPlugin(resp.plugin);
-                rj = VampJson::fromRpcResponse_Load(resp, m_mapper, id);
-            }
-        }
-        break;
-    }
-
-    case RRType::Configure:
-    {
-        auto req = VampJson::toRpcRequest_Configure(j, m_mapper, err);
-        if (err != "") {
-            rj = VampJson::fromError(err, type, id);
-        } else {
-            auto h = m_mapper.pluginToHandle(req.plugin);
-            if (h == m_mapper.INVALID_HANDLE) {
-                rj = VampJson::fromError("unknown or invalid plugin handle", type, id);
-            } else if (m_mapper.isConfigured(h)) {
-                rj = VampJson::fromError("plugin has already been configured", type, id);
-            } else {
-                auto resp = configurePlugin(req, err);
-                if (err != "") {
-                    rj = VampJson::fromError(err, type, id);
-                } else {
-                    m_mapper.markConfigured(h,
-                                            req.configuration.channelCount,
-                                            req.configuration.blockSize);
-                    rj = VampJson::fromRpcResponse_Configure(resp, m_mapper, id);
-                }
-            }
-        }
-        break;
-    }
-
-    case RRType::Process:
-    {
-        VampJson::BufferSerialisation serialisation;
-            
-        auto req = VampJson::toRpcRequest_Process(j, m_mapper,
-                                                   serialisation, err);
-        if (err != "") {
-            rj = VampJson::fromError(err, type, id);
-        } else {
-            auto h = m_mapper.pluginToHandle(req.plugin);
-            int channels = int(req.inputBuffers.size());
-            if (h == m_mapper.INVALID_HANDLE) {
-                rj = VampJson::fromError("unknown or invalid plugin handle", type, id);
-            } else if (!m_mapper.isConfigured(h)) {
-                rj = VampJson::fromError("plugin has not been configured", type, id);
-            } else if (channels != m_mapper.getChannelCount(h)) {
-                rj = VampJson::fromError("wrong number of channels supplied", type, id);
-            } else {
-
-                if (serialisation == VampJson::BufferSerialisation::Base64) {
-                    m_useBase64 = true;
-                }
-
-                size_t blockSize = m_mapper.getBlockSize(h);
-                
-                const float **fbuffers = new const float *[channels];
-                for (int i = 0; i < channels; ++i) {
-                    if (req.inputBuffers[i].size() != blockSize) {
-                        delete[] fbuffers;
-                        fbuffers = 0;
-                        rj = VampJson::fromError("wrong block size supplied", type, id);
-                        break;
-                    }
-                    fbuffers[i] = req.inputBuffers[i].data();
-                }
-
-                if (fbuffers) {
-                    ProcessResponse resp;
-                    resp.plugin = req.plugin;
-                    resp.features = req.plugin->process(fbuffers, req.timestamp);
-                    delete[] fbuffers;
-                    rj = VampJson::fromRpcResponse_Process
-                        (resp, m_mapper, serialisation, id);
-                }
-            }
-        }
-        break;
-    }
-
-    case RRType::Finish:
-    {
-        auto req = VampJson::toRpcRequest_Finish(j, m_mapper, err);
-        if (err != "") {
-            rj = VampJson::fromError(err, type, id);
-        } else {
-            auto h = m_mapper.pluginToHandle(req.plugin);
-            if (h == m_mapper.INVALID_HANDLE) {
-                rj = VampJson::fromError("unknown or invalid plugin handle", type, id);
-            } else {
-
-                FinishResponse resp;
-                resp.plugin = req.plugin;
-
-                // Finish can be called (to unload the plugin) even if
-                // the plugin has never been configured or used. But
-                // we want to make sure we call getRemainingFeatures
-                // only if we have actually configured the plugin.
-                if (m_mapper.isConfigured(h)) {
-                    resp.features = req.plugin->getRemainingFeatures();
-                }
-
-                rj = VampJson::fromRpcResponse_Finish
-                    (resp, m_mapper, serialisation, id);
-	
-                m_mapper.removePlugin(h);
-                delete req.plugin;
-            }
-        }
-        break;
-    }
-
-    case RRType::NotValid:
-        rj = VampJson::fromError("invalid request", type, id);
-        break;
-    }
-
-    return rj.dump();
-}
-
-}
-
--- a/PiperPluginLibrary.h	Mon Nov 07 14:49:05 2016 +0000
+++ /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: */
-
-/*
-    Piper Vamp JSON Adapter
-
-    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_PLUGIN_LIBRARY_H
-#define PIPER_PLUGIN_LIBRARY_H
-
-#include "vamp-support/CountingPluginHandleMapper.h"
-#include "vamp-support/PluginStaticData.h"
-#include "vamp-support/RequestResponse.h"
-
-#include <vector>
-#include <string>
-#include <cstring>
-
-namespace piper_vamp_js {
-
-class PiperAdapterInterface;
-
-class PiperPluginLibrary
-{
-public:
-    PiperPluginLibrary(std::vector<PiperAdapterInterface *> pp);
-    
-    const char *requestJson(const char *request) {
-	return strdup(requestJsonImpl(request).c_str());
-    }
-
-    const char *processRaw(int handle, const float *const *inputBuffers,
-                           int sec, int nsec) {
-        return strdup(processRawImpl(handle, inputBuffers, sec, nsec).c_str());
-    }
-    
-    void freeJson(const char *json) {
-	free(const_cast<char *>(json));
-    }
-    
-private:
-    std::string requestJsonImpl(std::string req);
-    std::string processRawImpl(int, const float *const *, int, int);
-
-    piper_vamp::ListResponse listPluginData(piper_vamp::ListRequest r) const;
-    piper_vamp::LoadResponse loadPlugin(piper_vamp::LoadRequest r,
-                                        std::string &err) const;
-    piper_vamp::ConfigurationResponse configurePlugin(piper_vamp::ConfigurationRequest r,
-                                                      std::string &err)
-        const;
-
-    // map from pluginKey -> adapter
-    std::map<std::string, PiperAdapterInterface *> m_adapters;
-    piper_vamp::CountingPluginHandleMapper m_mapper;
-    bool m_useBase64;
-};
-
-}
-
-#endif
--- a/base64.js	Mon Nov 07 14:49:05 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +0,0 @@
-'use strict'
-
-if (typeof document === "undefined") {
-    exports.byteLength = byteLength
-    exports.toByteArray = toByteArray
-    exports.fromByteArray = fromByteArray
-}
-
-var lookup = []
-var revLookup = []
-var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array
-
-var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
-for (var i = 0, len = code.length; i < len; ++i) {
-  lookup[i] = code[i]
-  revLookup[code.charCodeAt(i)] = i
-}
-
-revLookup['-'.charCodeAt(0)] = 62
-revLookup['_'.charCodeAt(0)] = 63
-
-function placeHoldersCount (b64) {
-  var len = b64.length
-  if (len % 4 > 0) {
-    throw new Error('Invalid string. Length must be a multiple of 4')
-  }
-
-  // the number of equal signs (place holders)
-  // if there are two placeholders, than the two characters before it
-  // represent one byte
-  // if there is only one, then the three characters before it represent 2 bytes
-  // this is just a cheap hack to not do indexOf twice
-  return b64[len - 2] === '=' ? 2 : b64[len - 1] === '=' ? 1 : 0
-}
-
-function byteLength (b64) {
-  // base64 is 4/3 + up to two characters of the original data
-  return b64.length * 3 / 4 - placeHoldersCount(b64)
-}
-
-function toByteArray (b64) {
-  var i, j, l, tmp, placeHolders, arr
-  var len = b64.length
-  placeHolders = placeHoldersCount(b64)
-
-  arr = new Arr(len * 3 / 4 - placeHolders)
-
-  // if there are placeholders, only get up to the last complete 4 chars
-  l = placeHolders > 0 ? len - 4 : len
-
-  var L = 0
-
-  for (i = 0, j = 0; i < l; i += 4, j += 3) {
-    tmp = (revLookup[b64.charCodeAt(i)] << 18) | (revLookup[b64.charCodeAt(i + 1)] << 12) | (revLookup[b64.charCodeAt(i + 2)] << 6) | revLookup[b64.charCodeAt(i + 3)]
-    arr[L++] = (tmp >> 16) & 0xFF
-    arr[L++] = (tmp >> 8) & 0xFF
-    arr[L++] = tmp & 0xFF
-  }
-
-  if (placeHolders === 2) {
-    tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4)
-    arr[L++] = tmp & 0xFF
-  } else if (placeHolders === 1) {
-    tmp = (revLookup[b64.charCodeAt(i)] << 10) | (revLookup[b64.charCodeAt(i + 1)] << 4) | (revLookup[b64.charCodeAt(i + 2)] >> 2)
-    arr[L++] = (tmp >> 8) & 0xFF
-    arr[L++] = tmp & 0xFF
-  }
-
-  return arr
-}
-
-function tripletToBase64 (num) {
-  return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F]
-}
-
-function encodeChunk (uint8, start, end) {
-  var tmp
-  var output = []
-  for (var i = start; i < end; i += 3) {
-    tmp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2])
-    output.push(tripletToBase64(tmp))
-  }
-  return output.join('')
-}
-
-function fromByteArray (uint8) {
-  var tmp
-  var len = uint8.length
-  var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes
-  var output = ''
-  var parts = []
-  var maxChunkLength = 16383 // must be multiple of 3
-
-  // go through the array every three bytes, we'll deal with trailing stuff later
-  for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
-    parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)))
-  }
-
-  // pad the end with zeros, but make sure to not forget the extra bytes
-  if (extraBytes === 1) {
-    tmp = uint8[len - 1]
-    output += lookup[tmp >> 2]
-    output += lookup[(tmp << 4) & 0x3F]
-    output += '=='
-  } else if (extraBytes === 2) {
-    tmp = (uint8[len - 2] << 8) + (uint8[len - 1])
-    output += lookup[tmp >> 10]
-    output += lookup[(tmp >> 4) & 0x3F]
-    output += lookup[(tmp << 2) & 0x3F]
-    output += '='
-  }
-
-  parts.push(output)
-
-  return parts.join('')
-}
--- a/example.cpp	Mon Nov 07 14:49:05 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Piper
-
-    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 "PiperAdapter.h"
-#include "PiperPluginLibrary.h"
-
-#include "examples/ZeroCrossing.h"
-#include "examples/SpectralCentroid.h"
-#include "examples/PercussionOnsetDetector.h"
-#include "examples/FixedTempoEstimator.h"
-#include "examples/AmplitudeFollower.h"
-#include "examples/PowerSpectrum.h"
-
-using piper_vamp_js::PiperAdapter;
-using piper_vamp_js::PiperPluginLibrary;
-
-static std::string soname("vamp-example-plugins");
-
-static PiperAdapter<ZeroCrossing> zeroCrossingAdapter(soname);
-static PiperAdapter<SpectralCentroid> spectralCentroidAdapter(soname);
-static PiperAdapter<PercussionOnsetDetector> percussionOnsetAdapter(soname);
-static PiperAdapter<FixedTempoEstimator> fixedTempoAdapter(soname);
-static PiperAdapter<AmplitudeFollower> amplitudeAdapter(soname);
-static PiperAdapter<PowerSpectrum> powerSpectrumAdapter(soname);
-
-static PiperPluginLibrary library({
-    &zeroCrossingAdapter,
-    &spectralCentroidAdapter,
-    &percussionOnsetAdapter,
-    &fixedTempoAdapter,
-    &amplitudeAdapter,
-    &powerSpectrumAdapter
-});
-
-extern "C" {
-
-const char *piperRequestJson(const char *request) {
-    return library.requestJson(request);
-}
-
-const char *piperProcessRaw(int handle,
-                              const float *const *inputBuffers,
-                              int sec,
-                              int nsec) {
-    return library.processRaw(handle, inputBuffers, sec, nsec);
-}
-    
-void piperFreeJson(const char *json) {
-    return library.freeJson(json);
-}
-
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/qm-vamp-plugins.cpp	Tue Nov 08 12:02:57 2016 +0000
@@ -0,0 +1,105 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Piper
+
+    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 "PiperAdapter.h"
+#include "PiperPluginLibrary.h"
+
+#include "plugins/BeatTrack.h"
+#include "plugins/OnsetDetect.h"
+#include "plugins/ChromagramPlugin.h"
+#include "plugins/ConstantQSpectrogram.h"
+#include "plugins/TonalChangeDetect.h"
+#include "plugins/KeyDetect.h"
+#include "plugins/MFCCPlugin.h"
+#include "plugins/SegmenterPlugin.h"
+#include "plugins/SimilarityPlugin.h"
+#include "plugins/BarBeatTrack.h"
+//!!!#include "plugins/AdaptiveSpectrogram.h"
+#include "plugins/DWT.h"
+#include "plugins/Transcription.h"
+
+using piper::PiperAdapter;
+using piper::PiperPluginLibrary;
+
+static std::string soname("qm-vamp-plugins");
+
+static PiperAdapter<BeatTracker> beatTrackerAdapter(soname);
+static PiperAdapter<OnsetDetector> onsetDetectorAdapter(soname);
+static PiperAdapter<ChromagramPlugin> chromagramPluginAdapter(soname);
+static PiperAdapter<ConstantQSpectrogram> constantQAdapter(soname);
+static PiperAdapter<TonalChangeDetect> tonalChangeDetectorAdapter(soname);
+static PiperAdapter<KeyDetector> keyDetectorAdapter(soname);
+static PiperAdapter<MFCCPlugin> mfccPluginAdapter(soname);
+static PiperAdapter<SegmenterPlugin> segmenterPluginAdapter(soname);
+static PiperAdapter<SimilarityPlugin> similarityPluginAdapter(soname);
+static PiperAdapter<BarBeatTracker> barBeatTrackPluginAdapter(soname);
+//!!!static PiperAdapter<AdaptiveSpectrogram> adaptiveSpectrogramAdapter(soname);
+static PiperAdapter<DWT> dwtAdapter(soname);
+static PiperAdapter<Transcription> transcriptionAdapter(soname);
+
+static PiperPluginLibrary library({
+	&beatTrackerAdapter,
+	    &onsetDetectorAdapter,
+	    &chromagramPluginAdapter,
+	    &constantQAdapter,
+	    &tonalChangeDetectorAdapter,
+	    &keyDetectorAdapter,
+	    &mfccPluginAdapter,
+	    &segmenterPluginAdapter,
+	    &similarityPluginAdapter,
+	    &barBeatTrackPluginAdapter,
+//!!!	    &adaptiveSpectrogramAdapter,
+	    &dwtAdapter,
+	    &transcriptionAdapter
+	    });
+
+extern "C" {
+
+const char *piperRequestJson(const char *request) {
+    return library.requestJson(request);
+}
+
+const char *piperProcessRaw(int handle,
+                              const float *const *inputBuffers,
+                              int sec,
+                              int nsec) {
+    return library.processRaw(handle, inputBuffers, sec, nsec);
+}
+    
+void piperFreeJson(const char *json) {
+    return library.freeJson(json);
+}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/silvet.cpp	Tue Nov 08 12:02:57 2016 +0000
@@ -0,0 +1,36 @@
+
+#include "PiperAdapter.h"
+#include "PiperPluginLibrary.h"
+
+#include "Silvet.h"
+
+using piper::PiperAdapter;
+using piper::PiperPluginLibrary;
+
+static std::string soname("silvet");
+
+static PiperAdapter<Silvet> silvetAdapter(soname);
+
+static PiperPluginLibrary library({
+    &silvetAdapter
+});
+
+extern "C" {
+
+const char *piperRequestJson(const char *request) {
+    return library.requestJson(request);
+}
+
+const char *piperProcessRaw(int handle,
+                              const float *const *inputBuffers,
+                              int sec,
+                              int nsec) {
+    return library.processRaw(handle, inputBuffers, sec, nsec);
+}
+    
+void piperFreeJson(const char *json) {
+    return library.freeJson(json);
+}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/vamp-example-plugins.cpp	Tue Nov 08 12:02:57 2016 +0000
@@ -0,0 +1,84 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Piper
+
+    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 "PiperAdapter.h"
+#include "PiperPluginLibrary.h"
+
+#include "examples/ZeroCrossing.h"
+#include "examples/SpectralCentroid.h"
+#include "examples/PercussionOnsetDetector.h"
+#include "examples/FixedTempoEstimator.h"
+#include "examples/AmplitudeFollower.h"
+#include "examples/PowerSpectrum.h"
+
+using piper_vamp_js::PiperAdapter;
+using piper_vamp_js::PiperPluginLibrary;
+
+static std::string soname("vamp-example-plugins");
+
+static PiperAdapter<ZeroCrossing> zeroCrossingAdapter(soname);
+static PiperAdapter<SpectralCentroid> spectralCentroidAdapter(soname);
+static PiperAdapter<PercussionOnsetDetector> percussionOnsetAdapter(soname);
+static PiperAdapter<FixedTempoEstimator> fixedTempoAdapter(soname);
+static PiperAdapter<AmplitudeFollower> amplitudeAdapter(soname);
+static PiperAdapter<PowerSpectrum> powerSpectrumAdapter(soname);
+
+static PiperPluginLibrary library({
+    &zeroCrossingAdapter,
+    &spectralCentroidAdapter,
+    &percussionOnsetAdapter,
+    &fixedTempoAdapter,
+    &amplitudeAdapter,
+    &powerSpectrumAdapter
+});
+
+extern "C" {
+
+const char *piperRequestJson(const char *request) {
+    return library.requestJson(request);
+}
+
+const char *piperProcessRaw(int handle,
+                              const float *const *inputBuffers,
+                              int sec,
+                              int nsec) {
+    return library.processRaw(handle, inputBuffers, sec, nsec);
+}
+    
+void piperFreeJson(const char *json) {
+    return library.freeJson(json);
+}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/vamp-test-plugin.cpp	Tue Nov 08 12:02:57 2016 +0000
@@ -0,0 +1,83 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+/*
+    Vamp Test Plugin
+    Copyright (c) 2013-2016 Queen Mary, University of London
+
+    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 AUTHOR 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 and Queen Mary, University of London shall not be
+    used in advertising or otherwise to promote the sale, use or other
+    dealings in this Software without prior written authorization.
+*/
+
+#include "PiperAdapter.h"
+#include "PiperPluginLibrary.h"
+
+#include "VampTestPlugin.h"
+
+using piper::PiperAdapter;
+using piper::PiperAdapterBase;
+using piper::PiperPluginLibrary;
+
+static std::string soname("vamp-test-plugin");
+
+class Adapter : public PiperAdapterBase<VampTestPlugin>
+{
+public:
+    Adapter(bool freq) :
+        PiperAdapterBase<VampTestPlugin>(soname),
+        m_freq(freq) { }
+
+protected:
+    bool m_freq;
+
+    Vamp::Plugin *createPlugin(float inputSampleRate) const {
+        return new VampTestPlugin(inputSampleRate, m_freq);
+    }
+};
+
+static Adapter timeAdapter(false);
+static Adapter freqAdapter(true);
+
+static PiperPluginLibrary library({
+    &timeAdapter,
+    &freqAdapter
+});
+
+extern "C" {
+
+const char *piperRequestJson(const char *request) {
+    return library.requestJson(request);
+}
+
+const char *piperProcessRaw(int handle,
+                              const float *const *inputBuffers,
+                              int sec,
+                              int nsec) {
+    return library.processRaw(handle, inputBuffers, sec, nsec);
+}
+    
+void piperFreeJson(const char *json) {
+    return library.freeJson(json);
+}
+
+}
+
--- a/perf-test-node.js	Mon Nov 07 14:49:05 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,223 +0,0 @@
-"use strict";
-
-var VampExamplePlugins = require("./VampExamplePlugins");
-var base64 = require("./base64");
-var exampleModule = VampExamplePlugins();
-
-// It is possible to declare both parameters and return values as
-// "string", in which case Emscripten will take care of
-// conversions. But it's not clear how one would manage memory for
-// newly-constructed returned C strings -- the returned pointer from
-// piperRequestJson would appear (?) to be thrown away by the
-// Emscripten string converter if we declare it as returning a string,
-// so we have no opportunity to pass it to piperFreeJson, which
-// suggests this would leak memory if the string isn't static. Not
-// wholly sure though. Anyway, passing and returning pointers (as
-// numbers) means we can manage the Emscripten heap memory however we
-// want in our request wrapper function below.
-
-var piperRequestJson = exampleModule.cwrap(
-    'piperRequestJson', 'number', ['number']
-);
-
-var piperProcessRaw = exampleModule.cwrap(
-    "piperProcessRaw", "number", ["number", "number", "number", "number"]
-);
-
-var piperFreeJson = exampleModule.cwrap(
-    'piperFreeJson', 'void', ['number']
-);
-
-function note(blah) {
-    console.log(blah);
-}
-
-function comment(blah) {
-    console.log(blah);
-}
-
-function processRaw(request) {
-    
-    const nChannels = request.processInput.inputBuffers.length;
-    const nFrames = request.processInput.inputBuffers[0].length;
-
-    const buffersPtr = exampleModule._malloc(nChannels * 4);
-    const buffers = new Uint32Array(
-        exampleModule.HEAPU8.buffer, buffersPtr, nChannels);
-
-    for (let i = 0; i < nChannels; ++i) {
-        const framesPtr = exampleModule._malloc(nFrames * 4);
-        const frames = new Float32Array(
-            exampleModule.HEAPU8.buffer, framesPtr, nFrames);
-        frames.set(request.processInput.inputBuffers[i]);
-        buffers[i] = framesPtr;
-    }
-    
-    const responseJson = piperProcessRaw(
-        request.handle,
-        buffersPtr,
-        request.processInput.timestamp.s,
-        request.processInput.timestamp.n);
-    
-    for (let i = 0; i < nChannels; ++i) {
-        exampleModule._free(buffers[i]);
-    }
-    exampleModule._free(buffersPtr);
-
-    const responseJstr = exampleModule.Pointer_stringify(responseJson);
-    const response = JSON.parse(responseJstr);
-    
-    piperFreeJson(responseJson);
-    
-    return response;
-}
-
-function makeTimestamp(seconds) {
-    if (seconds >= 0.0) {
-        return {
-            s: Math.floor(seconds),
-            n: Math.floor((seconds - Math.floor(seconds)) * 1e9 + 0.5)
-        };
-    } else {
-        const { s, n } = makeTimestamp(-seconds);
-        return { s: -s, n: -n };
-    }
-}
-
-function frame2timestamp(frame, rate) {
-    return makeTimestamp(frame / rate);
-}
-
-function request(jsonStr) {
-    note("Request JSON = " + jsonStr);
-    var m = exampleModule;
-    // Inspection reveals that intArrayFromString converts the string
-    // from utf16 to utf8, which is what we want (though the docs
-    // don't mention this). Note the *Cstr values are Emscripten heap
-    // pointers
-    var inCstr = m.allocate(m.intArrayFromString(jsonStr), 'i8', m.ALLOC_NORMAL);
-    var outCstr = piperRequestJson(inCstr);
-    m._free(inCstr);
-    var result = m.Pointer_stringify(outCstr);
-    piperFreeJson(outCstr);
-    note("Returned JSON = " + result);
-    return result;
-}
-
-function myFromBase64(b64) {
-    while (b64.length % 4 > 0) { b64 += "="; }
-    let conv = new Float32Array(base64.toByteArray(b64).buffer);
-    return conv;
-}
-
-function convertWireFeature(wfeature) {
-    let out = {};
-    if (wfeature.timestamp != null) {
-        out.timestamp = wfeature.timestamp;
-    }
-    if (wfeature.duration != null) {
-        out.duration = wfeature.duration;
-    }
-    if (wfeature.label != null) {
-        out.label = wfeature.label;
-    }
-    const vv = wfeature.featureValues;
-    if (vv != null) {
-        if (typeof vv === "string") {
-            out.featureValues = myFromBase64(vv);
-        } else {
-            out.featureValues = new Float32Array(vv);
-        }
-    }
-    return out;
-}
-
-function convertWireFeatureList(wfeatures) {
-    return wfeatures.map(convertWireFeature);
-}
-
-function responseToFeatureSet(response) {
-    const features = new Map();
-    const processResponse = response.result;
-    const wireFeatures = processResponse.features;
-    Object.keys(wireFeatures).forEach(key => {
-        return features.set(key, convertWireFeatureList(wireFeatures[key]));
-    });
-    return features;
-}
-
-function test() {
-
-    const rate = 44100;
-    
-    comment("Loading zero crossings plugin...");
-    let result = request('{"method":"load","params": {"key":"vamp-example-plugins:zerocrossing","inputSampleRate":' + rate + ',"adapterFlags":["AdaptAllSafe"]}}');
-
-    const blockSize = 1024;
-
-    result = request('{"method":"configure","params":{"handle":1,"configuration":{"blockSize": ' + blockSize + ', "channelCount": 1, "stepSize": ' + blockSize + '}}}');
-
-    const nblocks = 1000;
-
-    const makeBlock = (n => { 
-        return {
-            timestamp : frame2timestamp(n * blockSize, rate),
-            inputBuffers : [
-                new Float32Array(Array.from(Array(blockSize).keys(),
-                                            n => n / blockSize))
-            ],
-        }
-    });
-    
-    const blocks = Array.from(Array(nblocks).keys(), makeBlock);
-    
-    comment("Now processing " + nblocks + " blocks of 1024 samples each...");
-
-    let total = 0;
-    
-    let start = (new Date()).getTime();
-    comment("Start at " + start);
-    
-    for (let i = 0; i < nblocks; ++i) {
-	result = processRaw({
-	    "handle": 1,
-	    "processInput": blocks[i]
-	});
-        let features = responseToFeatureSet(result);
-        let count = features.get("counts")[0].featureValues[0];
-        total += count;
-    }
-
-    let finish = (new Date()).getTime();
-    comment("Finish at " + finish + " for a time of " + (finish - start) + " ms");
-
-    comment("Total = " + total);
-
-    comment("Again...");
-
-    total = 0;
-    
-    start = (new Date()).getTime();
-    comment("Start at " + start);
-    
-    for (let i = 0; i < nblocks; ++i) {
-	result = processRaw({
-	    "handle": 1,
-	    "processInput": blocks[i]
-	});
-        let features = responseToFeatureSet(result);
-        let count = features.get("counts")[0].featureValues[0];
-        total += count;
-    }
-
-    finish = (new Date()).getTime();
-    comment("Finish at " + finish + " for a time of " + (finish - start) + " ms");
-    
-    comment("Total = " + total);
-    
-    comment("Cleaning up the plugin and getting any remaining features...");
-    result = request('{"method":"finish","params":{"handle":1}}');
-}
-
-test();
-
--- a/perf-test.html	Mon Nov 07 14:49:05 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-<html>
-  <head>
-    <meta charset="UTF-8">
-    <title>VamPipe Adapter Test</title>
-
-    <style type="text/css">
-      body { margin: 5%; }
-      table, td, th { border: 0.1em solid #e0e0e0; border-collapse: collapse }
-      td, th { padding: 0.5em }
-    </style>
-
-    <script src="VampExamplePlugins.js"></script>
-    <script src="base64.js"></script>
-    <script src="perf-test.js"></script>
-  </head>
-  <body>
-    <h3>VamPipe Adapter Test</h3>
-
-    <p id="test-result"></p>
-    
-  </body>
-</html>
--- a/perf-test.js	Mon Nov 07 14:49:05 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,222 +0,0 @@
-"use strict";
-
-var exampleModule = VampExamplePluginsModule();
-
-// It is possible to declare both parameters and return values as
-// "string", in which case Emscripten will take care of
-// conversions. But it's not clear how one would manage memory for
-// newly-constructed returned C strings -- the returned pointer from
-// piperRequestJson would appear (?) to be thrown away by the
-// Emscripten string converter if we declare it as returning a string,
-// so we have no opportunity to pass it to piperFreeJson, which
-// suggests this would leak memory if the string isn't static. Not
-// wholly sure though. Anyway, passing and returning pointers (as
-// numbers) means we can manage the Emscripten heap memory however we
-// want in our request wrapper function below.
-
-var piperRequestJson = exampleModule.cwrap(
-    'piperRequestJson', 'number', ['number']
-);
-
-var piperProcessRaw = exampleModule.cwrap(
-    "piperProcessRaw", "number", ["number", "number", "number", "number"]
-);
-
-var piperFreeJson = exampleModule.cwrap(
-    'piperFreeJson', 'void', ['number']
-);
-
-function note(blah) {
-    document.getElementById("test-result").innerHTML += blah + "<br>";
-}
-
-function comment(blah) {
-    note("<br><i>" + blah + "</i>");
-}
-
-function processRaw(request) {
-    
-    const nChannels = request.processInput.inputBuffers.length;
-    const nFrames = request.processInput.inputBuffers[0].length;
-
-    const buffersPtr = exampleModule._malloc(nChannels * 4);
-    const buffers = new Uint32Array(
-        exampleModule.HEAPU8.buffer, buffersPtr, nChannels);
-
-    for (let i = 0; i < nChannels; ++i) {
-        const framesPtr = exampleModule._malloc(nFrames * 4);
-        const frames = new Float32Array(
-            exampleModule.HEAPU8.buffer, framesPtr, nFrames);
-        frames.set(request.processInput.inputBuffers[i]);
-        buffers[i] = framesPtr;
-    }
-    
-    const responseJson = piperProcessRaw(
-        request.handle,
-        buffersPtr,
-        request.processInput.timestamp.s,
-        request.processInput.timestamp.n);
-    
-    for (let i = 0; i < nChannels; ++i) {
-        exampleModule._free(buffers[i]);
-    }
-    exampleModule._free(buffersPtr);
-
-    const responseJstr = exampleModule.Pointer_stringify(responseJson);
-    const response = JSON.parse(responseJstr);
-    
-    piperFreeJson(responseJson);
-    
-    return response;
-}
-
-function makeTimestamp(seconds) {
-    if (seconds >= 0.0) {
-        return {
-            s: Math.floor(seconds),
-            n: Math.floor((seconds - Math.floor(seconds)) * 1e9 + 0.5)
-        };
-    } else {
-        const { s, n } = makeTimestamp(-seconds);
-        return { s: -s, n: -n };
-    }
-}
-
-function frame2timestamp(frame, rate) {
-    return makeTimestamp(frame / rate);
-}
-
-function request(jsonStr) {
-    note("Request JSON = " + jsonStr);
-    var m = exampleModule;
-    // Inspection reveals that intArrayFromString converts the string
-    // from utf16 to utf8, which is what we want (though the docs
-    // don't mention this). Note the *Cstr values are Emscripten heap
-    // pointers
-    var inCstr = m.allocate(m.intArrayFromString(jsonStr), 'i8', m.ALLOC_NORMAL);
-    var outCstr = piperRequestJson(inCstr);
-    m._free(inCstr);
-    var result = m.Pointer_stringify(outCstr);
-    piperFreeJson(outCstr);
-    note("Returned JSON = " + result);
-    return result;
-}
-
-function myFromBase64(b64) {
-    while (b64.length % 4 > 0) { b64 += "="; }
-    let conv = new Float32Array(toByteArray(b64).buffer);
-    return conv;
-}
-
-function convertWireFeature(wfeature) {
-    let out = {};
-    if (wfeature.timestamp != null) {
-        out.timestamp = wfeature.timestamp;
-    }
-    if (wfeature.duration != null) {
-        out.duration = wfeature.duration;
-    }
-    if (wfeature.label != null) {
-        out.label = wfeature.label;
-    }
-    const vv = wfeature.featureValues;
-    if (vv != null) {
-        if (typeof vv === "string") {
-            out.featureValues = myFromBase64(vv);
-        } else {
-            out.featureValues = new Float32Array(vv);
-        }
-    }
-    return out;
-}
-
-function convertWireFeatureList(wfeatures) {
-    return wfeatures.map(convertWireFeature);
-}
-
-function responseToFeatureSet(response) {
-    const features = new Map();
-    const processResponse = response.result;
-    const wireFeatures = processResponse.features;
-    Object.keys(wireFeatures).forEach(key => {
-        return features.set(key, convertWireFeatureList(wireFeatures[key]));
-    });
-    return features;
-}
-
-function test() {
-
-    const rate = 44100;
-    
-    comment("Loading zero crossings plugin...");
-    let result = request('{"method":"load","params": {"key":"vamp-example-plugins:zerocrossing","inputSampleRate":' + rate + ',"adapterFlags":["AdaptAllSafe"]}}');
-
-    const blockSize = 1024;
-
-    result = request('{"method":"configure","params":{"handle":1,"configuration":{"blockSize": ' + blockSize + ', "channelCount": 1, "stepSize": ' + blockSize + '}}}');
-
-    const nblocks = 1000;
-
-    const makeBlock = (n => { 
-        return {
-            timestamp : frame2timestamp(n * blockSize, rate),
-            inputBuffers : [
-                new Float32Array(Array.from(Array(blockSize).keys(),
-                                            n => n / blockSize))
-            ],
-        }
-    });
-    
-    const blocks = Array.from(Array(nblocks).keys(), makeBlock);
-    
-    comment("Now processing " + nblocks + " blocks of 1024 samples each...");
-
-    let total = 0;
-    
-    let start = (new Date()).getTime();
-    comment("Start at " + start);
-    
-    for (let i = 0; i < nblocks; ++i) {
-	result = processRaw({
-	    "handle": 1,
-	    "processInput": blocks[i]
-	});
-        let features = responseToFeatureSet(result);
-        let count = features.get("counts")[0].featureValues[0];
-        total += count;
-    }
-
-    let finish = (new Date()).getTime();
-    comment("Finish at " + finish + " for a time of " + (finish - start) + " ms");
-
-    comment("Total = " + total);
-
-    comment("Again...");
-
-    total = 0;
-    
-    start = (new Date()).getTime();
-    comment("Start at " + start);
-    
-    for (let i = 0; i < nblocks; ++i) {
-	result = processRaw({
-	    "handle": 1,
-	    "processInput": blocks[i]
-	});
-        let features = responseToFeatureSet(result);
-        let count = features.get("counts")[0].featureValues[0];
-        total += count;
-    }
-
-    finish = (new Date()).getTime();
-    comment("Finish at " + finish + " for a time of " + (finish - start) + " ms");
-    
-    comment("Total = " + total);
-    
-    comment("Cleaning up the plugin and getting any remaining features...");
-    result = request('{"method":"finish","params":{"handle":1}}');
-}
-
-window.onload = function() {
-    test();
-}
--- a/piper.map	Mon Nov 07 14:49:05 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-{
-	global: piperRequestJson; piperProcessRaw; piperFreeJson;
-	local: *;
-};
--- a/qm-vamp-plugins.cpp	Mon Nov 07 14:49:05 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,105 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Piper
-
-    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 "PiperAdapter.h"
-#include "PiperPluginLibrary.h"
-
-#include "plugins/BeatTrack.h"
-#include "plugins/OnsetDetect.h"
-#include "plugins/ChromagramPlugin.h"
-#include "plugins/ConstantQSpectrogram.h"
-#include "plugins/TonalChangeDetect.h"
-#include "plugins/KeyDetect.h"
-#include "plugins/MFCCPlugin.h"
-#include "plugins/SegmenterPlugin.h"
-#include "plugins/SimilarityPlugin.h"
-#include "plugins/BarBeatTrack.h"
-//!!!#include "plugins/AdaptiveSpectrogram.h"
-#include "plugins/DWT.h"
-#include "plugins/Transcription.h"
-
-using piper::PiperAdapter;
-using piper::PiperPluginLibrary;
-
-static std::string soname("qm-vamp-plugins");
-
-static PiperAdapter<BeatTracker> beatTrackerAdapter(soname);
-static PiperAdapter<OnsetDetector> onsetDetectorAdapter(soname);
-static PiperAdapter<ChromagramPlugin> chromagramPluginAdapter(soname);
-static PiperAdapter<ConstantQSpectrogram> constantQAdapter(soname);
-static PiperAdapter<TonalChangeDetect> tonalChangeDetectorAdapter(soname);
-static PiperAdapter<KeyDetector> keyDetectorAdapter(soname);
-static PiperAdapter<MFCCPlugin> mfccPluginAdapter(soname);
-static PiperAdapter<SegmenterPlugin> segmenterPluginAdapter(soname);
-static PiperAdapter<SimilarityPlugin> similarityPluginAdapter(soname);
-static PiperAdapter<BarBeatTracker> barBeatTrackPluginAdapter(soname);
-//!!!static PiperAdapter<AdaptiveSpectrogram> adaptiveSpectrogramAdapter(soname);
-static PiperAdapter<DWT> dwtAdapter(soname);
-static PiperAdapter<Transcription> transcriptionAdapter(soname);
-
-static PiperPluginLibrary library({
-	&beatTrackerAdapter,
-	    &onsetDetectorAdapter,
-	    &chromagramPluginAdapter,
-	    &constantQAdapter,
-	    &tonalChangeDetectorAdapter,
-	    &keyDetectorAdapter,
-	    &mfccPluginAdapter,
-	    &segmenterPluginAdapter,
-	    &similarityPluginAdapter,
-	    &barBeatTrackPluginAdapter,
-//!!!	    &adaptiveSpectrogramAdapter,
-	    &dwtAdapter,
-	    &transcriptionAdapter
-	    });
-
-extern "C" {
-
-const char *piperRequestJson(const char *request) {
-    return library.requestJson(request);
-}
-
-const char *piperProcessRaw(int handle,
-                              const float *const *inputBuffers,
-                              int sec,
-                              int nsec) {
-    return library.processRaw(handle, inputBuffers, sec, nsec);
-}
-    
-void piperFreeJson(const char *json) {
-    return library.freeJson(json);
-}
-
-}
-
--- a/quick-test.cpp	Mon Nov 07 14:49:05 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-
-#include <iostream>
-#include <dlfcn.h>
-
-using namespace std;
-
-int main(int, char **)
-{
-    string example = "./example.so";
-    
-    void *lib = dlopen(example.c_str(), RTLD_LAZY | RTLD_LOCAL);
-    if (!lib) {
-	cerr << "failed to open " + example + ": " << dlerror() << endl;
-	return 1;
-    }
-
-    typedef const char *(*RequestFn)(const char *);
-    RequestFn reqFn = (RequestFn)dlsym(lib, "piperRequestJson");
-    if (!reqFn) {
-	cerr << "failed to find request function in " +
-	    example + ": " << dlerror() << endl;
-	return 1;
-    }
-    
-    typedef void (*FreeFn)(const char *);
-    FreeFn freeFn = (FreeFn)dlsym(lib, "piperFreeJson");
-    if (!freeFn) {
-	cerr << "failed to find free function in " +
-	    example + ": " << dlerror() << endl;
-	return 1;
-    }
-
-    string listRequest = "{\"method\": \"list\"}";
-    const char *listResponse = reqFn(listRequest.c_str());
-    cout << listResponse << endl;
-    freeFn(listResponse);
-
-    string loadRequest = "{\"method\":\"load\",\"params\": {\"key\":\"vamp-example-plugins:powerspectrum\",\"inputSampleRate\":44100,\"adapterFlags\":[\"AdaptAllSafe\"]}}";
-    const char *loadResponse = reqFn(loadRequest.c_str());
-    cout << loadResponse << endl;
-    freeFn(loadResponse);
-
-    string configRequest = "{\"method\":\"configure\",\"params\":{\"handle\":1,\"configuration\":{\"blockSize\":8,\"channelCount\":1,\"stepSize\":8}}}";
-    const char *configResponse = reqFn(configRequest.c_str());
-    cout << configResponse << endl;
-    freeFn(configResponse);
-
-    string processRequest = "{\"method\":\"process\",\"id\": 6,\"params\":{\"handle\":1,\"processInput\":{\"timestamp\":{\"s\":0,\"n\":0},\"inputBuffers\":[[0,1,0,-1,0,1,0,-1]]}}}";
-    const char *processResponse = reqFn(processRequest.c_str());
-    cout << processResponse << endl;
-    freeFn(processResponse);
-
-    string b64processRequest = "{\"method\":\"process\",\"params\":{\"handle\":1,\"processInput\":{\"timestamp\":{\"s\":0,\"n\":0},\"inputBuffers\":[\"AAAAAAAAgD8AAAAAAACAvwAAAAAAAIA/AAAAAAAAgL8\"]}}}";
-    const char *b64processResponse = reqFn(b64processRequest.c_str());
-    cout << b64processResponse << endl;
-    freeFn(b64processResponse);
-    
-    string finishRequest = "{\"method\":\"finish\",\"params\":{\"handle\":1}}";
-    const char *finishResponse = reqFn(finishRequest.c_str());
-    cout << finishResponse << endl;
-    freeFn(finishResponse);
-}
-
--- a/quick-test.html	Mon Nov 07 14:49:05 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-<html>
-  <head>
-    <meta charset="UTF-8">
-    <title>Piper Adapter Test</title>
-
-    <style type="text/css">
-      body { margin: 5%; }
-      table, td, th { border: 0.1em solid #e0e0e0; border-collapse: collapse }
-      td, th { padding: 0.5em }
-    </style>
-
-    <script src="VampExamplePlugins.js"></script>
-    <script src="quick-test.js"></script>
-  </head>
-  <body>
-    <h3>Piper Adapter Test</h3>
-
-    <p id="test-result"></p>
-    
-  </body>
-</html>
--- a/quick-test.js	Mon Nov 07 14:49:05 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-"use strict";
-
-var exampleModule = VampExamplePluginsModule();
-
-// It is possible to declare both parameters and return values as
-// "string", in which case Emscripten will take care of
-// conversions. But it's not clear how one would manage memory for
-// newly-constructed returned C strings -- the returned pointer from
-// piperRequestJson would appear (?) to be thrown away by the
-// Emscripten string converter if we declare it as returning a string,
-// so we have no opportunity to pass it to piperFreeJson, which
-// suggests this would leak memory if the string isn't static. Not
-// wholly sure though. Anyway, passing and returning pointers (as
-// numbers) means we can manage the Emscripten heap memory however we
-// want in our request wrapper function below.
-
-var piperRequestJson = exampleModule.cwrap(
-    'piperRequestJson', 'number', ['number']
-);
-
-var piperFreeJson = exampleModule.cwrap(
-    'piperFreeJson', 'void', ['number']
-);
-
-function note(blah) {
-    document.getElementById("test-result").innerHTML += blah + "<br>";
-}
-
-function comment(blah) {
-    note("<br><i>" + blah + "</i>");
-}
-
-function request(jsonStr) {
-    note("Request JSON = " + jsonStr);
-    var m = exampleModule;
-    // Inspection reveals that intArrayFromString converts the string
-    // from utf16 to utf8, which is what we want (though the docs
-    // don't mention this). Note the *Cstr values are Emscripten heap
-    // pointers
-    var inCstr = m.allocate(m.intArrayFromString(jsonStr), 'i8', m.ALLOC_NORMAL);
-    var outCstr = piperRequestJson(inCstr);
-    m._free(inCstr);
-    var result = m.Pointer_stringify(outCstr);
-    piperFreeJson(outCstr);
-    note("Returned JSON = " + result);
-    return result;
-}
-
-function test() {
-
-    comment("Querying plugin list...");
-    var result = request('{"method": "list"}');
-
-    comment("Loading zero crossings plugin...");
-    result = request('{"method":"load","params": {"key":"vamp-example-plugins:powerspectrum","inputSampleRate":16,"adapterFlags":["AdaptAllSafe"]}}');
-
-    comment("I'm now assuming that the load succeeded and the returned handle was 1. I haven't bothered to parse the JSON. If those assumptions are wrong, this obviously isn't going to work. Configuring the plugin...");
-    result = request('{"method":"configure","params":{"handle":1,"configuration":{"blockSize": 8, "channelCount": 1, "stepSize": 8}}}');
-
-    comment("If I try to configure it again, it should fail because it's already configured... but this doesn't change anything, and subsequent processing should work fine. Just an example of a failure call. NB this only works if Emscripten has exception catching enabled -- it's off by default in opt builds, which would just end the script here. Wonder what the performance penalty is like.");
-    result = request('{"method":"configure","params":{"handle":1,"configuration":{"blockSize": 8, "channelCount": 1, "stepSize": 8}}}');
-
-    comment("Now processing a couple of blocks of data, on the same assumptions...");
-    result = request('{"method":"process","params":{"handle":1,"processInput":{"timestamp":{"s":0,"n":0},"inputBuffers":[[0,1,-1,0,1,-1,0,1]]}}}');
-    result = request('{"method":"process","params":{"handle":1,"processInput":{"timestamp":{"s":0,"n":500000000},"inputBuffers":[[0,1,-1,0,1,-1,0,1]]}}}');
-
-    comment("Cleaning up the plugin and getting any remaining features...");
-    result = request('{"method":"finish","params":{"handle":1}}');
-
-    comment("A process call should now fail, as the plugin has been cleaned up.");
-    result = request('{"method":"process","params":{"handle":1,"processInput":{"timestamp":{"s":0,"n":1000000000},"inputBuffers":[{"values":[0,1,-1,0,1,-1,0,1]}]}}}');
-}
-
-window.onload = function() {
-    test();
-}
--- a/silvet.cpp	Mon Nov 07 14:49:05 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-
-#include "PiperAdapter.h"
-#include "PiperPluginLibrary.h"
-
-#include "Silvet.h"
-
-using piper::PiperAdapter;
-using piper::PiperPluginLibrary;
-
-static std::string soname("silvet");
-
-static PiperAdapter<Silvet> silvetAdapter(soname);
-
-static PiperPluginLibrary library({
-    &silvetAdapter
-});
-
-extern "C" {
-
-const char *piperRequestJson(const char *request) {
-    return library.requestJson(request);
-}
-
-const char *piperProcessRaw(int handle,
-                              const float *const *inputBuffers,
-                              int sec,
-                              int nsec) {
-    return library.processRaw(handle, inputBuffers, sec, nsec);
-}
-    
-void piperFreeJson(const char *json) {
-    return library.freeJson(json);
-}
-
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/PiperAdapter.h	Tue Nov 08 12:02:57 2016 +0000
@@ -0,0 +1,147 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Piper Vamp JSON Adapter
+
+    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_ADAPTER_H
+#define PIPER_ADAPTER_H
+
+#include "vamp-support/PluginStaticData.h"
+#include "vamp-support/PluginConfiguration.h"
+#include "vamp-support/RequestResponse.h"
+
+#include <vamp-hostsdk/PluginInputDomainAdapter.h>
+#include <vamp-hostsdk/PluginBufferingAdapter.h>
+#include <vamp-hostsdk/PluginChannelAdapter.h>
+
+namespace piper_vamp_js { //!!! not a good name for this namespace
+
+class PiperAdapterInterface
+{
+public:
+    virtual std::string getLibraryName() const = 0;
+    virtual piper_vamp::PluginStaticData getStaticData() const = 0;
+    virtual piper_vamp::LoadResponse loadPlugin(piper_vamp::LoadRequest r) const = 0;
+    virtual Vamp::Plugin *createPlugin(float inputSampleRate) const = 0;
+};
+
+template <typename P>
+class PiperAdapterBase : public PiperAdapterInterface
+{
+    const int adaptInputDomain = 0x01;
+    const int adaptChannelCount = 0x02;
+    const int adaptBufferSize = 0x04;
+
+protected:
+    PiperAdapterBase(std::string libname) : m_soname(libname) { }
+    
+public:
+    virtual Vamp::Plugin *createPlugin(float inputSampleRate) const = 0;
+
+    virtual std::string getLibraryName() const override {
+        return m_soname;
+    }
+    
+    virtual piper_vamp::PluginStaticData getStaticData() const override {
+        Vamp::Plugin *p = createPlugin(44100.f);
+	auto data = piper_vamp::PluginStaticData::fromPlugin
+	    (m_soname + ":" + p->getIdentifier(),
+	     {}, //!!! todo: category - tricky one that
+	     p);
+        delete p;
+        return data;
+    }
+
+    virtual piper_vamp::LoadResponse loadPlugin(piper_vamp::LoadRequest r)
+        const override {
+	
+	// We assume the caller has guaranteed that the request is for
+	// the correct plugin (so we don't have to check the plugin
+	// key field here)
+
+	Vamp::Plugin *p = createPlugin(r.inputSampleRate);
+	
+	if (r.adapterFlags & adaptInputDomain) {
+	    if (p->getInputDomain() == Vamp::Plugin::FrequencyDomain) {
+		p = new Vamp::HostExt::PluginInputDomainAdapter(p);
+	    }
+	}
+
+	if (r.adapterFlags & adaptBufferSize) {
+	    p = new Vamp::HostExt::PluginBufferingAdapter(p);
+	}
+
+	if (r.adapterFlags & adaptChannelCount) {
+	    p = new Vamp::HostExt::PluginChannelAdapter(p);
+	}
+
+	piper_vamp::LoadResponse response;
+	response.plugin = p;
+
+	response.staticData = piper_vamp::PluginStaticData::fromPlugin
+	    (m_soname + ":" + p->getIdentifier(),
+	     {}, //!!! todo: category - tricky one that
+	     p);
+
+	int defaultChannels = 0;
+	if (p->getMinChannelCount() == p->getMaxChannelCount()) {
+	    defaultChannels = p->getMinChannelCount();
+	}
+    
+	response.defaultConfiguration = piper_vamp::PluginConfiguration::fromPlugin
+	    (p,
+	     defaultChannels,
+	     p->getPreferredStepSize(),
+	     p->getPreferredBlockSize());
+    
+	return response;
+    }
+    
+private:
+    std::string m_soname;
+};
+
+template <typename P>
+class PiperAdapter : public PiperAdapterBase<P>
+{
+public:
+    PiperAdapter(std::string libname) : PiperAdapterBase<P>(libname) { }
+    
+    virtual Vamp::Plugin *createPlugin(float inputSampleRate) const override {
+        return new P(inputSampleRate);
+    }
+};
+
+}
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/PiperPluginLibrary.cpp	Tue Nov 08 12:02:57 2016 +0000
@@ -0,0 +1,349 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Piper Vamp JSON Adapter
+
+    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 "PiperPluginLibrary.h"
+#include "PiperAdapter.h"
+
+#include "vamp-json/VampJson.h"
+
+using namespace std;
+using namespace json11;
+using namespace piper_vamp;
+
+namespace piper_vamp_js { //!!! not good
+
+//!!! too many explicit namespaces here
+
+//!!! dup with piper-convert
+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";
+    }
+    return j;
+}
+
+PiperPluginLibrary::PiperPluginLibrary(vector<PiperAdapterInterface *> pp) :
+    m_useBase64(false)
+{
+    for (PiperAdapterInterface *p: pp) {
+	string key = p->getStaticData().pluginKey;
+	m_adapters[key] = p;
+    }
+}
+
+ListResponse
+PiperPluginLibrary::listPluginData(ListRequest req) const
+{
+    bool filtered = !req.from.empty();
+    ListResponse resp;
+    for (auto a: m_adapters) {
+        if (filtered) {
+            auto n = a.second->getLibraryName();
+            bool found = false;
+            for (const auto &f: req.from) {
+                if (f == n) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                continue;
+            }
+        }
+	resp.available.push_back(a.second->getStaticData());
+    }
+    return resp;
+}
+
+LoadResponse
+PiperPluginLibrary::loadPlugin(LoadRequest req, string &err) const
+{
+    string key = req.pluginKey;
+    if (m_adapters.find(key) != m_adapters.end()) {
+        auto resp = m_adapters.at(key)->loadPlugin(req);
+        if (!resp.plugin) {
+            // This should not actually happen -- the load call here
+            // is just an object construction, not a dynamic load. But
+            // report it if it does...
+            err = "failed to construct plugin with key " + key;
+        }
+        return resp;
+    } else {
+	err = "no adapter for plugin key " + key;
+        return {};
+    }
+}
+
+ConfigurationResponse
+PiperPluginLibrary::configurePlugin(ConfigurationRequest req,
+                                      string &err) const
+{
+    for (PluginConfiguration::ParameterMap::const_iterator i =
+             req.configuration.parameterValues.begin();
+         i != req.configuration.parameterValues.end(); ++i) {
+        req.plugin->setParameter(i->first, i->second);
+    }
+
+    if (req.configuration.currentProgram != "") {
+        req.plugin->selectProgram(req.configuration.currentProgram);
+    }
+
+    ConfigurationResponse response;
+
+    response.plugin = req.plugin;
+
+    if (req.plugin->initialise(req.configuration.channelCount,
+                               req.configuration.stepSize,
+                               req.configuration.blockSize)) {
+        response.outputs = req.plugin->getOutputDescriptors();
+    } else {
+        err = "configuration failed (wrong channel count, step size, block size?)";
+    }
+
+    return response;
+}
+
+string
+PiperPluginLibrary::processRawImpl(int handle,
+                                     const float *const *inputBuffers,
+                                     int sec,
+                                     int nsec)
+{
+    Vamp::Plugin *plugin = m_mapper.handleToPlugin(handle);
+    if (!plugin) {
+        return VampJson::fromError("unknown plugin handle",
+                                   RRType::Process, Json())
+            .dump();
+    }
+    
+    if (!m_mapper.isConfigured(handle)) {
+        return VampJson::fromError("plugin has not been configured",
+                                   RRType::Process, Json())
+            .dump();
+    }
+
+    Vamp::RealTime timestamp(sec, nsec);
+
+    ProcessResponse resp;
+    resp.plugin = plugin;
+    resp.features = plugin->process(inputBuffers, timestamp);
+    
+    m_useBase64 = true;
+    
+    return VampJson::fromRpcResponse_Process
+        (resp, m_mapper,
+         VampJson::BufferSerialisation::Base64,
+         Json())
+        .dump();
+}
+
+string
+PiperPluginLibrary::requestJsonImpl(string req)
+{
+    string err;
+    
+    Json j = convertRequestJson(req, err);
+
+    // we don't care what this is, only that it is retained in the response:
+    auto id = j["id"];
+
+    Json rj;
+    if (err != "") {
+        return VampJson::fromError(err, RRType::NotValid, id).dump();
+    }
+    
+    RRType type = VampJson::getRequestResponseType(j, err);
+    if (err != "") {
+        return VampJson::fromError(err, RRType::NotValid, id).dump();
+    }
+
+    VampJson::BufferSerialisation serialisation =
+        (m_useBase64 ?
+         VampJson::BufferSerialisation::Base64 :
+         VampJson::BufferSerialisation::Array);
+    
+    switch (type) {
+
+    case RRType::List:
+    {
+        auto req = VampJson::toRpcRequest_List(j, err);
+        if (err != "") {
+            rj = VampJson::fromError(err, type, id);
+        } else {
+            rj = VampJson::fromRpcResponse_List(listPluginData(req), id);
+        }
+        break;
+    }
+
+    case RRType::Load:
+    {
+        auto req = VampJson::toRpcRequest_Load(j, err);
+        if (err != "") {
+            rj = VampJson::fromError(err, type, id);
+        } else {
+            auto resp = loadPlugin(req, err);
+            if (err != "") {
+                rj = VampJson::fromError(err, type, id);
+            } else {
+                m_mapper.addPlugin(resp.plugin);
+                rj = VampJson::fromRpcResponse_Load(resp, m_mapper, id);
+            }
+        }
+        break;
+    }
+
+    case RRType::Configure:
+    {
+        auto req = VampJson::toRpcRequest_Configure(j, m_mapper, err);
+        if (err != "") {
+            rj = VampJson::fromError(err, type, id);
+        } else {
+            auto h = m_mapper.pluginToHandle(req.plugin);
+            if (h == m_mapper.INVALID_HANDLE) {
+                rj = VampJson::fromError("unknown or invalid plugin handle", type, id);
+            } else if (m_mapper.isConfigured(h)) {
+                rj = VampJson::fromError("plugin has already been configured", type, id);
+            } else {
+                auto resp = configurePlugin(req, err);
+                if (err != "") {
+                    rj = VampJson::fromError(err, type, id);
+                } else {
+                    m_mapper.markConfigured(h,
+                                            req.configuration.channelCount,
+                                            req.configuration.blockSize);
+                    rj = VampJson::fromRpcResponse_Configure(resp, m_mapper, id);
+                }
+            }
+        }
+        break;
+    }
+
+    case RRType::Process:
+    {
+        VampJson::BufferSerialisation serialisation;
+            
+        auto req = VampJson::toRpcRequest_Process(j, m_mapper,
+                                                   serialisation, err);
+        if (err != "") {
+            rj = VampJson::fromError(err, type, id);
+        } else {
+            auto h = m_mapper.pluginToHandle(req.plugin);
+            int channels = int(req.inputBuffers.size());
+            if (h == m_mapper.INVALID_HANDLE) {
+                rj = VampJson::fromError("unknown or invalid plugin handle", type, id);
+            } else if (!m_mapper.isConfigured(h)) {
+                rj = VampJson::fromError("plugin has not been configured", type, id);
+            } else if (channels != m_mapper.getChannelCount(h)) {
+                rj = VampJson::fromError("wrong number of channels supplied", type, id);
+            } else {
+
+                if (serialisation == VampJson::BufferSerialisation::Base64) {
+                    m_useBase64 = true;
+                }
+
+                size_t blockSize = m_mapper.getBlockSize(h);
+                
+                const float **fbuffers = new const float *[channels];
+                for (int i = 0; i < channels; ++i) {
+                    if (req.inputBuffers[i].size() != blockSize) {
+                        delete[] fbuffers;
+                        fbuffers = 0;
+                        rj = VampJson::fromError("wrong block size supplied", type, id);
+                        break;
+                    }
+                    fbuffers[i] = req.inputBuffers[i].data();
+                }
+
+                if (fbuffers) {
+                    ProcessResponse resp;
+                    resp.plugin = req.plugin;
+                    resp.features = req.plugin->process(fbuffers, req.timestamp);
+                    delete[] fbuffers;
+                    rj = VampJson::fromRpcResponse_Process
+                        (resp, m_mapper, serialisation, id);
+                }
+            }
+        }
+        break;
+    }
+
+    case RRType::Finish:
+    {
+        auto req = VampJson::toRpcRequest_Finish(j, m_mapper, err);
+        if (err != "") {
+            rj = VampJson::fromError(err, type, id);
+        } else {
+            auto h = m_mapper.pluginToHandle(req.plugin);
+            if (h == m_mapper.INVALID_HANDLE) {
+                rj = VampJson::fromError("unknown or invalid plugin handle", type, id);
+            } else {
+
+                FinishResponse resp;
+                resp.plugin = req.plugin;
+
+                // Finish can be called (to unload the plugin) even if
+                // the plugin has never been configured or used. But
+                // we want to make sure we call getRemainingFeatures
+                // only if we have actually configured the plugin.
+                if (m_mapper.isConfigured(h)) {
+                    resp.features = req.plugin->getRemainingFeatures();
+                }
+
+                rj = VampJson::fromRpcResponse_Finish
+                    (resp, m_mapper, serialisation, id);
+	
+                m_mapper.removePlugin(h);
+                delete req.plugin;
+            }
+        }
+        break;
+    }
+
+    case RRType::NotValid:
+        rj = VampJson::fromError("invalid request", type, id);
+        break;
+    }
+
+    return rj.dump();
+}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/PiperPluginLibrary.h	Tue Nov 08 12:02:57 2016 +0000
@@ -0,0 +1,87 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Piper Vamp JSON Adapter
+
+    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_PLUGIN_LIBRARY_H
+#define PIPER_PLUGIN_LIBRARY_H
+
+#include "vamp-support/CountingPluginHandleMapper.h"
+#include "vamp-support/PluginStaticData.h"
+#include "vamp-support/RequestResponse.h"
+
+#include <vector>
+#include <string>
+#include <cstring>
+
+namespace piper_vamp_js {
+
+class PiperAdapterInterface;
+
+class PiperPluginLibrary
+{
+public:
+    PiperPluginLibrary(std::vector<PiperAdapterInterface *> pp);
+    
+    const char *requestJson(const char *request) {
+	return strdup(requestJsonImpl(request).c_str());
+    }
+
+    const char *processRaw(int handle, const float *const *inputBuffers,
+                           int sec, int nsec) {
+        return strdup(processRawImpl(handle, inputBuffers, sec, nsec).c_str());
+    }
+    
+    void freeJson(const char *json) {
+	free(const_cast<char *>(json));
+    }
+    
+private:
+    std::string requestJsonImpl(std::string req);
+    std::string processRawImpl(int, const float *const *, int, int);
+
+    piper_vamp::ListResponse listPluginData(piper_vamp::ListRequest r) const;
+    piper_vamp::LoadResponse loadPlugin(piper_vamp::LoadRequest r,
+                                        std::string &err) const;
+    piper_vamp::ConfigurationResponse configurePlugin(piper_vamp::ConfigurationRequest r,
+                                                      std::string &err)
+        const;
+
+    // map from pluginKey -> adapter
+    std::map<std::string, PiperAdapterInterface *> m_adapters;
+    piper_vamp::CountingPluginHandleMapper m_mapper;
+    bool m_useBase64;
+};
+
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/piper.map	Tue Nov 08 12:02:57 2016 +0000
@@ -0,0 +1,4 @@
+{
+	global: piperRequestJson; piperProcessRaw; piperFreeJson;
+	local: *;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/base64.js	Tue Nov 08 12:02:57 2016 +0000
@@ -0,0 +1,116 @@
+'use strict'
+
+if (typeof document === "undefined") {
+    exports.byteLength = byteLength
+    exports.toByteArray = toByteArray
+    exports.fromByteArray = fromByteArray
+}
+
+var lookup = []
+var revLookup = []
+var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array
+
+var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
+for (var i = 0, len = code.length; i < len; ++i) {
+  lookup[i] = code[i]
+  revLookup[code.charCodeAt(i)] = i
+}
+
+revLookup['-'.charCodeAt(0)] = 62
+revLookup['_'.charCodeAt(0)] = 63
+
+function placeHoldersCount (b64) {
+  var len = b64.length
+  if (len % 4 > 0) {
+    throw new Error('Invalid string. Length must be a multiple of 4')
+  }
+
+  // the number of equal signs (place holders)
+  // if there are two placeholders, than the two characters before it
+  // represent one byte
+  // if there is only one, then the three characters before it represent 2 bytes
+  // this is just a cheap hack to not do indexOf twice
+  return b64[len - 2] === '=' ? 2 : b64[len - 1] === '=' ? 1 : 0
+}
+
+function byteLength (b64) {
+  // base64 is 4/3 + up to two characters of the original data
+  return b64.length * 3 / 4 - placeHoldersCount(b64)
+}
+
+function toByteArray (b64) {
+  var i, j, l, tmp, placeHolders, arr
+  var len = b64.length
+  placeHolders = placeHoldersCount(b64)
+
+  arr = new Arr(len * 3 / 4 - placeHolders)
+
+  // if there are placeholders, only get up to the last complete 4 chars
+  l = placeHolders > 0 ? len - 4 : len
+
+  var L = 0
+
+  for (i = 0, j = 0; i < l; i += 4, j += 3) {
+    tmp = (revLookup[b64.charCodeAt(i)] << 18) | (revLookup[b64.charCodeAt(i + 1)] << 12) | (revLookup[b64.charCodeAt(i + 2)] << 6) | revLookup[b64.charCodeAt(i + 3)]
+    arr[L++] = (tmp >> 16) & 0xFF
+    arr[L++] = (tmp >> 8) & 0xFF
+    arr[L++] = tmp & 0xFF
+  }
+
+  if (placeHolders === 2) {
+    tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4)
+    arr[L++] = tmp & 0xFF
+  } else if (placeHolders === 1) {
+    tmp = (revLookup[b64.charCodeAt(i)] << 10) | (revLookup[b64.charCodeAt(i + 1)] << 4) | (revLookup[b64.charCodeAt(i + 2)] >> 2)
+    arr[L++] = (tmp >> 8) & 0xFF
+    arr[L++] = tmp & 0xFF
+  }
+
+  return arr
+}
+
+function tripletToBase64 (num) {
+  return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F]
+}
+
+function encodeChunk (uint8, start, end) {
+  var tmp
+  var output = []
+  for (var i = start; i < end; i += 3) {
+    tmp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2])
+    output.push(tripletToBase64(tmp))
+  }
+  return output.join('')
+}
+
+function fromByteArray (uint8) {
+  var tmp
+  var len = uint8.length
+  var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes
+  var output = ''
+  var parts = []
+  var maxChunkLength = 16383 // must be multiple of 3
+
+  // go through the array every three bytes, we'll deal with trailing stuff later
+  for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
+    parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)))
+  }
+
+  // pad the end with zeros, but make sure to not forget the extra bytes
+  if (extraBytes === 1) {
+    tmp = uint8[len - 1]
+    output += lookup[tmp >> 2]
+    output += lookup[(tmp << 4) & 0x3F]
+    output += '=='
+  } else if (extraBytes === 2) {
+    tmp = (uint8[len - 2] << 8) + (uint8[len - 1])
+    output += lookup[tmp >> 10]
+    output += lookup[(tmp >> 4) & 0x3F]
+    output += lookup[(tmp << 2) & 0x3F]
+    output += '='
+  }
+
+  parts.push(output)
+
+  return parts.join('')
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/perf-test-node.js	Tue Nov 08 12:02:57 2016 +0000
@@ -0,0 +1,223 @@
+"use strict";
+
+var VampExamplePlugins = require("./VampExamplePlugins");
+var base64 = require("./base64");
+var exampleModule = VampExamplePlugins();
+
+// It is possible to declare both parameters and return values as
+// "string", in which case Emscripten will take care of
+// conversions. But it's not clear how one would manage memory for
+// newly-constructed returned C strings -- the returned pointer from
+// piperRequestJson would appear (?) to be thrown away by the
+// Emscripten string converter if we declare it as returning a string,
+// so we have no opportunity to pass it to piperFreeJson, which
+// suggests this would leak memory if the string isn't static. Not
+// wholly sure though. Anyway, passing and returning pointers (as
+// numbers) means we can manage the Emscripten heap memory however we
+// want in our request wrapper function below.
+
+var piperRequestJson = exampleModule.cwrap(
+    'piperRequestJson', 'number', ['number']
+);
+
+var piperProcessRaw = exampleModule.cwrap(
+    "piperProcessRaw", "number", ["number", "number", "number", "number"]
+);
+
+var piperFreeJson = exampleModule.cwrap(
+    'piperFreeJson', 'void', ['number']
+);
+
+function note(blah) {
+    console.log(blah);
+}
+
+function comment(blah) {
+    console.log(blah);
+}
+
+function processRaw(request) {
+    
+    const nChannels = request.processInput.inputBuffers.length;
+    const nFrames = request.processInput.inputBuffers[0].length;
+
+    const buffersPtr = exampleModule._malloc(nChannels * 4);
+    const buffers = new Uint32Array(
+        exampleModule.HEAPU8.buffer, buffersPtr, nChannels);
+
+    for (let i = 0; i < nChannels; ++i) {
+        const framesPtr = exampleModule._malloc(nFrames * 4);
+        const frames = new Float32Array(
+            exampleModule.HEAPU8.buffer, framesPtr, nFrames);
+        frames.set(request.processInput.inputBuffers[i]);
+        buffers[i] = framesPtr;
+    }
+    
+    const responseJson = piperProcessRaw(
+        request.handle,
+        buffersPtr,
+        request.processInput.timestamp.s,
+        request.processInput.timestamp.n);
+    
+    for (let i = 0; i < nChannels; ++i) {
+        exampleModule._free(buffers[i]);
+    }
+    exampleModule._free(buffersPtr);
+
+    const responseJstr = exampleModule.Pointer_stringify(responseJson);
+    const response = JSON.parse(responseJstr);
+    
+    piperFreeJson(responseJson);
+    
+    return response;
+}
+
+function makeTimestamp(seconds) {
+    if (seconds >= 0.0) {
+        return {
+            s: Math.floor(seconds),
+            n: Math.floor((seconds - Math.floor(seconds)) * 1e9 + 0.5)
+        };
+    } else {
+        const { s, n } = makeTimestamp(-seconds);
+        return { s: -s, n: -n };
+    }
+}
+
+function frame2timestamp(frame, rate) {
+    return makeTimestamp(frame / rate);
+}
+
+function request(jsonStr) {
+    note("Request JSON = " + jsonStr);
+    var m = exampleModule;
+    // Inspection reveals that intArrayFromString converts the string
+    // from utf16 to utf8, which is what we want (though the docs
+    // don't mention this). Note the *Cstr values are Emscripten heap
+    // pointers
+    var inCstr = m.allocate(m.intArrayFromString(jsonStr), 'i8', m.ALLOC_NORMAL);
+    var outCstr = piperRequestJson(inCstr);
+    m._free(inCstr);
+    var result = m.Pointer_stringify(outCstr);
+    piperFreeJson(outCstr);
+    note("Returned JSON = " + result);
+    return result;
+}
+
+function myFromBase64(b64) {
+    while (b64.length % 4 > 0) { b64 += "="; }
+    let conv = new Float32Array(base64.toByteArray(b64).buffer);
+    return conv;
+}
+
+function convertWireFeature(wfeature) {
+    let out = {};
+    if (wfeature.timestamp != null) {
+        out.timestamp = wfeature.timestamp;
+    }
+    if (wfeature.duration != null) {
+        out.duration = wfeature.duration;
+    }
+    if (wfeature.label != null) {
+        out.label = wfeature.label;
+    }
+    const vv = wfeature.featureValues;
+    if (vv != null) {
+        if (typeof vv === "string") {
+            out.featureValues = myFromBase64(vv);
+        } else {
+            out.featureValues = new Float32Array(vv);
+        }
+    }
+    return out;
+}
+
+function convertWireFeatureList(wfeatures) {
+    return wfeatures.map(convertWireFeature);
+}
+
+function responseToFeatureSet(response) {
+    const features = new Map();
+    const processResponse = response.result;
+    const wireFeatures = processResponse.features;
+    Object.keys(wireFeatures).forEach(key => {
+        return features.set(key, convertWireFeatureList(wireFeatures[key]));
+    });
+    return features;
+}
+
+function test() {
+
+    const rate = 44100;
+    
+    comment("Loading zero crossings plugin...");
+    let result = request('{"method":"load","params": {"key":"vamp-example-plugins:zerocrossing","inputSampleRate":' + rate + ',"adapterFlags":["AdaptAllSafe"]}}');
+
+    const blockSize = 1024;
+
+    result = request('{"method":"configure","params":{"handle":1,"configuration":{"blockSize": ' + blockSize + ', "channelCount": 1, "stepSize": ' + blockSize + '}}}');
+
+    const nblocks = 1000;
+
+    const makeBlock = (n => { 
+        return {
+            timestamp : frame2timestamp(n * blockSize, rate),
+            inputBuffers : [
+                new Float32Array(Array.from(Array(blockSize).keys(),
+                                            n => n / blockSize))
+            ],
+        }
+    });
+    
+    const blocks = Array.from(Array(nblocks).keys(), makeBlock);
+    
+    comment("Now processing " + nblocks + " blocks of 1024 samples each...");
+
+    let total = 0;
+    
+    let start = (new Date()).getTime();
+    comment("Start at " + start);
+    
+    for (let i = 0; i < nblocks; ++i) {
+	result = processRaw({
+	    "handle": 1,
+	    "processInput": blocks[i]
+	});
+        let features = responseToFeatureSet(result);
+        let count = features.get("counts")[0].featureValues[0];
+        total += count;
+    }
+
+    let finish = (new Date()).getTime();
+    comment("Finish at " + finish + " for a time of " + (finish - start) + " ms");
+
+    comment("Total = " + total);
+
+    comment("Again...");
+
+    total = 0;
+    
+    start = (new Date()).getTime();
+    comment("Start at " + start);
+    
+    for (let i = 0; i < nblocks; ++i) {
+	result = processRaw({
+	    "handle": 1,
+	    "processInput": blocks[i]
+	});
+        let features = responseToFeatureSet(result);
+        let count = features.get("counts")[0].featureValues[0];
+        total += count;
+    }
+
+    finish = (new Date()).getTime();
+    comment("Finish at " + finish + " for a time of " + (finish - start) + " ms");
+    
+    comment("Total = " + total);
+    
+    comment("Cleaning up the plugin and getting any remaining features...");
+    result = request('{"method":"finish","params":{"handle":1}}');
+}
+
+test();
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/perf-test.html	Tue Nov 08 12:02:57 2016 +0000
@@ -0,0 +1,22 @@
+<html>
+  <head>
+    <meta charset="UTF-8">
+    <title>VamPipe Adapter Test</title>
+
+    <style type="text/css">
+      body { margin: 5%; }
+      table, td, th { border: 0.1em solid #e0e0e0; border-collapse: collapse }
+      td, th { padding: 0.5em }
+    </style>
+
+    <script src="VampExamplePlugins.js"></script>
+    <script src="base64.js"></script>
+    <script src="perf-test.js"></script>
+  </head>
+  <body>
+    <h3>VamPipe Adapter Test</h3>
+
+    <p id="test-result"></p>
+    
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/perf-test.js	Tue Nov 08 12:02:57 2016 +0000
@@ -0,0 +1,222 @@
+"use strict";
+
+var exampleModule = VampExamplePluginsModule();
+
+// It is possible to declare both parameters and return values as
+// "string", in which case Emscripten will take care of
+// conversions. But it's not clear how one would manage memory for
+// newly-constructed returned C strings -- the returned pointer from
+// piperRequestJson would appear (?) to be thrown away by the
+// Emscripten string converter if we declare it as returning a string,
+// so we have no opportunity to pass it to piperFreeJson, which
+// suggests this would leak memory if the string isn't static. Not
+// wholly sure though. Anyway, passing and returning pointers (as
+// numbers) means we can manage the Emscripten heap memory however we
+// want in our request wrapper function below.
+
+var piperRequestJson = exampleModule.cwrap(
+    'piperRequestJson', 'number', ['number']
+);
+
+var piperProcessRaw = exampleModule.cwrap(
+    "piperProcessRaw", "number", ["number", "number", "number", "number"]
+);
+
+var piperFreeJson = exampleModule.cwrap(
+    'piperFreeJson', 'void', ['number']
+);
+
+function note(blah) {
+    document.getElementById("test-result").innerHTML += blah + "<br>";
+}
+
+function comment(blah) {
+    note("<br><i>" + blah + "</i>");
+}
+
+function processRaw(request) {
+    
+    const nChannels = request.processInput.inputBuffers.length;
+    const nFrames = request.processInput.inputBuffers[0].length;
+
+    const buffersPtr = exampleModule._malloc(nChannels * 4);
+    const buffers = new Uint32Array(
+        exampleModule.HEAPU8.buffer, buffersPtr, nChannels);
+
+    for (let i = 0; i < nChannels; ++i) {
+        const framesPtr = exampleModule._malloc(nFrames * 4);
+        const frames = new Float32Array(
+            exampleModule.HEAPU8.buffer, framesPtr, nFrames);
+        frames.set(request.processInput.inputBuffers[i]);
+        buffers[i] = framesPtr;
+    }
+    
+    const responseJson = piperProcessRaw(
+        request.handle,
+        buffersPtr,
+        request.processInput.timestamp.s,
+        request.processInput.timestamp.n);
+    
+    for (let i = 0; i < nChannels; ++i) {
+        exampleModule._free(buffers[i]);
+    }
+    exampleModule._free(buffersPtr);
+
+    const responseJstr = exampleModule.Pointer_stringify(responseJson);
+    const response = JSON.parse(responseJstr);
+    
+    piperFreeJson(responseJson);
+    
+    return response;
+}
+
+function makeTimestamp(seconds) {
+    if (seconds >= 0.0) {
+        return {
+            s: Math.floor(seconds),
+            n: Math.floor((seconds - Math.floor(seconds)) * 1e9 + 0.5)
+        };
+    } else {
+        const { s, n } = makeTimestamp(-seconds);
+        return { s: -s, n: -n };
+    }
+}
+
+function frame2timestamp(frame, rate) {
+    return makeTimestamp(frame / rate);
+}
+
+function request(jsonStr) {
+    note("Request JSON = " + jsonStr);
+    var m = exampleModule;
+    // Inspection reveals that intArrayFromString converts the string
+    // from utf16 to utf8, which is what we want (though the docs
+    // don't mention this). Note the *Cstr values are Emscripten heap
+    // pointers
+    var inCstr = m.allocate(m.intArrayFromString(jsonStr), 'i8', m.ALLOC_NORMAL);
+    var outCstr = piperRequestJson(inCstr);
+    m._free(inCstr);
+    var result = m.Pointer_stringify(outCstr);
+    piperFreeJson(outCstr);
+    note("Returned JSON = " + result);
+    return result;
+}
+
+function myFromBase64(b64) {
+    while (b64.length % 4 > 0) { b64 += "="; }
+    let conv = new Float32Array(toByteArray(b64).buffer);
+    return conv;
+}
+
+function convertWireFeature(wfeature) {
+    let out = {};
+    if (wfeature.timestamp != null) {
+        out.timestamp = wfeature.timestamp;
+    }
+    if (wfeature.duration != null) {
+        out.duration = wfeature.duration;
+    }
+    if (wfeature.label != null) {
+        out.label = wfeature.label;
+    }
+    const vv = wfeature.featureValues;
+    if (vv != null) {
+        if (typeof vv === "string") {
+            out.featureValues = myFromBase64(vv);
+        } else {
+            out.featureValues = new Float32Array(vv);
+        }
+    }
+    return out;
+}
+
+function convertWireFeatureList(wfeatures) {
+    return wfeatures.map(convertWireFeature);
+}
+
+function responseToFeatureSet(response) {
+    const features = new Map();
+    const processResponse = response.result;
+    const wireFeatures = processResponse.features;
+    Object.keys(wireFeatures).forEach(key => {
+        return features.set(key, convertWireFeatureList(wireFeatures[key]));
+    });
+    return features;
+}
+
+function test() {
+
+    const rate = 44100;
+    
+    comment("Loading zero crossings plugin...");
+    let result = request('{"method":"load","params": {"key":"vamp-example-plugins:zerocrossing","inputSampleRate":' + rate + ',"adapterFlags":["AdaptAllSafe"]}}');
+
+    const blockSize = 1024;
+
+    result = request('{"method":"configure","params":{"handle":1,"configuration":{"blockSize": ' + blockSize + ', "channelCount": 1, "stepSize": ' + blockSize + '}}}');
+
+    const nblocks = 1000;
+
+    const makeBlock = (n => { 
+        return {
+            timestamp : frame2timestamp(n * blockSize, rate),
+            inputBuffers : [
+                new Float32Array(Array.from(Array(blockSize).keys(),
+                                            n => n / blockSize))
+            ],
+        }
+    });
+    
+    const blocks = Array.from(Array(nblocks).keys(), makeBlock);
+    
+    comment("Now processing " + nblocks + " blocks of 1024 samples each...");
+
+    let total = 0;
+    
+    let start = (new Date()).getTime();
+    comment("Start at " + start);
+    
+    for (let i = 0; i < nblocks; ++i) {
+	result = processRaw({
+	    "handle": 1,
+	    "processInput": blocks[i]
+	});
+        let features = responseToFeatureSet(result);
+        let count = features.get("counts")[0].featureValues[0];
+        total += count;
+    }
+
+    let finish = (new Date()).getTime();
+    comment("Finish at " + finish + " for a time of " + (finish - start) + " ms");
+
+    comment("Total = " + total);
+
+    comment("Again...");
+
+    total = 0;
+    
+    start = (new Date()).getTime();
+    comment("Start at " + start);
+    
+    for (let i = 0; i < nblocks; ++i) {
+	result = processRaw({
+	    "handle": 1,
+	    "processInput": blocks[i]
+	});
+        let features = responseToFeatureSet(result);
+        let count = features.get("counts")[0].featureValues[0];
+        total += count;
+    }
+
+    finish = (new Date()).getTime();
+    comment("Finish at " + finish + " for a time of " + (finish - start) + " ms");
+    
+    comment("Total = " + total);
+    
+    comment("Cleaning up the plugin and getting any remaining features...");
+    result = request('{"method":"finish","params":{"handle":1}}');
+}
+
+window.onload = function() {
+    test();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/quick-test.cpp	Tue Nov 08 12:02:57 2016 +0000
@@ -0,0 +1,63 @@
+
+#include <iostream>
+#include <dlfcn.h>
+
+using namespace std;
+
+int main(int, char **)
+{
+    string example = "./example.so";
+    
+    void *lib = dlopen(example.c_str(), RTLD_LAZY | RTLD_LOCAL);
+    if (!lib) {
+	cerr << "failed to open " + example + ": " << dlerror() << endl;
+	return 1;
+    }
+
+    typedef const char *(*RequestFn)(const char *);
+    RequestFn reqFn = (RequestFn)dlsym(lib, "piperRequestJson");
+    if (!reqFn) {
+	cerr << "failed to find request function in " +
+	    example + ": " << dlerror() << endl;
+	return 1;
+    }
+    
+    typedef void (*FreeFn)(const char *);
+    FreeFn freeFn = (FreeFn)dlsym(lib, "piperFreeJson");
+    if (!freeFn) {
+	cerr << "failed to find free function in " +
+	    example + ": " << dlerror() << endl;
+	return 1;
+    }
+
+    string listRequest = "{\"method\": \"list\"}";
+    const char *listResponse = reqFn(listRequest.c_str());
+    cout << listResponse << endl;
+    freeFn(listResponse);
+
+    string loadRequest = "{\"method\":\"load\",\"params\": {\"key\":\"vamp-example-plugins:powerspectrum\",\"inputSampleRate\":44100,\"adapterFlags\":[\"AdaptAllSafe\"]}}";
+    const char *loadResponse = reqFn(loadRequest.c_str());
+    cout << loadResponse << endl;
+    freeFn(loadResponse);
+
+    string configRequest = "{\"method\":\"configure\",\"params\":{\"handle\":1,\"configuration\":{\"blockSize\":8,\"channelCount\":1,\"stepSize\":8}}}";
+    const char *configResponse = reqFn(configRequest.c_str());
+    cout << configResponse << endl;
+    freeFn(configResponse);
+
+    string processRequest = "{\"method\":\"process\",\"id\": 6,\"params\":{\"handle\":1,\"processInput\":{\"timestamp\":{\"s\":0,\"n\":0},\"inputBuffers\":[[0,1,0,-1,0,1,0,-1]]}}}";
+    const char *processResponse = reqFn(processRequest.c_str());
+    cout << processResponse << endl;
+    freeFn(processResponse);
+
+    string b64processRequest = "{\"method\":\"process\",\"params\":{\"handle\":1,\"processInput\":{\"timestamp\":{\"s\":0,\"n\":0},\"inputBuffers\":[\"AAAAAAAAgD8AAAAAAACAvwAAAAAAAIA/AAAAAAAAgL8\"]}}}";
+    const char *b64processResponse = reqFn(b64processRequest.c_str());
+    cout << b64processResponse << endl;
+    freeFn(b64processResponse);
+    
+    string finishRequest = "{\"method\":\"finish\",\"params\":{\"handle\":1}}";
+    const char *finishResponse = reqFn(finishRequest.c_str());
+    cout << finishResponse << endl;
+    freeFn(finishResponse);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/quick-test.html	Tue Nov 08 12:02:57 2016 +0000
@@ -0,0 +1,21 @@
+<html>
+  <head>
+    <meta charset="UTF-8">
+    <title>Piper Adapter Test</title>
+
+    <style type="text/css">
+      body { margin: 5%; }
+      table, td, th { border: 0.1em solid #e0e0e0; border-collapse: collapse }
+      td, th { padding: 0.5em }
+    </style>
+
+    <script src="VampExamplePlugins.js"></script>
+    <script src="quick-test.js"></script>
+  </head>
+  <body>
+    <h3>Piper Adapter Test</h3>
+
+    <p id="test-result"></p>
+    
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/quick-test.js	Tue Nov 08 12:02:57 2016 +0000
@@ -0,0 +1,76 @@
+"use strict";
+
+var exampleModule = VampExamplePluginsModule();
+
+// It is possible to declare both parameters and return values as
+// "string", in which case Emscripten will take care of
+// conversions. But it's not clear how one would manage memory for
+// newly-constructed returned C strings -- the returned pointer from
+// piperRequestJson would appear (?) to be thrown away by the
+// Emscripten string converter if we declare it as returning a string,
+// so we have no opportunity to pass it to piperFreeJson, which
+// suggests this would leak memory if the string isn't static. Not
+// wholly sure though. Anyway, passing and returning pointers (as
+// numbers) means we can manage the Emscripten heap memory however we
+// want in our request wrapper function below.
+
+var piperRequestJson = exampleModule.cwrap(
+    'piperRequestJson', 'number', ['number']
+);
+
+var piperFreeJson = exampleModule.cwrap(
+    'piperFreeJson', 'void', ['number']
+);
+
+function note(blah) {
+    document.getElementById("test-result").innerHTML += blah + "<br>";
+}
+
+function comment(blah) {
+    note("<br><i>" + blah + "</i>");
+}
+
+function request(jsonStr) {
+    note("Request JSON = " + jsonStr);
+    var m = exampleModule;
+    // Inspection reveals that intArrayFromString converts the string
+    // from utf16 to utf8, which is what we want (though the docs
+    // don't mention this). Note the *Cstr values are Emscripten heap
+    // pointers
+    var inCstr = m.allocate(m.intArrayFromString(jsonStr), 'i8', m.ALLOC_NORMAL);
+    var outCstr = piperRequestJson(inCstr);
+    m._free(inCstr);
+    var result = m.Pointer_stringify(outCstr);
+    piperFreeJson(outCstr);
+    note("Returned JSON = " + result);
+    return result;
+}
+
+function test() {
+
+    comment("Querying plugin list...");
+    var result = request('{"method": "list"}');
+
+    comment("Loading zero crossings plugin...");
+    result = request('{"method":"load","params": {"key":"vamp-example-plugins:powerspectrum","inputSampleRate":16,"adapterFlags":["AdaptAllSafe"]}}');
+
+    comment("I'm now assuming that the load succeeded and the returned handle was 1. I haven't bothered to parse the JSON. If those assumptions are wrong, this obviously isn't going to work. Configuring the plugin...");
+    result = request('{"method":"configure","params":{"handle":1,"configuration":{"blockSize": 8, "channelCount": 1, "stepSize": 8}}}');
+
+    comment("If I try to configure it again, it should fail because it's already configured... but this doesn't change anything, and subsequent processing should work fine. Just an example of a failure call. NB this only works if Emscripten has exception catching enabled -- it's off by default in opt builds, which would just end the script here. Wonder what the performance penalty is like.");
+    result = request('{"method":"configure","params":{"handle":1,"configuration":{"blockSize": 8, "channelCount": 1, "stepSize": 8}}}');
+
+    comment("Now processing a couple of blocks of data, on the same assumptions...");
+    result = request('{"method":"process","params":{"handle":1,"processInput":{"timestamp":{"s":0,"n":0},"inputBuffers":[[0,1,-1,0,1,-1,0,1]]}}}');
+    result = request('{"method":"process","params":{"handle":1,"processInput":{"timestamp":{"s":0,"n":500000000},"inputBuffers":[[0,1,-1,0,1,-1,0,1]]}}}');
+
+    comment("Cleaning up the plugin and getting any remaining features...");
+    result = request('{"method":"finish","params":{"handle":1}}');
+
+    comment("A process call should now fail, as the plugin has been cleaned up.");
+    result = request('{"method":"process","params":{"handle":1,"processInput":{"timestamp":{"s":0,"n":1000000000},"inputBuffers":[{"values":[0,1,-1,0,1,-1,0,1]}]}}}');
+}
+
+window.onload = function() {
+    test();
+}
--- a/vamp-test-plugin.cpp	Mon Nov 07 14:49:05 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-/*
-    Vamp Test Plugin
-    Copyright (c) 2013-2016 Queen Mary, University of London
-
-    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 AUTHOR 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 and Queen Mary, University of London shall not be
-    used in advertising or otherwise to promote the sale, use or other
-    dealings in this Software without prior written authorization.
-*/
-
-#include "PiperAdapter.h"
-#include "PiperPluginLibrary.h"
-
-#include "VampTestPlugin.h"
-
-using piper::PiperAdapter;
-using piper::PiperAdapterBase;
-using piper::PiperPluginLibrary;
-
-static std::string soname("vamp-test-plugin");
-
-class Adapter : public PiperAdapterBase<VampTestPlugin>
-{
-public:
-    Adapter(bool freq) :
-        PiperAdapterBase<VampTestPlugin>(soname),
-        m_freq(freq) { }
-
-protected:
-    bool m_freq;
-
-    Vamp::Plugin *createPlugin(float inputSampleRate) const {
-        return new VampTestPlugin(inputSampleRate, m_freq);
-    }
-};
-
-static Adapter timeAdapter(false);
-static Adapter freqAdapter(true);
-
-static PiperPluginLibrary library({
-    &timeAdapter,
-    &freqAdapter
-});
-
-extern "C" {
-
-const char *piperRequestJson(const char *request) {
-    return library.requestJson(request);
-}
-
-const char *piperProcessRaw(int handle,
-                              const float *const *inputBuffers,
-                              int sec,
-                              int nsec) {
-    return library.processRaw(handle, inputBuffers, sec, nsec);
-}
-    
-void piperFreeJson(const char *json) {
-    return library.freeJson(json);
-}
-
-}
-