RAM2118 commited on
Commit
06d413f
·
verified ·
1 Parent(s): 42bb859

Upload Source/Plugin/PluginBridgeProcessor.cpp

Browse files
Files changed (1) hide show
  1. Source/Plugin/PluginBridgeProcessor.cpp +260 -136
Source/Plugin/PluginBridgeProcessor.cpp CHANGED
@@ -1,6 +1,6 @@
1
  #include "PluginBridgeProcessor.h"
2
  #include "PluginBridgeEditor.h"
3
- #include "IPCProtocol.h"
4
 
5
  #include "json.hpp"
6
  using json = nlohmann::json;
@@ -8,24 +8,25 @@ 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(PluginBridgeConstants::kDefaultMcpPort, helper)
13
  {
14
- helper.onHelperCrashed = [this]()
15
- {
16
- pluginLoaded = false;
17
- pluginName.clear();
18
- pluginParamCount = 0;
19
- DBG("PluginBridgeProcessor: Helper crashed — plugin unloaded");
20
- };
21
-
22
- mcpServer.start();
 
 
23
  }
24
 
25
  PluginBridgeProcessor::~PluginBridgeProcessor()
26
  {
27
- mcpServer.stop();
28
- helper.killHelper();
29
  }
30
 
31
  void PluginBridgeProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
@@ -33,29 +34,22 @@ void PluginBridgeProcessor::prepareToPlay(double sampleRate, int samplesPerBlock
33
  currentSampleRate = sampleRate;
34
  currentBlockSize = samplesPerBlock;
35
 
36
- if (helper.isHelperRunning())
 
37
  {
38
- json cmd = {{"cmd", "configure"},
39
- {"sampleRate", sampleRate},
40
- {"blockSize", samplesPerBlock},
41
- {"channels", 2}};
42
- helper.sendCommand(cmd.dump());
43
-
44
- auto* header = helper.getSharedAudio().getHeader();
45
- if (header)
46
- {
47
- header->sampleRate.store((uint32_t)sampleRate);
48
- header->blockSize.store((uint32_t)samplesPerBlock);
49
- header->numChannels.store(2);
50
- }
51
  }
52
  }
53
 
54
  void PluginBridgeProcessor::releaseResources()
55
  {
 
 
 
56
  }
57
 
58
- void PluginBridgeProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer&)
59
  {
60
  juce::ScopedNoDenormals noDenormals;
61
 
@@ -64,163 +58,217 @@ void PluginBridgeProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce:
64
  for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
65
  buffer.clear(i, 0, buffer.getNumSamples());
66
 
67
- if (!pluginLoaded || !helper.isHelperRunning())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  return;
 
69
 
70
- auto& shm = helper.getSharedAudio();
71
- if (!shm.isValid()) return;
 
 
 
 
 
72
 
73
- int numChannels = buffer.getNumChannels();
74
- int numSamples = buffer.getNumSamples();
75
 
76
- const float* inputPtrs[2] = {
77
- buffer.getReadPointer(0),
78
- numChannels > 1 ? buffer.getReadPointer(1) : buffer.getReadPointer(0)
79
- };
80
- shm.writeInput(inputPtrs, numChannels, numSamples);
81
 
82
- auto* header = shm.getHeader();
83
- header->blockSize.store((uint32_t)numSamples);
84
- header->numChannels.store((uint32_t)numChannels);
85
- header->state.store(kStateInputReady);
86
 
87
- helper.getInputSem().signal();
 
 
 
88
 
89
- if (helper.getOutputSem().wait(IPC::kProcessBlockTimeoutMs))
90
  {
91
- float* outputPtrs[2] = {
92
- buffer.getWritePointer(0),
93
- numChannels > 1 ? buffer.getWritePointer(1) : buffer.getWritePointer(0)
94
- };
95
- shm.readOutput(outputPtrs, numChannels, numSamples);
96
- header->state.store(kStateIdle);
97
  }
98
- }
99
 
100
- juce::AudioProcessorEditor* PluginBridgeProcessor::createEditor()
101
- {
102
- return new PluginBridgeEditor(*this);
103
- }
104
 
