@soundor/vst3-host 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CMakeLists.txt +194 -0
  2. package/CMakePresets.json +115 -0
  3. package/LICENSE +21 -0
  4. package/NOTICE.md +17 -0
  5. package/README.md +40 -2
  6. package/cmake/Vst3HostDependencies.cmake +179 -0
  7. package/dist/cli.d.mts +1 -0
  8. package/dist/cli.mjs +88 -0
  9. package/dist/cli.mjs.map +1 -0
  10. package/dist/index.d.mts +586 -0
  11. package/dist/index.d.mts.map +1 -0
  12. package/dist/index.mjs +1 -0
  13. package/dist/prepare-ayjTVhfk.mjs +1149 -0
  14. package/dist/prepare-ayjTVhfk.mjs.map +1 -0
  15. package/native/src/Bundle.cpp +253 -0
  16. package/native/src/Bundle.h +32 -0
  17. package/native/src/EditorArea.h +92 -0
  18. package/native/src/EditorAreaMac.mm +52 -0
  19. package/native/src/EditorAreaWindows.cpp +66 -0
  20. package/native/src/EditorAreaX11.cpp +512 -0
  21. package/native/src/EditorWindow.h +72 -0
  22. package/native/src/EditorWindowMac.mm +134 -0
  23. package/native/src/EditorWindowWindows.cpp +148 -0
  24. package/native/src/EventLoop.h +50 -0
  25. package/native/src/EventLoopLinux.cpp +204 -0
  26. package/native/src/EventLoopMac.mm +146 -0
  27. package/native/src/EventLoopWindows.cpp +132 -0
  28. package/native/src/HostContext.cpp +217 -0
  29. package/native/src/HostContext.h +116 -0
  30. package/native/src/HostInterface.cpp +1026 -0
  31. package/native/src/HostInterface.h +138 -0
  32. package/native/src/HostWindow.cpp +901 -0
  33. package/native/src/HostWindow.h +187 -0
  34. package/native/src/Ipc.cpp +158 -0
  35. package/native/src/Ipc.h +91 -0
  36. package/native/src/Json.cpp +426 -0
  37. package/native/src/Json.h +85 -0
  38. package/native/src/LiveAudio.cpp +416 -0
  39. package/native/src/LiveAudio.h +121 -0
  40. package/native/src/Main.cpp +178 -0
  41. package/native/src/Paths.h +20 -0
  42. package/native/src/Plugin.cpp +1865 -0
  43. package/native/src/Plugin.h +321 -0
  44. package/native/src/Session.cpp +656 -0
  45. package/native/src/Session.h +82 -0
  46. package/native/src/Settings.cpp +93 -0
  47. package/native/src/Settings.h +23 -0
  48. package/native/src/Version.h.in +11 -0
  49. package/native/src/X11Library.cpp +127 -0
  50. package/native/src/X11Library.h +83 -0
  51. package/native/tools/Embed.cpp +49 -0
  52. package/package.json +58 -3
  53. package/protocol.json +7 -0
  54. package/targets.json +33 -0
