Mercurial > hg > piper-vamp-js
changeset 117:2e8aacb7f883
Move some things around (have not yet updated builds)
author | Chris Cannam <c.cannam@qmul.ac.uk> |
---|---|
date | Tue, 08 Nov 2016 12:02:57 +0000 |
parents | 286c8e57abd0 |
children | 523047216129 |
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, - &litudeAdapter, - &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, + &litudeAdapter, + &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); -} - -} -