RAM2118 commited on
Commit
f36ed0a
·
verified ·
1 Parent(s): 644b727

Out-of-process rebuild: Add Plugin/PluginBridgeProcessor.cpp — routes audio through shared memory to helper

Browse files
Source/Plugin/PluginBridgeProcessor.cpp ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "PluginBridgeProcessor.h"
2
+ #include "PluginBridgeEditor.h"
3
+ #include "../Shared/IPCProtocol.h"
4
+
5
+ #include "json.hpp"
6
+ using json = nlohmann::json;
7
+
8
+ PluginBridgeProcessor::PluginBridgeProcessor()
9
+ : AudioProcessor(BusesProperties()
10
+ .withInput("Input", juce::AudioChannelSet::stereo(), true)
11
+ .withOutput("Output", juce::AudioChannelSet::stereo(), true)),
12
+ mcpServer(PluginBridgeConstants::kDefaultMcpPort, helper)
13
+ {
14
+ // Set up crash callback
15
+ helper.onHelperCrashed = [this]()
16
+ {
17
+ pluginLoaded = false;
18
+ pluginName.clear();
19
+ pluginParamCount = 0;
20
+ DBG("PluginBridgeProcessor: Helper crashed — plugin unloaded");
21
+ };
22
+
23
+ mcpServer.start();
24
+ }
25
+
26
+ PluginBridgeProcessor::~PluginBridgeProcessor()
27
+ {
28
+ mcpServer.stop();
29
+ helper.killHelper();
30
+ }
31
+
32
+ void PluginBridgeProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
33
+ {
34
+ currentSampleRate = sampleRate;
35
+ currentBlockSize = samplesPerBlock;
36
+
37
+ // If helper is connected, configure it
38
+ if (helper.isHelperRunning())
39
+ {
40
+ json cmd = {{"cmd", "configure"},
41
+ {"sampleRate", sampleRate},
42
+ {"blockSize", samplesPerBlock},
43
+ {"channels", 2}};
44
+ helper.sendCommand(cmd.dump());
45
+
46
+ // Update shared memory header
47
+ auto* header = helper.getSharedAudio().getHeader();
48
+ if (header)
49
+ {
50
+ header->sampleRate.store((uint32_t)sampleRate);
51
+ header->blockSize.store((uint32_t)samplesPerBlock);
52
+ header->numChannels.store(2);
53
+ }
54
+ }
55
+ }
56
+
57
+ void PluginBridgeProcessor::releaseResources()
58
+ {
59
+ }
60
+
61
+ void PluginBridgeProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer&)
62
+ {
63
+ juce::ScopedNoDenormals noDenormals;
64
+
65
+ auto totalNumInputChannels = getTotalNumInputChannels();
66
+ auto totalNumOutputChannels = getTotalNumOutputChannels();
67
+ for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
68
+ buffer.clear(i, 0, buffer.getNumSamples());
69
+
70
+ // If no plugin loaded or helper not running, pass through
71
+ if (!pluginLoaded || !helper.isHelperRunning())
72
+ return;
73
+
74
+ auto& shm = helper.getSharedAudio();
75
+ if (!shm.isValid()) return;
76
+
77
+ int numChannels = buffer.getNumChannels();
78
+ int numSamples = buffer.getNumSamples();
79
+
80
+ // Write input to shared memory
81
+ const float* inputPtrs[2] = {
82
+ buffer.getReadPointer(0),
83
+ numChannels > 1 ? buffer.getReadPointer(1) : buffer.getReadPointer(0)
84
+ };
85
+ shm.writeInput(inputPtrs, numChannels, numSamples);
86
+
87
+ // Update header
88
+ auto* header = shm.getHeader();
89
+ header->blockSize.store((uint32_t)numSamples);
90
+ header->numChannels.store((uint32_t)numChannels);
91
+ header->state.store(kStateInputReady);
92
+
93
+ // Signal helper: input is ready
94
+ helper.getInputSem().signal();
95
+
96
+ // Wait for helper to finish processing (with timeout)
97
+ if (helper.getOutputSem().wait(IPC::kProcessBlockTimeoutMs))
98
+ {
99
+ // Read output from shared memory
100
+ float* outputPtrs[2] = {
101
+ buffer.getWritePointer(0),
102
+ numChannels > 1 ? buffer.getWritePointer(1) : buffer.getWritePointer(0)
103
+ };
104
+ shm.readOutput(outputPtrs, numChannels, numSamples);
105
+ header->state.store(kStateIdle);
106
+ }
107
+ else
108
+ {
109
+ // Timeout — helper might be stuck or crashed. Pass through silence.
110
+ // (Don't pass through input — if plugin was supposed to process, silence is safer)
111
+ }
112
+ }
113
+
114
+ juce::AudioProcessorEditor* PluginBridgeProcessor::createEditor()
115
+ {
116
+ return new PluginBridgeEditor(*this);
117
+ }
118
+
119
+ void PluginBridgeProcessor::getStateInformation(juce::MemoryBlock& destData)
120
+ {
121
+ juce::ValueTree state("PluginBridgeState");
122
+ state.setProperty("pluginPath", pluginPath, nullptr);
123
+
124
+ // Get plugin state from helper
125
+ if (pluginLoaded && helper.isHelperRunning())
126
+ {
127
+ auto response = helper.sendCommand("{\"cmd\":\"get_state\"}");
128
+ try {
129
+ auto j = json::parse(response);
130
+ if (j.value("ok", false))
131
+ state.setProperty("pluginState", juce::String(j["state"].get<std::string>()), nullptr);
132
+ } catch (...) {}
133
+ }
134
+
135
+ juce::MemoryOutputStream stream(destData, false);
136
+ state.writeToStream(stream);
137
+ }
138
+
139
+ void PluginBridgeProcessor::setStateInformation(const void* data, int sizeInBytes)
140
+ {
141
+ auto state = juce::ValueTree::readFromData(data, (size_t)sizeInBytes);
142
+ if (state.isValid())
143
+ {
144
+ auto path = state.getProperty("pluginPath").toString();
145
+ if (path.isNotEmpty())
146
+ {
147
+ loadPlugin(path);
148
+
149
+ // Restore plugin state after loading
150
+ auto pluginState = state.getProperty("pluginState").toString();
151
+ if (pluginState.isNotEmpty() && helper.isHelperRunning())
152
+ {
153
+ json cmd = {{"cmd", "set_state"}, {"state", pluginState.toStdString()}};
154
+ helper.sendCommand(cmd.dump());
155
+ }
156
+ }
157
+ }
158
+ }
159
+
160
+ bool PluginBridgeProcessor::isMcpServerRunning() const
161
+ {
162
+ return mcpServer.isRunning();
163
+ }
164
+
165
+ int PluginBridgeProcessor::getMcpServerPort() const
166
+ {
167
+ return mcpServer.getPort();
168
+ }
169
+
170
+ void PluginBridgeProcessor::loadPlugin(const juce::String& path)
171
+ {
172
+ // Spawn helper if not running
173
+ if (!helper.isHelperRunning())
174
+ {
175
+ if (!helper.spawnHelper())
176
+ {
177
+ DBG("PluginBridgeProcessor: Failed to spawn helper");
178
+ return;
179
+ }
180
+
181
+ // Configure helper with current audio settings
182
+ json configCmd = {{"cmd", "configure"},
183
+ {"sampleRate", currentSampleRate},
184
+ {"blockSize", currentBlockSize},
185
+ {"channels", 2}};
186
+ helper.sendCommand(configCmd.dump());
187
+ }
188
+
189
+ // Send load command
190
+ json loadCmd = {{"cmd", "load"},
191
+ {"path", path.toStdString()},
192
+ {"sampleRate", currentSampleRate},
193
+ {"blockSize", currentBlockSize}};
194
+
195
+ auto response = helper.sendCommand(loadCmd.dump());
196
+
197
+ try
198
+ {
199
+ auto j = json::parse(response);
200
+ if (j.value("ok", false))
201
+ {
202
+ pluginLoaded = true;
203
+ pluginName = juce::String(j["name"].get<std::string>());
204
+ pluginParamCount = j.value("params", 0);
205
+ pluginPath = path;
206
+ DBG("PluginBridgeProcessor: Loaded " + pluginName + " (" + juce::String(pluginParamCount) + " params)");
207
+ }
208
+ else
209
+ {
210
+ pluginLoaded = false;
211
+ DBG("PluginBridgeProcessor: Failed to load: " + juce::String(j.value("error", "unknown")));
212
+ }
213
+ }
214
+ catch (...)
215
+ {
216
+ pluginLoaded = false;
217
+ }
218
+ }
219
+
220
+ void PluginBridgeProcessor::unloadPlugin()
221
+ {
222
+ if (helper.isHelperRunning())
223
+ helper.sendCommand("{\"cmd\":\"unload\"}");
224
+
225
+ pluginLoaded = false;
226
+ pluginName.clear();
227
+ pluginPath.clear();
228
+ pluginParamCount = 0;
229
+ }
230
+
231
+ void PluginBridgeProcessor::showPluginGui()
232
+ {
233
+ if (helper.isHelperRunning())
234
+ helper.sendCommand("{\"cmd\":\"show_gui\"}");
235
+ }
236
+
237
+ juce::Array<juce::File> PluginBridgeProcessor::getInstalledPluginFiles() const
238
+ {
239
+ juce::Array<juce::File> results;
240
+
241
+ juce::Array<juce::File> dirs = {
242
+ juce::File("/Library/Audio/Plug-Ins/VST3"),
243
+ juce::File::getSpecialLocation(juce::File::userHomeDirectory)
244
+ .getChildFile("Library/Audio/Plug-Ins/VST3"),
245
+ juce::File("/Library/Audio/Plug-Ins/Components"),
246
+ juce::File::getSpecialLocation(juce::File::userHomeDirectory)
247
+ .getChildFile("Library/Audio/Plug-Ins/Components")
248
+ };
249
+
250
+ for (auto& dir : dirs)
251
+ {
252
+ if (dir.isDirectory())
253
+ {
254
+ auto vst3s = dir.findChildFiles(juce::File::findDirectories, false, "*.vst3");
255
+ auto aus = dir.findChildFiles(juce::File::findDirectories, false, "*.component");
256
+ results.addArray(vst3s);
257
+ results.addArray(aus);
258
+ }
259
+ }
260
+
261
+ results.sort();
262
+ return results;
263
+ }
264
+
265
+ juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
266
+ {
267
+ return new PluginBridgeProcessor();
268
+ }