c@117
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
c@117
|
2
|
c@117
|
3 /*
|
c@117
|
4 Piper Vamp JSON Adapter
|
c@117
|
5
|
c@117
|
6 Centre for Digital Music, Queen Mary, University of London.
|
c@117
|
7 Copyright 2015-2016 QMUL.
|
c@117
|
8
|
c@117
|
9 Permission is hereby granted, free of charge, to any person
|
c@117
|
10 obtaining a copy of this software and associated documentation
|
c@117
|
11 files (the "Software"), to deal in the Software without
|
c@117
|
12 restriction, including without limitation the rights to use, copy,
|
c@117
|
13 modify, merge, publish, distribute, sublicense, and/or sell copies
|
c@117
|
14 of the Software, and to permit persons to whom the Software is
|
c@117
|
15 furnished to do so, subject to the following conditions:
|
c@117
|
16
|
c@117
|
17 The above copyright notice and this permission notice shall be
|
c@117
|
18 included in all copies or substantial portions of the Software.
|
c@117
|
19
|
c@117
|
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
c@117
|
21 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
c@117
|
22 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
c@117
|
23 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
|
c@117
|
24 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
c@117
|
25 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
c@117
|
26 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
c@117
|
27
|
c@117
|
28 Except as contained in this notice, the names of the Centre for
|
c@117
|
29 Digital Music; Queen Mary, University of London; and Chris Cannam
|
c@117
|
30 shall not be used in advertising or otherwise to promote the sale,
|
c@117
|
31 use or other dealings in this Software without prior written
|
c@117
|
32 authorization.
|
c@117
|
33 */
|
c@117
|
34
|
c@117
|
35 #include "PiperPluginLibrary.h"
|
c@117
|
36 #include "PiperAdapter.h"
|
c@117
|
37
|
c@117
|
38 #include "vamp-json/VampJson.h"
|
c@117
|
39
|
c@117
|
40 using namespace std;
|
c@117
|
41 using namespace json11;
|
c@117
|
42 using namespace piper_vamp;
|
c@117
|
43
|
c@117
|
44 namespace piper_vamp_js { //!!! not good
|
c@117
|
45
|
c@117
|
46 //!!! too many explicit namespaces here
|
c@117
|
47
|
c@117
|
48 //!!! dup with piper-convert
|
c@117
|
49 Json
|
c@117
|
50 convertRequestJson(string input, string &err)
|
c@117
|
51 {
|
c@117
|
52 Json j = Json::parse(input, err);
|
c@117
|
53 if (err != "") {
|
c@117
|
54 err = "invalid json: " + err;
|
c@117
|
55 return {};
|
c@117
|
56 }
|
c@117
|
57 if (!j.is_object()) {
|
c@117
|
58 err = "object expected at top level";
|
c@117
|
59 }
|
c@117
|
60 return j;
|
c@117
|
61 }
|
c@117
|
62
|
c@117
|
63 PiperPluginLibrary::PiperPluginLibrary(vector<PiperAdapterInterface *> pp) :
|
c@117
|
64 m_useBase64(false)
|
c@117
|
65 {
|
c@117
|
66 for (PiperAdapterInterface *p: pp) {
|
c@117
|
67 string key = p->getStaticData().pluginKey;
|
c@117
|
68 m_adapters[key] = p;
|
c@117
|
69 }
|
c@117
|
70 }
|
c@117
|
71
|
c@117
|
72 ListResponse
|
c@117
|
73 PiperPluginLibrary::listPluginData(ListRequest req) const
|
c@117
|
74 {
|
c@117
|
75 bool filtered = !req.from.empty();
|
c@117
|
76 ListResponse resp;
|
c@117
|
77 for (auto a: m_adapters) {
|
c@117
|
78 if (filtered) {
|
c@117
|
79 auto n = a.second->getLibraryName();
|
c@117
|
80 bool found = false;
|
c@117
|
81 for (const auto &f: req.from) {
|
c@117
|
82 if (f == n) {
|
c@117
|
83 found = true;
|
c@117
|
84 break;
|
c@117
|
85 }
|
c@117
|
86 }
|
c@117
|
87 if (!found) {
|
c@117
|
88 continue;
|
c@117
|
89 }
|
c@117
|
90 }
|
c@117
|
91 resp.available.push_back(a.second->getStaticData());
|
c@117
|
92 }
|
c@117
|
93 return resp;
|
c@117
|
94 }
|
c@117
|
95
|
c@117
|
96 LoadResponse
|
c@117
|
97 PiperPluginLibrary::loadPlugin(LoadRequest req, string &err) const
|
c@117
|
98 {
|
c@117
|
99 string key = req.pluginKey;
|
c@117
|
100 if (m_adapters.find(key) != m_adapters.end()) {
|
c@117
|
101 auto resp = m_adapters.at(key)->loadPlugin(req);
|
c@117
|
102 if (!resp.plugin) {
|
c@117
|
103 // This should not actually happen -- the load call here
|
c@117
|
104 // is just an object construction, not a dynamic load. But
|
c@117
|
105 // report it if it does...
|
c@117
|
106 err = "failed to construct plugin with key " + key;
|
c@117
|
107 }
|
c@117
|
108 return resp;
|
c@117
|
109 } else {
|
c@117
|
110 err = "no adapter for plugin key " + key;
|
c@117
|
111 return {};
|
c@117
|
112 }
|
c@117
|
113 }
|
c@117
|
114
|
c@117
|
115 ConfigurationResponse
|
c@117
|
116 PiperPluginLibrary::configurePlugin(ConfigurationRequest req,
|
cannam@152
|
117 const PluginStaticData &psd,
|
cannam@138
|
118 string &err) const
|
c@117
|
119 {
|
c@117
|
120 for (PluginConfiguration::ParameterMap::const_iterator i =
|
c@117
|
121 req.configuration.parameterValues.begin();
|
c@117
|
122 i != req.configuration.parameterValues.end(); ++i) {
|
c@117
|
123 req.plugin->setParameter(i->first, i->second);
|
c@117
|
124 }
|
c@117
|
125
|
c@117
|
126 if (req.configuration.currentProgram != "") {
|
c@117
|
127 req.plugin->selectProgram(req.configuration.currentProgram);
|
c@117
|
128 }
|
c@117
|
129
|
c@117
|
130 ConfigurationResponse response;
|
c@117
|
131 response.plugin = req.plugin;
|
cannam@152
|
132 response.staticOutputInfo = psd.staticOutputInfo;
|
c@117
|
133
|
cannam@138
|
134 Framing pluginPreferredFraming;
|
cannam@138
|
135 pluginPreferredFraming.stepSize = req.plugin->getPreferredStepSize();
|
cannam@138
|
136 pluginPreferredFraming.blockSize = req.plugin->getPreferredBlockSize();
|
cannam@138
|
137
|
c@117
|
138 if (req.plugin->initialise(req.configuration.channelCount,
|
cannam@138
|
139 req.configuration.framing.stepSize,
|
cannam@138
|
140 req.configuration.framing.blockSize)) {
|
cannam@138
|
141
|
c@117
|
142 response.outputs = req.plugin->getOutputDescriptors();
|
cannam@138
|
143
|
cannam@138
|
144 // If the Vamp plugin initialise() call succeeds, then by
|
cannam@138
|
145 // definition it is accepting the step and block size we
|
cannam@138
|
146 // passed to it
|
cannam@138
|
147 response.framing = req.configuration.framing;
|
cannam@138
|
148
|
c@117
|
149 } else {
|
cannam@138
|
150
|
cannam@138
|
151 // If initialise() fails, one reason could be that it didn't
|
cannam@138
|
152 // like the passed-in framing (step and block size). We need
|
cannam@138
|
153 // to check whether the passed-in framing differs from the
|
cannam@138
|
154 // plugin's preferences; if so, then we form a working
|
cannam@138
|
155 // supposition that initialise() failed because of this. Vamp
|
cannam@138
|
156 // contains nothing to allow us to test this, except to try
|
cannam@138
|
157 // initialise() again with different values. So we try again
|
cannam@138
|
158 // with the values the plugin told us it would prefer and, if
|
cannam@138
|
159 // that succeeds, return them in a successful response.
|
cannam@138
|
160 //
|
cannam@138
|
161 // See also LoaderRequests in piper-cpp/vamp-support.
|
cannam@138
|
162 //
|
cannam@138
|
163 if (req.plugin->initialise(req.configuration.channelCount,
|
cannam@138
|
164 pluginPreferredFraming.stepSize,
|
cannam@138
|
165 pluginPreferredFraming.blockSize)) {
|
cannam@138
|
166
|
cannam@138
|
167 response.outputs = req.plugin->getOutputDescriptors();
|
cannam@138
|
168 response.framing = pluginPreferredFraming;
|
cannam@138
|
169
|
cannam@138
|
170 } else {
|
cannam@138
|
171 err = "configuration failed (wrong channel count, step size, block size?)";
|
cannam@138
|
172 }
|
c@117
|
173 }
|
cannam@152
|
174
|
c@117
|
175 return response;
|
c@117
|
176 }
|
c@117
|
177
|
c@117
|
178 string
|
c@117
|
179 PiperPluginLibrary::processRawImpl(int handle,
|
c@117
|
180 const float *const *inputBuffers,
|
c@117
|
181 int sec,
|
c@117
|
182 int nsec)
|
c@117
|
183 {
|
c@117
|
184 Vamp::Plugin *plugin = m_mapper.handleToPlugin(handle);
|
c@117
|
185 if (!plugin) {
|
c@117
|
186 return VampJson::fromError("unknown plugin handle",
|
c@117
|
187 RRType::Process, Json())
|
c@117
|
188 .dump();
|
c@117
|
189 }
|
c@117
|
190
|
c@117
|
191 if (!m_mapper.isConfigured(handle)) {
|
c@117
|
192 return VampJson::fromError("plugin has not been configured",
|
c@117
|
193 RRType::Process, Json())
|
c@117
|
194 .dump();
|
c@117
|
195 }
|
c@117
|
196
|
c@117
|
197 Vamp::RealTime timestamp(sec, nsec);
|
c@117
|
198
|
c@117
|
199 ProcessResponse resp;
|
c@117
|
200 resp.plugin = plugin;
|
c@117
|
201 resp.features = plugin->process(inputBuffers, timestamp);
|
c@117
|
202
|
c@117
|
203 m_useBase64 = true;
|
c@117
|
204
|
c@117
|
205 return VampJson::fromRpcResponse_Process
|
c@117
|
206 (resp, m_mapper,
|
c@117
|
207 VampJson::BufferSerialisation::Base64,
|
c@117
|
208 Json())
|
c@117
|
209 .dump();
|
c@117
|
210 }
|
c@117
|
211
|
c@117
|
212 string
|
c@117
|
213 PiperPluginLibrary::requestJsonImpl(string req)
|
c@117
|
214 {
|
c@117
|
215 string err;
|
c@117
|
216
|
c@117
|
217 Json j = convertRequestJson(req, err);
|
c@117
|
218
|
c@117
|
219 // we don't care what this is, only that it is retained in the response:
|
c@117
|
220 auto id = j["id"];
|
c@117
|
221
|
c@117
|
222 Json rj;
|
c@117
|
223 if (err != "") {
|
c@117
|
224 return VampJson::fromError(err, RRType::NotValid, id).dump();
|
c@117
|
225 }
|
c@117
|
226
|
c@117
|
227 RRType type = VampJson::getRequestResponseType(j, err);
|
c@117
|
228 if (err != "") {
|
c@117
|
229 return VampJson::fromError(err, RRType::NotValid, id).dump();
|
c@117
|
230 }
|
c@117
|
231
|
c@117
|
232 VampJson::BufferSerialisation serialisation =
|
c@117
|
233 (m_useBase64 ?
|
c@117
|
234 VampJson::BufferSerialisation::Base64 :
|
c@117
|
235 VampJson::BufferSerialisation::Array);
|
c@117
|
236
|
c@117
|
237 switch (type) {
|
c@117
|
238
|
c@117
|
239 case RRType::List:
|
c@117
|
240 {
|
c@117
|
241 auto req = VampJson::toRpcRequest_List(j, err);
|
c@117
|
242 if (err != "") {
|
c@117
|
243 rj = VampJson::fromError(err, type, id);
|
c@117
|
244 } else {
|
c@117
|
245 rj = VampJson::fromRpcResponse_List(listPluginData(req), id);
|
c@117
|
246 }
|
c@117
|
247 break;
|
c@117
|
248 }
|
c@117
|
249
|
c@117
|
250 case RRType::Load:
|
c@117
|
251 {
|
c@117
|
252 auto req = VampJson::toRpcRequest_Load(j, err);
|
c@117
|
253 if (err != "") {
|
c@117
|
254 rj = VampJson::fromError(err, type, id);
|
c@117
|
255 } else {
|
c@117
|
256 auto resp = loadPlugin(req, err);
|
c@117
|
257 if (err != "") {
|
c@117
|
258 rj = VampJson::fromError(err, type, id);
|
c@117
|
259 } else {
|
c@117
|
260 m_mapper.addPlugin(resp.plugin);
|
cannam@152
|
261 m_pluginStaticData[m_mapper.pluginToHandle(resp.plugin)] =
|
cannam@152
|
262 resp.staticData;
|
c@117
|
263 rj = VampJson::fromRpcResponse_Load(resp, m_mapper, id);
|
c@117
|
264 }
|
c@117
|
265 }
|
c@117
|
266 break;
|
c@117
|
267 }
|
c@117
|
268
|
c@117
|
269 case RRType::Configure:
|
c@117
|
270 {
|
c@117
|
271 auto req = VampJson::toRpcRequest_Configure(j, m_mapper, err);
|
c@117
|
272 if (err != "") {
|
c@117
|
273 rj = VampJson::fromError(err, type, id);
|
c@117
|
274 } else {
|
c@117
|
275 auto h = m_mapper.pluginToHandle(req.plugin);
|
c@117
|
276 if (h == m_mapper.INVALID_HANDLE) {
|
cannam@152
|
277 rj = VampJson::fromError
|
cannam@152
|
278 ("unknown or invalid plugin handle", type, id);
|
c@117
|
279 } else if (m_mapper.isConfigured(h)) {
|
cannam@152
|
280 rj = VampJson::fromError
|
cannam@152
|
281 ("plugin has already been configured", type, id);
|
c@117
|
282 } else {
|
cannam@152
|
283 PluginStaticData psd(m_pluginStaticData[h]);
|
cannam@152
|
284 auto resp = configurePlugin(req, psd, err);
|
c@117
|
285 if (err != "") {
|
c@117
|
286 rj = VampJson::fromError(err, type, id);
|
c@117
|
287 } else {
|
c@117
|
288 m_mapper.markConfigured(h,
|
c@117
|
289 req.configuration.channelCount,
|
cannam@138
|
290 req.configuration.framing.blockSize);
|
c@117
|
291 rj = VampJson::fromRpcResponse_Configure(resp, m_mapper, id);
|
c@117
|
292 }
|
c@117
|
293 }
|
c@117
|
294 }
|
c@117
|
295 break;
|
c@117
|
296 }
|
c@117
|
297
|
c@117
|
298 case RRType::Process:
|
c@117
|
299 {
|
c@117
|
300 VampJson::BufferSerialisation serialisation;
|
c@117
|
301
|
c@117
|
302 auto req = VampJson::toRpcRequest_Process(j, m_mapper,
|
c@117
|
303 serialisation, err);
|
c@117
|
304 if (err != "") {
|
c@117
|
305 rj = VampJson::fromError(err, type, id);
|
c@117
|
306 } else {
|
c@117
|
307 auto h = m_mapper.pluginToHandle(req.plugin);
|
c@117
|
308 int channels = int(req.inputBuffers.size());
|
c@117
|
309 if (h == m_mapper.INVALID_HANDLE) {
|
cannam@152
|
310 rj = VampJson::fromError
|
cannam@152
|
311 ("unknown or invalid plugin handle", type, id);
|
c@117
|
312 } else if (!m_mapper.isConfigured(h)) {
|
cannam@152
|
313 rj = VampJson::fromError
|
cannam@152
|
314 ("plugin has not been configured", type, id);
|
c@117
|
315 } else if (channels != m_mapper.getChannelCount(h)) {
|
cannam@152
|
316 rj = VampJson::fromError
|
cannam@152
|
317 ("wrong number of channels supplied", type, id);
|
c@117
|
318 } else {
|
c@117
|
319
|
c@117
|
320 if (serialisation == VampJson::BufferSerialisation::Base64) {
|
c@117
|
321 m_useBase64 = true;
|
c@117
|
322 }
|
c@117
|
323
|
c@117
|
324 size_t blockSize = m_mapper.getBlockSize(h);
|
c@117
|
325
|
c@117
|
326 const float **fbuffers = new const float *[channels];
|
c@117
|
327 for (int i = 0; i < channels; ++i) {
|
c@117
|
328 if (req.inputBuffers[i].size() != blockSize) {
|
c@117
|
329 delete[] fbuffers;
|
c@117
|
330 fbuffers = 0;
|
cannam@152
|
331 rj = VampJson::fromError
|
cannam@152
|
332 ("wrong block size supplied", type, id);
|
c@117
|
333 break;
|
c@117
|
334 }
|
c@117
|
335 fbuffers[i] = req.inputBuffers[i].data();
|
c@117
|
336 }
|
c@117
|
337
|
c@117
|
338 if (fbuffers) {
|
c@117
|
339 ProcessResponse resp;
|
c@117
|
340 resp.plugin = req.plugin;
|
c@117
|
341 resp.features = req.plugin->process(fbuffers, req.timestamp);
|
c@117
|
342 delete[] fbuffers;
|
c@117
|
343 rj = VampJson::fromRpcResponse_Process
|
c@117
|
344 (resp, m_mapper, serialisation, id);
|
c@117
|
345 }
|
c@117
|
346 }
|
c@117
|
347 }
|
c@117
|
348 break;
|
c@117
|
349 }
|
c@117
|
350
|
c@117
|
351 case RRType::Finish:
|
c@117
|
352 {
|
c@117
|
353 auto req = VampJson::toRpcRequest_Finish(j, m_mapper, err);
|
c@117
|
354 if (err != "") {
|
c@117
|
355 rj = VampJson::fromError(err, type, id);
|
c@117
|
356 } else {
|
c@117
|
357 auto h = m_mapper.pluginToHandle(req.plugin);
|
c@117
|
358 if (h == m_mapper.INVALID_HANDLE) {
|
cannam@152
|
359 rj = VampJson::fromError
|
cannam@152
|
360 ("unknown or invalid plugin handle", type, id);
|
c@117
|
361 } else {
|
c@117
|
362
|
c@117
|
363 FinishResponse resp;
|
c@117
|
364 resp.plugin = req.plugin;
|
c@117
|
365
|
c@117
|
366 // Finish can be called (to unload the plugin) even if
|
c@117
|
367 // the plugin has never been configured or used. But
|
c@117
|
368 // we want to make sure we call getRemainingFeatures
|
c@117
|
369 // only if we have actually configured the plugin.
|
c@117
|
370 if (m_mapper.isConfigured(h)) {
|
c@117
|
371 resp.features = req.plugin->getRemainingFeatures();
|
c@117
|
372 }
|
c@117
|
373
|
c@117
|
374 rj = VampJson::fromRpcResponse_Finish
|
c@117
|
375 (resp, m_mapper, serialisation, id);
|
cannam@152
|
376
|
cannam@152
|
377 m_pluginStaticData.erase(h);
|
c@117
|
378 m_mapper.removePlugin(h);
|
c@117
|
379 delete req.plugin;
|
c@117
|
380 }
|
c@117
|
381 }
|
c@117
|
382 break;
|
c@117
|
383 }
|
c@117
|
384
|
c@117
|
385 case RRType::NotValid:
|
c@117
|
386 rj = VampJson::fromError("invalid request", type, id);
|
c@117
|
387 break;
|
c@117
|
388 }
|
c@117
|
389
|
c@117
|
390 return rj.dump();
|
c@117
|
391 }
|
c@117
|
392
|
c@117
|
393 }
|
c@117
|
394
|