RAM2118 commited on
Commit
3e55272
·
verified ·
1 Parent(s): 590be7d

Upload Source/Plugin/PluginBridgeProcessor.cpp

Browse files
Files changed (1) hide show
  1. Source/Plugin/PluginBridgeProcessor.cpp +148 -296
Source/Plugin/PluginBridgeProcessor.cpp CHANGED
@@ -1,13 +1,6 @@
1
  #include "PluginBridgeProcessor.h"
2
  #include "PluginBridgeEditor.h"
3
- #include "McpServer.h"
4
-
5
- #include <spawn.h>
6
- #include <sys/wait.h>
7
- #include <unistd.h>
8
- #include <signal.h>
9
-
10
- extern char** environ;
11
 
12
  #include "json.hpp"
13
  using json = nlohmann::json;
@@ -15,23 +8,45 @@ using json = nlohmann::json;
15
  PluginBridgeProcessor::PluginBridgeProcessor()
16
  : AudioProcessor(BusesProperties()
17
  .withInput("Input", juce::AudioChannelSet::stereo(), true)
18
- .withOutput("Output", juce::AudioChannelSet::stereo(), true))
 
19
  {
20
- #if JUCE_PLUGINHOST_VST3
21
- formatManager.addFormat(new juce::VST3PluginFormat());
22
- #endif
23
- #if JUCE_PLUGINHOST_AU
24
- formatManager.addFormat(new juce::AudioUnitPluginFormat());
25
- #endif
26
-
27
- mcpServer = std::make_unique<McpServer>(PluginBridgeConstants::kDefaultMcpPort, *this);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  mcpServer->start();
29
  }
30
 
31
  PluginBridgeProcessor::~PluginBridgeProcessor()
32
  {
33
  mcpServer->stop();
34
- unloadPlugin();
35
  }
36
 
37
  void PluginBridgeProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
@@ -39,22 +54,27 @@ void PluginBridgeProcessor::prepareToPlay(double sampleRate, int samplesPerBlock
39
  currentSampleRate = sampleRate;
40
  currentBlockSize = samplesPerBlock;
41
 
42
- juce::ScopedLock sl(pluginLock);
43
- if (hostedPlugin)
44
  {
45
- hostedPlugin->setRateAndBufferSizeDetails(sampleRate, samplesPerBlock);
46
- hostedPlugin->prepareToPlay(sampleRate, samplesPerBlock);
 
 
 
 
 
 
 
 
 
 
 
47
  }
48
  }
49
 
50
- void PluginBridgeProcessor::releaseResources()
51
- {
52
- juce::ScopedLock sl(pluginLock);
53
- if (hostedPlugin)
54
- hostedPlugin->releaseResources();
55
- }
56
 
57
- void PluginBridgeProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midi)
58
  {
59
  juce::ScopedNoDenormals noDenormals;
60
 
@@ -63,113 +83,48 @@ void PluginBridgeProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce:
63
  for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
64
  buffer.clear(i, 0, buffer.getNumSamples());
65
 
66
- juce::ScopedLock sl(pluginLock);
67
- if (hostedPlugin)
68
- hostedPlugin->processBlock(buffer, midi);
69
- }
70
-
71
- juce::AudioProcessorEditor* PluginBridgeProcessor::createEditor()
72
- {
73
- return new PluginBridgeEditor(*this);
74
- }
75
-
76
- // --- Helper Test Load ---
77
 
78
- static juce::File findHelperBinary()
79
- {
80
- // Helper .app is bundled at: PluginBridge.vst3/Contents/Resources/PluginBridgeHelper.app
81
- // The actual executable is at: PluginBridgeHelper.app/Contents/MacOS/PluginBridgeHelper
82
- auto resourcesDir = juce::File::getSpecialLocation(juce::File::currentExecutableFile)
83
- .getParentDirectory() // MacOS/
84
- .getParentDirectory() // Contents/
85
- .getChildFile("Resources");
86
-
87
- auto helperApp = resourcesDir.getChildFile("PluginBridgeHelper.app");
88
- auto helperExe = helperApp.getChildFile("Contents").getChildFile("MacOS").getChildFile("PluginBridgeHelper");
89
-
90
- if (helperExe.exists())
91
- return helperExe;
92
-
93
- // Fallback: bare binary (old layout)
94
- auto bareBinary = resourcesDir.getChildFile("PluginBridgeHelper");
95
- if (bareBinary.exists())
96
- return bareBinary;
97
-
98
- DBG("findHelperBinary: NOT FOUND. Looked at: " + helperExe.getFullPathName());
99
- return {};
100
- }
101
 
