@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,82 @@
1
+ // Requests, one at a time and in order, on the main thread. A process request
2
+ // runs on the audio worker while the main thread keeps running its loop (and
3
+ // an open editor); the next request waits until it is done.
4
+ #pragma once
5
+
6
+ #include "EventLoop.h"
7
+ #include "HostInterface.h"
8
+ #include "Ipc.h"
9
+ #include "LiveAudio.h"
10
+ #include "Plugin.h"
11
+ #include "Settings.h"
12
+
13
+ #include <condition_variable>
14
+ #include <deque>
15
+ #include <functional>
16
+ #include <mutex>
17
+ #include <thread>
18
+
19
+ namespace soundor::vst3host
20
+ {
21
+ /** The thread process() runs on. */
22
+ class AudioWorker
23
+ {
24
+ public:
25
+ AudioWorker();
26
+ ~AudioWorker();
27
+ AudioWorker(const AudioWorker&) = delete;
28
+ AudioWorker& operator=(const AudioWorker&) = delete;
29
+ /** Runs `work` on the worker thread; one job at a time. */
30
+ void run(std::function<void()> work);
31
+
32
+ private:
33
+ std::mutex mutex_;
34
+ std::condition_variable wake_;
35
+ std::function<void()> job_;
36
+ bool stop_ = false;
37
+ std::thread thread_;
38
+ };
39
+
40
+ class Session
41
+ {
42
+ public:
43
+ Session(EventLoop& loop, FrameWriter& writer, bool ui);
44
+ ~Session();
45
+
46
+ /** The handshake: the first frame the client sees. */
47
+ void hello(size_t maxPayload);
48
+ void onFrame(Frame frame);
49
+ /** The client closed the request stream. */
50
+ void onInputClosed();
51
+ /** The request stream is corrupt and cannot continue. */
52
+ void onProtocolError(const std::string& message);
53
+ int exitCode() const { return exitCode_; }
54
+
55
+ private:
56
+ void pump();
57
+ void handle(Frame& frame);
58
+ void respond(const Json& id, Json result, std::span<const uint8_t> payload = {});
59
+ void respondError(const Json& id, const std::string& code, const std::string& message,
60
+ const Json& details = {});
61
+ void emit(const std::string& event, Json params);
62
+ void finish(int code);
63
+ std::unique_ptr<EditorWindow> createWindow(const std::string& title, int width, int height,
64
+ EditorWindow::Callbacks callbacks, std::string& error);
65
+ Json openEditor(const Json& params, const std::string& where);
66
+ void applyAudio(const Json& params, const std::string& where);
67
+
68
+ EventLoop& loop_;
69
+ FrameWriter& writer_;
70
+ bool ui_;
71
+ Settings settings_;
72
+ /** The presentation of the next editor: "editor" or "host". */
73
+ std::string presentation_;
74
+ Plugin plugin_;
75
+ LiveAudio audio_;
76
+ HostModel model_;
77
+ AudioWorker worker_;
78
+ std::deque<Frame> queue_;
79
+ bool closing_ = false;
80
+ int exitCode_ = 0;
81
+ };
82
+ } // namespace soundor::vst3host
@@ -0,0 +1,93 @@
1
+ #include "Settings.h"
2
+
3
+ #include "Json.h"
4
+ #include "Paths.h"
5
+
6
+ #include <cstdlib>
7
+ #include <filesystem>
8
+ #include <fstream>
9
+ #include <sstream>
10
+
11
+ namespace soundor::vst3host
12
+ {
13
+ namespace
14
+ {
15
+ #if ! defined(_WIN32)
16
+ std::string environment(const char* name)
17
+ {
18
+ const char* value = std::getenv(name); // NOLINT(concurrency-mt-unsafe): read on the main thread
19
+ return value != nullptr ? value : "";
20
+ }
21
+ #endif
22
+
23
+ std::filesystem::path settingsPath()
24
+ {
25
+ #if defined(_WIN32)
26
+ // Windows' environment is UTF-16.
27
+ if (const wchar_t* path = _wgetenv(L"SOUNDOR_VST3_HOST_SETTINGS"); path != nullptr && *path != L'\0')
28
+ return path;
29
+ const wchar_t* base = _wgetenv(L"APPDATA");
30
+ return base == nullptr || *base == L'\0' ? std::filesystem::path()
31
+ : std::filesystem::path(base) / "Soundor" / "vst3-host.json";
32
+ #else
33
+ if (const std::string path = environment("SOUNDOR_VST3_HOST_SETTINGS"); ! path.empty())
34
+ return toPath(path);
35
+ #endif
36
+ #if defined(_WIN32)
37
+ #elif defined(__APPLE__)
38
+ const std::string home = environment("HOME");
39
+ return home.empty() ? std::filesystem::path()
40
+ : toPath(home) / "Library" / "Application Support" / "Soundor" / "vst3-host.json";
41
+ #else
42
+ if (const std::string config = environment("XDG_CONFIG_HOME"); ! config.empty())
43
+ return toPath(config) / "soundor" / "vst3-host.json";
44
+ const std::string home = environment("HOME");
45
+ return home.empty() ? std::filesystem::path() : toPath(home) / ".config" / "soundor" / "vst3-host.json";
46
+ #endif
47
+ }
48
+ } // namespace
49
+
50
+ Settings Settings::load()
51
+ {
52
+ Settings settings;
53
+ const auto path = settingsPath();
54
+ if (path.empty())
55
+ return settings;
56
+ std::ifstream input(path, std::ios::binary);
57
+ if (! input)
58
+ return settings;
59
+ std::stringstream text;
60
+ text << input.rdbuf();
61
+ try
62
+ {
63
+ const Json json = Json::parse(text.str());
64
+ if (const Json* value = json.find("presentation");
65
+ value != nullptr && value->isString() && (value->asString() == "host" || value->asString() == "editor"))
66
+ settings.presentation = value->asString();
67
+ if (const Json* value = json.find("panelOpen"); value != nullptr && value->isBool())
68
+ settings.panelOpen = value->asBool();
69
+ if (const Json* value = json.find("volume");
70
+ value != nullptr && value->isNumber() && value->asNumber() >= 0 && value->asNumber() <= 1)
71
+ settings.volume = value->asNumber();
72
+ }
73
+ catch (const std::exception&) // NOLINT(bugprone-empty-catch): a damaged file means the defaults
74
+ {
75
+ }
76
+ return settings;
77
+ }
78
+
79
+ void Settings::save() const
80
+ {
81
+ const auto path = settingsPath();
82
+ if (path.empty())
83
+ return;
84
+ std::error_code error;
85
+ std::filesystem::create_directories(path.parent_path(), error);
86
+ Json json = Json::object();
87
+ json.set("presentation", presentation);
88
+ json.set("panelOpen", panelOpen);
89
+ json.set("volume", volume);
90
+ std::ofstream output(path, std::ios::binary | std::ios::trunc);
91
+ output << json.dump() << "\n";
92
+ }
93
+ } // namespace soundor::vst3host
@@ -0,0 +1,23 @@
1
+ // The host interface's settings, kept between runs in a JSON file:
2
+ // $XDG_CONFIG_HOME/soundor/vst3-host.json (~/.config), ~/Library/Application
3
+ // Support/Soundor/vst3-host.json or %APPDATA%\Soundor\vst3-host.json;
4
+ // SOUNDOR_VST3_HOST_SETTINGS names another file. What a launch asks for
5
+ // explicitly always wins over them.
6
+ #pragma once
7
+
8
+ #include <string>
9
+
10
+ namespace soundor::vst3host
11
+ {
12
+ struct Settings
13
+ {
14
+ /** Open editors with the host interface ("host") or without ("editor"). */
15
+ std::string presentation = "host";
16
+ bool panelOpen = false;
17
+ double volume = 0.8;
18
+
19
+ static Settings load();
20
+ /** Best effort: a settings file that cannot be written is not an error. */
21
+ void save() const;
22
+ };
23
+ } // namespace soundor::vst3host
@@ -0,0 +1,11 @@
1
+ // Generated by CMakeLists.txt from package.json and protocol.json.
2
+ #pragma once
3
+
4
+ namespace soundor::vst3host
5
+ {
6
+ inline constexpr const char* hostVersion = "@SOUNDOR_VST3_HOST_VERSION@";
7
+ inline constexpr int protocolVersion = @SOUNDOR_VST3_HOST_PROTOCOL@;
8
+ inline constexpr unsigned long long defaultMaxPayload = @SOUNDOR_VST3_HOST_MAX_PAYLOAD@ULL;
9
+ inline constexpr unsigned long long maxHeader = @SOUNDOR_VST3_HOST_MAX_HEADER@ULL;
10
+ inline constexpr const char* sdkVersion = "@SOUNDOR_VST3_SDK_VERSION@";
11
+ } // namespace soundor::vst3host
@@ -0,0 +1,127 @@
1
+ #include "X11Library.h"
2
+
3
+ #include <dlfcn.h>
4
+
5
+ #include <cstdio>
6
+ #include <type_traits>
7
+
8
+ namespace soundor::vst3host
9
+ {
10
+ namespace
11
+ {
12
+ const Xlib* loadedXlib = nullptr;
13
+ Display* quietDisplay = nullptr;
14
+
15
+ int logError(Display* display, XErrorEvent* event)
16
+ {
17
+ if (display == quietDisplay)
18
+ return 0;
19
+ char text[256] = {};
20
+ loadedXlib->getErrorText(display, event->error_code, text, sizeof text);
21
+ std::fprintf(stderr, "X error (ignored): %s, request %d.%d, resource 0x%lx\n", text, event->request_code,
22
+ event->minor_code, event->resourceid);
23
+ return 0;
24
+ }
25
+ } // namespace
26
+
27
+ const Xlib* loadXlib(std::string& error)
28
+ {
29
+ static Xlib xlib;
30
+ static bool loaded = false;
31
+ if (loaded)
32
+ return &xlib;
33
+ void* library = dlopen("libX11.so.6", RTLD_NOW | RTLD_LOCAL);
34
+ if (library == nullptr)
35
+ {
36
+ error = "the editor needs libX11.so.6, which could not be loaded";
37
+ return nullptr;
38
+ }
39
+ bool complete = true;
40
+ auto bind = [&](auto& function, const char* name)
41
+ {
42
+ function = reinterpret_cast<std::remove_reference_t<decltype(function)>>(dlsym(library, name));
43
+ complete = complete && function != nullptr;
44
+ };
45
+ bind(xlib.openDisplay, "XOpenDisplay");
46
+ bind(xlib.closeDisplay, "XCloseDisplay");
47
+ bind(xlib.defaultScreen, "XDefaultScreen");
48
+ bind(xlib.rootWindow, "XRootWindow");
49
+ bind(xlib.blackPixel, "XBlackPixel");
50
+ bind(xlib.createSimpleWindow, "XCreateSimpleWindow");
51
+ bind(xlib.destroyWindow, "XDestroyWindow");
52
+ bind(xlib.mapWindow, "XMapWindow");
53
+ bind(xlib.storeName, "XStoreName");
54
+ bind(xlib.internAtom, "XInternAtom");
55
+ bind(xlib.changeProperty, "XChangeProperty");
56
+ bind(xlib.setWMProtocols, "XSetWMProtocols");
57
+ bind(xlib.selectInput, "XSelectInput");
58
+ bind(xlib.pending, "XPending");
59
+ bind(xlib.nextEvent, "XNextEvent");
60
+ bind(xlib.resizeWindow, "XResizeWindow");
61
+ bind(xlib.getWindowAttributes, "XGetWindowAttributes");
62
+ bind(xlib.flush, "XFlush");
63
+ bind(xlib.connectionNumber, "XConnectionNumber");
64
+ bind(xlib.allocSizeHints, "XAllocSizeHints");
65
+ bind(xlib.setWMNormalHints, "XSetWMNormalHints");
66
+ bind(xlib.free, "XFree");
67
+ bind(xlib.moveResizeWindow, "XMoveResizeWindow");
68
+ bind(xlib.setErrorHandler, "XSetErrorHandler");
69
+ bind(xlib.getErrorText, "XGetErrorText");
70
+ bind(xlib.displayString, "XDisplayString");
71
+ bind(xlib.queryTree, "XQueryTree");
72
+ bind(xlib.sync, "XSync");
73
+ bind(xlib.getImage, "XGetImage");
74
+ bind(xlib.freePixmap, "XFreePixmap");
75
+ bind(xlib.setWindowBackgroundPixmap, "XSetWindowBackgroundPixmap");
76
+ bind(xlib.sendEvent, "XSendEvent");
77
+ bind(xlib.setWindowBackground, "XSetWindowBackground");
78
+ bind(xlib.changeWindowAttributes, "XChangeWindowAttributes");
79
+ bind(xlib.translateCoordinates, "XTranslateCoordinates");
80
+ if (! complete)
81
+ {
82
+ error = "libX11.so.6 lacks a required function";
83
+ return nullptr;
84
+ }
85
+ loaded = true;
86
+ loadedXlib = &xlib;
87
+ xlib.setErrorHandler(&logError);
88
+ return &xlib;
89
+ }
90
+
91
+ const XLive* loadXLive()
92
+ {
93
+ static const XLive* const loaded = []() -> const XLive*
94
+ {
95
+ static XLive live;
96
+ void* composite = dlopen("libXcomposite.so.1", RTLD_NOW | RTLD_LOCAL);
97
+ void* damage = dlopen("libXdamage.so.1", RTLD_NOW | RTLD_LOCAL);
98
+ void* extensions = dlopen("libXext.so.6", RTLD_NOW | RTLD_LOCAL);
99
+ if (composite == nullptr || damage == nullptr || extensions == nullptr)
100
+ return nullptr;
101
+ bool complete = true;
102
+ auto bind = [&](void* library, auto& function, const char* name)
103
+ {
104
+ function = reinterpret_cast<std::remove_reference_t<decltype(function)>>(dlsym(library, name));
105
+ complete = complete && function != nullptr;
106
+ };
107
+ bind(composite, live.compositeQueryExtension, "XCompositeQueryExtension");
108
+ bind(composite, live.redirectSubwindows, "XCompositeRedirectSubwindows");
109
+ bind(composite, live.unredirectSubwindows, "XCompositeUnredirectSubwindows");
110
+ bind(composite, live.nameWindowPixmap, "XCompositeNameWindowPixmap");
111
+ bind(damage, live.damageQueryExtension, "XDamageQueryExtension");
112
+ bind(damage, live.damageCreate, "XDamageCreate");
113
+ bind(damage, live.damageDestroy, "XDamageDestroy");
114
+ bind(damage, live.damageSubtract, "XDamageSubtract");
115
+ bind(extensions, live.shapeQueryExtension, "XShapeQueryExtension");
116
+ bind(extensions, live.shapeCombineRectangles, "XShapeCombineRectangles");
117
+ bind(extensions, live.shapeCombineMask, "XShapeCombineMask");
118
+ return complete ? &live : nullptr;
119
+ }();
120
+ return loaded;
121
+ }
122
+
123
+ void setQuietDisplay(Display* display)
124
+ {
125
+ quietDisplay = display;
126
+ }
127
+ } // namespace soundor::vst3host
@@ -0,0 +1,83 @@
1
+ // Xlib, loaded when a window is first needed and never linked: headless mode
2
+ // runs on machines without X11 libraries or a display.
3
+ #pragma once
4
+
5
+ #include <X11/Xlib.h>
6
+ #include <X11/Xutil.h>
7
+ #include <X11/extensions/Xcomposite.h>
8
+ #include <X11/extensions/Xdamage.h>
9
+ #include <X11/extensions/shape.h>
10
+
11
+ #include <string>
12
+
13
+ namespace soundor::vst3host
14
+ {
15
+ struct Xlib
16
+ {
17
+ decltype(&XOpenDisplay) openDisplay = nullptr;
18
+ decltype(&XCloseDisplay) closeDisplay = nullptr;
19
+ decltype(&XDefaultScreen) defaultScreen = nullptr;
20
+ decltype(&XRootWindow) rootWindow = nullptr;
21
+ decltype(&XBlackPixel) blackPixel = nullptr;
22
+ decltype(&XCreateSimpleWindow) createSimpleWindow = nullptr;
23
+ decltype(&XDestroyWindow) destroyWindow = nullptr;
24
+ decltype(&XMapWindow) mapWindow = nullptr;
25
+ decltype(&XStoreName) storeName = nullptr;
26
+ decltype(&XInternAtom) internAtom = nullptr;
27
+ decltype(&XChangeProperty) changeProperty = nullptr;
28
+ decltype(&XSetWMProtocols) setWMProtocols = nullptr;
29
+ decltype(&XSelectInput) selectInput = nullptr;
30
+ decltype(&XPending) pending = nullptr;
31
+ decltype(&XNextEvent) nextEvent = nullptr;
32
+ decltype(&XResizeWindow) resizeWindow = nullptr;
33
+ decltype(&XGetWindowAttributes) getWindowAttributes = nullptr;
34
+ decltype(&XFlush) flush = nullptr;
35
+ decltype(&XConnectionNumber) connectionNumber = nullptr;
36
+ decltype(&XAllocSizeHints) allocSizeHints = nullptr;
37
+ decltype(&XSetWMNormalHints) setWMNormalHints = nullptr;
38
+ decltype(&XFree) free = nullptr;
39
+ decltype(&XMoveResizeWindow) moveResizeWindow = nullptr;
40
+ decltype(&XSetErrorHandler) setErrorHandler = nullptr;
41
+ decltype(&XGetErrorText) getErrorText = nullptr;
42
+ decltype(&XDisplayString) displayString = nullptr;
43
+ decltype(&XQueryTree) queryTree = nullptr;
44
+ decltype(&XSync) sync = nullptr;
45
+ decltype(&XGetImage) getImage = nullptr;
46
+ decltype(&XFreePixmap) freePixmap = nullptr;
47
+ decltype(&XSetWindowBackgroundPixmap) setWindowBackgroundPixmap = nullptr;
48
+ decltype(&XSendEvent) sendEvent = nullptr;
49
+ decltype(&XSetWindowBackground) setWindowBackground = nullptr;
50
+ decltype(&XChangeWindowAttributes) changeWindowAttributes = nullptr;
51
+ decltype(&XTranslateCoordinates) translateCoordinates = nullptr;
52
+ };
53
+
54
+ /** What live presentation needs (EditorArea) besides Xlib. */
55
+ struct XLive
56
+ {
57
+ decltype(&XCompositeQueryExtension) compositeQueryExtension = nullptr;
58
+ decltype(&XCompositeRedirectSubwindows) redirectSubwindows = nullptr;
59
+ decltype(&XCompositeUnredirectSubwindows) unredirectSubwindows = nullptr;
60
+ decltype(&XCompositeNameWindowPixmap) nameWindowPixmap = nullptr;
61
+ decltype(&XDamageQueryExtension) damageQueryExtension = nullptr;
62
+ decltype(&XDamageCreate) damageCreate = nullptr;
63
+ decltype(&XDamageDestroy) damageDestroy = nullptr;
64
+ decltype(&XDamageSubtract) damageSubtract = nullptr;
65
+ decltype(&XShapeQueryExtension) shapeQueryExtension = nullptr;
66
+ decltype(&XShapeCombineRectangles) shapeCombineRectangles = nullptr;
67
+ decltype(&XShapeCombineMask) shapeCombineMask = nullptr;
68
+ };
69
+
70
+ /** The functions the host uses, loaded once; nullptr and `error` without
71
+ libX11. Loading also replaces Xlib's default error handler, which
72
+ ends the process, with one that logs: a plugin's failed X request
73
+ is the plugin's problem, not the host's end. */
74
+ const Xlib* loadXlib(std::string& error);
75
+
76
+ /** Loaded once; nullptr without libXcomposite, libXdamage or libXext, where the
77
+ editor's view stays on the screen while it is resized. */
78
+ const XLive* loadXLive();
79
+
80
+ /** Errors on this connection are not logged: its requests may name a
81
+ plugin's window, which the plugin destroys when it likes. */
82
+ void setQuietDisplay(Display* display);
83
+ } // namespace soundor::vst3host
@@ -0,0 +1,49 @@
1
+ // Writes files into a C++ source as byte arrays:
2
+ //
3
+ // soundor_vst3_host_embed <output.cpp> <namespace> (<name> <file>)...
4
+ //
5
+ // Each <name> becomes `extern const unsigned char name[]` and
6
+ // `extern const std::size_t nameSize` in <namespace>.
7
+ #include <cstdio>
8
+ #include <fstream>
9
+ #include <iterator>
10
+ #include <string>
11
+ #include <vector>
12
+
13
+ int main(int argc, char** argv)
14
+ {
15
+ if (argc < 3 || (argc - 3) % 2 != 0)
16
+ {
17
+ std::fputs("usage: soundor_vst3_host_embed <output.cpp> <namespace> (<name> <file>)...\n", stderr);
18
+ return 2;
19
+ }
20
+ std::string code = "// Generated by soundor_vst3_host_embed.\n#include <cstddef>\n\nnamespace ";
21
+ code += argv[2];
22
+ code += "\n{\n";
23
+ for (int index = 3; index < argc; index += 2)
24
+ {
25
+ std::ifstream input(argv[index + 1], std::ios::binary);
26
+ if (! input)
27
+ {
28
+ std::fprintf(stderr, "cannot read %s\n", argv[index + 1]);
29
+ return 1;
30
+ }
31
+ const std::vector<unsigned char> bytes { std::istreambuf_iterator<char>(input),
32
+ std::istreambuf_iterator<char>() };
33
+ const std::string name = argv[index];
34
+ code += " extern const unsigned char " + name + "[];\n";
35
+ code += " extern const std::size_t " + name + "Size;\n";
36
+ code += " alignas(16) const unsigned char " + name + "[] = {";
37
+ for (size_t at = 0; at < bytes.size(); ++at)
38
+ {
39
+ if (at % 24 == 0)
40
+ code += "\n ";
41
+ code += std::to_string(bytes[at]) + ",";
42
+ }
43
+ code += "\n };\n const std::size_t " + name + "Size = " + std::to_string(bytes.size()) + ";\n";
44
+ }
45
+ code += "} // namespace " + std::string(argv[2]) + "\n";
46
+ std::ofstream output(argv[1], std::ios::binary);
47
+ output << code;
48
+ return output ? 0 : 1;
49
+ }
package/package.json CHANGED
@@ -1,6 +1,61 @@
1
1
  {
2
2
  "name": "@soundor/vst3-host",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "A standalone VST3 host for tests and development: a native executable driven from Node.js, with or without the plugin's editor.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/soundor/soundor.git",
9
+ "directory": "hosts/vst3-host"
10
+ },
11
+ "bin": {
12
+ "soundor-vst3-host": "./dist/cli.mjs"
13
+ },
14
+ "files": [
15
+ "README.md",
16
+ "LICENSE",
17
+ "NOTICE.md",
18
+ "CMakeLists.txt",
19
+ "CMakePresets.json",
20
+ "protocol.json",
21
+ "targets.json",
22
+ "cmake",
23
+ "native/src",
24
+ "native/tools",
25
+ "dist"
26
+ ],
27
+ "type": "module",
28
+ "sideEffects": false,
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.mts",
32
+ "import": "./dist/index.mjs"
33
+ },
34
+ "./package.json": "./package.json"
35
+ },
36
+ "devDependencies": {
37
+ "@soundor/tsdown-config": "^0.0.0",
38
+ "@soundor/typescript-config": "^0.0.0",
39
+ "@types/node": "^24.19.2",
40
+ "tsdown": "^0.23.4",
41
+ "typescript": "^7.0.2",
42
+ "vitest": "^5.0.5"
43
+ },
44
+ "engines": {
45
+ "node": "^22.18.0 || >=24.11.0"
46
+ },
47
+ "optionalDependencies": {
48
+ "@soundor/vst3-host-linux-x64-gnu": "0.1.0",
49
+ "@soundor/vst3-host-darwin-universal": "0.1.0",
50
+ "@soundor/vst3-host-win32-x64": "0.1.0"
51
+ },
52
+ "scripts": {
53
+ "build": "tsc -b && tsdown --clean",
54
+ "test": "vitest run",
55
+ "build:native": "node src/cli.ts prepare --mode source",
56
+ "test:native": "node scripts/native.ts test",
57
+ "tidy:native": "cmake --workflow --preset tidy",
58
+ "format:native": "node ../../scripts/clang-format.mjs --check native",
59
+ "format:native:fix": "node ../../scripts/clang-format.mjs --fix native"
60
+ }
6
61
  }
