@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.
- 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,146 @@
|
|
|
1
|
+
#include "EventLoop.h"
|
|
2
|
+
|
|
3
|
+
#import <Cocoa/Cocoa.h>
|
|
4
|
+
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
#include <map>
|
|
7
|
+
#include <memory>
|
|
8
|
+
|
|
9
|
+
namespace soundor::vst3host
|
|
10
|
+
{
|
|
11
|
+
namespace
|
|
12
|
+
{
|
|
13
|
+
// Work is posted to the main dispatch queue, which the main run loop
|
|
14
|
+
// drains in both modes. Headless mode never touches NSApplication.
|
|
15
|
+
class MacEventLoop final : public EventLoop
|
|
16
|
+
{
|
|
17
|
+
public:
|
|
18
|
+
explicit MacEventLoop(bool ui) : ui_(ui)
|
|
19
|
+
{
|
|
20
|
+
if (ui_)
|
|
21
|
+
{
|
|
22
|
+
[NSApplication sharedApplication];
|
|
23
|
+
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
|
24
|
+
[NSApp finishLaunching];
|
|
25
|
+
}
|
|
26
|
+
else
|
|
27
|
+
{
|
|
28
|
+
// A run loop without a source returns at once; this timer
|
|
29
|
+
// keeps CFRunLoopRunInMode waiting for posted work.
|
|
30
|
+
keepAlive_ = CFRunLoopTimerCreate(
|
|
31
|
+
nullptr, CFAbsoluteTimeGetCurrent() + 1e9, 1e9, 0, 0, [](CFRunLoopTimerRef, void*) {}, nullptr);
|
|
32
|
+
CFRunLoopAddTimer(CFRunLoopGetMain(), keepAlive_, kCFRunLoopCommonModes);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
~MacEventLoop() override
|
|
37
|
+
{
|
|
38
|
+
for (auto& [id, timer] : timers_)
|
|
39
|
+
{
|
|
40
|
+
CFRunLoopTimerInvalidate(timer->timer);
|
|
41
|
+
CFRelease(timer->timer);
|
|
42
|
+
}
|
|
43
|
+
if (keepAlive_ != nullptr)
|
|
44
|
+
{
|
|
45
|
+
CFRunLoopTimerInvalidate(keepAlive_);
|
|
46
|
+
CFRelease(keepAlive_);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
void run() override
|
|
51
|
+
{
|
|
52
|
+
running_ = true;
|
|
53
|
+
while (running_)
|
|
54
|
+
{
|
|
55
|
+
@autoreleasepool
|
|
56
|
+
{
|
|
57
|
+
if (ui_ && waiter_)
|
|
58
|
+
{
|
|
59
|
+
const auto wait = waiter_;
|
|
60
|
+
wait(0.25);
|
|
61
|
+
}
|
|
62
|
+
else if (ui_)
|
|
63
|
+
{
|
|
64
|
+
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
|
65
|
+
untilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]
|
|
66
|
+
inMode:NSDefaultRunLoopMode
|
|
67
|
+
dequeue:YES];
|
|
68
|
+
if (event != nil)
|
|
69
|
+
{
|
|
70
|
+
[NSApp sendEvent:event];
|
|
71
|
+
[NSApp updateWindows];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else
|
|
75
|
+
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.25, true);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
void quit() override { running_ = false; }
|
|
81
|
+
|
|
82
|
+
void post(std::function<void()> task) override
|
|
83
|
+
{
|
|
84
|
+
auto* heap = new std::function<void()>(std::move(task));
|
|
85
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
86
|
+
(*heap)();
|
|
87
|
+
delete heap;
|
|
88
|
+
});
|
|
89
|
+
CFRunLoopWakeUp(CFRunLoopGetMain());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
Id addTimer(unsigned milliseconds, std::function<void()> fire) override
|
|
93
|
+
{
|
|
94
|
+
const Id id = ++nextId_;
|
|
95
|
+
auto timer = std::make_unique<Timer>();
|
|
96
|
+
timer->fire = std::move(fire);
|
|
97
|
+
const double interval = std::max(1U, milliseconds) / 1000.0;
|
|
98
|
+
CFRunLoopTimerContext context {};
|
|
99
|
+
context.info = timer.get();
|
|
100
|
+
timer->timer = CFRunLoopTimerCreate(
|
|
101
|
+
nullptr, CFAbsoluteTimeGetCurrent() + interval, interval, 0, 0,
|
|
102
|
+
[](CFRunLoopTimerRef, void* info)
|
|
103
|
+
{
|
|
104
|
+
// A copy: the callback may remove this timer.
|
|
105
|
+
const auto fire = static_cast<Timer*>(info)->fire;
|
|
106
|
+
fire();
|
|
107
|
+
},
|
|
108
|
+
&context);
|
|
109
|
+
CFRunLoopAddTimer(CFRunLoopGetMain(), timer->timer, kCFRunLoopCommonModes);
|
|
110
|
+
timers_.emplace(id, std::move(timer));
|
|
111
|
+
return id;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
void removeTimer(Id id) override
|
|
115
|
+
{
|
|
116
|
+
const auto it = timers_.find(id);
|
|
117
|
+
if (it == timers_.end())
|
|
118
|
+
return;
|
|
119
|
+
CFRunLoopTimerInvalidate(it->second->timer);
|
|
120
|
+
CFRelease(it->second->timer);
|
|
121
|
+
timers_.erase(it);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
void setWaiter(std::function<void(double seconds)> wait) override { waiter_ = std::move(wait); }
|
|
125
|
+
|
|
126
|
+
private:
|
|
127
|
+
struct Timer
|
|
128
|
+
{
|
|
129
|
+
CFRunLoopTimerRef timer = nullptr;
|
|
130
|
+
std::function<void()> fire;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
bool ui_;
|
|
134
|
+
std::function<void(double seconds)> waiter_;
|
|
135
|
+
std::map<Id, std::unique_ptr<Timer>> timers_;
|
|
136
|
+
Id nextId_ = 0;
|
|
137
|
+
bool running_ = false;
|
|
138
|
+
CFRunLoopTimerRef keepAlive_ = nullptr;
|
|
139
|
+
};
|
|
140
|
+
} // namespace
|
|
141
|
+
|
|
142
|
+
std::unique_ptr<EventLoop> EventLoop::create(bool ui)
|
|
143
|
+
{
|
|
144
|
+
return std::make_unique<MacEventLoop>(ui);
|
|
145
|
+
}
|
|
146
|
+
} // namespace soundor::vst3host
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#include "EventLoop.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <deque>
|
|
5
|
+
#include <map>
|
|
6
|
+
#include <mutex>
|
|
7
|
+
|
|
8
|
+
#ifndef NOMINMAX
|
|
9
|
+
#define NOMINMAX
|
|
10
|
+
#endif
|
|
11
|
+
#include <windows.h>
|
|
12
|
+
|
|
13
|
+
namespace soundor::vst3host
|
|
14
|
+
{
|
|
15
|
+
namespace
|
|
16
|
+
{
|
|
17
|
+
constexpr UINT runTasksMessage = WM_APP + 1;
|
|
18
|
+
|
|
19
|
+
// Posted work goes through a message-only window rather than thread
|
|
20
|
+
// messages, which modal loops (a window being dragged or resized)
|
|
21
|
+
// would drop. Headless mode creates nothing visible.
|
|
22
|
+
class WindowsEventLoop final : public EventLoop
|
|
23
|
+
{
|
|
24
|
+
public:
|
|
25
|
+
WindowsEventLoop()
|
|
26
|
+
{
|
|
27
|
+
WNDCLASSEXW windowClass {};
|
|
28
|
+
windowClass.cbSize = sizeof windowClass;
|
|
29
|
+
windowClass.lpfnWndProc = &WindowsEventLoop::windowProc;
|
|
30
|
+
windowClass.hInstance = GetModuleHandleW(nullptr);
|
|
31
|
+
windowClass.lpszClassName = L"SoundorVst3HostMessages";
|
|
32
|
+
RegisterClassExW(&windowClass);
|
|
33
|
+
window_ = CreateWindowExW(0, windowClass.lpszClassName, L"", 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr,
|
|
34
|
+
windowClass.hInstance, nullptr);
|
|
35
|
+
SetWindowLongPtrW(window_, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
~WindowsEventLoop() override
|
|
39
|
+
{
|
|
40
|
+
if (window_ != nullptr)
|
|
41
|
+
DestroyWindow(window_);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
void run() override
|
|
45
|
+
{
|
|
46
|
+
running_ = true;
|
|
47
|
+
MSG message {};
|
|
48
|
+
while (running_ && GetMessageW(&message, nullptr, 0, 0) > 0)
|
|
49
|
+
{
|
|
50
|
+
TranslateMessage(&message);
|
|
51
|
+
DispatchMessageW(&message);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
void quit() override
|
|
56
|
+
{
|
|
57
|
+
running_ = false;
|
|
58
|
+
PostMessageW(window_, WM_NULL, 0, 0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
void post(std::function<void()> task) override
|
|
62
|
+
{
|
|
63
|
+
{
|
|
64
|
+
const std::scoped_lock lock(mutex_);
|
|
65
|
+
tasks_.push_back(std::move(task));
|
|
66
|
+
}
|
|
67
|
+
PostMessageW(window_, runTasksMessage, 0, 0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
Id addTimer(unsigned milliseconds, std::function<void()> fire) override
|
|
71
|
+
{
|
|
72
|
+
const Id id = ++nextId_;
|
|
73
|
+
timers_.emplace(id, std::move(fire));
|
|
74
|
+
SetTimer(window_, static_cast<UINT_PTR>(id), std::max<UINT>(USER_TIMER_MINIMUM, milliseconds), nullptr);
|
|
75
|
+
return id;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
void removeTimer(Id id) override
|
|
79
|
+
{
|
|
80
|
+
KillTimer(window_, static_cast<UINT_PTR>(id));
|
|
81
|
+
timers_.erase(id);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private:
|
|
85
|
+
static LRESULT CALLBACK windowProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam)
|
|
86
|
+
{
|
|
87
|
+
auto* self = reinterpret_cast<WindowsEventLoop*>(GetWindowLongPtrW(window, GWLP_USERDATA));
|
|
88
|
+
if (message == runTasksMessage)
|
|
89
|
+
{
|
|
90
|
+
if (self != nullptr)
|
|
91
|
+
self->runTasks();
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
if (message == WM_TIMER && self != nullptr)
|
|
95
|
+
{
|
|
96
|
+
const auto it = self->timers_.find(static_cast<Id>(wParam));
|
|
97
|
+
if (it != self->timers_.end())
|
|
98
|
+
{
|
|
99
|
+
// A copy: the callback may remove this timer.
|
|
100
|
+
const auto fire = it->second;
|
|
101
|
+
fire();
|
|
102
|
+
}
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
return DefWindowProcW(window, message, wParam, lParam);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
void runTasks()
|
|
109
|
+
{
|
|
110
|
+
std::deque<std::function<void()>> tasks;
|
|
111
|
+
{
|
|
112
|
+
const std::scoped_lock lock(mutex_);
|
|
113
|
+
tasks.swap(tasks_);
|
|
114
|
+
}
|
|
115
|
+
for (auto& task : tasks)
|
|
116
|
+
task();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
HWND window_ = nullptr;
|
|
120
|
+
bool running_ = false;
|
|
121
|
+
std::mutex mutex_;
|
|
122
|
+
std::deque<std::function<void()>> tasks_;
|
|
123
|
+
std::map<Id, std::function<void()>> timers_;
|
|
124
|
+
Id nextId_ = 0;
|
|
125
|
+
};
|
|
126
|
+
} // namespace
|
|
127
|
+
|
|
128
|
+
std::unique_ptr<EventLoop> EventLoop::create(bool /*ui*/)
|
|
129
|
+
{
|
|
130
|
+
return std::make_unique<WindowsEventLoop>();
|
|
131
|
+
}
|
|
132
|
+
} // namespace soundor::vst3host
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#include "HostContext.h"
|
|
2
|
+
|
|
3
|
+
#include "pluginterfaces/base/ustring.h"
|
|
4
|
+
|
|
5
|
+
#if defined(__linux__)
|
|
6
|
+
#include <sys/socket.h>
|
|
7
|
+
#include <sys/un.h>
|
|
8
|
+
|
|
9
|
+
#include <cstddef>
|
|
10
|
+
#include <string_view>
|
|
11
|
+
#endif
|
|
12
|
+
|
|
13
|
+
namespace soundor::vst3host
|
|
14
|
+
{
|
|
15
|
+
#if defined(__linux__)
|
|
16
|
+
namespace
|
|
17
|
+
{
|
|
18
|
+
constexpr unsigned xRecheckMilliseconds = 16;
|
|
19
|
+
|
|
20
|
+
bool isXConnection(int fd)
|
|
21
|
+
{
|
|
22
|
+
sockaddr_un address {};
|
|
23
|
+
socklen_t size = sizeof address;
|
|
24
|
+
if (getpeername(fd, reinterpret_cast<sockaddr*>(&address), &size) != 0 || address.sun_family != AF_UNIX
|
|
25
|
+
|| size <= offsetof(sockaddr_un, sun_path))
|
|
26
|
+
return false;
|
|
27
|
+
// Abstract sockets start with a NUL.
|
|
28
|
+
const std::string_view path(address.sun_path, size - offsetof(sockaddr_un, sun_path));
|
|
29
|
+
return path.find("/.X11-unix/") != std::string_view::npos;
|
|
30
|
+
}
|
|
31
|
+
} // namespace
|
|
32
|
+
|
|
33
|
+
// A handler that unregisters itself while it runs may drop the last other
|
|
34
|
+
// reference. It is released at once when it unregisters, never on a later
|
|
35
|
+
// turn: JUCE's handlers do not count references, and can be gone by then.
|
|
36
|
+
void RunLoop::fireFd(Linux::IEventHandler* handler, Linux::FileDescriptor fd)
|
|
37
|
+
{
|
|
38
|
+
const IPtr<Linux::IEventHandler> alive(handler);
|
|
39
|
+
handler->onFDIsSet(fd);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
tresult PLUGIN_API RunLoop::registerEventHandler(Linux::IEventHandler* handler, Linux::FileDescriptor fd)
|
|
43
|
+
{
|
|
44
|
+
if (handler == nullptr)
|
|
45
|
+
return kInvalidArgument;
|
|
46
|
+
handler->addRef();
|
|
47
|
+
const auto id = loop_.addFd(fd, [handler, fd] { fireFd(handler, fd); });
|
|
48
|
+
handlers_.emplace(handler, id);
|
|
49
|
+
// Xlib reads whatever is on its connection during any round trip into a
|
|
50
|
+
// queue of its own, and then the connection has nothing to read: an
|
|
51
|
+
// event that arrived during one (a click) would wait for the next. So
|
|
52
|
+
// an X connection's handler is asked to look again, every frame. Only
|
|
53
|
+
// those: they must drain that queue anyway, and do not block with
|
|
54
|
+
// nothing there, as a handler of any other descriptor might.
|
|
55
|
+
if (isXConnection(fd))
|
|
56
|
+
rechecks_.emplace(handler, loop_.addTimer(xRecheckMilliseconds, [handler, fd] { fireFd(handler, fd); }));
|
|
57
|
+
return kResultTrue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
tresult PLUGIN_API RunLoop::unregisterEventHandler(Linux::IEventHandler* handler)
|
|
61
|
+
{
|
|
62
|
+
const auto [first, last] = handlers_.equal_range(handler);
|
|
63
|
+
if (first == last)
|
|
64
|
+
return kInvalidArgument;
|
|
65
|
+
const auto [recheck, lastRecheck] = rechecks_.equal_range(handler);
|
|
66
|
+
for (auto it = recheck; it != lastRecheck; ++it)
|
|
67
|
+
loop_.removeTimer(it->second);
|
|
68
|
+
rechecks_.erase(recheck, lastRecheck);
|
|
69
|
+
for (auto it = first; it != last; ++it)
|
|
70
|
+
{
|
|
71
|
+
loop_.removeFd(it->second);
|
|
72
|
+
handler->release();
|
|
73
|
+
}
|
|
74
|
+
handlers_.erase(first, last);
|
|
75
|
+
return kResultTrue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
tresult PLUGIN_API RunLoop::registerTimer(Linux::ITimerHandler* handler, Linux::TimerInterval milliseconds)
|
|
79
|
+
{
|
|
80
|
+
if (handler == nullptr)
|
|
81
|
+
return kInvalidArgument;
|
|
82
|
+
handler->addRef();
|
|
83
|
+
const auto id = loop_.addTimer(static_cast<unsigned>(milliseconds),
|
|
84
|
+
[handler]
|
|
85
|
+
{
|
|
86
|
+
const IPtr<Linux::ITimerHandler> alive(handler);
|
|
87
|
+
handler->onTimer();
|
|
88
|
+
});
|
|
89
|
+
timers_.emplace(handler, id);
|
|
90
|
+
return kResultTrue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
tresult PLUGIN_API RunLoop::unregisterTimer(Linux::ITimerHandler* handler)
|
|
94
|
+
{
|
|
95
|
+
const auto [first, last] = timers_.equal_range(handler);
|
|
96
|
+
if (first == last)
|
|
97
|
+
return kInvalidArgument;
|
|
98
|
+
for (auto it = first; it != last; ++it)
|
|
99
|
+
{
|
|
100
|
+
loop_.removeTimer(it->second);
|
|
101
|
+
handler->release();
|
|
102
|
+
}
|
|
103
|
+
timers_.erase(first, last);
|
|
104
|
+
return kResultTrue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
void RunLoop::clear()
|
|
108
|
+
{
|
|
109
|
+
// The module is about to go: release now, not on a later turn.
|
|
110
|
+
for (const auto& [handler, id] : rechecks_)
|
|
111
|
+
loop_.removeTimer(id);
|
|
112
|
+
rechecks_.clear();
|
|
113
|
+
for (const auto& [handler, id] : handlers_)
|
|
114
|
+
{
|
|
115
|
+
loop_.removeFd(id);
|
|
116
|
+
handler->release();
|
|
117
|
+
}
|
|
118
|
+
for (const auto& [handler, id] : timers_)
|
|
119
|
+
{
|
|
120
|
+
loop_.removeTimer(id);
|
|
121
|
+
handler->release();
|
|
122
|
+
}
|
|
123
|
+
handlers_.clear();
|
|
124
|
+
timers_.clear();
|
|
125
|
+
}
|
|
126
|
+
#endif
|
|
127
|
+
|
|
128
|
+
tresult PLUGIN_API HostApplication::getName(Vst::String128 name)
|
|
129
|
+
{
|
|
130
|
+
UString(name, 128).fromAscii("Soundor VST3 Host");
|
|
131
|
+
return kResultOk;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
tresult PLUGIN_API HostApplication::queryInterface(const TUID iid, void** obj)
|
|
135
|
+
{
|
|
136
|
+
#if defined(__linux__)
|
|
137
|
+
if (runLoop_ != nullptr && FUnknownPrivate::iidEqual(iid, Linux::IRunLoop::iid))
|
|
138
|
+
return runLoop_->queryInterface(iid, obj);
|
|
139
|
+
#endif
|
|
140
|
+
return Vst::HostApplication::queryInterface(iid, obj);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
tresult PLUGIN_API ComponentHandler::beginEdit(Vst::ParamID id)
|
|
144
|
+
{
|
|
145
|
+
listener_.beginEdit(id);
|
|
146
|
+
return kResultOk;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
tresult PLUGIN_API ComponentHandler::performEdit(Vst::ParamID id, Vst::ParamValue valueNormalized)
|
|
150
|
+
{
|
|
151
|
+
listener_.performEdit(id, valueNormalized);
|
|
152
|
+
return kResultOk;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
tresult PLUGIN_API ComponentHandler::endEdit(Vst::ParamID id)
|
|
156
|
+
{
|
|
157
|
+
listener_.endEdit(id);
|
|
158
|
+
return kResultOk;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
tresult PLUGIN_API ComponentHandler::restartComponent(int32 flags)
|
|
162
|
+
{
|
|
163
|
+
listener_.restartComponent(flags);
|
|
164
|
+
return kResultOk;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
tresult PLUGIN_API ComponentHandler::setDirty(TBool state)
|
|
168
|
+
{
|
|
169
|
+
listener_.setDirty(state != 0);
|
|
170
|
+
return kResultOk;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
tresult PLUGIN_API ComponentHandler::requestOpenEditor(FIDString name)
|
|
174
|
+
{
|
|
175
|
+
if (name == nullptr || std::string_view(name) != Vst::ViewType::kEditor)
|
|
176
|
+
return kResultFalse;
|
|
177
|
+
listener_.requestOpenEditor();
|
|
178
|
+
return kResultOk;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
tresult PLUGIN_API PlugFrame::resizeView(IPlugView* view, ViewRect* newSize)
|
|
182
|
+
{
|
|
183
|
+
if (view == nullptr || newSize == nullptr)
|
|
184
|
+
return kInvalidArgument;
|
|
185
|
+
return listener_.resizeView(view, newSize) ? kResultTrue : kResultFalse;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
#if defined(__linux__)
|
|
189
|
+
namespace
|
|
190
|
+
{
|
|
191
|
+
IPtr<Linux::IRunLoop> runLoopOf(FUnknown* object)
|
|
192
|
+
{
|
|
193
|
+
return U::cast<Linux::IRunLoop>(object);
|
|
194
|
+
}
|
|
195
|
+
} // namespace
|
|
196
|
+
|
|
197
|
+
tresult PLUGIN_API PlugFrame::registerEventHandler(Linux::IEventHandler* handler, Linux::FileDescriptor fd)
|
|
198
|
+
{
|
|
199
|
+
return runLoopOf(runLoop_)->registerEventHandler(handler, fd);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
tresult PLUGIN_API PlugFrame::unregisterEventHandler(Linux::IEventHandler* handler)
|
|
203
|
+
{
|
|
204
|
+
return runLoopOf(runLoop_)->unregisterEventHandler(handler);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
tresult PLUGIN_API PlugFrame::registerTimer(Linux::ITimerHandler* handler, Linux::TimerInterval milliseconds)
|
|
208
|
+
{
|
|
209
|
+
return runLoopOf(runLoop_)->registerTimer(handler, milliseconds);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
tresult PLUGIN_API PlugFrame::unregisterTimer(Linux::ITimerHandler* handler)
|
|
213
|
+
{
|
|
214
|
+
return runLoopOf(runLoop_)->unregisterTimer(handler);
|
|
215
|
+
}
|
|
216
|
+
#endif
|
|
217
|
+
} // namespace soundor::vst3host
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// The host's side of VST3: what a plugin receives as its host context
|
|
2
|
+
// (IHostApplication, and on Linux IRunLoop), as its controller's component
|
|
3
|
+
// handler, and as its editor's frame.
|
|
4
|
+
#pragma once
|
|
5
|
+
|
|
6
|
+
#include "EventLoop.h"
|
|
7
|
+
#include "pluginterfaces/base/funknownimpl.h"
|
|
8
|
+
#include "pluginterfaces/gui/iplugview.h"
|
|
9
|
+
#include "pluginterfaces/vst/ivsteditcontroller.h"
|
|
10
|
+
#include "public.sdk/source/vst/hosting/hostclasses.h"
|
|
11
|
+
|
|
12
|
+
#include <map>
|
|
13
|
+
#include <vector>
|
|
14
|
+
|
|
15
|
+
namespace soundor::vst3host
|
|
16
|
+
{
|
|
17
|
+
using namespace Steinberg;
|
|
18
|
+
|
|
19
|
+
#if defined(__linux__)
|
|
20
|
+
/** Linux::IRunLoop over the main thread's EventLoop. A handler is held
|
|
21
|
+
while it runs, and released as soon as it unregisters. */
|
|
22
|
+
class RunLoop final : public U::Implements<U::Directly<Linux::IRunLoop>>
|
|
23
|
+
{
|
|
24
|
+
public:
|
|
25
|
+
explicit RunLoop(EventLoop& loop) : loop_(loop) {}
|
|
26
|
+
~RunLoop() override { clear(); }
|
|
27
|
+
|
|
28
|
+
tresult PLUGIN_API registerEventHandler(Linux::IEventHandler* handler, Linux::FileDescriptor fd) override;
|
|
29
|
+
tresult PLUGIN_API unregisterEventHandler(Linux::IEventHandler* handler) override;
|
|
30
|
+
tresult PLUGIN_API registerTimer(Linux::ITimerHandler* handler, Linux::TimerInterval milliseconds) override;
|
|
31
|
+
tresult PLUGIN_API unregisterTimer(Linux::ITimerHandler* handler) override;
|
|
32
|
+
|
|
33
|
+
/** Drops every registration: before a plugin's module is unloaded. */
|
|
34
|
+
void clear();
|
|
35
|
+
|
|
36
|
+
private:
|
|
37
|
+
static void fireFd(Linux::IEventHandler* handler, Linux::FileDescriptor fd);
|
|
38
|
+
|
|
39
|
+
EventLoop& loop_;
|
|
40
|
+
std::multimap<Linux::IEventHandler*, EventLoop::Id> handlers_;
|
|
41
|
+
/** The timers that ask handlers of X connections to look again. */
|
|
42
|
+
std::multimap<Linux::IEventHandler*, EventLoop::Id> rechecks_;
|
|
43
|
+
std::multimap<Linux::ITimerHandler*, EventLoop::Id> timers_;
|
|
44
|
+
};
|
|
45
|
+
#endif
|
|
46
|
+
|
|
47
|
+
/** IHostApplication (and IPlugInterfaceSupport), plus IRunLoop on Linux,
|
|
48
|
+
which plugins may ask their host context for even without an editor. */
|
|
49
|
+
class HostApplication final : public Vst::HostApplication
|
|
50
|
+
{
|
|
51
|
+
public:
|
|
52
|
+
explicit HostApplication(FUnknown* runLoop) : runLoop_(runLoop) {}
|
|
53
|
+
tresult PLUGIN_API getName(Vst::String128 name) override;
|
|
54
|
+
tresult PLUGIN_API queryInterface(const TUID iid, void** obj) override;
|
|
55
|
+
|
|
56
|
+
private:
|
|
57
|
+
[[maybe_unused]] FUnknown* runLoop_; // Linux only
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** What the plugin's controller and editor ask of the host. */
|
|
61
|
+
class HostListener
|
|
62
|
+
{
|
|
63
|
+
public:
|
|
64
|
+
virtual ~HostListener() = default;
|
|
65
|
+
virtual void beginEdit(Vst::ParamID id) = 0;
|
|
66
|
+
virtual void performEdit(Vst::ParamID id, Vst::ParamValue value) = 0;
|
|
67
|
+
virtual void endEdit(Vst::ParamID id) = 0;
|
|
68
|
+
virtual void restartComponent(int32 flags) = 0;
|
|
69
|
+
virtual void setDirty(bool dirty) = 0;
|
|
70
|
+
virtual void requestOpenEditor() = 0;
|
|
71
|
+
/** IPlugFrame::resizeView. */
|
|
72
|
+
virtual bool resizeView(IPlugView* view, ViewRect* size) = 0;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
class ComponentHandler final : public U::Implements<U::Directly<Vst::IComponentHandler, Vst::IComponentHandler2>>
|
|
76
|
+
{
|
|
77
|
+
public:
|
|
78
|
+
explicit ComponentHandler(HostListener& listener) : listener_(listener) {}
|
|
79
|
+
|
|
80
|
+
tresult PLUGIN_API beginEdit(Vst::ParamID id) override;
|
|
81
|
+
tresult PLUGIN_API performEdit(Vst::ParamID id, Vst::ParamValue valueNormalized) override;
|
|
82
|
+
tresult PLUGIN_API endEdit(Vst::ParamID id) override;
|
|
83
|
+
tresult PLUGIN_API restartComponent(int32 flags) override;
|
|
84
|
+
|
|
85
|
+
tresult PLUGIN_API setDirty(TBool state) override;
|
|
86
|
+
tresult PLUGIN_API requestOpenEditor(FIDString name) override;
|
|
87
|
+
tresult PLUGIN_API startGroupEdit() override { return kResultOk; }
|
|
88
|
+
tresult PLUGIN_API finishGroupEdit() override { return kResultOk; }
|
|
89
|
+
|
|
90
|
+
private:
|
|
91
|
+
HostListener& listener_;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
#if defined(__linux__)
|
|
95
|
+
class PlugFrame final : public U::Implements<U::Directly<IPlugFrame, Linux::IRunLoop>>
|
|
96
|
+
#else
|
|
97
|
+
class PlugFrame final : public U::Implements<U::Directly<IPlugFrame>>
|
|
98
|
+
#endif
|
|
99
|
+
{
|
|
100
|
+
public:
|
|
101
|
+
PlugFrame(HostListener& listener, FUnknown* runLoop) : listener_(listener), runLoop_(runLoop) {}
|
|
102
|
+
|
|
103
|
+
tresult PLUGIN_API resizeView(IPlugView* view, ViewRect* newSize) override;
|
|
104
|
+
|
|
105
|
+
#if defined(__linux__)
|
|
106
|
+
tresult PLUGIN_API registerEventHandler(Linux::IEventHandler* handler, Linux::FileDescriptor fd) override;
|
|
107
|
+
tresult PLUGIN_API unregisterEventHandler(Linux::IEventHandler* handler) override;
|
|
108
|
+
tresult PLUGIN_API registerTimer(Linux::ITimerHandler* handler, Linux::TimerInterval milliseconds) override;
|
|
109
|
+
tresult PLUGIN_API unregisterTimer(Linux::ITimerHandler* handler) override;
|
|
110
|
+
#endif
|
|
111
|
+
|
|
112
|
+
private:
|
|
113
|
+
HostListener& listener_;
|
|
114
|
+
[[maybe_unused]] FUnknown* runLoop_; // Linux only
|
|
115
|
+
};
|
|
116
|
+
} // namespace soundor::vst3host
|