102
- PluginBridgeProcessor::TestResult PluginBridgeProcessor::testPluginSafety(const juce::String& path)
103
- {
104
- if (blocklist.isBlocked(path))
105
- return TestResult::Unsafe;
106
 
107
- auto helperBinary = findHelperBinary();
108
- if (!helperBinary.exists())
109
- {
110
- DBG("testPluginSafety: Helper not found, skipping test (assuming safe)");
111
- return TestResult::Safe;
112
- }
113
-
114
- auto helperPathStr = helperBinary.getFullPathName().toStdString();
115
- auto pluginPathStr = path.toStdString();
116
-
117
- pid_t pid;
118
- char* argv[] = {
119
- (char*)helperPathStr.c_str(),
120
- (char*)"--test",
121
- (char*)pluginPathStr.c_str(),
122
- nullptr
123
  };
 
124
 
125
- int spawnResult = posix_spawn(&pid, helperPathStr.c_str(), nullptr, nullptr, argv, environ);
126
- if (spawnResult != 0)
127
- {
128
- DBG("testPluginSafety: Failed to spawn helper: " + juce::String(strerror(spawnResult)));
129
- return TestResult::Safe;
130
- }
131
-
132
- // Wait for helper to exit (max 10 seconds)
133
- int status = 0;
134
- int waitedMs = 0;
135
- const int timeoutMs = 10000;
136
-
137
- while (waitedMs < timeoutMs)
138
- {
139
- int waitResult = waitpid(pid, &status, WNOHANG);
140
- if (waitResult == pid)
141
- break;
142
- if (waitResult < 0)
143
- break;
144
-
145
- usleep(50000); // 50ms
146
- waitedMs += 50;
147
- }
148
 
149
- if (waitedMs >= timeoutMs)
150
- {
151
- kill(pid, SIGKILL);
152
- waitpid(pid, &status, 0);
153
- DBG("testPluginSafety: Timed out for " + path);
154
- return TestResult::Unsafe;
155
- }
156
 
157
- if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
158
  {
159
- DBG("testPluginSafety: SAFE — " + path);
160
- return TestResult::Safe;
 
 
 
 
161
  }
 
162
 
163
- DBG("testPluginSafety: UNSAFE — " + path + " (exit: " + juce::String(WEXITSTATUS(status)) + ")");
164
- return TestResult::Unsafe;
 
165
  }
166
 
167
  // --- Plugin Loading ---
168
 
169
  void PluginBridgeProcessor::loadPlugin(const juce::String& path)