105
- void PluginBridgeProcessor::getStateInformation(juce::MemoryBlock& destData)
106
- {
107
- juce::ValueTree state("PluginBridgeState");
108
- state.setProperty("pluginPath", pluginPath, nullptr);
 
 
 
 
 
 
 
 
109
 
110
- if (pluginLoaded && helper.isHelperRunning())
 
 
 
 
 
 
 
 
 
 
111
  {
112
- auto response = helper.sendCommand("{\"cmd\":\"get_state\"}");
113
- try {
114
- auto j = json::parse(response);
115
- if (j.value("ok", false))
116
- state.setProperty("pluginState", juce::String(j["state"].get<std::string>()), nullptr);
117
- } catch (...) {}
118
  }
119
 
120
- juce::MemoryOutputStream stream(destData, false);
121
- state.writeToStream(stream);
 
 
 
 
122
  }
123
 
124
- void PluginBridgeProcessor::setStateInformation(const void* data, int sizeInBytes)
125
  {
126
- auto state = juce::ValueTree::readFromData(data, (size_t)sizeInBytes);
127
- if (state.isValid())
128
  {
129
- auto path = state.getProperty("pluginPath").toString();
130
- if (path.isNotEmpty())
131
  {
132
- loadPlugin(path);
133
-
134
- auto pluginState = state.getProperty("pluginState").toString();
135
- if (pluginState.isNotEmpty() && helper.isHelperRunning())
136
- {
137
- json cmd = {{"cmd", "set_state"}, {"state", pluginState.toStdString()}};
138
- helper.sendCommand(cmd.dump());
139
- }
140
  }
141
  }
 
 
 
 
 
142
  }
143
 
144
- bool PluginBridgeProcessor::isMcpServerRunning() const
145
  {
146
- return mcpServer.isRunning();
 
147
  }
148
 
149
- int PluginBridgeProcessor::getMcpServerPort() const
150
  {
151
- return mcpServer.getPort();
 
152
  }
153
 
154
- void PluginBridgeProcessor::loadPlugin(const juce::String& path)
 
 
155
  {
156
- if (!helper.isHelperRunning())
 
 
 
 
 
 
 
157
  {
158
- if (!helper.spawnHelper())
 
 
 
159
  {
160
- DBG("PluginBridgeProcessor: Failed to spawn helper");
161
- return;
 
 
 
 
 
 
 
 
 
 
 
 
162
  }
163
-
164
- json configCmd = {{"cmd", "configure"},
165
- {"sampleRate", currentSampleRate},
166
- {"blockSize", currentBlockSize},
167
- {"channels", 2}};
168
- helper.sendCommand(configCmd.dump());
169
  }
170
 
171
- json loadCmd = {{"cmd", "load"},
172
- {"path", path.toStdString()},
173
- {"sampleRate", currentSampleRate},
174
- {"blockSize", currentBlockSize}};
175
 
176
- auto response = helper.sendCommand(loadCmd.dump());
 
 
 
 
177
 
178
- try
 
179
  {
180
- auto j = json::parse(response);
181
- if (j.value("ok", false))
182
- {
183
- pluginLoaded = true;
184
- pluginName = juce::String(j["name"].get<std::string>());
185
- pluginParamCount = j.value("params", 0);
186
- pluginPath = path;
187
- DBG("PluginBridgeProcessor: Loaded " + pluginName + " (" + juce::String(pluginParamCount) + " params)");
188
- }
189
- else
190
- {
191
- pluginLoaded = false;
192
- DBG("PluginBridgeProcessor: Failed to load: " + juce::String(j.value("error", "unknown")));
193
- }
194
- }
195
- catch (...)
196
- {
197
- pluginLoaded = false;
198
  }
 
199
  }
200
 
201
- void PluginBridgeProcessor::unloadPlugin()
202
  {
203
- if (helper.isHelperRunning())
204
- helper.sendCommand("{\"cmd\":\"unload\"}");
205
 
206
- pluginLoaded = false;
207
- pluginName.clear();
208
- pluginPath.clear();
209
- pluginParamCount = 0;
 
 
 
 
 
 
 
 
210
  }
211
 
212
- void PluginBridgeProcessor::showPluginGui()
213
- {
214
- if (helper.isHelperRunning())
215
- helper.sendCommand("{\"cmd\":\"show_gui\"}");
216
- }
217
 
218
- void PluginBridgeProcessor::hidePluginGui()
219
  {
220
- if (helper.isHelperRunning())
221
- helper.sendCommand("{\"cmd\":\"hide_gui\"}");
 
 
222
  }
223
 
 
 
224
  juce::Array<juce::File> PluginBridgeProcessor::getInstalledPluginFiles() const
