@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,426 @@
1
+ #include "Json.h"
2
+
3
+ #include <array>
4
+ #include <cmath>
5
+ #include <cstdio>
6
+ #include <locale>
7
+ #include <sstream>
8
+
9
+ namespace soundor::vst3host
10
+ {
11
+ const Json* Json::find(std::string_view key) const
12
+ {
13
+ if (type_ != Type::Object)
14
+ return nullptr;
15
+ for (const auto& [name, value] : object_)
16
+ if (name == key)
17
+ return &value;
18
+ return nullptr;
19
+ }
20
+
21
+ Json& Json::set(std::string key, Json value)
22
+ {
23
+ for (auto& [name, existing] : object_)
24
+ if (name == key)
25
+ return existing = std::move(value);
26
+ object_.emplace_back(std::move(key), std::move(value));
27
+ return object_.back().second;
28
+ }
29
+
30
+ std::string Json::dump() const
31
+ {
32
+ std::string out;
33
+ dumpTo(out);
34
+ return out;
35
+ }
36
+
37
+ namespace
38
+ {
39
+ void dumpString(std::string& out, const std::string& value)
40
+ {
41
+ out += '"';
42
+ for (const char c : value)
43
+ {
44
+ const auto byte = static_cast<unsigned char>(c);
45
+ switch (c)
46
+ {
47
+ case '"':
48
+ out += "\\\"";
49
+ break;
50
+ case '\\':
51
+ out += "\\\\";
52
+ break;
53
+ case '\n':
54
+ out += "\\n";
55
+ break;
56
+ case '\r':
57
+ out += "\\r";
58
+ break;
59
+ case '\t':
60
+ out += "\\t";
61
+ break;
62
+ default:
63
+ if (byte < 0x20)
64
+ {
65
+ std::array<char, 8> escaped {};
66
+ std::snprintf(escaped.data(), escaped.size(), "\\u%04x", byte);
67
+ out += escaped.data();
68
+ }
69
+ else
70
+ out += c;
71
+ }
72
+ }
73
+ out += '"';
74
+ }
75
+ } // namespace
76
+
77
+ void Json::dumpTo(std::string& out) const
78
+ {
79
+ switch (type_)
80
+ {
81
+ case Type::Null:
82
+ out += "null";
83
+ break;
84
+ case Type::Bool:
85
+ out += bool_ ? "true" : "false";
86
+ break;
87
+ case Type::Number:
88
+ {
89
+ if (! std::isfinite(number_))
90
+ {
91
+ out += "null";
92
+ break;
93
+ }
94
+ if (number_ == std::floor(number_) && std::fabs(number_) < 9007199254740992.0)
95
+ {
96
+ out += std::to_string(static_cast<long long>(number_));
97
+ break;
98
+ }
99
+ std::ostringstream stream;
100
+ stream.imbue(std::locale::classic());
101
+ stream.precision(17);
102
+ stream << number_;
103
+ out += stream.str();
104
+ break;
105
+ }
106
+ case Type::String:
107
+ dumpString(out, string_);
108
+ break;
109
+ case Type::Array:
110
+ {
111
+ out += '[';
112
+ bool first = true;
113
+ for (const auto& item : array_)
114
+ {
115
+ if (! first)
116
+ out += ',';
117
+ first = false;
118
+ item.dumpTo(out);
119
+ }
120
+ out += ']';
121
+ break;
122
+ }
123
+ case Type::Object:
124
+ {
125
+ out += '{';
126
+ bool first = true;
127
+ for (const auto& [name, value] : object_)
128
+ {
129
+ if (! first)
130
+ out += ',';
131
+ first = false;
132
+ dumpString(out, name);
133
+ out += ':';
134
+ value.dumpTo(out);
135
+ }
136
+ out += '}';
137
+ break;
138
+ }
139
+ }
140
+ }
141
+
142
+ namespace
143
+ {
144
+ class Parser
145
+ {
146
+ public:
147
+ explicit Parser(std::string_view text) : text_(text) {}
148
+
149
+ Json document()
150
+ {
151
+ Json value = parseValue(0);
152
+ skipSpace();
153
+ if (pos_ != text_.size())
154
+ fail("unexpected data after the value");
155
+ return value;
156
+ }
157
+
158
+ private:
159
+ static constexpr int maxDepth = 64;
160
+
161
+ [[noreturn]] void fail(const std::string& what) const
162
+ {
163
+ throw JsonError("invalid JSON at offset " + std::to_string(pos_) + ": " + what);
164
+ }
165
+
166
+ void skipSpace()
167
+ {
168
+ while (pos_ < text_.size()
169
+ && (text_[pos_] == ' ' || text_[pos_] == '\n' || text_[pos_] == '\r' || text_[pos_] == '\t'))
170
+ ++pos_;
171
+ }
172
+
173
+ bool consume(std::string_view literal)
174
+ {
175
+ if (text_.substr(pos_, literal.size()) != literal)
176
+ return false;
177
+ pos_ += literal.size();
178
+ return true;
179
+ }
180
+
181
+ Json parseValue(int depth)
182
+ {
183
+ if (depth > maxDepth)
184
+ fail("nested too deeply");
185
+ skipSpace();
186
+ if (pos_ >= text_.size())
187
+ fail("unexpected end");
188
+ const char c = text_[pos_];
189
+ if (c == '{')
190
+ return parseObject(depth);
191
+ if (c == '[')
192
+ return parseArray(depth);
193
+ if (c == '"')
194
+ return { parseString() };
195
+ if (consume("true"))
196
+ return { true };
197
+ if (consume("false"))
198
+ return { false };
199
+ if (consume("null"))
200
+ return { nullptr };
201
+ if (c == '-' || (c >= '0' && c <= '9'))
202
+ return { parseNumber() };
203
+ fail(std::string("unexpected character '") + c + "'");
204
+ }
205
+
206
+ Json parseObject(int depth)
207
+ {
208
+ ++pos_;
209
+ Json result = Json::object();
210
+ skipSpace();
211
+ if (pos_ < text_.size() && text_[pos_] == '}')
212
+ {
213
+ ++pos_;
214
+ return result;
215
+ }
216
+ while (true)
217
+ {
218
+ skipSpace();
219
+ if (pos_ >= text_.size() || text_[pos_] != '"')
220
+ fail("expected a member name");
221
+ std::string key = parseString();
222
+ skipSpace();
223
+ if (pos_ >= text_.size() || text_[pos_] != ':')
224
+ fail("expected ':'");
225
+ ++pos_;
226
+ result.set(std::move(key), parseValue(depth + 1));
227
+ skipSpace();
228
+ if (pos_ < text_.size() && text_[pos_] == ',')
229
+ {
230
+ ++pos_;
231
+ continue;
232
+ }
233
+ if (pos_ < text_.size() && text_[pos_] == '}')
234
+ {
235
+ ++pos_;
236
+ return result;
237
+ }
238
+ fail("expected ',' or '}'");
239
+ }
240
+ }
241
+
242
+ Json parseArray(int depth)
243
+ {
244
+ ++pos_;
245
+ Json result = Json::array();
246
+ skipSpace();
247
+ if (pos_ < text_.size() && text_[pos_] == ']')
248
+ {
249
+ ++pos_;
250
+ return result;
251
+ }
252
+ while (true)
253
+ {
254
+ result.push(parseValue(depth + 1));
255
+ skipSpace();
256
+ if (pos_ < text_.size() && text_[pos_] == ',')
257
+ {
258
+ ++pos_;
259
+ continue;
260
+ }
261
+ if (pos_ < text_.size() && text_[pos_] == ']')
262
+ {
263
+ ++pos_;
264
+ return result;
265
+ }
266
+ fail("expected ',' or ']'");
267
+ }
268
+ }
269
+
270
+ unsigned hex4()
271
+ {
272
+ if (pos_ + 4 > text_.size())
273
+ fail("truncated \\u escape");
274
+ unsigned value = 0;
275
+ for (int i = 0; i < 4; ++i)
276
+ {
277
+ const char c = text_[pos_++];
278
+ value <<= 4U;
279
+ if (c >= '0' && c <= '9')
280
+ value |= static_cast<unsigned>(c - '0');
281
+ else if (c >= 'a' && c <= 'f')
282
+ value |= static_cast<unsigned>(c - 'a' + 10);
283
+ else if (c >= 'A' && c <= 'F')
284
+ value |= static_cast<unsigned>(c - 'A' + 10);
285
+ else
286
+ fail("invalid \\u escape");
287
+ }
288
+ return value;
289
+ }
290
+
291
+ static void appendUtf8(std::string& out, unsigned code)
292
+ {
293
+ if (code < 0x80)
294
+ out += static_cast<char>(code);
295
+ else if (code < 0x800)
296
+ {
297
+ out += static_cast<char>(0xC0U | (code >> 6U));
298
+ out += static_cast<char>(0x80U | (code & 0x3FU));
299
+ }
300
+ else if (code < 0x10000)
301
+ {
302
+ out += static_cast<char>(0xE0U | (code >> 12U));
303
+ out += static_cast<char>(0x80U | ((code >> 6U) & 0x3FU));
304
+ out += static_cast<char>(0x80U | (code & 0x3FU));
305
+ }
306
+ else
307
+ {
308
+ out += static_cast<char>(0xF0U | (code >> 18U));
309
+ out += static_cast<char>(0x80U | ((code >> 12U) & 0x3FU));
310
+ out += static_cast<char>(0x80U | ((code >> 6U) & 0x3FU));
311
+ out += static_cast<char>(0x80U | (code & 0x3FU));
312
+ }
313
+ }
314
+
315
+ std::string parseString()
316
+ {
317
+ ++pos_;
318
+ std::string out;
319
+ while (true)
320
+ {
321
+ if (pos_ >= text_.size())
322
+ fail("unterminated string");
323
+ const char c = text_[pos_++];
324
+ if (c == '"')
325
+ return out;
326
+ if (static_cast<unsigned char>(c) < 0x20)
327
+ fail("control character in string");
328
+ if (c != '\\')
329
+ {
330
+ out += c;
331
+ continue;
332
+ }
333
+ if (pos_ >= text_.size())
334
+ fail("unterminated escape");
335
+ const char escape = text_[pos_++];
336
+ switch (escape)
337
+ {
338
+ case '"':
339
+ case '\\':
340
+ case '/':
341
+ out += escape;
342
+ break;
343
+ case 'b':
344
+ out += '\b';
345
+ break;
346
+ case 'f':
347
+ out += '\f';
348
+ break;
349
+ case 'n':
350
+ out += '\n';
351
+ break;
352
+ case 'r':
353
+ out += '\r';
354
+ break;
355
+ case 't':
356
+ out += '\t';
357
+ break;
358
+ case 'u':
359
+ {
360
+ unsigned code = hex4();
361
+ if (code >= 0xD800 && code < 0xDC00)
362
+ {
363
+ if (! consume("\\u"))
364
+ fail("unpaired surrogate");
365
+ const unsigned low = hex4();
366
+ if (low < 0xDC00 || low >= 0xE000)
367
+ fail("unpaired surrogate");
368
+ code = 0x10000 + ((code - 0xD800) << 10U) + (low - 0xDC00);
369
+ }
370
+ else if (code >= 0xDC00 && code < 0xE000)
371
+ fail("unpaired surrogate");
372
+ appendUtf8(out, code);
373
+ break;
374
+ }
375
+ default:
376
+ fail("invalid escape");
377
+ }
378
+ }
379
+ }
380
+
381
+ double parseNumber()
382
+ {
383
+ const size_t start = pos_;
384
+ if (text_[pos_] == '-')
385
+ ++pos_;
386
+ auto digits = [this]
387
+ {
388
+ const size_t from = pos_;
389
+ while (pos_ < text_.size() && text_[pos_] >= '0' && text_[pos_] <= '9')
390
+ ++pos_;
391
+ return pos_ - from;
392
+ };
393
+ if (digits() == 0)
394
+ fail("invalid number");
395
+ if (pos_ < text_.size() && text_[pos_] == '.')
396
+ {
397
+ ++pos_;
398
+ if (digits() == 0)
399
+ fail("invalid number");
400
+ }
401
+ if (pos_ < text_.size() && (text_[pos_] == 'e' || text_[pos_] == 'E'))
402
+ {
403
+ ++pos_;
404
+ if (pos_ < text_.size() && (text_[pos_] == '+' || text_[pos_] == '-'))
405
+ ++pos_;
406
+ if (digits() == 0)
407
+ fail("invalid number");
408
+ }
409
+ // The classic locale: a plugin may change the process's C locale.
410
+ std::istringstream stream { std::string(text_.substr(start, pos_ - start)) };
411
+ stream.imbue(std::locale::classic());
412
+ double value = 0;
413
+ stream >> value;
414
+ return value;
415
+ }
416
+
417
+ std::string_view text_;
418
+ size_t pos_ = 0;
419
+ };
420
+ } // namespace
421
+
422
+ Json Json::parse(std::string_view text)
423
+ {
424
+ return Parser(text).document();
425
+ }
426
+ } // namespace soundor::vst3host
@@ -0,0 +1,85 @@
1
+ // A small JSON value for the host's control messages (protocol.json). Audio
2
+ // never passes through it: buffers travel in each frame's binary payload.
3
+ #pragma once
4
+
5
+ #include <cstdint>
6
+ #include <optional>
7
+ #include <stdexcept>
8
+ #include <string>
9
+ #include <string_view>
10
+ #include <utility>
11
+ #include <vector>
12
+
13
+ namespace soundor::vst3host
14
+ {
15
+ class Json
16
+ {
17
+ public:
18
+ enum class Type
19
+ {
20
+ Null,
21
+ Bool,
22
+ Number,
23
+ String,
24
+ Array,
25
+ Object
26
+ };
27
+ using Array = std::vector<Json>;
28
+ using Object = std::vector<std::pair<std::string, Json>>;
29
+
30
+ Json() = default;
31
+ Json(std::nullptr_t) {}
32
+ Json(bool value) : type_(Type::Bool), bool_(value) {}
33
+ Json(double value) : type_(Type::Number), number_(value) {}
34
+ Json(int value) : type_(Type::Number), number_(value) {}
35
+ Json(int64_t value) : type_(Type::Number), number_(static_cast<double>(value)) {}
36
+ Json(uint32_t value) : type_(Type::Number), number_(value) {}
37
+ Json(const char* value) : type_(Type::String), string_(value) {}
38
+ Json(std::string value) : type_(Type::String), string_(std::move(value)) {}
39
+ Json(Array value) : type_(Type::Array), array_(std::move(value)) {}
40
+ Json(Object value) : type_(Type::Object), object_(std::move(value)) {}
41
+
42
+ static Json object() { return Json(Object {}); }
43
+ static Json array() { return Json(Array {}); }
44
+
45
+ Type type() const { return type_; }
46
+ bool isNull() const { return type_ == Type::Null; }
47
+ bool isBool() const { return type_ == Type::Bool; }
48
+ bool isNumber() const { return type_ == Type::Number; }
49
+ bool isString() const { return type_ == Type::String; }
50
+ bool isArray() const { return type_ == Type::Array; }
51
+ bool isObject() const { return type_ == Type::Object; }
52
+
53
+ bool asBool() const { return bool_; }
54
+ double asNumber() const { return number_; }
55
+ const std::string& asString() const { return string_; }
56
+ const Array& asArray() const { return array_; }
57
+ Array& asArray() { return array_; }
58
+ const Object& asObject() const { return object_; }
59
+
60
+ /** The member named `key`, or nullptr (also when this is not an object). */
61
+ const Json* find(std::string_view key) const;
62
+ /** Adds or replaces a member; this must be an object. */
63
+ Json& set(std::string key, Json value);
64
+ /** Appends to an array. */
65
+ void push(Json value) { array_.push_back(std::move(value)); }
66
+
67
+ std::string dump() const;
68
+ static Json parse(std::string_view text);
69
+
70
+ private:
71
+ void dumpTo(std::string& out) const;
72
+
73
+ Type type_ = Type::Null;
74
+ bool bool_ = false;
75
+ double number_ = 0;
76
+ std::string string_;
77
+ Array array_;
78
+ Object object_;
79
+ };
80
+
81
+ struct JsonError : std::runtime_error
82
+ {
83
+ using std::runtime_error::runtime_error;
84
+ };
85
+ } // namespace soundor::vst3host