@soundor/vst3-host 0.0.0-stage → 0.1.1
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.
- package/CMakeLists.txt +194 -0
- package/CMakePresets.json +115 -0
- package/LICENSE +21 -0
- package/NOTICE.md +17 -0
- package/README.md +40 -2
- package/cmake/Vst3HostDependencies.cmake +179 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +88 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +586 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1 -0
- package/dist/prepare-ayjTVhfk.mjs +1149 -0
- package/dist/prepare-ayjTVhfk.mjs.map +1 -0
- package/native/src/Bundle.cpp +253 -0
- package/native/src/Bundle.h +32 -0
- package/native/src/EditorArea.h +92 -0
- package/native/src/EditorAreaMac.mm +52 -0
- package/native/src/EditorAreaWindows.cpp +66 -0
- package/native/src/EditorAreaX11.cpp +512 -0
- package/native/src/EditorWindow.h +72 -0
- package/native/src/EditorWindowMac.mm +134 -0
- package/native/src/EditorWindowWindows.cpp +148 -0
- package/native/src/EventLoop.h +50 -0
- package/native/src/EventLoopLinux.cpp +204 -0
- package/native/src/EventLoopMac.mm +146 -0
- package/native/src/EventLoopWindows.cpp +132 -0
- package/native/src/HostContext.cpp +217 -0
- package/native/src/HostContext.h +116 -0
- package/native/src/HostInterface.cpp +1026 -0
- package/native/src/HostInterface.h +138 -0
- package/native/src/HostWindow.cpp +901 -0
- package/native/src/HostWindow.h +187 -0
- package/native/src/Ipc.cpp +158 -0
- package/native/src/Ipc.h +91 -0
- package/native/src/Json.cpp +426 -0
- package/native/src/Json.h +85 -0
- package/native/src/LiveAudio.cpp +416 -0
- package/native/src/LiveAudio.h +121 -0
- package/native/src/Main.cpp +178 -0
- package/native/src/Paths.h +20 -0
- package/native/src/Plugin.cpp +1865 -0
- package/native/src/Plugin.h +321 -0
- package/native/src/Session.cpp +656 -0
- package/native/src/Session.h +82 -0
- package/native/src/Settings.cpp +93 -0
- package/native/src/Settings.h +23 -0
- package/native/src/Version.h.in +11 -0
- package/native/src/X11Library.cpp +127 -0
- package/native/src/X11Library.h +83 -0
- package/native/tools/Embed.cpp +49 -0
- package/package.json +58 -3
- package/protocol.json +7 -0
- 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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
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.7",
|
|
41
|
+
"typescript": "^7.0.2",
|
|
42
|
+
"vitest": "^5.1.0"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": "^22.18.0 || >=24.11.0"
|
|
46
|
+
},
|
|
47
|
+
"optionalDependencies": {
|
|
48
|
+
"@soundor/vst3-host-linux-x64-gnu": "0.1.1",
|
|
49
|
+
"@soundor/vst3-host-darwin-universal": "0.1.1",
|
|
50
|
+
"@soundor/vst3-host-win32-x64": "0.1.1"
|
|
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
|
+
}
|