225
  {
226
  juce::Array<juce::File> results;
@@ -249,6 +297,82 @@ juce::Array<juce::File> PluginBridgeProcessor::getInstalledPluginFiles() const
249
  return results;
250
  }
251
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
253
  {
254
  return new PluginBridgeProcessor();
 
1
  #include "PluginBridgeProcessor.h"
2
  #include "PluginBridgeEditor.h"
3
+ #include "McpServer.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
  {
13
+ // Register plugin formats
14
+ #if JUCE_PLUGINHOST_VST3
15
+ formatManager.addFormat(new juce::VST3PluginFormat());
16
+ #endif
17
+ #if JUCE_PLUGINHOST_AU
18
+ formatManager.addFormat(new juce::AudioUnitPluginFormat());
19
+ #endif
20
+
21
+ // Start MCP server
22
+ mcpServer = std::make_unique<McpServer>(PluginBridgeConstants::kDefaultMcpPort, *this);
23
+ mcpServer->start();
24
  }
25
 
26
  PluginBridgeProcessor::~PluginBridgeProcessor()
27
  {
28
+ mcpServer->stop();
29
+ unloadPlugin();
30
  }
31
 
32
  void PluginBridgeProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
 
34
  currentSampleRate = sampleRate;
35
  currentBlockSize = samplesPerBlock;
36
 
37
+ juce::ScopedLock sl(pluginLock);
38
+ if (hostedPlugin)
39
  {
40
+ hostedPlugin->setRateAndBufferSizeDetails(sampleRate, samplesPerBlock);
41
+ hostedPlugin->prepareToPlay(sampleRate, samplesPerBlock);
 
 
 
 
 
 
 
 
 
 
 
42
  }
43
  }
44
 
45
  void PluginBridgeProcessor::releaseResources()
46
  {
47
+ juce::ScopedLock sl(pluginLock);
48
+ if (hostedPlugin)
49
+ hostedPlugin->releaseResources();
50
  }
51
 
52
+ void PluginBridgeProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midi)
53
  {
54
  juce::ScopedNoDenormals noDenormals;
55
 
 
58
  for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
59
  buffer.clear(i, 0, buffer.getNumSamples());
60
 
61
+ juce::ScopedLock sl(pluginLock);
62
+ if (hostedPlugin)
63
+ hostedPlugin->processBlock(buffer, midi);
64
+ }
65
+
66
+ juce::AudioProcessorEditor* PluginBridgeProcessor::createEditor()
67
+ {
68
+ return new PluginBridgeEditor(*this);
69
+ }
70
+
71
+ // --- Plugin Loading (IN-PROCESS) ---
72
+
73
+ void PluginBridgeProcessor::loadPlugin(const juce::String& path)
74
+ {
75
+ unloadPlugin();
76
+
77
+ // Check blocklist
78
+ if (blocklist.isBlocked(path))
79
+ {
80
+ if (onLoadFailed)
81
+ onLoadFailed("Plugin is blocklisted (crashed previously)");
82
  return;
83
+ }
84
 
85
+ juce::File pluginFile(path);
86
+ if (!pluginFile.exists())
87
+ {
88
+ if (onLoadFailed)
89
+ onLoadFailed("Plugin file not found");
90
+ return;
91
+ }
92
 
93
+ // Find plugin description
94
+ juce::OwnedArray<juce::PluginDescription> typesFound;
95
 
96
+ for (int i = 0; i < formatManager.getNumFormats(); ++i)
97
+ {
98
+ auto* format = formatManager.getFormat(i);
99
+ bool isVST3 = pluginFile.getFileExtension() == ".vst3";
100
+ bool isAU = pluginFile.getFileExtension() == ".component";
101
 
102
+ if (isVST3 && format->getName() != "VST3") continue;
103
+ if (isAU && format->getName() != "AudioUnit") continue;
 
 
104
 
105
+ format->findAllTypesForFile(typesFound, pluginFile.getFullPathName());
106
+ if (typesFound.size() > 0)
107
+ break;
108
+ }
109
 
110
+ if (typesFound.isEmpty())
111
  {
112
+ if (onLoadFailed)
113
+ onLoadFailed("Plugin format not recognized");
114
+ return;
 
 
 
115
  }
 
116
 
117
+ // Create plugin instance
118
+ juce::String errorMessage;
119
+ auto instance = formatManager.createPluginInstance(
120
+ *typesFound[0], currentSampleRate, currentBlockSize, errorMessage);
121
 
122
+ if (instance == nullptr)
123
+ {
124
+ DBG("PluginBridgeProcessor: Failed to load: " + errorMessage);
125
+ // Add to blocklist if it fails
126
+ blocklist.addBlocked(path);
127
+ if (onLoadFailed)
128
+ onLoadFailed("Failed to load: " + errorMessage);
129
+ return;
130
+ }
131
+
132
+ // Configure the plugin
133
+ instance->enableAllBuses();
134
 
135
+ auto layout = juce::AudioProcessor::BusesLayout();
136
+ layout.inputBuses.add(juce::AudioChannelSet::stereo());
137
+ layout.outputBuses.add(juce::AudioChannelSet::stereo());
138
+
139
+ if (instance->checkBusesLayoutSupported(layout))
140
+ instance->setBusesLayout(layout);
141
+
142
+ instance->setRateAndBufferSizeDetails(currentSampleRate, currentBlockSize);
143
+ instance->prepareToPlay(currentSampleRate, currentBlockSize);
144
+
145
+ // Swap in
146
  {
147
+ juce::ScopedLock sl(pluginLock);
148
+ hostedPlugin = std::move(instance);
 
 
 
 
149
  }
150
 
151
+ pluginPath = path;
152
+ DBG("PluginBridgeProcessor: Loaded " + getLoadedPluginName() +
153
+ " (" + juce::String(getLoadedPluginParamCount()) + " params)");
154
+
155
+ if (onPluginLoaded)
156
+ onPluginLoaded();
157
  }
