c@5: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
c@5: 
c@18: /*
c@18:     VamPipe
c@18: 
c@18:     Centre for Digital Music, Queen Mary, University of London.
c@18:     Copyright 2015-2016 QMUL.
c@18:   
c@18:     Permission is hereby granted, free of charge, to any person
c@18:     obtaining a copy of this software and associated documentation
c@18:     files (the "Software"), to deal in the Software without
c@18:     restriction, including without limitation the rights to use, copy,
c@18:     modify, merge, publish, distribute, sublicense, and/or sell copies
c@18:     of the Software, and to permit persons to whom the Software is
c@18:     furnished to do so, subject to the following conditions:
c@18: 
c@18:     The above copyright notice and this permission notice shall be
c@18:     included in all copies or substantial portions of the Software.
c@18: 
c@18:     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
c@18:     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
c@18:     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
c@18:     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
c@18:     ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
c@18:     CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
c@18:     WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
c@18: 
c@18:     Except as contained in this notice, the names of the Centre for
c@18:     Digital Music; Queen Mary, University of London; and Chris Cannam
c@18:     shall not be used in advertising or otherwise to promote the sale,
c@18:     use or other dealings in this Software without prior written
c@18:     authorization.
c@18: */
c@18: 
c@5: #ifndef VAMP_JSON_H
c@5: #define VAMP_JSON_H
c@5: 
c@5: #include <vector>
c@5: #include <string>
c@5: #include <sstream>
c@5: #include <stdexcept>
c@5: 
c@5: #include <json11/json11.hpp>
c@5: #include <base-n/include/basen.hpp>
c@5: 
c@5: #include <vamp-hostsdk/Plugin.h>
c@5: #include <vamp-hostsdk/PluginLoader.h>
c@5: 
c@10: #include "bits/PluginHandleMapper.h"
c@25: #include "bits/RequestResponseType.h"
c@10: 
c@10: namespace vampipe {
c@10: 
c@6: /**
c@6:  * Convert the structures laid out in the Vamp SDK classes into JSON
c@6:  * (and back again) following the schema in the vamp-json-schema
c@6:  * project repo.
c@6:  */
c@5: class VampJson
c@5: {
c@5: public:
c@5:     class Failure : virtual public std::runtime_error {
c@5:     public:
c@5:         Failure(std::string s) : runtime_error(s) { }
c@5:     };
c@5:     
c@5:     template <typename T>
c@5:     static json11::Json
c@5:     fromBasicDescriptor(const T &t) {
c@5:         return json11::Json::object { 
c@5:             { "identifier", t.identifier },
c@5:             { "name", t.name },
c@5:             { "description", t.description }
c@5:         };
c@5:     }
c@5: 
c@5:     template <typename T>
c@5:     static void
c@5:     toBasicDescriptor(json11::Json j, T &t) {
c@5:         if (!j.is_object()) {
c@5:             throw Failure("object expected for basic descriptor content");
c@5:         }
c@5:         if (!j["identifier"].is_string()) {
c@5:             throw Failure("string expected for identifier");
c@5:         }
c@5:         t.identifier = j["identifier"].string_value();
c@5:         t.name = j["name"].string_value();
c@5:         t.description = j["description"].string_value();
c@5:     }
c@5: 
c@5:     template <typename T>
c@5:     static json11::Json
c@5:     fromValueExtents(const T &t) {
c@5:         return json11::Json::object {
c@5:             { "min", t.minValue },
c@5:             { "max", t.maxValue }
c@5:         };
c@5:     }
c@5: 
c@5:     template <typename T>
c@5:     static bool
c@5:     toValueExtents(json11::Json j, T &t) {
c@5:         if (j["extents"].is_null()) {
c@5:             return false;
c@5:         } else if (j["extents"].is_object()) {
c@5:             if (j["extents"]["min"].is_number() &&
c@5:                 j["extents"]["max"].is_number()) {
c@5:                 t.minValue = j["extents"]["min"].number_value();
c@5:                 t.maxValue = j["extents"]["max"].number_value();
c@5:                 return true;
c@5:             } else {
c@5:                 throw Failure("numbers expected for min and max");
c@5:             }
c@5:         } else {
c@5:             throw Failure("object expected for extents (if present)");
c@5:         }
c@5:     }
c@5: 
c@5:     static json11::Json
c@5:     fromRealTime(const Vamp::RealTime &r) {
c@5:         return json11::Json::object {
c@5:             { "s", r.sec },
c@5:             { "n", r.nsec }
c@5:         };
c@5:     }
c@5: 
c@5:     static Vamp::RealTime
c@5:     toRealTime(json11::Json j) {
c@5:         json11::Json sec = j["s"];
c@5:         json11::Json nsec = j["n"];
c@5:         if (!sec.is_number() || !nsec.is_number()) {
c@5:             throw Failure("invalid Vamp::RealTime object " + j.dump());
c@5:         }
c@5:         return Vamp::RealTime(sec.int_value(), nsec.int_value());
c@5:     }
c@5: 
c@5:     static std::string
c@5:     fromSampleType(Vamp::Plugin::OutputDescriptor::SampleType type) {
c@5:         switch (type) {
c@5:         case Vamp::Plugin::OutputDescriptor::OneSamplePerStep:
c@5:             return "OneSamplePerStep";
c@5:         case Vamp::Plugin::OutputDescriptor::FixedSampleRate:
c@5:             return "FixedSampleRate";
c@5:         case Vamp::Plugin::OutputDescriptor::VariableSampleRate:
c@5:             return "VariableSampleRate";
c@5:         }
c@5:         return "";
c@5:     }
c@5: 
c@5:     static Vamp::Plugin::OutputDescriptor::SampleType
c@5:     toSampleType(std::string text) {
c@5:         if (text == "OneSamplePerStep") {
c@5:             return Vamp::Plugin::OutputDescriptor::OneSamplePerStep;
c@5:         } else if (text == "FixedSampleRate") {
c@5:             return Vamp::Plugin::OutputDescriptor::FixedSampleRate;
c@5:         } else if (text == "VariableSampleRate") {
c@5:             return Vamp::Plugin::OutputDescriptor::VariableSampleRate;
c@5:         } else {
c@5:             throw Failure("invalid sample type string: " + text);
c@5:         }
c@5:     }
c@5: 
c@5:     static json11::Json
c@5:     fromOutputDescriptor(const Vamp::Plugin::OutputDescriptor &desc) {
c@5:         json11::Json::object jo {
c@5:             { "basic", fromBasicDescriptor(desc) },
c@5:             { "unit", desc.unit },
c@5:             { "sampleType", fromSampleType(desc.sampleType) },
c@5:             { "sampleRate", desc.sampleRate },
c@5:             { "hasDuration", desc.hasDuration }
c@5:         };
c@5:         if (desc.hasFixedBinCount) {
c@5:             jo["binCount"] = int(desc.binCount);
c@5:             jo["binNames"] = json11::Json::array
c@5:                 (desc.binNames.begin(), desc.binNames.end());
c@5:         }
c@5:         if (desc.hasKnownExtents) {
c@5:             jo["extents"] = fromValueExtents(desc);
c@5:         }
c@5:         if (desc.isQuantized) {
c@5:             jo["quantizeStep"] = desc.quantizeStep;
c@5:         }
c@5:         return json11::Json(jo);
c@5:     }
c@12:     
c@5:     static Vamp::Plugin::OutputDescriptor
c@5:     toOutputDescriptor(json11::Json j) {
c@5: 
c@5:         Vamp::Plugin::OutputDescriptor od;
c@5:         if (!j.is_object()) {
c@5:             throw Failure("object expected for output descriptor");
c@5:         }
c@5:     
c@5:         toBasicDescriptor(j["basic"], od);
c@5: 
c@5:         od.unit = j["unit"].string_value();
c@5: 
c@5:         od.sampleType = toSampleType(j["sampleType"].string_value());
c@5: 
c@5:         if (!j["sampleRate"].is_number()) {
c@5:             throw Failure("number expected for sample rate");
c@5:         }
c@5:         od.sampleRate = j["sampleRate"].number_value();
c@5:         od.hasDuration = j["hasDuration"].bool_value();
c@5: 
c@5:         if (j["binCount"].is_number() && j["binCount"].int_value() > 0) {
c@5:             od.hasFixedBinCount = true;
c@5:             od.binCount = j["binCount"].int_value();
c@5:             for (auto &n: j["binNames"].array_items()) {
c@5:                 if (!n.is_string()) {
c@5:                     throw Failure("string expected for bin name");
c@5:                 }
c@5:                 od.binNames.push_back(n.string_value());
c@5:             }
c@5:         } else {
c@5:             od.hasFixedBinCount = false;
c@5:         }
c@5: 
c@5:         bool extentsPresent = toValueExtents(j, od);
c@5:         od.hasKnownExtents = extentsPresent;
c@5: 
c@5:         if (j["quantizeStep"].is_number()) {
c@5:             od.isQuantized = true;
c@5:             od.quantizeStep = j["quantizeStep"].number_value();
c@5:         } else {
c@5:             od.isQuantized = false;
c@5:         }
c@5: 
c@5:         return od;
c@5:     }
c@5: 
c@5:     static json11::Json
c@5:     fromParameterDescriptor(const Vamp::PluginBase::ParameterDescriptor &desc) {
c@5: 
c@5:         json11::Json::object jo {
c@5:             { "basic", fromBasicDescriptor(desc) },
c@5:             { "unit", desc.unit },
c@5:             { "extents", fromValueExtents(desc) },
c@5:             { "defaultValue", desc.defaultValue },
c@5:             { "valueNames", json11::Json::array
c@5:                     (desc.valueNames.begin(), desc.valueNames.end()) }
c@5:         };
c@5:         if (desc.isQuantized) {
c@5:             jo["quantizeStep"] = desc.quantizeStep;
c@5:         }
c@5:         return json11::Json(jo);
c@5:     }
c@5: 
c@5:     static Vamp::PluginBase::ParameterDescriptor
c@5:     toParameterDescriptor(json11::Json j) {
c@5: 
c@5:         Vamp::PluginBase::ParameterDescriptor pd;
c@5:         if (!j.is_object()) {
c@5:             throw Failure("object expected for parameter descriptor");
c@5:         }
c@5:     
c@5:         toBasicDescriptor(j["basic"], pd);
c@5: 
c@5:         pd.unit = j["unit"].string_value();
c@5: 
c@5:         bool extentsPresent = toValueExtents(j, pd);
c@5:         if (!extentsPresent) {
c@5:             throw Failure("extents must be present in parameter descriptor");
c@5:         }
c@5:     
c@5:         if (!j["defaultValue"].is_number()) {
c@5:             throw Failure("number expected for default value");
c@5:         }
c@5:     
c@5:         pd.defaultValue = j["defaultValue"].number_value();
c@5: 
c@5:         pd.valueNames.clear();
c@5:         for (auto &n: j["valueNames"].array_items()) {
c@5:             if (!n.is_string()) {
c@5:                 throw Failure("string expected for value name");
c@5:             }
c@5:             pd.valueNames.push_back(n.string_value());
c@5:         }
c@5: 
c@5:         if (j["quantizeStep"].is_number()) {
c@5:             pd.isQuantized = true;
c@5:             pd.quantizeStep = j["quantizeStep"].number_value();
c@5:         } else {
c@5:             pd.isQuantized = false;
c@5:         }
c@5: 
c@5:         return pd;
c@5:     }
c@5: 
c@5:     static std::string
c@5:     fromFloatBuffer(const float *buffer, size_t nfloats) {
c@5:         // must use char pointers, otherwise the converter will only
c@5:         // encode every 4th byte (as it will count up in float* steps)
c@5:         const char *start = reinterpret_cast<const char *>(buffer);
c@5:         const char *end = reinterpret_cast<const char *>(buffer + nfloats);
c@5:         std::string encoded;
c@5:         bn::encode_b64(start, end, back_inserter(encoded));
c@5:         return encoded;
c@5:     }
c@5: 
c@5:     static std::vector<float>
c@5:     toFloatBuffer(std::string encoded) {
c@5:         std::string decoded;
c@5:         bn::decode_b64(encoded.begin(), encoded.end(), back_inserter(decoded));
c@5:         const float *buffer = reinterpret_cast<const float *>(decoded.c_str());
c@5:         size_t n = decoded.size() / sizeof(float);
c@5:         return std::vector<float>(buffer, buffer + n);
c@5:     }
c@5: 
c@5:     static json11::Json
c@5:     fromFeature(const Vamp::Plugin::Feature &f) {
c@5: 
c@5:         json11::Json::object jo;
c@5:         if (f.values.size() > 0) {
c@5:             jo["b64values"] = fromFloatBuffer(f.values.data(), f.values.size());
c@5:         }
c@5:         if (f.label != "") {
c@5:             jo["label"] = f.label;
c@5:         }
c@5:         if (f.hasTimestamp) {
c@5:             jo["timestamp"] = fromRealTime(f.timestamp);
c@5:         }
c@5:         if (f.hasDuration) {
c@5:             jo["duration"] = fromRealTime(f.duration);
c@5:         }
c@5:         return json11::Json(jo);
c@5:     }
c@5: 
c@5:     static Vamp::Plugin::Feature
c@5:     toFeature(json11::Json j) {
c@5: 
c@5:         Vamp::Plugin::Feature f;
c@5:         if (!j.is_object()) {
c@5:             throw Failure("object expected for feature");
c@5:         }
c@5:         if (j["timestamp"].is_object()) {
c@5:             f.timestamp = toRealTime(j["timestamp"]);
c@5:             f.hasTimestamp = true;
c@5:         }
c@5:         if (j["duration"].is_object()) {
c@5:             f.duration = toRealTime(j["duration"]);
c@5:             f.hasDuration = true;
c@5:         }
c@5:         if (j["b64values"].is_string()) {
c@5:             f.values = toFloatBuffer(j["b64values"].string_value());
c@5:         } else if (j["values"].is_array()) {
c@5:             for (auto v : j["values"].array_items()) {
c@5:                 f.values.push_back(v.number_value());
c@5:             }
c@5:         }
c@5:         f.label = j["label"].string_value();
c@5:         return f;
c@5:     }
c@5: 
c@5:     static json11::Json
c@5:     fromFeatureSet(const Vamp::Plugin::FeatureSet &fs) {
c@5: 
c@5:         json11::Json::object jo;
c@5:         for (const auto &fsi : fs) {
c@5:             std::vector<json11::Json> fj;
c@5:             for (const Vamp::Plugin::Feature &f: fsi.second) {
c@5:                 fj.push_back(fromFeature(f));
c@5:             }
c@5:             std::stringstream sstr;
c@5:             sstr << fsi.first;
c@5:             std::string n = sstr.str();
c@5:             jo[n] = fj;
c@5:         }
c@5:         return json11::Json(jo);
c@5:     }
c@5: 
c@5:     static Vamp::Plugin::FeatureList
c@5:     toFeatureList(json11::Json j) {
c@5: 
c@5:         Vamp::Plugin::FeatureList fl;
c@5:         if (!j.is_array()) {
c@5:             throw Failure("array expected for feature list");
c@5:         }
c@5:         for (const json11::Json &fj : j.array_items()) {
c@5:             fl.push_back(toFeature(fj));
c@5:         }
c@5:         return fl;
c@5:     }
c@5: 
c@5:     static Vamp::Plugin::FeatureSet
c@5:     toFeatureSet(json11::Json j) {
c@5: 
c@5:         Vamp::Plugin::FeatureSet fs;
c@5:         if (!j.is_object()) {
c@5:             throw Failure("object expected for feature set");
c@5:         }
c@5:         for (auto &entry : j.object_items()) {
c@5:             std::string nstr = entry.first;
c@5:             size_t count = 0;
c@5:             int n = stoi(nstr, &count);
c@5:             if (n < 0 || fs.find(n) != fs.end() || count < nstr.size()) {
c@5:                 throw Failure("invalid or duplicate numerical index for output");
c@5:             }
c@5:             fs[n] = toFeatureList(entry.second);
c@5:         }
c@5:         return fs;
c@5:     }
c@5: 
c@5:     static std::string
c@5:     fromInputDomain(Vamp::Plugin::InputDomain domain) {
c@5: 
c@5:         switch (domain) {
c@5:         case Vamp::Plugin::TimeDomain:
c@5:             return "TimeDomain";
c@5:         case Vamp::Plugin::FrequencyDomain:
c@5:             return "FrequencyDomain";
c@5:         }
c@5:         return "";
c@5:     }
c@5: 
c@5:     static Vamp::Plugin::InputDomain
c@5:     toInputDomain(std::string text) {
c@5: 
c@5:         if (text == "TimeDomain") {
c@5:             return Vamp::Plugin::TimeDomain;
c@5:         } else if (text == "FrequencyDomain") {
c@5:             return Vamp::Plugin::FrequencyDomain;
c@5:         } else {
c@5:             throw Failure("invalid input domain string: " + text);
c@5:         }
c@5:     }
c@5: 
c@5:     static json11::Json
c@5:     fromPluginStaticData(const Vamp::HostExt::PluginStaticData &d) {
c@5: 
c@5:         json11::Json::object jo;
c@5:         jo["pluginKey"] = d.pluginKey;
c@5:         jo["basic"] = fromBasicDescriptor(d.basic);
c@5:         jo["maker"] = d.maker;
c@5:         jo["copyright"] = d.copyright;
c@5:         jo["pluginVersion"] = d.pluginVersion;
c@5: 
c@5:         json11::Json::array cat;
c@5:         for (const std::string &c: d.category) cat.push_back(c);
c@5:         jo["category"] = cat;
c@5: 
c@5:         jo["minChannelCount"] = d.minChannelCount;
c@5:         jo["maxChannelCount"] = d.maxChannelCount;
c@5: 
c@5:         json11::Json::array params;
c@5:         Vamp::PluginBase::ParameterList vparams = d.parameters;
c@5:         for (auto &p: vparams) params.push_back(fromParameterDescriptor(p));
c@5:         jo["parameters"] = params;
c@5: 
c@5:         json11::Json::array progs;
c@5:         Vamp::PluginBase::ProgramList vprogs = d.programs;
c@5:         for (auto &p: vprogs) progs.push_back(p);
c@5:         jo["programs"] = progs;
c@5: 
c@5:         jo["inputDomain"] = fromInputDomain(d.inputDomain);
c@5: 
c@5:         json11::Json::array outinfo;
c@5:         auto vouts = d.basicOutputInfo;
c@5:         for (auto &o: vouts) outinfo.push_back(fromBasicDescriptor(o));
c@5:         jo["basicOutputInfo"] = outinfo;
c@5:     
c@5:         return json11::Json(jo);
c@5:     }
c@5: 
c@5:     static Vamp::HostExt::PluginStaticData
c@5:     toPluginStaticData(json11::Json j) {
c@5: 
c@5:         std::string err;
c@5:         if (!j.has_shape({
c@5:                     { "pluginKey", json11::Json::STRING },
c@5:                     { "pluginVersion", json11::Json::NUMBER },
c@5:                     { "minChannelCount", json11::Json::NUMBER },
c@5:                     { "maxChannelCount", json11::Json::NUMBER },
c@5:                     { "inputDomain", json11::Json::STRING }}, err)) {
c@5:             throw Failure("malformed plugin static data: " + err);
c@5:         }
c@5: 
c@5:         if (!j["basicOutputInfo"].is_array()) {
c@5:             throw Failure("array expected for basic output info");
c@5:         }
c@5: 
c@5:         if (!j["maker"].is_null() &&
c@5:             !j["maker"].is_string()) {
c@5:             throw Failure("string expected for maker");
c@5:         }
c@5:         
c@5:         if (!j["copyright"].is_null() &&
c@5:             !j["copyright"].is_string()) {
c@5:             throw Failure("string expected for copyright");
c@5:         }
c@5: 
c@5:         if (!j["category"].is_null() &&
c@5:             !j["category"].is_array()) {
c@5:             throw Failure("array expected for category");
c@5:         }
c@5: 
c@5:         if (!j["parameters"].is_null() &&
c@5:             !j["parameters"].is_array()) {
c@5:             throw Failure("array expected for parameters");
c@5:         }
c@5: 
c@5:         if (!j["programs"].is_null() &&
c@5:             !j["programs"].is_array()) {
c@5:             throw Failure("array expected for programs");
c@5:         }
c@5: 
c@5:         if (!j["inputDomain"].is_null() &&
c@5:             !j["inputDomain"].is_string()) {
c@5:             throw Failure("string expected for inputDomain");
c@5:         }
c@5: 
c@5:         if (!j["basicOutputInfo"].is_null() &&
c@5:             !j["basicOutputInfo"].is_array()) {
c@5:             throw Failure("array expected for basicOutputInfo");
c@5:         }
c@5: 
c@5:         Vamp::HostExt::PluginStaticData psd;
c@5: 
c@5:         psd.pluginKey = j["pluginKey"].string_value();
c@5: 
c@5:         toBasicDescriptor(j["basic"], psd.basic);
c@5: 
c@5:         psd.maker = j["maker"].string_value();
c@5:         psd.copyright = j["copyright"].string_value();
c@5:         psd.pluginVersion = j["pluginVersion"].int_value();
c@5: 
c@5:         for (const auto &c : j["category"].array_items()) {
c@5:             if (!c.is_string()) {
c@5:                 throw Failure("strings expected in category array");
c@5:             }
c@5:             psd.category.push_back(c.string_value());
c@5:         }
c@5: 
c@5:         psd.minChannelCount = j["minChannelCount"].int_value();
c@5:         psd.maxChannelCount = j["maxChannelCount"].int_value();
c@5: 
c@5:         for (const auto &p : j["parameters"].array_items()) {
c@5:             auto pd = toParameterDescriptor(p);
c@5:             psd.parameters.push_back(pd);
c@5:         }
c@5: 
c@5:         for (const auto &p : j["programs"].array_items()) {
c@5:             if (!p.is_string()) {
c@5:                 throw Failure("strings expected in programs array");
c@5:             }
c@5:             psd.programs.push_back(p.string_value());
c@5:         }
c@5: 
c@5:         psd.inputDomain = toInputDomain(j["inputDomain"].string_value());
c@5: 
c@5:         for (const auto &bo : j["basicOutputInfo"].array_items()) {
c@5:             Vamp::HostExt::PluginStaticData::Basic b;
c@5:             toBasicDescriptor(bo, b);
c@5:             psd.basicOutputInfo.push_back(b);
c@5:         }
c@5: 
c@5:         return psd;
c@5:     }
c@5: 
c@5:     static json11::Json
c@5:     fromPluginConfiguration(const Vamp::HostExt::PluginConfiguration &c) {
c@5: 
c@5:         json11::Json::object jo;
c@5: 
c@5:         json11::Json::object paramValues;
c@5:         for (auto &vp: c.parameterValues) {
c@5:             paramValues[vp.first] = vp.second;
c@5:         }
c@5:         jo["parameterValues"] = paramValues;
c@5: 
c@5:         if (c.currentProgram != "") {
c@5:             jo["currentProgram"] = c.currentProgram;
c@5:         }
c@5: 
c@5:         jo["channelCount"] = c.channelCount;
c@5:         jo["stepSize"] = c.stepSize;
c@5:         jo["blockSize"] = c.blockSize;
c@5:     
c@5:         return json11::Json(jo);
c@5:     }
c@5: 
c@5:     static Vamp::HostExt::PluginConfiguration
c@5:     toPluginConfiguration(json11::Json j) {
c@5:         
c@5:         std::string err;
c@5:         if (!j.has_shape({
c@5:                     { "channelCount", json11::Json::NUMBER },
c@5:                     { "stepSize", json11::Json::NUMBER },
c@5:                     { "blockSize", json11::Json::NUMBER } }, err)) {
c@5:             throw Failure("malformed plugin configuration: " + err);
c@5:         }
c@5: 
c@5:         if (!j["parameterValues"].is_null() &&
c@5:             !j["parameterValues"].is_object()) {
c@5:             throw Failure("object expected for parameter values");
c@5:         }
c@5: 
c@5:         for (auto &pv : j["parameterValues"].object_items()) {
c@5:             if (!pv.second.is_number()) {
c@5:                 throw Failure("number expected for parameter value");
c@5:             }
c@5:         }
c@5:     
c@5:         if (!j["currentProgram"].is_null() &&
c@5:             !j["currentProgram"].is_string()) {
c@5:             throw Failure("string expected for program name");
c@5:         }
c@5: 
c@5:         Vamp::HostExt::PluginConfiguration config;
c@5: 
c@5:         config.channelCount = j["channelCount"].number_value();
c@5:         config.stepSize = j["stepSize"].number_value();
c@5:         config.blockSize = j["blockSize"].number_value();
c@5:         
c@5:         for (auto &pv : j["parameterValues"].object_items()) {
c@5:             config.parameterValues[pv.first] = pv.second.number_value();
c@5:         }
c@5: 
c@5:         if (j["currentProgram"].is_string()) {
c@5:             config.currentProgram = j["currentProgram"].string_value();
c@5:         }
c@5: 
c@5:         return config;
c@5:     }
c@5: 
c@5:     static json11::Json
c@5:     fromAdapterFlags(int flags) {
c@5: 
c@5:         json11::Json::array arr;
c@5: 
c@5:         if (flags & Vamp::HostExt::PluginLoader::ADAPT_INPUT_DOMAIN) {
c@5:             arr.push_back("AdaptInputDomain");
c@5:         }
c@5:         if (flags & Vamp::HostExt::PluginLoader::ADAPT_CHANNEL_COUNT) {
c@5:             arr.push_back("AdaptChannelCount");
c@5:         }
c@5:         if (flags & Vamp::HostExt::PluginLoader::ADAPT_BUFFER_SIZE) {
c@5:             arr.push_back("AdaptBufferSize");
c@5:         }
c@5: 
c@5:         return json11::Json(arr);
c@5:     }
c@5: 
c@5:     static Vamp::HostExt::PluginLoader::AdapterFlags
c@5:     toAdapterFlags(json11::Json j) {
c@5: 
c@5:         if (!j.is_array()) {
c@5:             throw Failure("array expected for adapter flags");
c@5:         }
c@5:         int flags = 0x0;
c@5: 
c@5:         for (auto &jj: j.array_items()) {
c@5:             if (!jj.is_string()) {
c@5:                 throw Failure("string expected for adapter flag");
c@5:             }
c@5:             std::string text = jj.string_value();
c@5:             if (text == "AdaptInputDomain") {
c@5:                 flags |= Vamp::HostExt::PluginLoader::ADAPT_INPUT_DOMAIN;
c@5:             } else if (text == "AdaptChannelCount") {
c@5:                 flags |= Vamp::HostExt::PluginLoader::ADAPT_CHANNEL_COUNT;
c@5:             } else if (text == "AdaptBufferSize") {
c@5:                 flags |= Vamp::HostExt::PluginLoader::ADAPT_BUFFER_SIZE;
c@5:             } else if (text == "AdaptAllSafe") {
c@5:                 flags |= Vamp::HostExt::PluginLoader::ADAPT_ALL_SAFE;
c@5:             } else if (text == "AdaptAll") {
c@5:                 flags |= Vamp::HostExt::PluginLoader::ADAPT_ALL;
c@5:             } else {
c@5:                 throw Failure("invalid adapter flag string: " + text);
c@5:             }
c@5:         }
c@5: 
c@5:         return Vamp::HostExt::PluginLoader::AdapterFlags(flags);
c@5:     }
c@5: 
c@5:     static json11::Json
c@17:     fromLoadRequest(const Vamp::HostExt::LoadRequest &req) {
c@5: 
c@5:         json11::Json::object jo;
c@5:         jo["pluginKey"] = req.pluginKey;
c@5:         jo["inputSampleRate"] = req.inputSampleRate;
c@5:         jo["adapterFlags"] = fromAdapterFlags(req.adapterFlags);
c@5:         return json11::Json(jo);
c@5:     }
c@5: 
c@5:     static Vamp::HostExt::LoadRequest
c@5:     toLoadRequest(json11::Json j) {
c@5:         
c@5:         std::string err;
c@5: 
c@5:         if (!j.has_shape({
c@5:                     { "pluginKey", json11::Json::STRING },
c@5:                     { "inputSampleRate", json11::Json::NUMBER },
c@5:                     { "adapterFlags", json11::Json::ARRAY } }, err)) {
c@12:             throw Failure("malformed load request: " + err);
c@5:         }
c@5:     
c@5:         Vamp::HostExt::LoadRequest req;
c@5:         req.pluginKey = j["pluginKey"].string_value();
c@5:         req.inputSampleRate = j["inputSampleRate"].number_value();
c@5:         req.adapterFlags = toAdapterFlags(j["adapterFlags"]);
c@5:         return req;
c@5:     }
c@10: 
c@10:     static json11::Json
c@17:     fromLoadResponse(const Vamp::HostExt::LoadResponse &resp,
c@10:                      PluginHandleMapper &mapper) {
c@10: 
c@10:         json11::Json::object jo;
c@10:         jo["pluginHandle"] = double(mapper.pluginToHandle(resp.plugin));
c@10:         jo["staticData"] = fromPluginStaticData(resp.staticData);
c@10:         jo["defaultConfiguration"] =
c@10:             fromPluginConfiguration(resp.defaultConfiguration);
c@10:         return json11::Json(jo);
c@10:     }
c@10: 
c@10:     static Vamp::HostExt::LoadResponse
c@10:     toLoadResponse(json11::Json j,
c@10:                    PluginHandleMapper &mapper) {
c@10: 
c@10:         std::string err;
c@10: 
c@10:         if (!j.has_shape({
c@10:                     { "pluginHandle", json11::Json::NUMBER },
c@10:                     { "staticData", json11::Json::OBJECT },
c@10:                     { "defaultConfiguration", json11::Json::OBJECT } }, err)) {
c@12:             throw Failure("malformed load response: " + err);
c@10:         }
c@10: 
c@10:         Vamp::HostExt::LoadResponse resp;
c@10:         resp.plugin = mapper.handleToPlugin(j["pluginHandle"].int_value());
c@10:         resp.staticData = toPluginStaticData(j["staticData"]);
c@10:         resp.defaultConfiguration = toPluginConfiguration(j["defaultConfiguration"]);
c@10:         return resp;
c@10:     }
c@12: 
c@12:     static json11::Json
c@13:     fromConfigurationRequest(const Vamp::HostExt::ConfigurationRequest &cr,
c@13:                              PluginHandleMapper &mapper) {
c@13: 
c@13:         json11::Json::object jo;
c@13: 
c@13:         jo["pluginHandle"] = mapper.pluginToHandle(cr.plugin);
c@13:         jo["configuration"] = fromPluginConfiguration(cr.configuration);
c@13:         
c@13:         return json11::Json(jo);
c@13:     }
c@13: 
c@13:     static Vamp::HostExt::ConfigurationRequest
c@13:     toConfigurationRequest(json11::Json j,
c@13:                            PluginHandleMapper &mapper) {
c@13: 
c@13:         std::string err;
c@13: 
c@13:         if (!j.has_shape({
c@13:                     { "pluginHandle", json11::Json::NUMBER },
c@13:                     { "configuration", json11::Json::OBJECT } }, err)) {
c@13:             throw Failure("malformed configuration request: " + err);
c@13:         }
c@13: 
c@13:         Vamp::HostExt::ConfigurationRequest cr;
c@13:         cr.plugin = mapper.handleToPlugin(j["pluginHandle"].int_value());
c@13:         cr.configuration = toPluginConfiguration(j["configuration"]);
c@13:         return cr;
c@13:     }
c@13: 
c@13:     static json11::Json
c@12:     fromConfigurationResponse(const Vamp::HostExt::ConfigurationResponse &cr) {
c@12: 
c@13:         json11::Json::object jo;
c@12:         
c@12:         json11::Json::array outs;
c@12:         for (auto &d: cr.outputs) {
c@12:             outs.push_back(fromOutputDescriptor(d));
c@12:         }
c@13:         jo["outputList"] = outs;
c@12:         
c@13:         return json11::Json(jo);
c@12:     }
c@12: 
c@13:     static Vamp::HostExt::ConfigurationResponse
c@13:     toConfigurationResponse(json11::Json j) {
c@13:         
c@12:         Vamp::HostExt::ConfigurationResponse cr;
c@12: 
c@12:         if (!j["outputList"].is_array()) {
c@12:             throw Failure("array expected for output list");
c@12:         }
c@12: 
c@12:         for (const auto &o: j["outputList"].array_items()) {
c@12:             cr.outputs.push_back(toOutputDescriptor(o));
c@12:         }
c@12: 
c@12:         return cr;
c@12:     }
c@16: 
c@16:     static json11::Json
c@16:     fromProcessRequest(const Vamp::HostExt::ProcessRequest &r,
c@16:                        PluginHandleMapper &mapper) {
c@16: 
c@16:         json11::Json::object jo;
c@16:         jo["pluginHandle"] = mapper.pluginToHandle(r.plugin);
c@16: 
c@16:         json11::Json::object io;
c@16:         io["timestamp"] = fromRealTime(r.timestamp);
c@16: 
c@16:         json11::Json::array chans;
c@16:         for (size_t i = 0; i < r.inputBuffers.size(); ++i) {
c@16:             json11::Json::object c;
c@16:             c["b64values"] = fromFloatBuffer(r.inputBuffers[i].data(),
c@16:                                              r.inputBuffers[i].size());
c@16:             chans.push_back(c);
c@16:         }
c@16:         io["inputBuffers"] = chans;
c@16:         
c@16:         jo["processInput"] = io;
c@16:         return json11::Json(jo);
c@16:     }
c@17: 
c@17:     static Vamp::HostExt::ProcessRequest
c@17:     toProcessRequest(json11::Json j, PluginHandleMapper &mapper) {
c@17: 
c@17:         std::string err;
c@17: 
c@17:         if (!j.has_shape({
c@17:                     { "pluginHandle", json11::Json::NUMBER },
c@17:                     { "processInput", json11::Json::OBJECT } }, err)) {
c@17:             throw Failure("malformed process request: " + err);
c@17:         }
c@17: 
c@17:         auto input = j["processInput"];
c@17: 
c@17:         if (!input.has_shape({
c@17:                     { "timestamp", json11::Json::OBJECT },
c@17:                     { "inputBuffers", json11::Json::ARRAY } }, err)) {
c@17:             throw Failure("malformed process request: " + err);
c@17:         }
c@17: 
c@17:         Vamp::HostExt::ProcessRequest r;
c@17:         r.plugin = mapper.handleToPlugin(j["pluginHandle"].int_value());
c@17: 
c@17:         r.timestamp = toRealTime(input["timestamp"]);
c@17: 
c@17:         for (auto a: input["inputBuffers"].array_items()) {
c@17:             if (a["b64values"].is_string()) {
c@17:                 r.inputBuffers.push_back(toFloatBuffer
c@17:                                          (a["b64values"].string_value()));
c@17:             } else if (a["values"].is_array()) {
c@17:                 std::vector<float> buf;
c@17:                 for (auto v : a["values"].array_items()) {
c@17:                     buf.push_back(v.number_value());
c@17:                 }
c@17:                 r.inputBuffers.push_back(buf);
c@17:             } else {
c@17:                 throw Failure("expected values or b64values in inputBuffers object");
c@17:             }
c@17:         }
c@17: 
c@17:         return r;
c@17:     }
c@17: 
c@17:     static json11::Json
c@17:     fromVampRequest_List() {
c@17: 
c@17:         json11::Json::object jo;
c@17:         jo["type"] = "list";
c@17:         return json11::Json(jo);
c@17:     }
c@17: 
c@17:     static json11::Json
c@17:     fromVampResponse_List(std::string errorText,
c@17:                           const std::vector<Vamp::HostExt::PluginStaticData> &d) {
c@17: 
c@17:         json11::Json::object jo;
c@24:         jo["type"] = "list";
c@17:         jo["success"] = (errorText == "");
c@17:         jo["errorText"] = errorText;
c@17: 
c@17:         json11::Json::array arr;
c@17:         for (const auto &a: d) {
c@17:             arr.push_back(fromPluginStaticData(a));
c@17:         }
c@24:         json11::Json::object po;
c@24:         po["plugins"] = arr;
c@24: 
c@24:         jo["content"] = po;
c@17:         return json11::Json(jo);
c@17:     }
c@17:     
c@17:     static json11::Json
c@17:     fromVampRequest_Load(const Vamp::HostExt::LoadRequest &req) {
c@17: 
c@17:         json11::Json::object jo;
c@17:         jo["type"] = "load";
c@17:         jo["content"] = fromLoadRequest(req);
c@17:         return json11::Json(jo);
c@17:     }    
c@17: 
c@17:     static json11::Json
c@17:     fromVampResponse_Load(const Vamp::HostExt::LoadResponse &resp,
c@17:                           PluginHandleMapper &mapper) {
c@17: 
c@17:         json11::Json::object jo;
c@24:         jo["type"] = "load";
c@17:         jo["success"] = (resp.plugin != 0);
c@17:         jo["errorText"] = "";
c@24:         jo["content"] = fromLoadResponse(resp, mapper);
c@17:         return json11::Json(jo);
c@17:     }
c@17: 
c@17:     static json11::Json
c@17:     fromVampRequest_Configure(const Vamp::HostExt::ConfigurationRequest &req,
c@17:                               PluginHandleMapper &mapper) {
c@17: 
c@17:         json11::Json::object jo;
c@17:         jo["type"] = "configure";
c@17:         jo["content"] = fromConfigurationRequest(req, mapper);
c@17:         return json11::Json(jo);
c@17:     }    
c@17: 
c@17:     static json11::Json
c@17:     fromVampResponse_Configure(const Vamp::HostExt::ConfigurationResponse &resp) {
c@17:         
c@17:         json11::Json::object jo;
c@24:         jo["type"] = "configure";
c@17:         jo["success"] = (!resp.outputs.empty());
c@17:         jo["errorText"] = "";
c@24:         jo["content"] = fromConfigurationResponse(resp);
c@17:         return json11::Json(jo);
c@17:     }
c@17:     
c@17:     static json11::Json
c@17:     fromVampRequest_Process(const Vamp::HostExt::ProcessRequest &req,
c@17:                             PluginHandleMapper &mapper) {
c@17: 
c@17:         json11::Json::object jo;
c@17:         jo["type"] = "process";
c@17:         jo["content"] = fromProcessRequest(req, mapper);
c@17:         return json11::Json(jo);
c@17:     }    
c@17: 
c@17:     static json11::Json
c@17:     fromVampResponse_Process(const Vamp::HostExt::ProcessResponse &resp) {
c@17:         
c@17:         json11::Json::object jo;
c@24:         jo["type"] = "process";
c@17:         jo["success"] = true;
c@17:         jo["errorText"] = "";
c@24:         jo["content"] = fromFeatureSet(resp.features);
c@17:         return json11::Json(jo);
c@17:     }
c@17:     
c@17:     static json11::Json
c@24:     fromVampRequest_Finish(Vamp::Plugin *p,
c@24:                            PluginHandleMapper &mapper) {
c@17: 
c@17:         json11::Json::object jo;
c@17:         jo["type"] = "finish";
c@24:         json11::Json::object fo;
c@24:         fo["pluginHandle"] = mapper.pluginToHandle(p);
c@24:         jo["content"] = fo;
c@17:         return json11::Json(jo);
c@17:     }    
c@17:     
c@17:     static json11::Json
c@17:     fromVampResponse_Finish(const Vamp::HostExt::ProcessResponse &resp) {
c@17: 
c@17:         json11::Json::object jo;
c@24:         jo["type"] = "finish";
c@17:         jo["success"] = true;
c@17:         jo["errorText"] = "";
c@24:         jo["content"] = fromFeatureSet(resp.features);
c@17:         return json11::Json(jo);
c@17:     }
c@24: 
c@24: private: // go private briefly for a couple of helper functions
c@24:     
c@24:     static void
c@24:     checkTypeField(json11::Json j, std::string expected) {
c@24:         if (!j["type"].is_string()) {
c@24:             throw Failure("string expected for type");
c@24:         }
c@24:         if (j["type"].string_value() != expected) {
c@24:             throw Failure("expected value \"" + expected + "\" for type");
c@24:         }
c@24:     }
c@24: 
c@24:     static bool
c@24:     successful(json11::Json j) {
c@24:         if (!j["success"].is_bool()) {
c@24:             throw Failure("bool expected for success");
c@24:         }
c@24:         return j["success"].bool_value();
c@24:     }
c@24: 
c@24: public:
c@25:     static RRType
c@25:     getRequestResponseType(json11::Json j) {
c@25: 
c@25:         if (!j["type"].is_string()) {
c@25:             throw Failure("string expected for type");
c@25:         }
c@25:         
c@25:         std::string type = j["type"].string_value();
c@25: 
c@25: 	if (type == "list") return RRType::List;
c@25: 	else if (type == "load") return RRType::Load;
c@25: 	else if (type == "configure") return RRType::Configure;
c@25: 	else if (type == "process") return RRType::Process;
c@25: 	else if (type == "finish") return RRType::Finish;
c@25: 	else {
c@25: 	    throw Failure("unknown or unexpected request/response type \"" +
c@25:                           type + "\"");
c@25: 	}
c@25:     }
c@25:     
c@24:     static void
c@24:     toVampRequest_List(json11::Json j) {
c@24:         
c@24:         checkTypeField(j, "list");
c@24:     }
c@24: 
c@24:     static std::vector<Vamp::HostExt::PluginStaticData>
c@24:     toVampResponse_List(json11::Json j) {
c@24: 
c@24:         std::vector<Vamp::HostExt::PluginStaticData> arr;
c@24:         if (successful(j)) {
c@24:             for (const auto &a: j["content"]["plugins"].array_items()) {
c@24:                 arr.push_back(toPluginStaticData(a));
c@24:             }
c@24:         }
c@24:         return arr;
c@24:     }
c@24: 
c@24:     static Vamp::HostExt::LoadRequest
c@24:     toVampRequest_Load(json11::Json j) {
c@24:         
c@24:         checkTypeField(j, "load");
c@24:         return toLoadRequest(j["content"]);
c@24:     }
c@24:     
c@24:     static Vamp::HostExt::LoadResponse
c@24:     toVampResponse_Load(json11::Json j, PluginHandleMapper &mapper) {
c@24:         
c@24:         Vamp::HostExt::LoadResponse resp;
c@24:         if (successful(j)) {
c@24:             resp = toLoadResponse(j["content"], mapper);
c@24:         }
c@24:         return resp;
c@24:     }
c@24:     
c@24:     static Vamp::HostExt::ConfigurationRequest
c@24:     toVampRequest_Configure(json11::Json j, PluginHandleMapper &mapper) {
c@24:         
c@24:         checkTypeField(j, "configure");
c@24:         return toConfigurationRequest(j["content"], mapper);
c@24:     }
c@24:     
c@24:     static Vamp::HostExt::ConfigurationResponse
c@24:     toVampResponse_Configure(json11::Json j) {
c@24:         
c@24:         Vamp::HostExt::ConfigurationResponse resp;
c@24:         if (successful(j)) {
c@24:             resp = toConfigurationResponse(j["content"]);
c@24:         }
c@24:         return resp;
c@24:     }
c@24:     
c@24:     static Vamp::HostExt::ProcessRequest
c@24:     toVampRequest_Process(json11::Json j, PluginHandleMapper &mapper) {
c@24:         
c@24:         checkTypeField(j, "process");
c@24:         return toProcessRequest(j["content"], mapper);
c@24:     }
c@24:     
c@24:     static Vamp::HostExt::ProcessResponse
c@24:     toVampResponse_Process(json11::Json j) {
c@24:         
c@24:         Vamp::HostExt::ProcessResponse resp;
c@24:         if (successful(j)) {
c@24:             resp.features = toFeatureSet(j["content"]);
c@24:         }
c@24:         return resp;
c@24:     }
c@24:     
c@24:     static Vamp::Plugin *
c@24:     toVampRequest_Finish(json11::Json j, PluginHandleMapper &mapper) {
c@24:         
c@24:         checkTypeField(j, "finish");
c@24:         return mapper.handleToPlugin(j["content"]["pluginHandle"].int_value());
c@24:     }
c@24:     
c@24:     static Vamp::HostExt::ProcessResponse
c@24:     toVampResponse_Finish(json11::Json j) {
c@24:         
c@24:         Vamp::HostExt::ProcessResponse resp;
c@24:         if (successful(j)) {
c@24:             resp.features = toFeatureSet(j["content"]);
c@24:         }
c@24:         return resp;
c@24:     }
c@5: };
c@5: 
c@10: }
c@5: 
c@5: #endif