@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,187 @@
|
|
|
1
|
+
// An SDL window drawing the host interface (HostInterface) around a native view
|
|
2
|
+
// the editor attaches to (EditorArea).
|
|
3
|
+
//
|
|
4
|
+
// In the `host` presentation the editor's area always has the editor's size:
|
|
5
|
+
// resizing or maximizing the window, or opening or closing the panel, changes
|
|
6
|
+
// the stage around it, never the editor, whether or not the editor can be
|
|
7
|
+
// resized. As in the web host, its grip and its own requests can use the stage,
|
|
8
|
+
// and the window never grows for them. The window's minimum keeps room for the
|
|
9
|
+
// editor.
|
|
10
|
+
//
|
|
11
|
+
// On Linux, the `editor` presentation is a bare window that is the editor. Its
|
|
12
|
+
// border asks the editor for each size, and the editor takes the answer at once
|
|
13
|
+
// (VST3 has no call for a shape, so an edge that the answer refuses is asked
|
|
14
|
+
// again, with the other side left to the editor). The window manager owns the
|
|
15
|
+
// window through a drag: the window takes the editor's size once it has stopped.
|
|
16
|
+
//
|
|
17
|
+
// A change of the editor's geometry is presented as one, as a browser's are:
|
|
18
|
+
// where the platform needs it, the window draws the editor's images itself
|
|
19
|
+
// until the change is over (EditorArea's live presentation), and a window
|
|
20
|
+
// manager waits for the frame of a size it asks for (_NET_WM_SYNC_REQUEST).
|
|
21
|
+
// The window and the interface follow the user every frame, with the editor's
|
|
22
|
+
// last image stretched to its new size; the editor is asked for one size at a
|
|
23
|
+
// time, and for another only once it has painted the last.
|
|
24
|
+
//
|
|
25
|
+
// The interface is presented when its drawing changes, never in a loop: a
|
|
26
|
+
// frame is built on input, at the monitor refresh rate while audio plays, and every 100 ms
|
|
27
|
+
// otherwise (values change without input too), and presented only if it
|
|
28
|
+
// differs from the last one.
|
|
29
|
+
#pragma once
|
|
30
|
+
|
|
31
|
+
#include "EditorArea.h"
|
|
32
|
+
#include "EditorWindow.h"
|
|
33
|
+
#include "HostInterface.h"
|
|
34
|
+
|
|
35
|
+
#include <array>
|
|
36
|
+
#include <chrono>
|
|
37
|
+
#include <cstdint>
|
|
38
|
+
#include <memory>
|
|
39
|
+
|
|
40
|
+
struct SDL_Renderer;
|
|
41
|
+
struct SDL_Texture;
|
|
42
|
+
struct SDL_Window;
|
|
43
|
+
union SDL_Event;
|
|
44
|
+
struct ImGuiContext;
|
|
45
|
+
|
|
46
|
+
namespace soundor::vst3host
|
|
47
|
+
{
|
|
48
|
+
class HostWindow final : public EditorWindow
|
|
49
|
+
{
|
|
50
|
+
public:
|
|
51
|
+
/** nullptr and `error` when no window can be made (no display). A bare
|
|
52
|
+
window has no interface: the editor is all of it, its border resizes
|
|
53
|
+
the editor, and it cannot be maximized or minimized. */
|
|
54
|
+
static std::unique_ptr<EditorWindow> create(EventLoop& loop, HostModel& model, const std::string& title,
|
|
55
|
+
int width, int height, bool bare, Callbacks callbacks,
|
|
56
|
+
std::string& error);
|
|
57
|
+
|
|
58
|
+
HostWindow(EventLoop& loop, HostModel& model, bool bare, Callbacks callbacks);
|
|
59
|
+
~HostWindow() override;
|
|
60
|
+
HostWindow(const HostWindow&) = delete;
|
|
61
|
+
HostWindow& operator=(const HostWindow&) = delete;
|
|
62
|
+
|
|
63
|
+
[[nodiscard]] const char* platformType() const override { return area_->platformType(); }
|
|
64
|
+
[[nodiscard]] void* parentHandle() const override { return area_->handle(); }
|
|
65
|
+
void setResizable(bool resizable) override;
|
|
66
|
+
void setContentSize(int width, int height) override;
|
|
67
|
+
void constrainContentSize(int width, int height) override;
|
|
68
|
+
void setEditorMinimum(int width, int height) override;
|
|
69
|
+
void setEditorMaximum(int width, int height) override;
|
|
70
|
+
[[nodiscard]] std::optional<std::pair<int, int>> editorRoom() const override;
|
|
71
|
+
void show() override;
|
|
72
|
+
[[nodiscard]] double scale() const override;
|
|
73
|
+
[[nodiscard]] const char* presentation() const override { return bare_ ? "editor" : "host"; }
|
|
74
|
+
[[nodiscard]] std::pair<int, int> windowSize() const override;
|
|
75
|
+
|
|
76
|
+
/** Tests check that an idle window draws nothing. */
|
|
77
|
+
[[nodiscard]] std::optional<uint64_t> interfaceFrames() const override { return presented_; }
|
|
78
|
+
|
|
79
|
+
private:
|
|
80
|
+
bool open(const std::string& title, int width, int height, std::string& error);
|
|
81
|
+
void pump();
|
|
82
|
+
void handle(const SDL_Event& event);
|
|
83
|
+
/** The window's natural size for the editor and the panel. */
|
|
84
|
+
[[nodiscard]] std::pair<int, int> naturalSize() const;
|
|
85
|
+
/** What the editor, the dock and the panel take, however short the
|
|
86
|
+
panel scrolls. */
|
|
87
|
+
[[nodiscard]] std::pair<int, int> requiredSize() const;
|
|
88
|
+
void windowResized();
|
|
89
|
+
/** Maximized or restored since the last frame: the editor follows. */
|
|
90
|
+
/** The window for the editor and the panel, keeping the space the
|
|
91
|
+
user made: `extraWidth` more of it for a panel that opened. */
|
|
92
|
+
void fitWindow(int extraWidth = 0);
|
|
93
|
+
void applyLimits();
|
|
94
|
+
void layout();
|
|
95
|
+
void askWindowSize(int width, int height);
|
|
96
|
+
/** A bare window's border has changed the editor it is. */
|
|
97
|
+
void bareWindowResized();
|
|
98
|
+
/** Gives a bare window the editor's size after a drag has stopped. */
|
|
99
|
+
void settleWindow(std::chrono::steady_clock::time_point now);
|
|
100
|
+
/** What the interface takes around the editor: nothing in a bare window. */
|
|
101
|
+
[[nodiscard]] float paddingSpace() const;
|
|
102
|
+
[[nodiscard]] float dockHeight() const;
|
|
103
|
+
[[nodiscard]] float dockSpan() const;
|
|
104
|
+
/** What the panel takes from the stage now. */
|
|
105
|
+
[[nodiscard]] float panelSpace() const;
|
|
106
|
+
void applyScale();
|
|
107
|
+
void applyRefreshRate();
|
|
108
|
+
void invalidate(int frames = 2) { pending_ = std::max(pending_, frames); }
|
|
109
|
+
void render();
|
|
110
|
+
/** The editor's geometry is changing: presents it live until it stops. */
|
|
111
|
+
void touchLive();
|
|
112
|
+
void endLive();
|
|
113
|
+
/** The editor's size for the window's stage. */
|
|
114
|
+
[[nodiscard]] std::pair<int, int> stageEditorSize() const;
|
|
115
|
+
void updateLiveImage();
|
|
116
|
+
|
|
117
|
+
EventLoop& loop_;
|
|
118
|
+
HostModel& model_;
|
|
119
|
+
Callbacks callbacks_;
|
|
120
|
+
SDL_Window* window_ = nullptr;
|
|
121
|
+
SDL_Renderer* renderer_ = nullptr;
|
|
122
|
+
ImGuiContext* context_ = nullptr;
|
|
123
|
+
std::unique_ptr<EditorArea> area_;
|
|
124
|
+
std::unique_ptr<HostInterface> interface_;
|
|
125
|
+
EventLoop::Id timer_ = 0;
|
|
126
|
+
|
|
127
|
+
int editorWidth_ = 0;
|
|
128
|
+
int editorHeight_ = 0;
|
|
129
|
+
std::array<int, 4> editorFrame_ {};
|
|
130
|
+
bool resizable_ = false;
|
|
131
|
+
bool shown_ = false;
|
|
132
|
+
bool live_ = false;
|
|
133
|
+
std::chrono::steady_clock::time_point liveTouched_ {};
|
|
134
|
+
SDL_Texture* liveTexture_ = nullptr;
|
|
135
|
+
int liveWidth_ = 0;
|
|
136
|
+
int liveHeight_ = 0;
|
|
137
|
+
/** The part of the image that holds the editor's pixels, and where it
|
|
138
|
+
goes in the window; none for a whole image, which fills the editor. */
|
|
139
|
+
struct LivePart
|
|
140
|
+
{
|
|
141
|
+
float x, y, width, height, u0, v0, u1, v1;
|
|
142
|
+
};
|
|
143
|
+
std::optional<LivePart> livePart_;
|
|
144
|
+
bool livePartial_ = false;
|
|
145
|
+
bool viewHidden_ = false;
|
|
146
|
+
/** The image to draw is an older one, not what the view shows. */
|
|
147
|
+
bool hideViewFirst_ = false;
|
|
148
|
+
std::chrono::steady_clock::time_point windowResizedAt_ {};
|
|
149
|
+
int minimumWidth_ = 1;
|
|
150
|
+
int minimumHeight_ = 1;
|
|
151
|
+
/** The panel the stage leaves room for: panelOpen_ once the window has
|
|
152
|
+
taken its width. */
|
|
153
|
+
bool stagePanelOpen_ = false;
|
|
154
|
+
std::chrono::steady_clock::time_point stageFollowsAt_ {};
|
|
155
|
+
/** The user's newest grip drag, for the editor to take when it can. */
|
|
156
|
+
std::optional<std::pair<int, int>> gripSize_;
|
|
157
|
+
bool bare_ = false;
|
|
158
|
+
int maximumWidth_ = 0;
|
|
159
|
+
int maximumHeight_ = 0;
|
|
160
|
+
/** A drag has changed a bare window: it takes the editor's size once
|
|
161
|
+
the drag has stopped. */
|
|
162
|
+
bool settling_ = false;
|
|
163
|
+
int settlingAsks_ = 0;
|
|
164
|
+
/** The bare window's size at the last step of a drag. */
|
|
165
|
+
std::pair<int, int> draggedSize_ { 0, 0 };
|
|
166
|
+
/** A size a bare window has asked for and not yet got. */
|
|
167
|
+
std::optional<std::pair<int, int>> askedSize_;
|
|
168
|
+
std::chrono::steady_clock::time_point askedUntil_ {};
|
|
169
|
+
bool panelOpen_ = false;
|
|
170
|
+
float scale_ = 1;
|
|
171
|
+
/** The natural size the window was last fitted to. */
|
|
172
|
+
std::pair<int, int> fitted_ { 0, 0 };
|
|
173
|
+
/** The window's limits, as last set. */
|
|
174
|
+
std::pair<int, int> limits_ { 0, 0 };
|
|
175
|
+
int pending_ = 0;
|
|
176
|
+
bool forcePresent_ = true;
|
|
177
|
+
uint64_t lastHash_ = 0;
|
|
178
|
+
uint64_t presented_ = 0;
|
|
179
|
+
std::chrono::steady_clock::time_point lastFrame_ {};
|
|
180
|
+
std::chrono::steady_clock::time_point nextFrame_ {};
|
|
181
|
+
std::chrono::steady_clock::duration frameInterval_ {};
|
|
182
|
+
bool closing_ = false;
|
|
183
|
+
/** False once this window is gone. On macOS, SDL's wait for events runs
|
|
184
|
+
the main queue's work, which can close the window it is waiting for. */
|
|
185
|
+
std::shared_ptr<bool> alive_ = std::make_shared<bool>(true);
|
|
186
|
+
};
|
|
187
|
+
} // namespace soundor::vst3host
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#include "Ipc.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <array>
|
|
5
|
+
#include <cstring>
|
|
6
|
+
|
|
7
|
+
#ifdef _WIN32
|
|
8
|
+
#include <windows.h>
|
|
9
|
+
#else
|
|
10
|
+
#include <unistd.h>
|
|
11
|
+
|
|
12
|
+
#include <cerrno>
|
|
13
|
+
#endif
|
|
14
|
+
|
|
15
|
+
namespace soundor::vst3host
|
|
16
|
+
{
|
|
17
|
+
void Payload::resize(size_t size)
|
|
18
|
+
{
|
|
19
|
+
storage_.assign((size + sizeof(double) - 1) / sizeof(double), 0.0);
|
|
20
|
+
size_ = size;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
namespace
|
|
24
|
+
{
|
|
25
|
+
uint32_t readU32(const uint8_t* bytes)
|
|
26
|
+
{
|
|
27
|
+
return static_cast<uint32_t>(bytes[0]) | (static_cast<uint32_t>(bytes[1]) << 8U)
|
|
28
|
+
| (static_cast<uint32_t>(bytes[2]) << 16U) | (static_cast<uint32_t>(bytes[3]) << 24U);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
void writeU32(uint8_t* bytes, uint32_t value)
|
|
32
|
+
{
|
|
33
|
+
bytes[0] = static_cast<uint8_t>(value);
|
|
34
|
+
bytes[1] = static_cast<uint8_t>(value >> 8U);
|
|
35
|
+
bytes[2] = static_cast<uint8_t>(value >> 16U);
|
|
36
|
+
bytes[3] = static_cast<uint8_t>(value >> 24U);
|
|
37
|
+
}
|
|
38
|
+
} // namespace
|
|
39
|
+
|
|
40
|
+
bool FrameReader::readExact(uint8_t* data, size_t size, bool& end) const
|
|
41
|
+
{
|
|
42
|
+
size_t done = 0;
|
|
43
|
+
while (done < size)
|
|
44
|
+
{
|
|
45
|
+
#ifdef _WIN32
|
|
46
|
+
DWORD count = 0;
|
|
47
|
+
const DWORD chunk = static_cast<DWORD>(std::min<size_t>(size - done, 1U << 30U));
|
|
48
|
+
if (! ReadFile(reinterpret_cast<HANDLE>(handle_), data + done, chunk, &count, nullptr) || count == 0)
|
|
49
|
+
{
|
|
50
|
+
end = done == 0;
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
#else
|
|
54
|
+
const ssize_t count = ::read(static_cast<int>(handle_), data + done, size - done);
|
|
55
|
+
if (count < 0 && errno == EINTR)
|
|
56
|
+
continue;
|
|
57
|
+
if (count <= 0)
|
|
58
|
+
{
|
|
59
|
+
end = done == 0;
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
#endif
|
|
63
|
+
done += static_cast<size_t>(count);
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
FrameReader::Status FrameReader::read(Frame& frame, std::string& error)
|
|
69
|
+
{
|
|
70
|
+
std::array<uint8_t, frameHeaderBytes> prefix {};
|
|
71
|
+
bool end = false;
|
|
72
|
+
if (! readExact(prefix.data(), prefix.size(), end))
|
|
73
|
+
{
|
|
74
|
+
if (end)
|
|
75
|
+
return Status::End;
|
|
76
|
+
error = "the request stream ended inside a frame header";
|
|
77
|
+
return Status::Error;
|
|
78
|
+
}
|
|
79
|
+
if (readU32(prefix.data()) != frameMagic)
|
|
80
|
+
{
|
|
81
|
+
error = "the request stream is not framed (bad magic)";
|
|
82
|
+
return Status::Error;
|
|
83
|
+
}
|
|
84
|
+
const uint32_t headerSize = readU32(prefix.data() + 4);
|
|
85
|
+
const uint32_t payloadSize = readU32(prefix.data() + 8);
|
|
86
|
+
if (headerSize > maxHeader_)
|
|
87
|
+
{
|
|
88
|
+
error = "a request header of " + std::to_string(headerSize) + " bytes exceeds the limit of "
|
|
89
|
+
+ std::to_string(maxHeader_);
|
|
90
|
+
return Status::Error;
|
|
91
|
+
}
|
|
92
|
+
if (payloadSize > maxPayload_)
|
|
93
|
+
{
|
|
94
|
+
error = "a request payload of " + std::to_string(payloadSize) + " bytes exceeds the limit of "
|
|
95
|
+
+ std::to_string(maxPayload_);
|
|
96
|
+
return Status::Error;
|
|
97
|
+
}
|
|
98
|
+
std::string header(headerSize, '\0');
|
|
99
|
+
if (headerSize > 0 && ! readExact(reinterpret_cast<uint8_t*>(header.data()), headerSize, end))
|
|
100
|
+
{
|
|
101
|
+
error = "the request stream ended inside a frame";
|
|
102
|
+
return Status::Error;
|
|
103
|
+
}
|
|
104
|
+
frame.payload.resize(payloadSize);
|
|
105
|
+
if (payloadSize > 0 && ! readExact(frame.payload.data(), payloadSize, end))
|
|
106
|
+
{
|
|
107
|
+
error = "the request stream ended inside a frame";
|
|
108
|
+
return Status::Error;
|
|
109
|
+
}
|
|
110
|
+
try
|
|
111
|
+
{
|
|
112
|
+
frame.header = Json::parse(header);
|
|
113
|
+
}
|
|
114
|
+
catch (const JsonError&)
|
|
115
|
+
{
|
|
116
|
+
frame.header = Json();
|
|
117
|
+
}
|
|
118
|
+
return Status::Frame;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
bool FrameWriter::writeAll(const uint8_t* data, size_t size) const
|
|
122
|
+
{
|
|
123
|
+
size_t done = 0;
|
|
124
|
+
while (done < size)
|
|
125
|
+
{
|
|
126
|
+
#ifdef _WIN32
|
|
127
|
+
DWORD count = 0;
|
|
128
|
+
const DWORD chunk = static_cast<DWORD>(std::min<size_t>(size - done, 1U << 30U));
|
|
129
|
+
if (! WriteFile(reinterpret_cast<HANDLE>(handle_), data + done, chunk, &count, nullptr))
|
|
130
|
+
return false;
|
|
131
|
+
#else
|
|
132
|
+
const ssize_t count = ::write(static_cast<int>(handle_), data + done, size - done);
|
|
133
|
+
if (count < 0 && errno == EINTR)
|
|
134
|
+
continue;
|
|
135
|
+
if (count <= 0)
|
|
136
|
+
return false;
|
|
137
|
+
#endif
|
|
138
|
+
done += static_cast<size_t>(count);
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
bool FrameWriter::write(const Json& header, std::span<const uint8_t> payload)
|
|
144
|
+
{
|
|
145
|
+
const std::string text = header.dump();
|
|
146
|
+
std::array<uint8_t, frameHeaderBytes> prefix {};
|
|
147
|
+
writeU32(prefix.data(), frameMagic);
|
|
148
|
+
writeU32(prefix.data() + 4, static_cast<uint32_t>(text.size()));
|
|
149
|
+
writeU32(prefix.data() + 8, static_cast<uint32_t>(payload.size()));
|
|
150
|
+
const std::scoped_lock lock(mutex_);
|
|
151
|
+
if (broken_)
|
|
152
|
+
return false;
|
|
153
|
+
broken_ = ! writeAll(prefix.data(), prefix.size())
|
|
154
|
+
|| ! writeAll(reinterpret_cast<const uint8_t*>(text.data()), text.size())
|
|
155
|
+
|| ! writeAll(payload.data(), payload.size());
|
|
156
|
+
return ! broken_;
|
|
157
|
+
}
|
|
158
|
+
} // namespace soundor::vst3host
|
package/native/src/Ipc.h
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Framing of the host's protocol (README, "Protocol"). Each frame is
|
|
2
|
+
//
|
|
3
|
+
// magic "SVH1" | u32 LE header length | u32 LE payload length | header | payload
|
|
4
|
+
//
|
|
5
|
+
// The header is a UTF-8 JSON object; the payload is raw bytes (audio as
|
|
6
|
+
// little-endian float32, plugin state). Requests arrive on stdin, responses
|
|
7
|
+
// and events leave on the process's original stdout, which Main.cpp moves
|
|
8
|
+
// out of the way of plugins writing to fd 1.
|
|
9
|
+
#pragma once
|
|
10
|
+
|
|
11
|
+
#include "Json.h"
|
|
12
|
+
|
|
13
|
+
#include <cstdint>
|
|
14
|
+
#include <memory>
|
|
15
|
+
#include <mutex>
|
|
16
|
+
#include <span>
|
|
17
|
+
#include <string>
|
|
18
|
+
#include <vector>
|
|
19
|
+
|
|
20
|
+
namespace soundor::vst3host
|
|
21
|
+
{
|
|
22
|
+
inline constexpr uint32_t frameMagic = 0x31485653; // "SVH1", little-endian
|
|
23
|
+
inline constexpr size_t frameHeaderBytes = 12;
|
|
24
|
+
|
|
25
|
+
/** A byte buffer whose data is aligned for float and double access. */
|
|
26
|
+
class Payload
|
|
27
|
+
{
|
|
28
|
+
public:
|
|
29
|
+
Payload() = default;
|
|
30
|
+
explicit Payload(size_t size) { resize(size); }
|
|
31
|
+
void resize(size_t size);
|
|
32
|
+
uint8_t* data() { return storage_.empty() ? nullptr : reinterpret_cast<uint8_t*>(storage_.data()); }
|
|
33
|
+
const uint8_t* data() const
|
|
34
|
+
{
|
|
35
|
+
return storage_.empty() ? nullptr : reinterpret_cast<const uint8_t*>(storage_.data());
|
|
36
|
+
}
|
|
37
|
+
size_t size() const { return size_; }
|
|
38
|
+
|
|
39
|
+
private:
|
|
40
|
+
std::vector<double> storage_;
|
|
41
|
+
size_t size_ = 0;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
struct Frame
|
|
45
|
+
{
|
|
46
|
+
Json header;
|
|
47
|
+
Payload payload;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** A native file handle: an fd on POSIX, a HANDLE on Windows. */
|
|
51
|
+
using NativeHandle = intptr_t;
|
|
52
|
+
|
|
53
|
+
class FrameReader
|
|
54
|
+
{
|
|
55
|
+
public:
|
|
56
|
+
enum class Status
|
|
57
|
+
{
|
|
58
|
+
Frame,
|
|
59
|
+
End,
|
|
60
|
+
Error
|
|
61
|
+
};
|
|
62
|
+
FrameReader(NativeHandle handle, size_t maxHeader, size_t maxPayload)
|
|
63
|
+
: handle_(handle), maxHeader_(maxHeader), maxPayload_(maxPayload)
|
|
64
|
+
{
|
|
65
|
+
}
|
|
66
|
+
/** Blocks for the next frame. On Error, `error` says why; the stream
|
|
67
|
+
cannot be resynchronised afterwards. A header that is not valid
|
|
68
|
+
JSON still returns a Frame whose header is null. */
|
|
69
|
+
Status read(Frame& frame, std::string& error);
|
|
70
|
+
|
|
71
|
+
private:
|
|
72
|
+
bool readExact(uint8_t* data, size_t size, bool& end) const;
|
|
73
|
+
NativeHandle handle_;
|
|
74
|
+
size_t maxHeader_;
|
|
75
|
+
size_t maxPayload_;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
class FrameWriter
|
|
79
|
+
{
|
|
80
|
+
public:
|
|
81
|
+
explicit FrameWriter(NativeHandle handle) : handle_(handle) {}
|
|
82
|
+
/** Thread-safe; false once the reader has gone. */
|
|
83
|
+
bool write(const Json& header, std::span<const uint8_t> payload = {});
|
|
84
|
+
|
|
85
|
+
private:
|
|
86
|
+
bool writeAll(const uint8_t* data, size_t size) const;
|
|
87
|
+
std::mutex mutex_;
|
|
88
|
+
NativeHandle handle_;
|
|
89
|
+
bool broken_ = false;
|
|
90
|
+
};
|
|
91
|
+
} // namespace soundor::vst3host
|