158
 
159
+ void PluginBridgeProcessor::unloadPlugin()
160
  {
 
 
161
  {
162
+ juce::ScopedLock sl(pluginLock);
163
+ if (hostedPlugin)
164
  {
165
+ hostedPlugin->releaseResources();
166
+ hostedPlugin.reset();
 
 
 
 
 
 
167
  }
168
  }
169
+
170
+ pluginPath.clear();
171
+
172
+ if (onPluginUnloaded)
173
+ onPluginUnloaded();
174
  }
175
 
176
+ juce::String PluginBridgeProcessor::getLoadedPluginName() const
177
  {
178
+ juce::ScopedLock sl(pluginLock);
179
+ return hostedPlugin ? hostedPlugin->getName() : juce::String();
180
  }
181
 
182
+ int PluginBridgeProcessor::getLoadedPluginParamCount() const
183
  {
184
+ juce::ScopedLock sl(pluginLock);
185
+ return hostedPlugin ? hostedPlugin->getParameters().size() : 0;
186
  }
187
 
188
+ // --- Parameters ---
189
+
190
+ std::vector<PluginBridgeProcessor::ParamInfo> PluginBridgeProcessor::searchParams(const juce::String& keyword) const
191
  {
192
+ std::vector<ParamInfo> results;
193
+ juce::ScopedLock sl(pluginLock);
194
+ if (!hostedPlugin) return results;
195
+
196
+ auto& params = hostedPlugin->getParameters();
197
+ auto keywordLower = keyword.toLowerCase();
198
+
199
+ for (int i = 0; i < params.size(); ++i)
200
  {
201
+ auto* param = params[i];
202
+ auto name = param->getName(128);
203
+
204
+ if (keyword.isEmpty() || name.toLowerCase().contains(keywordLower))
205
  {
206
+ ParamInfo info;
207
+ info.index = i;
208
+ info.name = name;
209
+ info.value = param->getValue();
210
+ info.displayText = param->getText(param->getValue(), 64);
211
+
212
+ if (auto* hosted = dynamic_cast<juce::AudioPluginInstance::HostedParameter*>(param))
213
+ info.id = hosted->getParameterID();
214
+ else
215
+ info.id = juce::String(i);
216
+
217
+ results.push_back(info);
218
+ if (results.size() >= PluginBridgeConstants::kMaxParamResults)
219
+ break;
220
  }
 
 
 
 
 
 
221
  }
222
 
223
+ return results;
224
+ }
 
 
225
 
226
+ std::vector<std::pair<int, float>> PluginBridgeProcessor::getParams(const std::vector<int>& ids) const
227
+ {
228
+ std::vector<std::pair<int, float>> results;
229
+ juce::ScopedLock sl(pluginLock);
230
+ if (!hostedPlugin) return results;
231
 
232
+ auto& params = hostedPlugin->getParameters();
233
+ for (int idx : ids)
234
  {
235
+ if (idx >= 0 && idx < params.size())
236
+ results.push_back({idx, params[idx]->getValue()});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  }
238
+ return results;
239
  }