package/protocol.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "$comment": "The host's IPC protocol identity, read by the native build (CMakeLists.txt) and the Node client (src/protocol.ts). Bump `version` for any incompatible change to frames or messages.",
3
+ "version": 1,
4
+ "magic": "SVH1",
5
+ "maxHeaderBytes": 1048576,
6
+ "defaultMaxPayloadBytes": 268435456
7
+ }
package/targets.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "$comment": "The platform packages the release generates (scripts/release.ts). Each carries one executable; npm or pnpm installs the one matching `os`, `cpu` and `libc`. Not workspace packages: they are generated, versioned as @soundor/vst3-host, and never committed.",
3
+ "targets": [
4
+ {
5
+ "target": "linux-x64-gnu",
6
+ "package": "@soundor/vst3-host-linux-x64-gnu",
7
+ "os": ["linux"],
8
+ "cpu": ["x64"],
9
+ "libc": ["glibc"],
10
+ "runner": "ubuntu-24.04",
11
+ "architectures": ["x64"],
12
+ "executable": "bin/soundor-vst3-host"
13
+ },
14
+ {
15
+ "target": "darwin-universal",
16
+ "package": "@soundor/vst3-host-darwin-universal",
17
+ "os": ["darwin"],
18
+ "cpu": ["arm64", "x64"],
19
+ "runner": "macos-15",
20
+ "architectures": ["arm64", "x64"],
21
+ "executable": "bin/soundor-vst3-host"
22
+ },
23
+ {
24
+ "target": "win32-x64",
25
+ "package": "@soundor/vst3-host-win32-x64",
26
+ "os": ["win32"],
27
+ "cpu": ["x64"],
28
+ "runner": "windows-2025",
29
+ "architectures": ["x64"],
30
+ "executable": "bin/soundor-vst3-host.exe"
31
+ }
32
+ ]
33
+ }