170
  {
171
- unloadPlugin();
172
-
173
  if (blocklist.isBlocked(path))
174
  {
175
  if (onLoadFailed)
@@ -177,192 +132,90 @@ void PluginBridgeProcessor::loadPlugin(const juce::String& path)
177
  return;
178
  }
179
 
180
- juce::File pluginFile(path);
181
- if (!pluginFile.exists())
182
  {
183
- if (onLoadFailed)
184
- onLoadFailed("Plugin file not found");
185
- return;
186
- }
 
 
 
187
 
188
- // Test safety first
189
- auto testResult = testPluginSafety(path);
190
- if (testResult == TestResult::Unsafe)
191
- {
192
- blocklist.addBlocked(path);
193
- if (onLoadFailed)
194
- onLoadFailed("Not compatible with PluginBridge");
195
- return;
196
  }
197
 
198
- // Safe — load in-process
199
- juce::OwnedArray<juce::PluginDescription> typesFound;
 
 
200
 
201
- for (int i = 0; i < formatManager.getNumFormats(); ++i)
202
- {
203
- auto* format = formatManager.getFormat(i);
204
- bool isVST3 = pluginFile.getFileExtension() == ".vst3";
205
- bool isAU = pluginFile.getFileExtension() == ".component";
206
-
207
- if (isVST3 && format->getName() != "VST3") continue;
208
- if (isAU && format->getName() != "AudioUnit") continue;
209
-
210
- format->findAllTypesForFile(typesFound, pluginFile.getFullPathName());
211
- if (typesFound.size() > 0)
212
- break;
213
- }
214
 
215
- if (typesFound.isEmpty())
216
  {
217
- if (onLoadFailed)
218
- onLoadFailed("Plugin format not recognized");
219
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  }
221
-
222
- juce::String errorMessage;
223
- auto instance = formatManager.createPluginInstance(
224
- *typesFound[0], currentSampleRate, currentBlockSize, errorMessage);
225
-
226
- if (instance == nullptr)
227
  {
228
- DBG("PluginBridgeProcessor: Failed to load: " + errorMessage);
229
  blocklist.addBlocked(path);
230
  if (onLoadFailed)
231
- onLoadFailed("Failed to load: " + errorMessage);
232
- return;
233
- }
234
-
235
- instance->enableAllBuses();
236
-
237
- auto layout = juce::AudioProcessor::BusesLayout();
238
- layout.inputBuses.add(juce::AudioChannelSet::stereo());
239
- layout.outputBuses.add(juce::AudioChannelSet::stereo());
240
-
241
- if (instance->checkBusesLayoutSupported(layout))
242
- instance->setBusesLayout(layout);
243
-
244
- instance->setRateAndBufferSizeDetails(currentSampleRate, currentBlockSize);
245
- instance->prepareToPlay(currentSampleRate, currentBlockSize);
246
-
247
- {
248
- juce::ScopedLock sl(pluginLock);
249
- hostedPlugin = std::move(instance);
250
  }
251
-
252
- pluginPath = path;
253
- DBG("PluginBridgeProcessor: Loaded " + getLoadedPluginName() +
254
- " (" + juce::String(getLoadedPluginParamCount()) + " params)");
255
-
256
- if (onPluginLoaded)
257
- onPluginLoaded();
258
  }
259
 
260
  void PluginBridgeProcessor::unloadPlugin()
261
  {
262
- {
263
- juce::ScopedLock sl(pluginLock);
264
- if (hostedPlugin)
265
- {
266
- hostedPlugin->releaseResources();
267
- hostedPlugin.reset();
268
- }
269
- }
270
- pluginPath.clear();
271
- if (onPluginUnloaded)
272
- onPluginUnloaded();
273
- }
274
-
275
- juce::String PluginBridgeProcessor::getLoadedPluginName() const
276
- {
277
- juce::ScopedLock sl(pluginLock);
278
- return hostedPlugin ? hostedPlugin->getName() : juce::String();
279
- }
280
-
281
- int PluginBridgeProcessor::getLoadedPluginParamCount() const
282
- {
283
- juce::ScopedLock sl(pluginLock);
284
- return hostedPlugin ? hostedPlugin->getParameters().size() : 0;
285
- }
286
-
287
- // --- Parameters ---
288
-
289
- std::vector<PluginBridgeProcessor::ParamInfo> PluginBridgeProcessor::searchParams(const juce::String& keyword) const
290
- {
291
- std::vector<ParamInfo> results;
292
- juce::ScopedLock sl(pluginLock);
293
- if (!hostedPlugin) return results;
294
-
295
- auto& params = hostedPlugin->getParameters();
296
- auto keywordLower = keyword.toLowerCase();
297
-
298
- for (int i = 0; i < params.size(); ++i)
299
- {
300
- auto* param = params[i];
301
- auto name = param->getName(128);
302
-
303
- if (keyword.isEmpty() || name.toLowerCase().contains(keywordLower))
304
- {
305
- ParamInfo info;
306
- info.index = i;
307
- info.name = name;
308
- info.value = param->getValue();
309
- info.displayText = param->getText(param->getValue(), 64);
310
-
311
- if (auto* hosted = dynamic_cast<juce::AudioPluginInstance::HostedParameter*>(param))
312
- info.id = hosted->getParameterID();
313
- else
314
- info.id = juce::String(i);
315
-
316
- results.push_back(info);
317
- if (results.size() >= PluginBridgeConstants::kMaxParamResults)
318
- break;
319
- }
320
- }
321
- return results;
322
- }
323
 