240
 
241
+ bool PluginBridgeProcessor::setParams(const std::map<int, float>& values)
242
  {
243
+ juce::ScopedLock sl(pluginLock);
244
+ if (!hostedPlugin) return false;
245
 
246
+ auto& params = hostedPlugin->getParameters();
247
+ for (auto& [idx, val] : values)
248
+ {
249
+ if (idx >= 0 && idx < params.size())
250
+ {
251
+ auto* param = params[idx];
252
+ param->beginChangeGesture();
253
+ param->setValueNotifyingHost(juce::jlimit(0.0f, 1.0f, val));
254
+ param->endChangeGesture();
255
+ }
256
+ }
257
+ return true;
258
  }
259
 
260
+ // --- Hosted Plugin Editor ---
 
 
 
 
261
 
262
+ juce::AudioProcessorEditor* PluginBridgeProcessor::createHostedPluginEditor()
263
  {
264
+ juce::ScopedLock sl(pluginLock);
265
+ if (hostedPlugin && hostedPlugin->hasEditor())
266
+ return hostedPlugin->createEditor();
267
+ return nullptr;
268
  }
269
 
270
+ // --- Plugin List ---
271
+
272
  juce::Array<juce::File> PluginBridgeProcessor::getInstalledPluginFiles() const
273
  {
274
  juce::Array<juce::File> results;
 
297
  return results;
298
  }
299
 
300
+ // --- Blocklist ---
301
+
302
+ bool PluginBridgeProcessor::isBlocked(const juce::String& path) const
303
+ {
304
+ return blocklist.isBlocked(path);
305
+ }
306
+
307
+ // --- State ---
308
+
309
+ void PluginBridgeProcessor::getStateInformation(juce::MemoryBlock& destData)
310
+ {
311
+ juce::ValueTree state("PluginBridgeState");
312
+ state.setProperty("pluginPath", pluginPath, nullptr);
313
+
314
+ juce::ScopedLock sl(pluginLock);
315
+ if (hostedPlugin)
316
+ {
317
+ juce::MemoryBlock pluginState;
318
+ hostedPlugin->getStateInformation(pluginState);
319
+ state.setProperty("pluginState", pluginState.toBase64Encoding(), nullptr);
320
+ }
321
+
322
+ juce::MemoryOutputStream stream(destData, false);
323
+ state.writeToStream(stream);
324
+ }
325
+
326
+ void PluginBridgeProcessor::setStateInformation(const void* data, int sizeInBytes)
327
+ {
328
+ auto state = juce::ValueTree::readFromData(data, (size_t)sizeInBytes);
329
+ if (state.isValid())
330
+ {
331
+ auto path = state.getProperty("pluginPath").toString();
332
+ if (path.isNotEmpty())
333
+ {
334
+ loadPlugin(path);
335
+
336
+ auto pluginStateB64 = state.getProperty("pluginState").toString();
337
+ if (pluginStateB64.isNotEmpty())
338
+ {
339
+ juce::MemoryBlock pluginState;
340
+ if (pluginState.fromBase64Encoding(pluginStateB64))
341
+ {
342
+ juce::ScopedLock sl(pluginLock);
343
+ if (hostedPlugin)
344
+ hostedPlugin->setStateInformation(pluginState.getData(), (int)pluginState.getSize());
345
+ }
346
+ }
347
+ }
348
+ }
349
+ }
350
+
351
+ // --- MCP ---
352
+
353
+ bool PluginBridgeProcessor::isMcpServerRunning() const
354
+ {
355
+ return mcpServer && mcpServer->isRunning();
356
+ }
357
+
358
+ int PluginBridgeProcessor::getMcpServerPort() const
359
+ {
360
+ return mcpServer ? mcpServer->getPort() : 0;
361
+ }
362
+
363
+ // --- Test Safety (Helper-based) ---
364
+
365
+ PluginBridgeProcessor::TestResult PluginBridgeProcessor::testPluginSafety(const juce::String& path)
366
+ {
367
+ // TODO: Spawn helper to test-load plugin
368
+ // For now, all plugins are treated as safe unless blocklisted
369
+ if (blocklist.isBlocked(path))
370
+ return TestResult::Unsafe;
371
+ return TestResult::Safe;
372
+ }
373
+
374
+ // --- Entry Point ---
375
+
376
  juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
377
  {
378
  return new PluginBridgeProcessor();