@@ -0,0 +1,656 @@
1
+ #include "Session.h"
2
+
3
+ #include "Bundle.h"
4
+ #include "HostWindow.h"
5
+ #include "Version.h"
6
+ #include "pluginterfaces/vst/vstspeaker.h"
7
+
8
+ #include <cctype>
9
+ #include <cmath>
10
+ #include <cstring>
11
+
12
+ namespace soundor::vst3host
13
+ {
14
+ using namespace Steinberg::Vst;
15
+
16
+ AudioWorker::AudioWorker()
17
+ : thread_(
18
+ [this]
19
+ {
20
+ while (true)
21
+ {
22
+ std::function<void()> job;
23
+ {
24
+ std::unique_lock lock(mutex_);
25
+ wake_.wait(lock, [this] { return stop_ || job_ != nullptr; });
26
+ if (stop_ && job_ == nullptr)
27
+ return;
28
+ job = std::move(job_);
29
+ job_ = nullptr;
30
+ }
31
+ job();
32
+ }
33
+ })
34
+ {
35
+ }
36
+
37
+ AudioWorker::~AudioWorker()
38
+ {
39
+ {
40
+ const std::scoped_lock lock(mutex_);
41
+ stop_ = true;
42
+ }
43
+ wake_.notify_one();
44
+ thread_.join();
45
+ }
46
+
47
+ void AudioWorker::run(std::function<void()> work)
48
+ {
49
+ {
50
+ const std::scoped_lock lock(mutex_);
51
+ job_ = std::move(work);
52
+ }
53
+ wake_.notify_one();
54
+ }
55
+
56
+ namespace
57
+ {
58
+ const char* platformName()
59
+ {
60
+ #if defined(_WIN32)
61
+ return "win32";
62
+ #elif defined(__APPLE__)
63
+ return "darwin";
64
+ #else
65
+ return "linux";
66
+ #endif
67
+ }
68
+
69
+ std::string requireString(const Json& params, const char* name, const std::string& where)
70
+ {
71
+ const Json* value = params.find(name);
72
+ if (value == nullptr || ! value->isString() || value->asString().empty())
73
+ throw Failure("invalid-request", where + "." + name + " must be a non-empty string");
74
+ return value->asString();
75
+ }
76
+
77
+ std::string optionalString(const Json& params, const char* name, const std::string& where)
78
+ {
79
+ const Json* value = params.find(name);
80
+ if (value == nullptr || value->isNull())
81
+ return {};
82
+ if (! value->isString())
83
+ throw Failure("invalid-request", where + "." + name + " must be a string");
84
+ return value->asString();
85
+ }
86
+
87
+ SpeakerArrangement arrangementForChannels(int64 channels)
88
+ {
89
+ switch (channels)
90
+ {
91
+ case 0:
92
+ return SpeakerArr::kEmpty;
93
+ case 1:
94
+ return SpeakerArr::kMono;
95
+ case 2:
96
+ return SpeakerArr::kStereo;
97
+ case 3:
98
+ return SpeakerArr::k30Cine;
99
+ case 4:
100
+ return SpeakerArr::k40Music;
101
+ case 5:
102
+ return SpeakerArr::k50;
103
+ case 6:
104
+ return SpeakerArr::k51;
105
+ case 8:
106
+ return SpeakerArr::k71Cine;
107
+ default:
108
+ return (SpeakerArrangement { 1 } << static_cast<unsigned>(channels)) - 1;
109
+ }
110
+ }
111
+
112
+ SpeakerArrangement parseArrangement(const std::string& text, const std::string& where)
113
+ {
114
+ if (text.starts_with("0x") || text.starts_with("0X"))
115
+ {
116
+ try
117
+ {
118
+ size_t used = 0;
119
+ const auto value = std::stoull(text.substr(2), &used, 16);
120
+ if (used == text.size() - 2)
121
+ return static_cast<SpeakerArrangement>(value);
122
+ }
123
+ catch (const std::exception&) // NOLINT(bugprone-empty-catch): reported below
124
+ {
125
+ }
126
+ throw Failure("invalid-request", where + ".arrangement \"" + text + "\" is not a hexadecimal mask");
127
+ }
128
+ const std::string lower = [&]
129
+ {
130
+ std::string result = text;
131
+ for (char& c : result)
132
+ c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
133
+ return result;
134
+ }();
135
+ if (lower == "mono")
136
+ return SpeakerArr::kMono;
137
+ if (lower == "stereo")
138
+ return SpeakerArr::kStereo;
139
+ if (lower == "empty")
140
+ return SpeakerArr::kEmpty;
141
+ // The SDK's names ("5.1", "7.1 Cine", ...).
142
+ const SpeakerArrangement named = SpeakerArr::getSpeakerArrangementFromString(text.c_str());
143
+ if (named != SpeakerArr::kEmpty)
144
+ return named;
145
+ throw Failure("invalid-request", where + ".arrangement \"" + text
146
+ + "\" is not a known speaker arrangement (mono, stereo, an SDK "
147
+ "name such as \"5.1\", or a hexadecimal mask such as \"0x3\")");
148
+ }
149
+
150
+ std::vector<std::optional<BusRequest>> parseBuses(const Json* list, const std::string& where)
151
+ {
152
+ std::vector<std::optional<BusRequest>> result;
153
+ if (list == nullptr || list->isNull())
154
+ return result;
155
+ if (! list->isArray())
156
+ throw Failure("invalid-request", where + " must be an array of bus settings");
157
+ for (size_t index = 0; index < list->asArray().size(); ++index)
158
+ {
159
+ const Json& entry = list->asArray()[index];
160
+ const std::string at = where + "[" + std::to_string(index) + "]";
161
+ if (entry.isNull())
162
+ {
163
+ result.emplace_back();
164
+ continue;
165
+ }
166
+ if (! entry.isObject())
167
+ throw Failure("invalid-request", at + " must be null or { channels, arrangement, active }");
168
+ BusRequest request;
169
+ const Json* channels = entry.find("channels");
170
+ if (channels != nullptr && ! channels->isNull())
171
+ {
172
+ if (! channels->isNumber() || channels->asNumber() != std::floor(channels->asNumber())
173
+ || channels->asNumber() < 0 || channels->asNumber() > 64)
174
+ throw Failure("invalid-request", at + ".channels must be an integer from 0 to 64");
175
+ request.arrangement = arrangementForChannels(static_cast<int64>(channels->asNumber()));
176
+ }
177
+ const std::string arrangement = optionalString(entry, "arrangement", at);
178
+ if (! arrangement.empty())
179
+ {
180
+ const auto parsed = parseArrangement(arrangement, at);
181
+ if (request.arrangement
182
+ && SpeakerArr::getChannelCount(parsed) != SpeakerArr::getChannelCount(*request.arrangement))
183
+ {
184
+ std::string message = at;
185
+ message += ".arrangement \"" + arrangement + "\" does not have ";
186
+ message += std::to_string(SpeakerArr::getChannelCount(*request.arrangement)) + " channels";
187
+ throw Failure("invalid-request", message);
188
+ }
189
+ request.arrangement = parsed;
190
+ }
191
+ if (const Json* active = entry.find("active"); active != nullptr && ! active->isNull())
192
+ {
193
+ if (! active->isBool())
194
+ throw Failure("invalid-request", at + ".active must be a boolean");
195
+ request.active = active->asBool();
196
+ }
197
+ result.emplace_back(request);
198
+ }
199
+ return result;
200
+ }
201
+
202
+ Setup parseSetup(const Json& params, const Setup& base, const std::string& where)
203
+ {
204
+ Setup setup = base;
205
+ if (const Json* rate = params.find("sampleRate"); rate != nullptr && ! rate->isNull())
206
+ {
207
+ if (! rate->isNumber() || ! std::isfinite(rate->asNumber()) || rate->asNumber() < 1000
208
+ || rate->asNumber() > 1536000)
209
+ throw Failure("invalid-request", where + ".sampleRate must be from 1000 to 1536000 Hz");
210
+ setup.sampleRate = rate->asNumber();
211
+ }
212
+ if (const Json* block = params.find("blockSize"); block != nullptr && ! block->isNull())
213
+ {
214
+ if (! block->isNumber() || block->asNumber() != std::floor(block->asNumber()) || block->asNumber() < 1
215
+ || block->asNumber() > 65536)
216
+ throw Failure("invalid-request", where + ".blockSize must be an integer from 1 to 65536");
217
+ setup.blockSize = static_cast<int32>(block->asNumber());
218
+ }
219
+ const std::string mode = optionalString(params, "processMode", where);
220
+ if (mode == "realtime")
221
+ setup.processMode = kRealtime;
222
+ else if (mode == "offline")
223
+ setup.processMode = kOffline;
224
+ else if (mode == "prefetch")
225
+ setup.processMode = kPrefetch;
226
+ else if (! mode.empty())
227
+ throw Failure("invalid-request", where + R"(.processMode must be "realtime", "offline" or "prefetch")");
228
+ if (params.find("inputs") != nullptr)
229
+ setup.inputs = parseBuses(params.find("inputs"), where + ".inputs");
230
+ if (params.find("outputs") != nullptr)
231
+ setup.outputs = parseBuses(params.find("outputs"), where + ".outputs");
232
+ return setup;
233
+ }
234
+
235
+ ParamID parameterId(const Json& params, const std::string& where)
236
+ {
237
+ const Json* id = params.find("id");
238
+ if (id == nullptr || ! id->isNumber() || id->asNumber() != std::floor(id->asNumber()) || id->asNumber() < 0
239
+ || id->asNumber() > 4294967295.0)
240
+ throw Failure("invalid-request",
241
+ where + ".id must be a VST3 parameter id (an unsigned 32-bit integer)");
242
+ return static_cast<ParamID>(id->asNumber());
243
+ }
244
+ } // namespace
245
+
246
+ Session::Session(EventLoop& loop, FrameWriter& writer, bool ui)
247
+ : loop_(loop),
248
+ writer_(writer),
249
+ ui_(ui),
250
+ settings_(Settings::load()),
251
+ presentation_(settings_.presentation),
252
+ plugin_(loop, ui,
253
+ PluginCallbacks { [this](const std::string& event, Json params) { emit(event, std::move(params)); },
254
+ [this](const std::string& title, int width, int height,
255
+ EditorWindow::Callbacks callbacks, std::string& error)
256
+ { return createWindow(title, width, height, std::move(callbacks), error); } }),
257
+ audio_(loop, plugin_),
258
+ model_ { plugin_, audio_, settings_,
259
+ [this](const std::string& event, Json params) { emit(event, std::move(params)); } }
260
+ {
261
+ audio_.setVolume(settings_.volume);
262
+ }
263
+
264
+ std::unique_ptr<EditorWindow> Session::createWindow(const std::string& title, int width, int height,
265
+ EditorWindow::Callbacks callbacks, std::string& error)
266
+ {
267
+ #if defined(__linux__)
268
+ // X11 needs the same live presentation with or without the interface.
269
+ return HostWindow::create(loop_, model_, title, width, height, presentation_ != "host", std::move(callbacks),
270
+ error);
271
+ #else
272
+ if (presentation_ == "host")
273
+ return HostWindow::create(loop_, model_, title, width, height, false, std::move(callbacks), error);
274
+ return EditorWindow::create(loop_, title, width, height, std::move(callbacks), error);
275
+ #endif
276
+ }
277
+
278
+ Json Session::openEditor(const Json& params, const std::string& where)
279
+ {
280
+ std::optional<double> scale;
281
+ if (const Json* value = params.find("scale"); value != nullptr && ! value->isNull())
282
+ {
283
+ if (! value->isNumber() || value->asNumber() <= 0 || value->asNumber() > 8)
284
+ throw Failure("invalid-request", where + ".scale must be from 0 to 8");
285
+ scale = value->asNumber();
286
+ }
287
+ const std::string presentation = optionalString(params, "presentation", where);
288
+ if (! presentation.empty() && presentation != "editor" && presentation != "host")
289
+ throw Failure("invalid-request", where + R"(.presentation must be "editor" or "host")");
290
+ // An editor already open in another presentation opens again in this
291
+ // one, at the same size and scale.
292
+ if (! presentation.empty() && presentation != presentation_)
293
+ {
294
+ presentation_ = presentation;
295
+ if (plugin_.editorOpen())
296
+ {
297
+ const Json before = plugin_.editorInfo();
298
+ if (! scale)
299
+ scale = plugin_.editorScale();
300
+ plugin_.closeEditor();
301
+ plugin_.openEditor(scale);
302
+ if (plugin_.editorOpen())
303
+ {
304
+ const Json* width = before.find("width");
305
+ const Json* height = before.find("height");
306
+ const Json after = plugin_.editorInfo();
307
+ if (after.find("resizable")->asBool()
308
+ && (after.find("width")->asNumber() != width->asNumber()
309
+ || after.find("height")->asNumber() != height->asNumber()))
310
+ plugin_.resizeEditor(static_cast<int>(width->asNumber()), static_cast<int>(height->asNumber()));
311
+ }
312
+ return plugin_.editorInfo();
313
+ }
314
+ }
315
+ return plugin_.openEditor(scale);
316
+ }
317
+
318
+ void Session::applyAudio(const Json& params, const std::string& where)
319
+ {
320
+ if (const Json* muted = params.find("muted"); muted != nullptr && ! muted->isNull())
321
+ {
322
+ if (! muted->isBool())
323
+ throw Failure("invalid-request", where + ".muted must be a boolean");
324
+ audio_.setMuted(muted->asBool());
325
+ }
326
+ if (const Json* volume = params.find("volume"); volume != nullptr && ! volume->isNull())
327
+ {
328
+ if (! volume->isNumber() || volume->asNumber() < 0 || volume->asNumber() > 1)
329
+ throw Failure("invalid-request", where + ".volume must be from 0 to 1");
330
+ audio_.setVolume(volume->asNumber());
331
+ }
332
+ const std::string input = optionalString(params, "input", where);
333
+ const std::string file = optionalString(params, "file", where);
334
+ if (! input.empty())
335
+ {
336
+ const auto parsed = parseInput(input);
337
+ if (! parsed)
338
+ throw Failure("invalid-request",
339
+ where + R"(.input must be "oscillator", "file", "microphone" or "none")");
340
+ audio_.setInput(*parsed, file);
341
+ }
342
+ else if (! file.empty())
343
+ audio_.setInput(AudioInput::file, file);
344
+ }
345
+
346
+ Session::~Session() = default;
347
+
348
+ void Session::hello(size_t maxPayload)
349
+ {
350
+ Json params = Json::object();
351
+ params.set("protocol", protocolVersion);
352
+ params.set("version", hostVersion);
353
+ params.set("sdkVersion", sdkVersion);
354
+ params.set("platform", platformName());
355
+ params.set("arch", hostArchitecture());
356
+ params.set("mode", ui_ ? "ui" : "headless");
357
+ params.set("maxPayloadBytes", static_cast<double>(maxPayload));
358
+ emit("hello", std::move(params));
359
+ }
360
+
361
+ void Session::emit(const std::string& event, Json params)
362
+ {
363
+ Json header = Json::object();
364
+ header.set("event", event);
365
+ header.set("params", std::move(params));
366
+ writer_.write(header);
367
+ }
368
+
369
+ void Session::respond(const Json& id, Json result, std::span<const uint8_t> payload)
370
+ {
371
+ Json header = Json::object();
372
+ header.set("id", id);
373
+ header.set("result", std::move(result));
374
+ writer_.write(header, payload);
375
+ }
376
+
377
+ void Session::respondError(const Json& id, const std::string& code, const std::string& message, const Json& details)
378
+ {
379
+ Json error = Json::object();
380
+ error.set("code", code);
381
+ error.set("message", message);
382
+ if (! details.isNull())
383
+ error.set("details", details);
384
+ Json header = Json::object();
385
+ header.set("id", id);
386
+ header.set("error", std::move(error));
387
+ writer_.write(header);
388
+ }
389
+
390
+ void Session::onFrame(Frame frame)
391
+ {
392
+ if (closing_)
393
+ return;
394
+ queue_.push_back(std::move(frame));
395
+ pump();
396
+ }
397
+
398
+ void Session::pump()
399
+ {
400
+ while (! queue_.empty() && ! plugin_.busy && ! closing_)
401
+ {
402
+ plugin_.applyPendingRestarts();
403
+ Frame frame = std::move(queue_.front());
404
+ queue_.pop_front();
405
+ handle(frame);
406
+ }
407
+ }
408
+
409
+ void Session::finish(int code)
410
+ {
411
+ closing_ = true;
412
+ exitCode_ = code;
413
+ queue_.clear();
414
+ audio_.stop();
415
+ plugin_.unload();
416
+ loop_.quit();
417
+ }
418
+
419
+ void Session::onInputClosed()
420
+ {
421
+ // The client went away: stop once the current job, if any, is done.
422
+ closing_ = true;
423
+ if (! plugin_.busy)
424
+ finish(0);
425
+ }
426
+
427
+ void Session::onProtocolError(const std::string& message)
428
+ {
429
+ Json params = Json::object();
430
+ params.set("message", message);
431
+ emit("protocolError", std::move(params));
432
+ if (! plugin_.busy)
433
+ finish(2);
434
+ else
435
+ closing_ = true;
436
+ }
437
+
438
+ void Session::handle(Frame& frame)
439
+ {
440
+ const Json& header = frame.header;
441
+ if (! header.isObject())
442
+ {
443
+ Json params = Json::object();
444
+ params.set("message", "a request header is not a JSON object");
445
+ emit("protocolError", std::move(params));
446
+ return;
447
+ }
448
+ const Json* idValue = header.find("id");
449
+ const Json id = idValue != nullptr ? *idValue : Json();
450
+ if (! id.isNumber())
451
+ {
452
+ Json params = Json::object();
453
+ params.set("message", "a request has no numeric id");
454
+ emit("protocolError", std::move(params));
455
+ return;
456
+ }
457
+ const Json* methodValue = header.find("method");
458
+ const Json emptyParams = Json::object();
459
+ const Json* paramsValue = header.find("params");
460
+ const Json& params = paramsValue != nullptr && paramsValue->isObject() ? *paramsValue : emptyParams;
461
+ if (methodValue == nullptr || ! methodValue->isString())
462
+ {
463
+ respondError(id, "invalid-request", "a request needs a method");
464
+ return;
465
+ }
466
+ if (paramsValue != nullptr && ! paramsValue->isObject() && ! paramsValue->isNull())
467
+ {
468
+ respondError(id, "invalid-request", "a request's params must be an object");
469
+ return;
470
+ }
471
+ const std::string& method = methodValue->asString();
472
+ const std::string where = method;
473
+
474
+ try
475
+ {
476
+ if (method == "ping")
477
+ respond(id, Json::object());
478
+ else if (method == "load")
479
+ {
480
+ const std::string path = requireString(params, "path", where);
481
+ const std::string className = optionalString(params, "className", where);
482
+ const std::string classId = optionalString(params, "classId", where);
483
+ const Setup setup = parseSetup(params, Setup {}, where);
484
+ plugin_.load(path, className, classId, setup);
485
+ audio_.formatChanged();
486
+ Json result = plugin_.info();
487
+ if (const Json* open = params.find("openEditor"); open != nullptr && open->isBool() && open->asBool())
488
+ result.set("editor", openEditor(params, where));
489
+ respond(id, std::move(result));
490
+ }
491
+ else if (method == "info")
492
+ respond(id, plugin_.info());
493
+ else if (method == "getParameters")
494
+ respond(id, plugin_.parameters());
495
+ else if (method == "getParameter")
496
+ respond(id, plugin_.parameter(parameterId(params, where)));
497
+ else if (method == "setParameter")
498
+ {
499
+ const ParamID parameter = parameterId(params, where);
500
+ const Json* normalized = params.find("normalized");
501
+ const Json* plain = params.find("plain");
502
+ const bool hasNormalized = normalized != nullptr && ! normalized->isNull();
503
+ const bool hasPlain = plain != nullptr && ! plain->isNull();
504
+ if (hasNormalized == hasPlain)
505
+ throw Failure("invalid-request", "setParameter takes either normalized or plain");
506
+ if ((hasNormalized && ! normalized->isNumber()) || (hasPlain && ! plain->isNumber()))
507
+ throw Failure("invalid-request", "a parameter value must be a number");
508
+ respond(id, plugin_.setParameter(parameter,
509
+ hasNormalized ? std::optional(normalized->asNumber()) : std::nullopt,
510
+ hasPlain ? std::optional(plain->asNumber()) : std::nullopt));
511
+ }
512
+ else if (method == "getLatency")
513
+ respond(id, plugin_.latencyAndTail());
514
+ else if (method == "process")
515
+ {
516
+ if (audio_.running())
517
+ throw Failure("invalid-state", "live audio is running: stop it (stopAudio) to process offline");
518
+ std::shared_ptr<ProcessJob> job = plugin_.prepareProcess(params, std::move(frame.payload));
519
+ plugin_.busy = true;
520
+ worker_.run(
521
+ [this, job, id]
522
+ {
523
+ plugin_.runProcess(*job);
524
+ loop_.post(
525
+ [this, job, id]
526
+ {
527
+ plugin_.busy = false;
528
+ try
529
+ {
530
+ Json result = plugin_.finishProcess(*job);
531
+ respond(id, std::move(result),
532
+ std::span<const uint8_t>(job->output.data(), job->output.size()));
533
+ }
534
+ catch (const Failure& failure)
535
+ {
536
+ respondError(id, failure.code(), failure.what(), failure.details());
537
+ }
538
+ if (closing_)
539
+ finish(exitCode_);
540
+ else
541
+ {
542
+ plugin_.applyPendingRestarts();
543
+ pump();
544
+ }
545
+ });
546
+ });
547
+ }
548
+ else if (method == "getState")
549
+ {
550
+ size_t componentSize = 0;
551
+ const auto bytes = plugin_.getState(componentSize);
552
+ Json result = Json::object();
553
+ result.set("componentBytes", static_cast<double>(componentSize));
554
+ result.set("controllerBytes", static_cast<double>(bytes.size() - componentSize));
555
+ respond(id, std::move(result), bytes);
556
+ }
557
+ else if (method == "setState")
558
+ {
559
+ const Json* componentBytes = params.find("componentBytes");
560
+ const Json* controllerBytes = params.find("controllerBytes");
561
+ if (componentBytes == nullptr || ! componentBytes->isNumber() || controllerBytes == nullptr
562
+ || ! controllerBytes->isNumber() || componentBytes->asNumber() < 0
563
+ || controllerBytes->asNumber() < 0
564
+ || componentBytes->asNumber() + controllerBytes->asNumber()
565
+ != static_cast<double>(frame.payload.size()))
566
+ throw Failure("invalid-request", "setState's componentBytes and controllerBytes must add up to "
567
+ "the payload's size");
568
+ const auto componentSize = static_cast<size_t>(componentBytes->asNumber());
569
+ plugin_.setState(frame.payload.data(), componentSize, frame.payload.data() + componentSize,
570
+ frame.payload.size() - componentSize);
571
+ respond(id, Json::object());
572
+ }
573
+ else if (method == "reset")
574
+ {
575
+ plugin_.reset();
576
+ respond(id, Json::object());
577
+ }
578
+ else if (method == "recreate")
579
+ {
580
+ plugin_.recreate();
581
+ audio_.formatChanged();
582
+ respond(id, plugin_.info());
583
+ }
584
+ else if (method == "configure")
585
+ {
586
+ // Bus requests not given keep their current arrangements.
587
+ plugin_.configure(parseSetup(params, plugin_.setup(), where));
588
+ audio_.formatChanged();
589
+ respond(id, plugin_.info());
590
+ }
591
+ else if (method == "openEditor")
592
+ respond(id, openEditor(params, where));
593
+ else if (method == "startAudio")
594
+ {
595
+ applyAudio(params, where);
596
+ audio_.start();
597
+ respond(id, audio_.status());
598
+ }
599
+ else if (method == "stopAudio")
600
+ {
601
+ audio_.stop();
602
+ respond(id, audio_.status());
603
+ }
604
+ else if (method == "setAudio")
605
+ {
606
+ applyAudio(params, where);
607
+ respond(id, audio_.status());
608
+ }
609
+ else if (method == "getAudio")
610
+ respond(id, audio_.status());
611
+ else if (method == "getTransport")
612
+ {
613
+ if (! plugin_.loaded())
614
+ throw Failure("invalid-state", "no plugin is loaded");
615
+ respond(id, plugin_.transportJson());
616
+ }
617
+ else if (method == "setTransport")
618
+ respond(id, plugin_.setTransport(params));
619
+ else if (method == "closeEditor")
620
+ {
621
+ plugin_.closeEditor();
622
+ respond(id, plugin_.editorInfo());
623
+ }
624
+ else if (method == "resizeEditor")
625
+ {
626
+ const Json* width = params.find("width");
627
+ const Json* height = params.find("height");
628
+ if (width == nullptr || height == nullptr || ! width->isNumber() || ! height->isNumber())
629
+ throw Failure("invalid-request", "resizeEditor takes a width and a height");
630
+ respond(id, plugin_.resizeEditor(static_cast<int>(width->asNumber()),
631
+ static_cast<int>(height->asNumber())));
632
+ }
633
+ else if (method == "getEditor")
634
+ respond(id, plugin_.editorInfo());
635
+ else if (method == "shutdown")
636
+ {
637
+ audio_.stop();
638
+ plugin_.unload();
639
+ respond(id, Json::object());
640
+ finish(0);
641
+ }
642
+ else
643
+ respondError(id, "unknown-method",
644
+ "\"" + method + "\" is not a method of this host (protocol "
645
+ + std::to_string(protocolVersion) + ")");
646
+ }
647
+ catch (const Failure& failure)
648
+ {
649
+ respondError(id, failure.code(), failure.what(), failure.details());
650
+ }
651
+ catch (const std::exception& error)
652
+ {
653
+ respondError(id, "internal-error", error.what());
654
+ }
655
+ }
656
+ } // namespace soundor::vst3host