324
- std::vector<std::pair<int, float>> PluginBridgeProcessor::getParams(const std::vector<int>& ids) const
325
- {
326
- std::vector<std::pair<int, float>> results;
327
- juce::ScopedLock sl(pluginLock);
328
- if (!hostedPlugin) return results;
 
 
329
 
330
- auto& params = hostedPlugin->getParameters();
331
- for (int idx : ids)
332
- {
333
- if (idx >= 0 && idx < params.size())
334
- results.push_back({idx, params[idx]->getValue()});
335
- }
336
- return results;
337
  }
338
 
339
- bool PluginBridgeProcessor::setParams(const std::map<int, float>& values)
340
  {
341
- juce::ScopedLock sl(pluginLock);
342
- if (!hostedPlugin) return false;
343
-
344
- auto& params = hostedPlugin->getParameters();
345
- for (auto& [idx, val] : values)
346
- {
347
- if (idx >= 0 && idx < params.size())
348
- {
349
- auto* param = params[idx];
350
- param->beginChangeGesture();
351
- param->setValueNotifyingHost(juce::jlimit(0.0f, 1.0f, val));
352
- param->endChangeGesture();
353
- }
354
- }
355
- return true;
356
  }
357
 
358
- // --- Hosted Plugin Editor ---
359
-
360
- juce::AudioProcessorEditor* PluginBridgeProcessor::createHostedPluginEditor()
361
  {
362
- juce::ScopedLock sl(pluginLock);
363
- if (hostedPlugin && hostedPlugin->hasEditor())
364
- return hostedPlugin->createEditor();
365
- return nullptr;
366
  }
367
 
368
  // --- Plugin List ---
@@ -407,12 +260,14 @@ void PluginBridgeProcessor::getStateInformation(juce::MemoryBlock& destData)
407
  juce::ValueTree state("PluginBridgeState");
408
  state.setProperty("pluginPath", pluginPath, nullptr);
409
 
410
- juce::ScopedLock sl(pluginLock);
411
- if (hostedPlugin)
412
  {
413
- juce::MemoryBlock pluginState;
414
- hostedPlugin->getStateInformation(pluginState);
415
- state.setProperty("pluginState", pluginState.toBase64Encoding(), nullptr);
 
 
 
416
  }
417
 
418
  juce::MemoryOutputStream stream(destData, false);
@@ -429,21 +284,18 @@ void PluginBridgeProcessor::setStateInformation(const void* data, int sizeInByte
429
  {
430
  loadPlugin(path);
431
 
432
- auto pluginStateB64 = state.getProperty("pluginState").toString();
433
- if (pluginStateB64.isNotEmpty())
434
  {
435
- juce::MemoryBlock pluginState;
436
- if (pluginState.fromBase64Encoding(pluginStateB64))
437
- {
438
- juce::ScopedLock sl(pluginLock);
439
- if (hostedPlugin)
440
- hostedPlugin->setStateInformation(pluginState.getData(), (int)pluginState.getSize());
441
- }
442
  }
443
  }
444
  }
445
  }
446
 
 
 
447
  bool PluginBridgeProcessor::isMcpServerRunning() const
