c@117: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ c@117: c@117: /* c@117: Piper Vamp JSON Adapter c@117: c@117: Centre for Digital Music, Queen Mary, University of London. c@117: Copyright 2015-2016 QMUL. c@117: c@117: Permission is hereby granted, free of charge, to any person c@117: obtaining a copy of this software and associated documentation c@117: files (the "Software"), to deal in the Software without c@117: restriction, including without limitation the rights to use, copy, c@117: modify, merge, publish, distribute, sublicense, and/or sell copies c@117: of the Software, and to permit persons to whom the Software is c@117: furnished to do so, subject to the following conditions: c@117: c@117: The above copyright notice and this permission notice shall be c@117: included in all copies or substantial portions of the Software. c@117: c@117: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, c@117: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF c@117: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND c@117: NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR c@117: ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF c@117: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION c@117: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. c@117: c@117: Except as contained in this notice, the names of the Centre for c@117: Digital Music; Queen Mary, University of London; and Chris Cannam c@117: shall not be used in advertising or otherwise to promote the sale, c@117: use or other dealings in this Software without prior written c@117: authorization. c@117: */ c@117: c@117: #include "PiperPluginLibrary.h" c@117: #include "PiperAdapter.h" c@117: c@117: #include "vamp-json/VampJson.h" c@117: c@117: using namespace std; c@117: using namespace json11; c@117: using namespace piper_vamp; c@117: c@117: namespace piper_vamp_js { //!!! not good c@117: c@117: //!!! too many explicit namespaces here c@117: c@117: //!!! dup with piper-convert c@117: Json c@117: convertRequestJson(string input, string &err) c@117: { c@117: Json j = Json::parse(input, err); c@117: if (err != "") { c@117: err = "invalid json: " + err; c@117: return {}; c@117: } c@117: if (!j.is_object()) { c@117: err = "object expected at top level"; c@117: } c@117: return j; c@117: } c@117: c@117: PiperPluginLibrary::PiperPluginLibrary(vector pp) : c@117: m_useBase64(false) c@117: { c@117: for (PiperAdapterInterface *p: pp) { c@117: string key = p->getStaticData().pluginKey; c@117: m_adapters[key] = p; c@117: } c@117: } c@117: c@117: ListResponse c@117: PiperPluginLibrary::listPluginData(ListRequest req) const c@117: { c@117: bool filtered = !req.from.empty(); c@117: ListResponse resp; c@117: for (auto a: m_adapters) { c@117: if (filtered) { c@117: auto n = a.second->getLibraryName(); c@117: bool found = false; c@117: for (const auto &f: req.from) { c@117: if (f == n) { c@117: found = true; c@117: break; c@117: } c@117: } c@117: if (!found) { c@117: continue; c@117: } c@117: } c@117: resp.available.push_back(a.second->getStaticData()); c@117: } c@117: return resp; c@117: } c@117: c@117: LoadResponse c@117: PiperPluginLibrary::loadPlugin(LoadRequest req, string &err) const c@117: { c@117: string key = req.pluginKey; c@117: if (m_adapters.find(key) != m_adapters.end()) { c@117: auto resp = m_adapters.at(key)->loadPlugin(req); c@117: if (!resp.plugin) { c@117: // This should not actually happen -- the load call here c@117: // is just an object construction, not a dynamic load. But c@117: // report it if it does... c@117: err = "failed to construct plugin with key " + key; c@117: } c@117: return resp; c@117: } else { c@117: err = "no adapter for plugin key " + key; c@117: return {}; c@117: } c@117: } c@117: c@117: ConfigurationResponse c@117: PiperPluginLibrary::configurePlugin(ConfigurationRequest req, cannam@152: const PluginStaticData &psd, cannam@138: string &err) const c@117: { c@117: for (PluginConfiguration::ParameterMap::const_iterator i = c@117: req.configuration.parameterValues.begin(); c@117: i != req.configuration.parameterValues.end(); ++i) { c@117: req.plugin->setParameter(i->first, i->second); c@117: } c@117: c@117: if (req.configuration.currentProgram != "") { c@117: req.plugin->selectProgram(req.configuration.currentProgram); c@117: } c@117: c@117: ConfigurationResponse response; c@117: response.plugin = req.plugin; cannam@152: response.staticOutputInfo = psd.staticOutputInfo; c@117: cannam@138: Framing pluginPreferredFraming; cannam@138: pluginPreferredFraming.stepSize = req.plugin->getPreferredStepSize(); cannam@138: pluginPreferredFraming.blockSize = req.plugin->getPreferredBlockSize(); cannam@138: c@117: if (req.plugin->initialise(req.configuration.channelCount, cannam@138: req.configuration.framing.stepSize, cannam@138: req.configuration.framing.blockSize)) { cannam@138: c@117: response.outputs = req.plugin->getOutputDescriptors(); cannam@138: cannam@138: // If the Vamp plugin initialise() call succeeds, then by cannam@138: // definition it is accepting the step and block size we cannam@138: // passed to it cannam@138: response.framing = req.configuration.framing; cannam@138: c@117: } else { cannam@138: cannam@138: // If initialise() fails, one reason could be that it didn't cannam@138: // like the passed-in framing (step and block size). We need cannam@138: // to check whether the passed-in framing differs from the cannam@138: // plugin's preferences; if so, then we form a working cannam@138: // supposition that initialise() failed because of this. Vamp cannam@138: // contains nothing to allow us to test this, except to try cannam@138: // initialise() again with different values. So we try again cannam@138: // with the values the plugin told us it would prefer and, if cannam@138: // that succeeds, return them in a successful response. cannam@138: // cannam@138: // See also LoaderRequests in piper-cpp/vamp-support. cannam@138: // cannam@138: if (req.plugin->initialise(req.configuration.channelCount, cannam@138: pluginPreferredFraming.stepSize, cannam@138: pluginPreferredFraming.blockSize)) { cannam@138: cannam@138: response.outputs = req.plugin->getOutputDescriptors(); cannam@138: response.framing = pluginPreferredFraming; cannam@138: cannam@138: } else { cannam@138: err = "configuration failed (wrong channel count, step size, block size?)"; cannam@138: } c@117: } cannam@152: c@117: return response; c@117: } c@117: c@117: string c@117: PiperPluginLibrary::processRawImpl(int handle, c@117: const float *const *inputBuffers, c@117: int sec, c@117: int nsec) c@117: { c@117: Vamp::Plugin *plugin = m_mapper.handleToPlugin(handle); c@117: if (!plugin) { c@117: return VampJson::fromError("unknown plugin handle", c@117: RRType::Process, Json()) c@117: .dump(); c@117: } c@117: c@117: if (!m_mapper.isConfigured(handle)) { c@117: return VampJson::fromError("plugin has not been configured", c@117: RRType::Process, Json()) c@117: .dump(); c@117: } c@117: c@117: Vamp::RealTime timestamp(sec, nsec); c@117: c@117: ProcessResponse resp; c@117: resp.plugin = plugin; c@117: resp.features = plugin->process(inputBuffers, timestamp); c@117: c@117: m_useBase64 = true; c@117: c@117: return VampJson::fromRpcResponse_Process c@117: (resp, m_mapper, c@117: VampJson::BufferSerialisation::Base64, c@117: Json()) c@117: .dump(); c@117: } c@117: c@117: string c@117: PiperPluginLibrary::requestJsonImpl(string req) c@117: { c@117: string err; c@117: c@117: Json j = convertRequestJson(req, err); c@117: c@117: // we don't care what this is, only that it is retained in the response: c@117: auto id = j["id"]; c@117: c@117: Json rj; c@117: if (err != "") { c@117: return VampJson::fromError(err, RRType::NotValid, id).dump(); c@117: } c@117: c@117: RRType type = VampJson::getRequestResponseType(j, err); c@117: if (err != "") { c@117: return VampJson::fromError(err, RRType::NotValid, id).dump(); c@117: } c@117: c@117: VampJson::BufferSerialisation serialisation = c@117: (m_useBase64 ? c@117: VampJson::BufferSerialisation::Base64 : c@117: VampJson::BufferSerialisation::Array); c@117: c@117: switch (type) { c@117: c@117: case RRType::List: c@117: { c@117: auto req = VampJson::toRpcRequest_List(j, err); c@117: if (err != "") { c@117: rj = VampJson::fromError(err, type, id); c@117: } else { c@117: rj = VampJson::fromRpcResponse_List(listPluginData(req), id); c@117: } c@117: break; c@117: } c@117: c@117: case RRType::Load: c@117: { c@117: auto req = VampJson::toRpcRequest_Load(j, err); c@117: if (err != "") { c@117: rj = VampJson::fromError(err, type, id); c@117: } else { c@117: auto resp = loadPlugin(req, err); c@117: if (err != "") { c@117: rj = VampJson::fromError(err, type, id); c@117: } else { c@117: m_mapper.addPlugin(resp.plugin); cannam@152: m_pluginStaticData[m_mapper.pluginToHandle(resp.plugin)] = cannam@152: resp.staticData; c@117: rj = VampJson::fromRpcResponse_Load(resp, m_mapper, id); c@117: } c@117: } c@117: break; c@117: } c@117: c@117: case RRType::Configure: c@117: { c@117: auto req = VampJson::toRpcRequest_Configure(j, m_mapper, err); c@117: if (err != "") { c@117: rj = VampJson::fromError(err, type, id); c@117: } else { c@117: auto h = m_mapper.pluginToHandle(req.plugin); c@117: if (h == m_mapper.INVALID_HANDLE) { cannam@152: rj = VampJson::fromError cannam@152: ("unknown or invalid plugin handle", type, id); c@117: } else if (m_mapper.isConfigured(h)) { cannam@152: rj = VampJson::fromError cannam@152: ("plugin has already been configured", type, id); c@117: } else { cannam@152: PluginStaticData psd(m_pluginStaticData[h]); cannam@152: auto resp = configurePlugin(req, psd, err); c@117: if (err != "") { c@117: rj = VampJson::fromError(err, type, id); c@117: } else { c@117: m_mapper.markConfigured(h, c@117: req.configuration.channelCount, cannam@138: req.configuration.framing.blockSize); c@117: rj = VampJson::fromRpcResponse_Configure(resp, m_mapper, id); c@117: } c@117: } c@117: } c@117: break; c@117: } c@117: c@117: case RRType::Process: c@117: { c@117: VampJson::BufferSerialisation serialisation; c@117: c@117: auto req = VampJson::toRpcRequest_Process(j, m_mapper, c@117: serialisation, err); c@117: if (err != "") { c@117: rj = VampJson::fromError(err, type, id); c@117: } else { c@117: auto h = m_mapper.pluginToHandle(req.plugin); c@117: int channels = int(req.inputBuffers.size()); c@117: if (h == m_mapper.INVALID_HANDLE) { cannam@152: rj = VampJson::fromError cannam@152: ("unknown or invalid plugin handle", type, id); c@117: } else if (!m_mapper.isConfigured(h)) { cannam@152: rj = VampJson::fromError cannam@152: ("plugin has not been configured", type, id); c@117: } else if (channels != m_mapper.getChannelCount(h)) { cannam@152: rj = VampJson::fromError cannam@152: ("wrong number of channels supplied", type, id); c@117: } else { c@117: c@117: if (serialisation == VampJson::BufferSerialisation::Base64) { c@117: m_useBase64 = true; c@117: } c@117: c@117: size_t blockSize = m_mapper.getBlockSize(h); c@117: c@117: const float **fbuffers = new const float *[channels]; c@117: for (int i = 0; i < channels; ++i) { c@117: if (req.inputBuffers[i].size() != blockSize) { c@117: delete[] fbuffers; c@117: fbuffers = 0; cannam@152: rj = VampJson::fromError cannam@152: ("wrong block size supplied", type, id); c@117: break; c@117: } c@117: fbuffers[i] = req.inputBuffers[i].data(); c@117: } c@117: c@117: if (fbuffers) { c@117: ProcessResponse resp; c@117: resp.plugin = req.plugin; c@117: resp.features = req.plugin->process(fbuffers, req.timestamp); c@117: delete[] fbuffers; c@117: rj = VampJson::fromRpcResponse_Process c@117: (resp, m_mapper, serialisation, id); c@117: } c@117: } c@117: } c@117: break; c@117: } c@117: c@117: case RRType::Finish: c@117: { c@117: auto req = VampJson::toRpcRequest_Finish(j, m_mapper, err); c@117: if (err != "") { c@117: rj = VampJson::fromError(err, type, id); c@117: } else { c@117: auto h = m_mapper.pluginToHandle(req.plugin); c@117: if (h == m_mapper.INVALID_HANDLE) { cannam@152: rj = VampJson::fromError cannam@152: ("unknown or invalid plugin handle", type, id); c@117: } else { c@117: c@117: FinishResponse resp; c@117: resp.plugin = req.plugin; c@117: c@117: // Finish can be called (to unload the plugin) even if c@117: // the plugin has never been configured or used. But c@117: // we want to make sure we call getRemainingFeatures c@117: // only if we have actually configured the plugin. c@117: if (m_mapper.isConfigured(h)) { c@117: resp.features = req.plugin->getRemainingFeatures(); c@117: } c@117: c@117: rj = VampJson::fromRpcResponse_Finish c@117: (resp, m_mapper, serialisation, id); cannam@152: cannam@152: m_pluginStaticData.erase(h); c@117: m_mapper.removePlugin(h); c@117: delete req.plugin; c@117: } c@117: } c@117: break; c@117: } c@117: c@117: case RRType::NotValid: c@117: rj = VampJson::fromError("invalid request", type, id); c@117: break; c@117: } c@117: c@117: return rj.dump(); c@117: } c@117: c@117: } c@117: