Mercurial > hg > piper-cpp
view vamp-json/VampJson.h @ 289:26027c3a99a0
Further wiring for ProgramParameters - should now be supported throughout
author | Chris Cannam <cannam@all-day-breakfast.com> |
---|---|
date | Wed, 08 Apr 2020 15:02:24 +0100 |
parents | 292ec9b50280 |
children |
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Piper C++ Centre for Digital Music, Queen Mary, University of London. Copyright 2015-2017 QMUL. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Except as contained in this notice, the names of the Centre for Digital Music; Queen Mary, University of London; and Chris Cannam shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization. */ #ifndef PIPER_VAMP_JSON_H #define PIPER_VAMP_JSON_H #include <vector> #include <string> #include <sstream> #include <iterator> #include <cmath> #include <json11/json11.hpp> #include <base-n/include/basen.hpp> #include <vamp-hostsdk/Plugin.h> #include <vamp-hostsdk/PluginLoader.h> #include "vamp-support/StaticOutputDescriptor.h" #include "vamp-support/PluginStaticData.h" #include "vamp-support/PluginConfiguration.h" #include "vamp-support/RequestResponse.h" #include "vamp-support/PluginHandleMapper.h" #include "vamp-support/PluginOutputIdMapper.h" #include "vamp-support/RequestResponseType.h" namespace piper_vamp { /** * Convert the structures laid out in the Vamp SDK classes into JSON * (and back again) following the schema in the vamp-json-schema * project repo. * * Functions with names starting "from" convert from a Vamp SDK object * to JSON output. Most of them return a json11::Json object, with a * few exceptions for low-level utilities that return a string. These * functions succeed all of the time. * * Functions with names starting "to" convert to a Vamp SDK object * from JSON input. These functions all accept a json11::Json object * as first argument, with a few exceptions for low-level utilities * that accept a string. These functions all accept a string reference * as a final argument and return an error string through it if the * conversion fails. If conversion fails the return value is * undefined, and any returned object may be incomplete or * invalid. Callers should check for an empty error string (indicating * success) before using the returned value. */ class VampJson { public: /** Serialisation format for arrays of floats (process input and * feature values). Wherever such an array appears, it may * alternatively be replaced by a single string containing a * base-64 encoding of the IEEE float buffer. When parsing, if a * string is found instead of an array in this case, it will be * interpreted as a base-64 encoded buffer. Only array or base-64 * encoding may be provided, not both. */ enum class BufferSerialisation { /** Default JSON serialisation of values in array form. This * is relatively slow to parse and serialise, and can take a * lot of space. */ Array, /** Base64-encoded string of the raw data as packed * little-endian IEEE 32-bit floats. Faster and more compact * than the text encoding but more complicated to * provide. Note that Base64 serialisations produced by this * library do not including padding characters and so are not * necessarily multiples of 4 characters long. You will need * to pad them yourself if concatenating them or supplying to * a consumer that expects padding. */ Base64 }; static bool failed(const std::string &err) { return err != ""; } template <typename T> static json11::Json fromBasicDescriptor(const T &t) { return json11::Json::object { { "identifier", t.identifier }, { "name", t.name }, { "description", t.description } }; } template <typename T> static void toBasicDescriptor(json11::Json j, T &t, std::string &err) { if (!j.is_object()) { err = "object expected for basic descriptor content"; return; } if (!j["identifier"].is_string()) { err = "string expected for identifier"; return; } t.identifier = j["identifier"].string_value(); t.name = j["name"].string_value(); t.description = j["description"].string_value(); } template <typename T> static json11::Json fromValueExtents(const T &t) { return json11::Json::object { { "min", t.minValue }, { "max", t.maxValue } }; } template <typename T> static bool toValueExtents(json11::Json j, T &t, std::string &err) { if (j["extents"].is_null()) { return false; } else if (j["extents"].is_object()) { if (j["extents"]["min"].is_number() && j["extents"]["max"].is_number()) { t.minValue = float(j["extents"]["min"].number_value()); t.maxValue = float(j["extents"]["max"].number_value()); return true; } else { err = "numbers expected for min and max"; return false; } } else { err = "object expected for extents (if present)"; return false; } } static json11::Json fromRealTime(const Vamp::RealTime &r) { return json11::Json::object { { "s", r.sec }, { "n", r.nsec } }; } static Vamp::RealTime toRealTime(json11::Json j, std::string &err) { json11::Json sec = j["s"]; json11::Json nsec = j["n"]; if (!sec.is_number() || !nsec.is_number()) { err = "invalid Vamp::RealTime object " + j.dump(); return {}; } return Vamp::RealTime(sec.int_value(), nsec.int_value()); } static std::string fromSampleType(Vamp::Plugin::OutputDescriptor::SampleType type) { switch (type) { case Vamp::Plugin::OutputDescriptor::OneSamplePerStep: return "OneSamplePerStep"; case Vamp::Plugin::OutputDescriptor::FixedSampleRate: return "FixedSampleRate"; case Vamp::Plugin::OutputDescriptor::VariableSampleRate: return "VariableSampleRate"; } return ""; } static Vamp::Plugin::OutputDescriptor::SampleType toSampleType(std::string text, std::string &err) { if (text == "OneSamplePerStep") { return Vamp::Plugin::OutputDescriptor::OneSamplePerStep; } else if (text == "FixedSampleRate") { return Vamp::Plugin::OutputDescriptor::FixedSampleRate; } else if (text == "VariableSampleRate") { return Vamp::Plugin::OutputDescriptor::VariableSampleRate; } else { err = "invalid sample type string: " + text; return Vamp::Plugin::OutputDescriptor::OneSamplePerStep; } } static json11::Json fromStaticOutputDescriptor(const StaticOutputDescriptor &sd) { json11::Json::object jo; if (sd.typeURI != "") { jo["typeURI"] = sd.typeURI; } return json11::Json(jo); } static json11::Json fromConfiguredOutputDescriptor(const Vamp::Plugin::OutputDescriptor &desc) { json11::Json::object jo { { "unit", desc.unit }, { "sampleType", fromSampleType(desc.sampleType) }, { "sampleRate", desc.sampleRate }, { "hasDuration", desc.hasDuration } }; if (desc.hasFixedBinCount) { jo["binCount"] = int(desc.binCount); jo["binNames"] = json11::Json::array (desc.binNames.begin(), desc.binNames.end()); } if (desc.hasKnownExtents) { jo["extents"] = fromValueExtents(desc); } if (desc.isQuantized) { jo["quantizeStep"] = desc.quantizeStep; } return json11::Json(jo); } static json11::Json fromOutputDescriptor(const Vamp::Plugin::OutputDescriptor &desc, const StaticOutputDescriptor &sd) { json11::Json::object jo { { "basic", fromBasicDescriptor(desc) }, { "static", fromStaticOutputDescriptor(sd) }, { "configured", fromConfiguredOutputDescriptor(desc) } }; return json11::Json(jo); } static StaticOutputDescriptor toStaticOutputDescriptor(json11::Json j, std::string &err) { StaticOutputDescriptor sd; if (!j.is_object()) { err = "object expected for static output descriptor"; return sd; } sd.typeURI = j["typeURI"].string_value(); return sd; } static Vamp::Plugin::OutputDescriptor toConfiguredOutputDescriptor(json11::Json j, std::string &err) { Vamp::Plugin::OutputDescriptor od; if (!j.is_object()) { err = "object expected for output descriptor"; return {}; } od.unit = j["unit"].string_value(); od.sampleType = toSampleType(j["sampleType"].string_value(), err); if (failed(err)) return {}; if (!j["sampleRate"].is_number()) { err = "number expected for sample rate"; return {}; } od.sampleRate = float(j["sampleRate"].number_value()); od.hasDuration = j["hasDuration"].bool_value(); if (j["binCount"].is_number() && j["binCount"].int_value() > 0) { od.hasFixedBinCount = true; od.binCount = j["binCount"].int_value(); for (auto &n: j["binNames"].array_items()) { if (!n.is_string()) { err = "string expected for bin name"; return {}; } od.binNames.push_back(n.string_value()); } } else { od.hasFixedBinCount = false; } bool extentsPresent = toValueExtents(j, od, err); if (failed(err)) return {}; od.hasKnownExtents = extentsPresent; if (j["quantizeStep"].is_number()) { od.isQuantized = true; od.quantizeStep = float(j["quantizeStep"].number_value()); } else { od.isQuantized = false; } return od; } static std::pair<Vamp::Plugin::OutputDescriptor, StaticOutputDescriptor> toOutputDescriptor(json11::Json j, std::string &err) { Vamp::Plugin::OutputDescriptor od; if (!j.is_object()) { err = "object expected for output descriptor"; return {}; } od = toConfiguredOutputDescriptor(j["configured"], err); if (failed(err)) return {}; toBasicDescriptor(j["basic"], od, err); if (failed(err)) return {}; StaticOutputDescriptor sd; if (j["static"] != json11::Json()) { sd = toStaticOutputDescriptor(j["static"], err); if (failed(err)) return {}; } return { od, sd }; } static json11::Json fromParameterDescriptor(const Vamp::PluginBase::ParameterDescriptor &desc) { json11::Json::object jo { { "basic", fromBasicDescriptor(desc) }, { "unit", desc.unit }, { "extents", fromValueExtents(desc) }, { "defaultValue", desc.defaultValue }, { "valueNames", json11::Json::array (desc.valueNames.begin(), desc.valueNames.end()) } }; if (desc.isQuantized) { jo["quantizeStep"] = desc.quantizeStep; } return json11::Json(jo); } static Vamp::PluginBase::ParameterDescriptor toParameterDescriptor(json11::Json j, std::string &err) { Vamp::PluginBase::ParameterDescriptor pd; if (!j.is_object()) { err = "object expected for parameter descriptor"; return {}; } toBasicDescriptor(j["basic"], pd, err); if (failed(err)) return {}; pd.unit = j["unit"].string_value(); bool extentsPresent = toValueExtents(j, pd, err); if (failed(err)) return {}; if (!extentsPresent) { err = "extents must be present in parameter descriptor"; return {}; } if (!j["defaultValue"].is_number()) { err = "number expected for default value"; return {}; } pd.defaultValue = float(j["defaultValue"].number_value()); pd.valueNames.clear(); for (auto &n: j["valueNames"].array_items()) { if (!n.is_string()) { err = "string expected for value name"; return {}; } pd.valueNames.push_back(n.string_value()); } if (j["quantizeStep"].is_number()) { pd.isQuantized = true; pd.quantizeStep = float(j["quantizeStep"].number_value()); } else { pd.isQuantized = false; } return pd; } static std::string fromFloatBuffer(const float *buffer, size_t nfloats) { // must use char pointers, otherwise the converter will only // encode every 4th byte (as it will count up in float* steps) const char *start = reinterpret_cast<const char *>(buffer); const char *end = reinterpret_cast<const char *>(buffer + nfloats); std::string encoded; bn::encode_b64(start, end, back_inserter(encoded)); return encoded; } static std::vector<float> toFloatBuffer(std::string encoded, std::string & /* err */) { std::string decoded; bn::decode_b64(encoded.begin(), encoded.end(), back_inserter(decoded)); const float *buffer = reinterpret_cast<const float *>(decoded.c_str()); size_t n = decoded.size() / sizeof(float); return std::vector<float>(buffer, buffer + n); } static json11::Json fromFeature(const Vamp::Plugin::Feature &f, BufferSerialisation serialisation) { json11::Json::object jo; if (f.values.size() > 0) { if (serialisation == BufferSerialisation::Array) { jo["featureValues"] = json11::Json::array(f.values.begin(), f.values.end()); } else { jo["featureValues"] = fromFloatBuffer(f.values.data(), f.values.size()); } } if (f.label != "") { jo["label"] = f.label; } if (f.hasTimestamp) { jo["timestamp"] = fromRealTime(f.timestamp); } if (f.hasDuration) { jo["duration"] = fromRealTime(f.duration); } return json11::Json(jo); } static Vamp::Plugin::Feature toFeature(json11::Json j, BufferSerialisation &serialisation, std::string &err) { Vamp::Plugin::Feature f; if (!j.is_object()) { err = "object expected for feature"; return {}; } if (j["timestamp"].is_object()) { f.timestamp = toRealTime(j["timestamp"], err); if (failed(err)) return {}; f.hasTimestamp = true; } if (j["duration"].is_object()) { f.duration = toRealTime(j["duration"], err); if (failed(err)) return {}; f.hasDuration = true; } if (j["featureValues"].is_string()) { f.values = toFloatBuffer(j["featureValues"].string_value(), err); if (failed(err)) return {}; serialisation = BufferSerialisation::Base64; } else if (j["featureValues"].is_array()) { for (auto v : j["featureValues"].array_items()) { f.values.push_back(float(v.number_value())); } serialisation = BufferSerialisation::Array; } f.label = j["label"].string_value(); return f; } static json11::Json fromFeatureSet(const Vamp::Plugin::FeatureSet &fs, const PluginOutputIdMapper &omapper, BufferSerialisation serialisation) { json11::Json::object jo; for (const auto &fsi : fs) { std::vector<json11::Json> fj; for (const Vamp::Plugin::Feature &f: fsi.second) { fj.push_back(fromFeature(f, serialisation)); } jo[omapper.indexToId(fsi.first)] = fj; } return json11::Json(jo); } static Vamp::Plugin::FeatureList toFeatureList(json11::Json j, BufferSerialisation &serialisation, std::string &err) { Vamp::Plugin::FeatureList fl; if (!j.is_array()) { err = "array expected for feature list"; return fl; } for (const json11::Json &fj : j.array_items()) { fl.push_back(toFeature(fj, serialisation, err)); if (failed(err)) return fl; } return fl; } static Vamp::Plugin::FeatureSet toFeatureSet(json11::Json j, const PluginOutputIdMapper &omapper, BufferSerialisation &serialisation, std::string &err) { Vamp::Plugin::FeatureSet fs; if (!j.is_object()) { err = "object expected for feature set"; return fs; } for (auto &entry : j.object_items()) { int n = omapper.idToIndex(entry.first); if (fs.find(n) != fs.end()) { err = "duplicate numerical index for output"; return fs; } fs[n] = toFeatureList(entry.second, serialisation, err); if (failed(err)) return fs; } return fs; } static std::string fromInputDomain(Vamp::Plugin::InputDomain domain) { switch (domain) { case Vamp::Plugin::TimeDomain: return "TimeDomain"; case Vamp::Plugin::FrequencyDomain: return "FrequencyDomain"; } return ""; } static Vamp::Plugin::InputDomain toInputDomain(std::string text, std::string &err) { if (text == "TimeDomain") { return Vamp::Plugin::TimeDomain; } else if (text == "FrequencyDomain") { return Vamp::Plugin::FrequencyDomain; } else { err = "invalid input domain string: " + text; return {}; } } static json11::Json fromPluginStaticData(const PluginStaticData &d) { json11::Json::object jo; jo["key"] = d.pluginKey; jo["basic"] = fromBasicDescriptor(d.basic); jo["maker"] = d.maker; jo["rights"] = d.copyright; jo["version"] = d.pluginVersion; json11::Json::array cat; for (const std::string &c: d.category) cat.push_back(c); jo["category"] = cat; jo["minChannelCount"] = int(d.minChannelCount); jo["maxChannelCount"] = int(d.maxChannelCount); json11::Json::array params; Vamp::PluginBase::ParameterList vparams = d.parameters; for (auto &p: vparams) params.push_back(fromParameterDescriptor(p)); jo["parameters"] = params; json11::Json::array progs; Vamp::PluginBase::ProgramList vprogs = d.programs; for (auto &p: vprogs) progs.push_back(p); jo["programs"] = progs; jo["inputDomain"] = fromInputDomain(d.inputDomain); json11::Json::array outinfo; auto vouts = d.basicOutputInfo; for (auto &o: vouts) outinfo.push_back(fromBasicDescriptor(o)); jo["basicOutputInfo"] = outinfo; json11::Json::object statinfo; auto souts = d.staticOutputInfo; for (auto &s: souts) { statinfo[s.first] = fromStaticOutputDescriptor(s.second); } jo["staticOutputInfo"] = statinfo; return json11::Json(jo); } static StaticOutputInfo toStaticOutputInfo(json11::Json j, std::string &err) { if (j == json11::Json()) return {}; if (!j.is_object()) { err = "object expected for static output info"; return {}; } StaticOutputInfo sinfo; auto items = j.object_items(); for (auto i: items) { sinfo[i.first] = toStaticOutputDescriptor(i.second, err); if (failed(err)) return {}; } return sinfo; } static PluginStaticData toPluginStaticData(json11::Json j, std::string &err) { if (!j.has_shape({ { "key", json11::Json::STRING }, { "version", json11::Json::NUMBER }, { "minChannelCount", json11::Json::NUMBER }, { "maxChannelCount", json11::Json::NUMBER }, { "inputDomain", json11::Json::STRING }}, err)) { err = "malformed plugin static data: " + err; } else if (!j["basicOutputInfo"].is_array()) { err = "array expected for basic output info"; } else if (!j["maker"].is_null() && !j["maker"].is_string()) { err = "string expected for maker"; } else if (!j["rights"].is_null() && !j["rights"].is_string()) { err = "string expected for rights"; } else if (!j["category"].is_null() && !j["category"].is_array()) { err = "array expected for category"; } else if (!j["parameters"].is_null() && !j["parameters"].is_array()) { err = "array expected for parameters"; } else if (!j["programs"].is_null() && !j["programs"].is_array()) { err = "array expected for programs"; } else if (!j["inputDomain"].is_null() && !j["inputDomain"].is_string()) { err = "string expected for inputDomain"; } else if (!j["staticOutputInfo"].is_null() && !j["staticOutputInfo"].is_object()) { err = "object expected for staticOutputInfo"; } else { PluginStaticData psd; psd.pluginKey = j["key"].string_value(); toBasicDescriptor(j["basic"], psd.basic, err); if (failed(err)) return {}; psd.maker = j["maker"].string_value(); psd.copyright = j["rights"].string_value(); psd.pluginVersion = j["version"].int_value(); for (const auto &c : j["category"].array_items()) { if (!c.is_string()) { err = "strings expected in category array"; return {}; } psd.category.push_back(c.string_value()); } psd.minChannelCount = j["minChannelCount"].int_value(); psd.maxChannelCount = j["maxChannelCount"].int_value(); for (const auto &p : j["parameters"].array_items()) { auto pd = toParameterDescriptor(p, err); if (failed(err)) return {}; psd.parameters.push_back(pd); } for (const auto &p : j["programs"].array_items()) { if (!p.is_string()) { err = "strings expected in programs array"; return {}; } psd.programs.push_back(p.string_value()); } psd.inputDomain = toInputDomain(j["inputDomain"].string_value(), err); if (failed(err)) return {}; for (const auto &bo : j["basicOutputInfo"].array_items()) { PluginStaticData::Basic b; toBasicDescriptor(bo, b, err); if (failed(err)) return {}; psd.basicOutputInfo.push_back(b); } StaticOutputInfo sinfo = toStaticOutputInfo(j["staticOutputInfo"], err); if (failed(err)) return {}; psd.staticOutputInfo = sinfo; return psd; } // fallthrough error case return {}; } static json11::Json fromPluginConfiguration(const PluginConfiguration &c) { json11::Json::object jo; json11::Json::object paramValues; for (auto &vp: c.parameterValues) { paramValues[vp.first] = vp.second; } jo["parameterValues"] = paramValues; if (c.currentProgram != "") { jo["currentProgram"] = c.currentProgram; } jo["channelCount"] = c.channelCount; json11::Json::object framing; framing["stepSize"] = c.framing.stepSize; framing["blockSize"] = c.framing.blockSize; jo["framing"] = framing; return json11::Json(jo); } static PluginConfiguration toPluginConfiguration(json11::Json j, std::string &err) { if (!j.has_shape({ { "channelCount", json11::Json::NUMBER } }, err)) { err = "malformed plugin configuration: " + err; return {}; } if (!j["framing"].has_shape({ { "stepSize", json11::Json::NUMBER }, { "blockSize", json11::Json::NUMBER } }, err)) { err = "malformed framing: " + err; return {}; } if (!j["parameterValues"].is_null() && !j["parameterValues"].is_object()) { err = "object expected for parameter values"; return {}; } for (auto &pv : j["parameterValues"].object_items()) { if (!pv.second.is_number()) { err = "number expected for parameter value"; return {}; } } if (!j["currentProgram"].is_null() && !j["currentProgram"].is_string()) { err = "string expected for program name"; return {}; } PluginConfiguration config; config.channelCount = int(round(j["channelCount"].number_value())); config.framing.stepSize = int(round(j["framing"]["stepSize"].number_value())); config.framing.blockSize = int(round(j["framing"]["blockSize"].number_value())); for (auto &pv : j["parameterValues"].object_items()) { config.parameterValues[pv.first] = float(pv.second.number_value()); } if (j["currentProgram"].is_string()) { config.currentProgram = j["currentProgram"].string_value(); } return config; } static json11::Json fromAdapterFlags(int flags) { json11::Json::array arr; if (flags & Vamp::HostExt::PluginLoader::ADAPT_INPUT_DOMAIN) { arr.push_back("AdaptInputDomain"); } if (flags & Vamp::HostExt::PluginLoader::ADAPT_CHANNEL_COUNT) { arr.push_back("AdaptChannelCount"); } if (flags & Vamp::HostExt::PluginLoader::ADAPT_BUFFER_SIZE) { arr.push_back("AdaptBufferSize"); } return json11::Json(arr); } static Vamp::HostExt::PluginLoader::AdapterFlags toAdapterFlags(json11::Json j, std::string &err) { int flags = 0x0; if (!j.is_array()) { err = "array expected for adapter flags"; } else { for (auto &jj: j.array_items()) { if (!jj.is_string()) { err = "string expected for adapter flag"; break; } std::string text = jj.string_value(); if (text == "AdaptInputDomain") { flags |= Vamp::HostExt::PluginLoader::ADAPT_INPUT_DOMAIN; } else if (text == "AdaptChannelCount") { flags |= Vamp::HostExt::PluginLoader::ADAPT_CHANNEL_COUNT; } else if (text == "AdaptBufferSize") { flags |= Vamp::HostExt::PluginLoader::ADAPT_BUFFER_SIZE; } else if (text == "AdaptAllSafe") { flags |= Vamp::HostExt::PluginLoader::ADAPT_ALL_SAFE; } else if (text == "AdaptAll") { flags |= Vamp::HostExt::PluginLoader::ADAPT_ALL; } else { err = "invalid adapter flag string: " + text; break; } } } return Vamp::HostExt::PluginLoader::AdapterFlags(flags); } static json11::Json fromProgramParameters(const PluginProgramParameters &programParameters) { json11::Json::object jo; for (const auto &pp: programParameters.programParameters) { auto program = pp.first; json11::Json::object po; for (const auto &pv: pp.second) { po[pv.first] = pv.second; } jo[program] = po; } return json11::Json(jo); } static PluginProgramParameters toProgramParameters(json11::Json j, std::string &err) { if (!j.is_object()) { err = "object expected for program parameters"; return {}; } PluginProgramParameters params; auto pp = j.object_items(); for (auto program: pp) { std::string name = program.first; if (!program.second.is_object()) { err = std::string("object expected for program parameter map ") + "(in program \"" + name + "\")"; return {}; } for (auto p: program.second.object_items()) { if (!p.second.is_number()) { err = std::string("number expected for program parameter value ") + "(in program \"" + name + "\")"; return {}; } params.programParameters[name][p.first] = p.second.number_value(); } } return params; } static json11::Json fromListRequest(const ListRequest &req) { json11::Json::object jo; json11::Json::array arr; for (const auto &f: req.from) { arr.push_back(f); } jo["from"] = arr; return json11::Json(jo); } static ListRequest toListRequest(json11::Json j, std::string &err) { ListRequest req; if (!j["from"].is_null() && !j["from"].is_array()) { err = "array expected for from field"; return {}; } for (const auto &a: j["from"].array_items()) { if (!a.is_string()) { err = "string expected for element in from array"; return {}; } req.from.push_back(a.string_value()); } return req; } static json11::Json fromListResponse(const ListResponse &resp) { json11::Json::array arr; for (const auto &a: resp.available) { arr.push_back(fromPluginStaticData(a)); } json11::Json::object jo; jo["available"] = arr; return json11::Json(jo); } static ListResponse toListResponse(json11::Json j, std::string &err) { ListResponse resp; for (const auto &a: j["available"].array_items()) { resp.available.push_back(toPluginStaticData(a, err)); if (failed(err)) return {}; } return resp; } static json11::Json fromLoadRequest(const LoadRequest &req) { json11::Json::object jo; jo["key"] = req.pluginKey; jo["inputSampleRate"] = req.inputSampleRate; jo["adapterFlags"] = fromAdapterFlags(req.adapterFlags); return json11::Json(jo); } static LoadRequest toLoadRequest(json11::Json j, std::string &err) { if (!j.has_shape({ { "key", json11::Json::STRING }, { "inputSampleRate", json11::Json::NUMBER } }, err)) { err = "malformed load request: " + err; return {}; } LoadRequest req; req.pluginKey = j["key"].string_value(); req.inputSampleRate = float(j["inputSampleRate"].number_value()); if (!j["adapterFlags"].is_null()) { req.adapterFlags = toAdapterFlags(j["adapterFlags"], err); if (failed(err)) return {}; } return req; } static json11::Json fromLoadResponse(const LoadResponse &resp, const PluginHandleMapper &pmapper) { json11::Json::object jo; jo["handle"] = double(pmapper.pluginToHandle(resp.plugin)); jo["staticData"] = fromPluginStaticData(resp.staticData); jo["defaultConfiguration"] = fromPluginConfiguration(resp.defaultConfiguration); jo["programParameters"] = fromProgramParameters(resp.programParameters); return json11::Json(jo); } static LoadResponse toLoadResponse(json11::Json j, const PluginHandleMapper &pmapper, std::string &err) { if (!j.has_shape({ { "handle", json11::Json::NUMBER }, { "staticData", json11::Json::OBJECT }, { "defaultConfiguration", json11::Json::OBJECT } }, err)) { err = "malformed load response: " + err; return {}; } LoadResponse resp; auto h = j["handle"].int_value(); resp.plugin = pmapper.handleToPlugin(h); resp.staticData = toPluginStaticData(j["staticData"], err); if (failed(err)) return {}; resp.defaultConfiguration = toPluginConfiguration(j["defaultConfiguration"], err); if (failed(err)) return {}; if (j.object_items().find("programParameters") != j.object_items().end()) { resp.programParameters = toProgramParameters(j["programParameters"], err); if (failed(err)) return {}; } return resp; } static json11::Json fromConfigurationRequest(const ConfigurationRequest &cr, const PluginHandleMapper &pmapper) { json11::Json::object jo; jo["handle"] = double(pmapper.pluginToHandle(cr.plugin)); jo["configuration"] = fromPluginConfiguration(cr.configuration); return json11::Json(jo); } static ConfigurationRequest toConfigurationRequest(json11::Json j, const PluginHandleMapper &pmapper, std::string &err) { if (!j.has_shape({ { "handle", json11::Json::NUMBER }, { "configuration", json11::Json::OBJECT } }, err)) { err = "malformed configuration request: " + err; return {}; } ConfigurationRequest cr; auto h = j["handle"].int_value(); cr.plugin = pmapper.handleToPlugin(h); cr.configuration = toPluginConfiguration(j["configuration"], err); if (failed(err)) return {}; return cr; } static json11::Json fromConfigurationResponse(const ConfigurationResponse &cr, const PluginHandleMapper &pmapper) { json11::Json::object jo; jo["handle"] = double(pmapper.pluginToHandle(cr.plugin)); json11::Json::array outs; for (auto &d: cr.outputs) { auto id = d.identifier; StaticOutputDescriptor sd; if (cr.staticOutputInfo.find(id) != cr.staticOutputInfo.end()) { sd = cr.staticOutputInfo.at(id); } outs.push_back(fromOutputDescriptor(d, sd)); } jo["outputList"] = outs; json11::Json::object framing; framing["stepSize"] = cr.framing.stepSize; framing["blockSize"] = cr.framing.blockSize; jo["framing"] = framing; return json11::Json(jo); } static ConfigurationResponse toConfigurationResponse(json11::Json j, const PluginHandleMapper &pmapper, std::string &err) { ConfigurationResponse cr; if (!j["framing"].has_shape({ { "stepSize", json11::Json::NUMBER }, { "blockSize", json11::Json::NUMBER } }, err)) { err = "malformed framing: " + err; return {}; } if (!j["outputList"].is_array()) { err = "array expected for output list"; return {}; } auto h = j["handle"].int_value(); cr.plugin = pmapper.handleToPlugin(h); for (const auto &o: j["outputList"].array_items()) { auto odpair = toOutputDescriptor(o, err); if (failed(err)) return {}; cr.outputs.push_back(odpair.first); cr.staticOutputInfo[odpair.first.identifier] = odpair.second; } cr.framing.stepSize = int(round(j["framing"]["stepSize"].number_value())); cr.framing.blockSize = int(round(j["framing"]["blockSize"].number_value())); return cr; } static json11::Json fromProcessRequest(const ProcessRequest &r, const PluginHandleMapper &pmapper, BufferSerialisation serialisation) { json11::Json::object jo; jo["handle"] = double(pmapper.pluginToHandle(r.plugin)); json11::Json::object io; io["timestamp"] = fromRealTime(r.timestamp); json11::Json::array chans; for (size_t i = 0; i < r.inputBuffers.size(); ++i) { if (serialisation == BufferSerialisation::Array) { chans.push_back(json11::Json::array(r.inputBuffers[i].begin(), r.inputBuffers[i].end())); } else { chans.push_back(fromFloatBuffer(r.inputBuffers[i].data(), r.inputBuffers[i].size())); } } io["inputBuffers"] = chans; jo["processInput"] = io; return json11::Json(jo); } static ProcessRequest toProcessRequest(json11::Json j, const PluginHandleMapper &pmapper, BufferSerialisation &serialisation, std::string &err) { if (!j.has_shape({ { "handle", json11::Json::NUMBER }, { "processInput", json11::Json::OBJECT } }, err)) { err = "malformed process request: " + err; return {}; } auto input = j["processInput"]; if (!input.has_shape({ { "timestamp", json11::Json::OBJECT }, { "inputBuffers", json11::Json::ARRAY } }, err)) { err = "malformed process request: " + err; return {}; } ProcessRequest r; auto h = j["handle"].int_value(); r.plugin = pmapper.handleToPlugin(h); r.timestamp = toRealTime(input["timestamp"], err); if (failed(err)) return {}; for (const auto &a: input["inputBuffers"].array_items()) { if (a.is_string()) { std::vector<float> buf = toFloatBuffer(a.string_value(), err); if (failed(err)) return {}; r.inputBuffers.push_back(buf); serialisation = BufferSerialisation::Base64; } else if (a.is_array()) { std::vector<float> buf; for (auto v : a.array_items()) { buf.push_back(float(v.number_value())); } r.inputBuffers.push_back(buf); serialisation = BufferSerialisation::Array; } else { err = "expected arrays or strings in inputBuffers array"; return {}; } } return r; } private: // go private briefly for a couple of helper functions static void checkRpcRequestType(json11::Json j, std::string expected, std::string &err) { if (!j["method"].is_string()) { err = "string expected for method"; return; } if (j["method"].string_value() != expected) { err = "expected value \"" + expected + "\" for type"; return; } if (!j["params"].is_null() && !j["params"].is_object()) { err = "object expected for params"; return; } if (!j["id"].is_null() && !j["id"].is_number() && !j["id"].is_string()) { err = "number or string expected for id"; return; } if (!j["jsonrpc"].is_null() && !j["jsonrpc"].is_string()) { err = "string expected for jsonrpc"; return; } for (const auto &kv: j.object_items()) { if (kv.first != "method" && kv.first != "params" && kv.first != "id" && kv.first != "jsonrpc") { err = "unexpected field \"" + kv.first + "\" in rpc request object"; return; } } } static bool successful(json11::Json j, std::string &err) { const bool hasResult = j["result"].is_object(); const bool hasError = j["error"].is_object(); if (hasResult && hasError) { err = "valid response may contain only one of result and error objects"; return false; } else if (hasError) { return false; } else if (!hasResult) { err = "either a result or an error object is required for a valid response"; return false; } else { return true; } } static void markRPC(json11::Json::object &jo) { jo["jsonrpc"] = "2.0"; } static void addId(json11::Json::object &jo, const json11::Json &id) { if (!id.is_null()) { jo["id"] = id; } } public: static json11::Json fromRpcRequest_List(const ListRequest &req, const json11::Json &id) { json11::Json::object jo; markRPC(jo); jo["method"] = "list"; jo["params"] = fromListRequest(req); addId(jo, id); return json11::Json(jo); } static json11::Json fromRpcResponse_List(const ListResponse &resp, const json11::Json &id) { json11::Json::object jo; markRPC(jo); jo["method"] = "list"; jo["result"] = fromListResponse(resp); addId(jo, id); return json11::Json(jo); } static json11::Json fromRpcRequest_Load(const LoadRequest &req, const json11::Json &id) { json11::Json::object jo; markRPC(jo); jo["method"] = "load"; jo["params"] = fromLoadRequest(req); addId(jo, id); return json11::Json(jo); } static json11::Json fromRpcResponse_Load(const LoadResponse &resp, const PluginHandleMapper &pmapper, const json11::Json &id) { if (resp.plugin) { json11::Json::object jo; markRPC(jo); jo["method"] = "load"; jo["result"] = fromLoadResponse(resp, pmapper); addId(jo, id); return json11::Json(jo); } else { return fromError("Failed to load plugin", RRType::Load, id); } } static json11::Json fromRpcRequest_Configure(const ConfigurationRequest &req, const PluginHandleMapper &pmapper, const json11::Json &id) { json11::Json::object jo; markRPC(jo); jo["method"] = "configure"; jo["params"] = fromConfigurationRequest(req, pmapper); addId(jo, id); return json11::Json(jo); } static json11::Json fromRpcResponse_Configure(const ConfigurationResponse &resp, const PluginHandleMapper &pmapper, const json11::Json &id) { if (!resp.outputs.empty()) { json11::Json::object jo; markRPC(jo); jo["method"] = "configure"; jo["result"] = fromConfigurationResponse(resp, pmapper); addId(jo, id); return json11::Json(jo); } else { return fromError("Failed to configure plugin", RRType::Configure, id); } } static json11::Json fromRpcRequest_Process(const ProcessRequest &req, const PluginHandleMapper &pmapper, BufferSerialisation serialisation, const json11::Json &id) { json11::Json::object jo; markRPC(jo); jo["method"] = "process"; jo["params"] = fromProcessRequest(req, pmapper, serialisation); addId(jo, id); return json11::Json(jo); } static json11::Json fromRpcResponse_Process(const ProcessResponse &resp, const PluginHandleMapper &pmapper, BufferSerialisation serialisation, const json11::Json &id) { json11::Json::object jo; markRPC(jo); json11::Json::object po; po["handle"] = double(pmapper.pluginToHandle(resp.plugin)); po["features"] = fromFeatureSet(resp.features, *pmapper.pluginToOutputIdMapper(resp.plugin), serialisation); jo["method"] = "process"; jo["result"] = po; addId(jo, id); return json11::Json(jo); } static json11::Json fromRpcRequest_Finish(const FinishRequest &req, const PluginHandleMapper &pmapper, const json11::Json &id) { json11::Json::object jo; markRPC(jo); json11::Json::object fo; fo["handle"] = double(pmapper.pluginToHandle(req.plugin)); jo["method"] = "finish"; jo["params"] = fo; addId(jo, id); return json11::Json(jo); } static json11::Json fromRpcResponse_Finish(const FinishResponse &resp, const PluginHandleMapper &pmapper, BufferSerialisation serialisation, const json11::Json &id) { json11::Json::object jo; markRPC(jo); json11::Json::object po; po["handle"] = double(pmapper.pluginToHandle(resp.plugin)); po["features"] = fromFeatureSet(resp.features, *pmapper.pluginToOutputIdMapper(resp.plugin), serialisation); jo["method"] = "finish"; jo["result"] = po; addId(jo, id); return json11::Json(jo); } static json11::Json fromError(std::string errorText, RRType responseType, const json11::Json &id, bool writeVerbatimError = false) { json11::Json::object jo; markRPC(jo); std::string type; if (responseType == RRType::List) type = "list"; else if (responseType == RRType::Load) type = "load"; else if (responseType == RRType::Configure) type = "configure"; else if (responseType == RRType::Process) type = "process"; else if (responseType == RRType::Finish) type = "finish"; else type = "invalid"; json11::Json::object eo; eo["code"] = 0; if (responseType == RRType::NotValid || writeVerbatimError) { eo["message"] = errorText; } else { eo["message"] = std::string("error in ") + type + " request: " + errorText; } jo["method"] = type; jo["error"] = eo; addId(jo, id); return json11::Json(jo); } static RRType getRequestResponseType(json11::Json j, std::string &err) { if (!j["method"].is_string()) { err = "string expected for method"; return RRType::NotValid; } std::string type = j["method"].string_value(); if (type == "list") return RRType::List; else if (type == "load") return RRType::Load; else if (type == "configure") return RRType::Configure; else if (type == "process") return RRType::Process; else if (type == "finish") return RRType::Finish; else if (type == "invalid") return RRType::NotValid; else { err = "unknown or unexpected request/response type \"" + type + "\""; return RRType::NotValid; } } static ListRequest toRpcRequest_List(json11::Json j, std::string &err) { checkRpcRequestType(j, "list", err); if (failed(err)) return {}; return toListRequest(j["params"], err); } static ListResponse toRpcResponse_List(json11::Json j, std::string &err) { ListResponse resp; if (successful(j, err) && !failed(err)) { resp = toListResponse(j["result"], err); } return resp; } static LoadRequest toRpcRequest_Load(json11::Json j, std::string &err) { checkRpcRequestType(j, "load", err); if (failed(err)) return {}; return toLoadRequest(j["params"], err); } static LoadResponse toRpcResponse_Load(json11::Json j, const PluginHandleMapper &pmapper, std::string &err) { LoadResponse resp; if (successful(j, err) && !failed(err)) { resp = toLoadResponse(j["result"], pmapper, err); } return resp; } static ConfigurationRequest toRpcRequest_Configure(json11::Json j, const PluginHandleMapper &pmapper, std::string &err) { checkRpcRequestType(j, "configure", err); if (failed(err)) return {}; return toConfigurationRequest(j["params"], pmapper, err); } static ConfigurationResponse toRpcResponse_Configure(json11::Json j, const PluginHandleMapper &pmapper, std::string &err) { ConfigurationResponse resp; if (successful(j, err) && !failed(err)) { resp = toConfigurationResponse(j["result"], pmapper, err); } return resp; } static ProcessRequest toRpcRequest_Process(json11::Json j, const PluginHandleMapper &pmapper, BufferSerialisation &serialisation, std::string &err) { checkRpcRequestType(j, "process", err); if (failed(err)) return {}; return toProcessRequest(j["params"], pmapper, serialisation, err); } static ProcessResponse toRpcResponse_Process(json11::Json j, const PluginHandleMapper &pmapper, BufferSerialisation &serialisation, std::string &err) { ProcessResponse resp; if (successful(j, err) && !failed(err)) { auto jc = j["result"]; auto h = jc["handle"].int_value(); resp.plugin = pmapper.handleToPlugin(h); resp.features = toFeatureSet(jc["features"], *pmapper.handleToOutputIdMapper(h), serialisation, err); } return resp; } static FinishRequest toRpcRequest_Finish(json11::Json j, const PluginHandleMapper &pmapper, std::string &err) { checkRpcRequestType(j, "finish", err); if (failed(err)) return {}; FinishRequest req; auto h = j["params"]["handle"].int_value(); req.plugin = pmapper.handleToPlugin(h); return req; } static FinishResponse toRpcResponse_Finish(json11::Json j, const PluginHandleMapper &pmapper, BufferSerialisation &serialisation, std::string &err) { FinishResponse resp; if (successful(j, err) && !failed(err)) { auto jc = j["result"]; auto h = jc["handle"].int_value(); resp.plugin = pmapper.handleToPlugin(h); resp.features = toFeatureSet(jc["features"], *pmapper.handleToOutputIdMapper(h), serialisation, err); } return resp; } }; } #endif