448
  {
449
  return mcpServer && mcpServer->isRunning();
 
1
  #include "PluginBridgeProcessor.h"
2
  #include "PluginBridgeEditor.h"
3
+ #include "IPCProtocol.h"
 
 
 
 
 
 
 
4
 
5
  #include "json.hpp"
6
  using json = nlohmann::json;
 
8
  PluginBridgeProcessor::PluginBridgeProcessor()
9
  : AudioProcessor(BusesProperties()
10
  .withInput("Input", juce::AudioChannelSet::stereo(), true)
11
+ .withOutput("Output", juce::AudioChannelSet::stereo(), true)),
12
+ mcpServer(std::make_unique<McpServer>(PluginBridgeConstants::kDefaultMcpPort, helper))
13
  {
14
+ helper.onHelperCrashed = [this]()
15
+ {
16
+ pluginLoaded = false;
17
+ pluginName.clear();
18
+ pluginParamCount = 0;
19
+ remoteLayerPortName.clear();
20
+ remoteGuiWidth = 0;
21
+ remoteGuiHeight = 0;
22
+ DBG("PluginBridgeProcessor: Helper crashed");
23
+ };
24
+
25
+ // When helper sends "ready" notification with layer port info
26
+ helper.onGuiReady = [this](const juce::String& portName, int width, int height)
27
+ {
28
+ remoteLayerPortName = portName;
29
+ remoteGuiWidth = width;
30
+ remoteGuiHeight = height;
31
+ if (onGuiReady)
32
+ juce::MessageManager::callAsync([this]() { if (onGuiReady) onGuiReady(); });
33
+ };
34
+
35
+ helper.onGuiResized = [this](int width, int height)
36
+ {
37
+ remoteGuiWidth = width;
38
+ remoteGuiHeight = height;
39
+ if (onGuiResized)
40
+ juce::MessageManager::callAsync([this, width, height]() { if (onGuiResized) onGuiResized(width, height); });
41
+ };
42
+
43
  mcpServer->start();
44
  }
45
 
46
  PluginBridgeProcessor::~PluginBridgeProcessor()
47
  {
48
  mcpServer->stop();
49
+ helper.killHelper();
50
  }
51
 
52
  void PluginBridgeProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
 
54
  currentSampleRate = sampleRate;
55
  currentBlockSize = samplesPerBlock;
56
 
57
+ if (helper.isHelperRunning())
 
58
  {
59
+ json cmd = {{"cmd", "configure"},
60
+ {"sampleRate", sampleRate},
61
+ {"blockSize", samplesPerBlock},
62
+ {"channels", 2}};
63
+ helper.sendCommand(cmd.dump());
64
+
65
+ auto* header = helper.getSharedAudio().getHeader();
66
+ if (header)
67
+ {
68
+ header->sampleRate.store((uint32_t)sampleRate);
69
+ header->blockSize.store((uint32_t)samplesPerBlock);
70
+ header->numChannels.store(2);
71
+ }
72
  }
73
  }
74
 
75
+ void PluginBridgeProcessor::releaseResources() {}
 
 
 
 
 
76
 
77
+ void PluginBridgeProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer&)
78
  {
79
  juce::ScopedNoDenormals noDenormals;
80
 
 
83
  for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
84
  buffer.clear(i, 0, buffer.getNumSamples());
85
 
86
+ if (!pluginLoaded || !helper.isHelperRunning())
87
+ return;
 
 
 
 
 
 
 
 
 
88
 
89
+ auto& shm = helper.getSharedAudio();
90
+ if (!shm.isValid()) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
+ int numChannels = buffer.getNumChannels();
93
+ int numSamples = buffer.getNumSamples();
 
 
94
 
95
+ const float* inputPtrs[2] = {
96
+ buffer.getReadPointer(0),
97
+ numChannels > 1 ? buffer.getReadPointer(1) : buffer.getReadPointer(0)
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  };
99
+ shm.writeInput(inputPtrs, numChannels, numSamples);
100
 
101
+ auto* header = shm.getHeader();
102
+ header->blockSize.store((uint32_t)numSamples);
103
+ header->numChannels.store((uint32_t)numChannels);
104
+ header->state.store(kStateInputReady);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
+ helper.getInputSem().signal();
 
 
 
 
 
 
107
 
108
+ if (helper.getOutputSem().wait(IPC::kProcessBlockTimeoutMs))
109
  {
110
+ float* outputPtrs[2] = {
111
+ buffer.getWritePointer(0),
112
+ numChannels > 1 ? buffer.getWritePointer(1) : buffer.getWritePointer(0)
113
+ };
114
+ shm.readOutput(outputPtrs, numChannels, numSamples);
115
+ header->state.store(kStateIdle);
116
  }
117
+ }
118
 
119
+ juce::AudioProcessorEditor* PluginBridgeProcessor::createEditor()
120
+ {
121
+ return new PluginBridgeEditor(*this);
122
  }
123
 
124
  // --- Plugin Loading ---
125
 
126
  void PluginBridgeProcessor::loadPlugin(const juce::String& path)
