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