127
  {
 
 
128
  if (blocklist.isBlocked(path))
129
  {
130
  if (onLoadFailed)
 
132
  return;
133
  }
134
 
135
+ if (!helper.isHelperRunning())
 
136
  {
137
+ if (!helper.spawnHelper())
138
+ {
139
+ DBG("PluginBridgeProcessor: Failed to spawn helper");
140
+ if (onLoadFailed)
141
+ onLoadFailed("Failed to start helper process");
142
+ return;
143
+ }
144
 
145
+ json configCmd = {{"cmd", "configure"},
146
+ {"sampleRate", currentSampleRate},
147
+ {"blockSize", currentBlockSize},
148
+ {"channels", 2}};
149
+ helper.sendCommand(configCmd.dump());
 
 
 
150
  }
151
 
152
+ json loadCmd = {{"cmd", "load"},
153
+ {"path", path.toStdString()},
154
+ {"sampleRate", currentSampleRate},
155
+ {"blockSize", currentBlockSize}};
156
 
157
+ auto response = helper.sendCommand(loadCmd.dump());
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
+ try
160
  {
161
+ auto j = json::parse(response);
162
+ if (j.value("ok", false))
163
+ {
164
+ pluginLoaded = true;
165
+ pluginName = juce::String(j["name"].get<std::string>());
166
+ pluginParamCount = j.value("params", 0);
167
+ pluginPath = path;
168
+ DBG("PluginBridgeProcessor: Loaded " + pluginName);
169
+
170
+ if (onPluginLoaded)
171
+ juce::MessageManager::callAsync([this]() { if (onPluginLoaded) onPluginLoaded(); });
172
+ }
173
+ else
174
+ {
175
+ pluginLoaded = false;
176
+ auto error = juce::String(j.value("error", "unknown"));
177
+ // If plugin failed in helper, blocklist it
178
+ blocklist.addBlocked(path);
179
+ if (onLoadFailed)
180
+ onLoadFailed("Not compatible: " + error);
181
+ }
182
  }
183
+ catch (...)
 
 
 
 
 
184
  {
185
+ pluginLoaded = false;
186
  blocklist.addBlocked(path);
187
  if (onLoadFailed)
188
+ onLoadFailed("Helper communication error");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  }
 
 
 
 
 
 
 
190
  }
191
 
192
  void PluginBridgeProcessor::unloadPlugin()
193
  {
194
+ if (helper.isHelperRunning())
195
+ helper.sendCommand("{\"cmd\":\"unload\"}");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
197
+ pluginLoaded = false;
198
+ pluginName.clear();
199
+ pluginPath.clear();
200
+ pluginParamCount = 0;
201
+ remoteLayerPortName.clear();
202
+ remoteGuiWidth = 0;
203
+ remoteGuiHeight = 0;
204
 
205
+ if (onPluginUnloaded)
206
+ juce::MessageManager::callAsync([this]() { if (onPluginUnloaded) onPluginUnloaded(); });
 
 
 
 
 
207
  }
208
 
209
+ void PluginBridgeProcessor::showPluginGui()
210
  {
211
+ if (helper.isHelperRunning())
212
+ helper.sendCommand("{\"cmd\":\"show_gui\"}");
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  }
214
 
215
+ void PluginBridgeProcessor::hidePluginGui()
 
 
216
  {
217
+ if (helper.isHelperRunning())
218
+ helper.sendCommand("{\"cmd\":\"hide_gui\"}");
 
 
219
  }
220
 
221
  // --- Plugin List ---
 
260
  juce::ValueTree state("PluginBridgeState");
261
  state.setProperty("pluginPath", pluginPath, nullptr);
262
 
263
+ if (pluginLoaded && helper.isHelperRunning())
 
264
  {
265
+ auto response = helper.sendCommand("{\"cmd\":\"get_state\"}");
266
+ try {
267
+ auto j = json::parse(response);
268
+ if (j.value("ok", false))
269
+ state.setProperty("pluginState", juce::String(j["state"].get<std::string>()), nullptr);
270
+ } catch (...) {}
271
  }
272
 
273
  juce::MemoryOutputStream stream(destData, false);
 
284
  {
285
  loadPlugin(path);
286
 
287
+ auto pluginState = state.getProperty("pluginState").toString();
288
+ if (pluginState.isNotEmpty() && helper.isHelperRunning())
289
  {
290
+ json cmd = {{"cmd", "set_state"}, {"state", pluginState.toStdString()}};
291
+ helper.sendCommand(cmd.dump());
 
 
 
 
 
292
  }
293
  }
294
  }
295
  }
296
 
297
+ // --- MCP ---
298
+
299
  bool PluginBridgeProcessor::isMcpServerRunning() const
300
  {
301
  return mcpServer && mcpServer->isRunning();