RAM2118 commited on
Commit
9c7e94f
·
verified ·
1 Parent(s): df8b77e

Cleanup: Remove old in-process Source/ files (replaced by Source/Plugin/ and Source/Helper/)

Browse files
Source/AudioAnalyser.cpp DELETED
@@ -1,7 +0,0 @@
1
- #include "AudioAnalyser.h"
2
-
3
- // Phase 2: Full implementation with:
4
- // - prepareToPlay(sampleRate, blockSize)
5
- // - processBlock(buffer) — called from audio thread
6
- // - getCompactAnalysis() — thread-safe query from HTTP thread
7
- // - LUFS (Ebu128LoudnessMeter), FFT bands, stereo width, True Peak
 
 
 
 
 
 
 
 
Source/AudioAnalyser.h DELETED
@@ -1,11 +0,0 @@
1
- #pragma once
2
-
3
- // Phase 2 stub — will be implemented in Sprint 4
4
- // Real-time audio analysis: LUFS, FFT bands, stereo width, True Peak
5
-
6
- class AudioAnalyser
7
- {
8
- public:
9
- AudioAnalyser() = default;
10
- ~AudioAnalyser() = default;
11
- };
 
 
 
 
 
 
 
 
 
 
 
 
Source/HostedPluginManager.cpp DELETED
@@ -1,268 +0,0 @@
1
- #include "HostedPluginManager.h"
2
-
3
- HostedPluginManager::HostedPluginManager()
4
- {
5
- #if JUCE_PLUGINHOST_VST3
6
- formatManager.addFormat(new juce::VST3PluginFormat());
7
- #endif
8
- #if JUCE_PLUGINHOST_AU
9
- formatManager.addFormat(new juce::AudioUnitPluginFormat());
10
- #endif
11
- }
12
-
13
- HostedPluginManager::~HostedPluginManager()
14
- {
15
- unloadPlugin();
16
- }
17
-
18
- bool HostedPluginManager::validatePluginFile(const juce::File& pluginFile)
19
- {
20
- // Use a child process to test if the plugin can be loaded without crashing
21
- // The child process runs: pluginval-style check — just tries to dlopen the binary
22
- // If the child crashes, we know the plugin is unsafe to load in-process
23
-
24
- juce::ChildProcess validator;
25
-
26
- // Use macOS 'file' command on the binary inside the bundle to verify it's a valid Mach-O
27
- // This doesn't execute plugin code, just validates the binary exists and is loadable
28
- juce::File binary;
29
- if (pluginFile.getFileExtension() == ".vst3")
30
- binary = pluginFile.getChildFile("Contents/MacOS")
31
- .findChildFiles(juce::File::findFiles, false).getFirst();
32
- else
33
- binary = pluginFile.getChildFile("Contents/MacOS")
34
- .findChildFiles(juce::File::findFiles, false).getFirst();
35
-
36
- if (!binary.exists())
37
- return false;
38
-
39
- // Quick validation: check binary is valid Mach-O using 'file' command
40
- if (!validator.start("file " + binary.getFullPathName().quoted()))
41
- return false;
42
-
43
- validator.waitForProcessToFinish(2000); // 2 second timeout
44
- auto output = validator.readAllProcessOutput();
45
-
46
- // If it contains "Mach-O" it's at least a valid binary
47
- return output.contains("Mach-O");
48
- }
49
-
50
- void HostedPluginManager::loadPlugin(const juce::File& pluginFile,
51
- double sampleRate, int blockSize)
52
- {
53
- unloadPlugin();
54
-
55
- currentSampleRate = sampleRate;
56
- currentBlockSize = blockSize;
57
-
58
- // Quick sanity check — is this even a valid plugin bundle?
59
- if (!validatePluginFile(pluginFile))
60
- {
61
- DBG("PluginBridge: Invalid plugin binary: " + pluginFile.getFullPathName());
62
- lastError = "Invalid plugin binary";
63
- return;
64
- }
65
-
66
- juce::OwnedArray<juce::PluginDescription> typesFound;
67
-
68
- // Install signal handler to catch SIGABRT from misbehaving plugins
69
- // Note: this won't fully prevent crashes from plugins that call abort()
70
- // but gives us a fighting chance with ObjC exceptions
71
-
72
- for (int i = 0; i < formatManager.getNumFormats(); ++i)
73
- {
74
- auto* format = formatManager.getFormat(i);
75
-
76
- // Only try the format that matches the file extension
77
- bool isVST3 = pluginFile.getFileExtension() == ".vst3";
78
- bool isAU = pluginFile.getFileExtension() == ".component";
79
-
80
- if (isVST3 && format->getName() != "VST3") continue;
81
- if (isAU && format->getName() != "AudioUnit") continue;
82
-
83
- format->findAllTypesForFile(typesFound, pluginFile.getFullPathName());
84
- if (typesFound.size() > 0)
85
- break;
86
- }
87
-
88
- if (typesFound.isEmpty())
89
- {
90
- DBG("PluginBridge: No plugin found in file: " + pluginFile.getFullPathName());
91
- lastError = "Plugin not recognized";
92
- return;
93
- }
94
-
95
- auto desc = *typesFound[0];
96
-
97
- // Create instance asynchronously (safer — allows message loop to run)
98
- formatManager.createPluginInstanceAsync(desc, sampleRate, blockSize,
99
- [this, pluginFile](std::unique_ptr<juce::AudioPluginInstance> instance,
100
- const juce::String& error)
101
- {
102
- if (instance == nullptr)
103
- {
104
- DBG("PluginBridge: Failed to create plugin: " + error);
105
- lastError = error;
106
- return;
107
- }
108
-
109
- instance->enableAllBuses();
110
-
111
- auto layout = juce::AudioProcessor::BusesLayout();
112
- layout.inputBuses.add(juce::AudioChannelSet::stereo());
113
- layout.outputBuses.add(juce::AudioChannelSet::stereo());
114
-
115
- if (instance->checkBusesLayoutSupported(layout))
116
- instance->setBusesLayout(layout);
117
-
118
- instance->prepareToPlay(currentSampleRate, currentBlockSize);
119
-
120
- {
121
- const juce::ScopedLock sl(pluginLock);
122
- hostedPlugin = std::move(instance);
123
- loadedPluginPath = pluginFile.getFullPathName();
124
- lastError.clear();
125
- }
126
-
127
- DBG("PluginBridge: Loaded plugin: " + getPluginName()
128
- + " (" + juce::String(getParameterCount()) + " params)");
129
- });
130
- }
131
-
132
- void HostedPluginManager::unloadPlugin()
133
- {
134
- const juce::ScopedLock sl(pluginLock);
135
- if (hostedPlugin)
136
- {
137
- hostedPlugin->releaseResources();
138
- hostedPlugin.reset();
139
- loadedPluginPath.clear();
140
- }
141
- }
142
-
143
- bool HostedPluginManager::isLoaded() const
144
- {
145
- return hostedPlugin != nullptr;
146
- }
147
-
148
- juce::String HostedPluginManager::getPluginName() const
149
- {
150
- if (hostedPlugin)
151
- return hostedPlugin->getName();
152
- return {};
153
- }
154
-
155
- void HostedPluginManager::prepareToPlay(double sampleRate, int blockSize)
156
- {
157
- currentSampleRate = sampleRate;
158
- currentBlockSize = blockSize;
159
-
160
- const juce::ScopedLock sl(pluginLock);
161
- if (hostedPlugin)
162
- hostedPlugin->prepareToPlay(sampleRate, blockSize);
163
- }
164
-
165
- void HostedPluginManager::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midi)
166
- {
167
- const juce::ScopedLock sl(pluginLock);
168
- if (hostedPlugin)
169
- hostedPlugin->processBlock(buffer, midi);
170
- }
171
-
172
- void HostedPluginManager::releaseResources()
173
- {
174
- const juce::ScopedLock sl(pluginLock);
175
- if (hostedPlugin)
176
- hostedPlugin->releaseResources();
177
- }
178
-
179
- // --- Parameter Surface ---
180
-
181
- int HostedPluginManager::getParameterCount() const
182
- {
183
- if (hostedPlugin)
184
- return hostedPlugin->getParameters().size();
185
- return 0;
186
- }
187
-
188
- std::vector<HostedPluginManager::ParamInfo> HostedPluginManager::searchParams(
189
- const juce::String& keyword) const
190
- {
191
- std::vector<ParamInfo> results;
192
- if (!hostedPlugin) return results;
193
-
194
- auto& params = hostedPlugin->getParameters();
195
- auto keywordLower = keyword.toLowerCase();
196
-
197
- for (int i = 0; i < params.size(); ++i)
198
- {
199
- auto* param = params[i];
200
- auto name = param->getName(128);
201
-
202
- if (name.toLowerCase().contains(keywordLower))
203
- {
204
- ParamInfo info;
205
- info.index = i;
206
- info.name = name;
207
- info.value = param->getValue();
208
- info.displayText = param->getText(param->getValue(), 64);
209
- info.automatable = param->isAutomatable();
210
-
211
- if (auto* hosted = dynamic_cast<juce::AudioPluginInstance::HostedParameter*>(param))
212
- info.id = hosted->getParameterID();
213
- else
214
- info.id = juce::String(i);
215
-
216
- results.push_back(info);
217
- }
218
-
219
- if (results.size() >= 20)
220
- break;
221
- }
222
-
223
- return results;
224
- }
225
-
226
- std::vector<std::pair<int, float>> HostedPluginManager::getParams(
227
- const std::vector<int>& indices) const
228
- {
229
- std::vector<std::pair<int, float>> results;
230
- if (!hostedPlugin) return results;
231
-
232
- auto& params = hostedPlugin->getParameters();
233
-
234
- for (int idx : indices)
235
- {
236
- if (idx >= 0 && idx < params.size())
237
- results.push_back({idx, params[idx]->getValue()});
238
- }
239
-
240
- return results;
241
- }
242
-
243
- bool HostedPluginManager::setParams(const std::map<int, float>& values)
244
- {
245
- if (!hostedPlugin) return false;
246
-
247
- auto& params = hostedPlugin->getParameters();
248
-
249
- for (auto& [idx, val] : values)
250
- {
251
- if (idx >= 0 && idx < params.size())
252
- {
253
- auto* param = params[idx];
254
- float clampedVal = juce::jlimit(0.0f, 1.0f, val);
255
-
256
- param->beginChangeGesture();
257
- param->setValueNotifyingHost(clampedVal);
258
- param->endChangeGesture();
259
- }
260
- }
261
-
262
- return true;
263
- }
264
-
265
- juce::String HostedPluginManager::getLoadedPluginPath() const
266
- {
267
- return loadedPluginPath;
268
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Source/HostedPluginManager.h DELETED
@@ -1,59 +0,0 @@
1
- #pragma once
2
-
3
- #include <juce_audio_processors/juce_audio_processors.h>
4
- #include <vector>
5
- #include <map>
6
-
7
- class HostedPluginManager
8
- {
9
- public:
10
- HostedPluginManager();
11
- ~HostedPluginManager();
12
-
13
- // Plugin lifecycle
14
- void loadPlugin(const juce::File& pluginFile, double sampleRate, int blockSize);
15
- void unloadPlugin();
16
- bool isLoaded() const;
17
- juce::String getPluginName() const;
18
- juce::String getLastError() const { return lastError; }
19
-
20
- // Audio processing — called from audio thread
21
- void prepareToPlay(double sampleRate, int blockSize);
22
- void processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midi);
23
- void releaseResources();
24
-
25
- // Parameter surface — called from HTTP thread (thread-safe)
26
- struct ParamInfo {
27
- int index;
28
- juce::String id;
29
- juce::String name;
30
- float value;
31
- juce::String displayText;
32
- bool automatable;
33
- };
34
-
35
- std::vector<ParamInfo> searchParams(const juce::String& keyword) const;
36
- std::vector<std::pair<int, float>> getParams(const std::vector<int>& indices) const;
37
- bool setParams(const std::map<int, float>& values);
38
- int getParameterCount() const;
39
-
40
- // Thread safety
41
- juce::CriticalSection& getPluginLock() { return pluginLock; }
42
-
43
- // File path of currently loaded plugin
44
- juce::String getLoadedPluginPath() const;
45
-
46
- private:
47
- bool validatePluginFile(const juce::File& pluginFile);
48
-
49
- juce::AudioPluginFormatManager formatManager;
50
- std::unique_ptr<juce::AudioPluginInstance> hostedPlugin;
51
- juce::CriticalSection pluginLock;
52
-
53
- double currentSampleRate = 44100.0;
54
- int currentBlockSize = 512;
55
- juce::String loadedPluginPath;
56
- juce::String lastError;
57
-
58
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(HostedPluginManager)
59
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Source/McpServer.cpp DELETED
@@ -1,319 +0,0 @@
1
- #include "McpServer.h"
2
- #include "HostedPluginManager.h"
3
-
4
- // httplib included in exactly ONE .cpp
5
- #define CPPHTTPLIB_THREAD_POOL_COUNT 2
6
- #include "httplib.h"
7
- #include "json.hpp"
8
-
9
- using json = nlohmann::json;
10
-
11
- McpServer::McpServer(int port, HostedPluginManager& pluginManager)
12
- : server(std::make_unique<httplib::Server>()),
13
- port(port),
14
- pluginManager(pluginManager)
15
- {
16
- setupRoutes();
17
- }
18
-
19
- McpServer::~McpServer()
20
- {
21
- stop();
22
- }
23
-
24
- void McpServer::setupRoutes()
25
- {
26
- // Single MCP endpoint
27
- server->Post("/mcp", [this](const httplib::Request& req, httplib::Response& res) {
28
- res.set_header("Access-Control-Allow-Origin", "http://localhost");
29
-
30
- try {
31
- auto body = json::parse(req.body);
32
- std::string method = body.value("method", "");
33
- std::string responseStr;
34
-
35
- if (method == "initialize") {
36
- responseStr = handleInitialize(req.body);
37
- }
38
- else if (method == "notifications/initialized") {
39
- initialized = true;
40
- res.status = 204;
41
- return;
42
- }
43
- else if (method == "tools/list") {
44
- responseStr = handleToolsList(req.body);
45
- }
46
- else if (method == "tools/call") {
47
- responseStr = handleToolsCall(req.body);
48
- }
49
- else {
50
- json errResponse = {
51
- {"jsonrpc", "2.0"},
52
- {"id", body.value("id", json(nullptr))},
53
- {"error", {{"code", -32601}, {"message", "Method not found"}}}
54
- };
55
- responseStr = errResponse.dump();
56
- }
57
-
58
- if (!responseStr.empty())
59
- res.set_content(responseStr, "application/json");
60
- }
61
- catch (const json::exception&) {
62
- json errResponse = {
63
- {"jsonrpc", "2.0"},
64
- {"id", nullptr},
65
- {"error", {{"code", -32700}, {"message", "Parse error"}}}
66
- };
67
- res.set_content(errResponse.dump(), "application/json");
68
- }
69
- });
70
-
71
- // Health check
72
- server->Get("/health", [this](const httplib::Request&, httplib::Response& res) {
73
- json health = {
74
- {"status", "ok"},
75
- {"port", port},
76
- {"initialized", initialized},
77
- {"plugin", pluginManager.isLoaded() ? pluginManager.getPluginName().toStdString() : "none"},
78
- {"params", pluginManager.getParameterCount()}
79
- };
80
- res.set_content(health.dump(), "application/json");
81
- });
82
-
83
- // CORS preflight
84
- server->Options("/mcp", [](const httplib::Request&, httplib::Response& res) {
85
- res.set_header("Access-Control-Allow-Origin", "http://localhost");
86
- res.set_header("Access-Control-Allow-Methods", "POST, OPTIONS");
87
- res.set_header("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
88
- res.status = 204;
89
- });
90
- }
91
-
92
- void McpServer::start()
93
- {
94
- if (running.load()) return;
95
-
96
- serverThread = std::thread([this]() {
97
- running.store(true);
98
- server->listen("127.0.0.1", port);
99
- running.store(false);
100
- });
101
-
102
- server->wait_until_ready();
103
- }
104
-
105
- void McpServer::stop()
106
- {
107
- if (server && server->is_running())
108
- server->stop();
109
- if (serverThread.joinable())
110
- serverThread.join();
111
- running.store(false);
112
- }
113
-
114
- bool McpServer::isRunning() const
115
- {
116
- return running.load();
117
- }
118
-
119
- // --- MCP Protocol Handlers ---
120
-
121
- std::string McpServer::handleInitialize(const std::string& requestBody)
122
- {
123
- auto body = json::parse(requestBody);
124
- sessionId = "pb-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
125
-
126
- json response = {
127
- {"jsonrpc", "2.0"},
128
- {"id", body.value("id", json(nullptr))},
129
- {"result", {
130
- {"protocolVersion", "2025-06-18"},
131
- {"capabilities", {{"tools", {{"listChanged", false}}}}},
132
- {"serverInfo", {{"name", "PluginBridge"}, {"version", "0.2.0"}}}
133
- }}
134
- };
135
- return response.dump();
136
- }
137
-
138
- std::string McpServer::handleToolsList(const std::string& requestBody)
139
- {
140
- auto body = json::parse(requestBody);
141
-
142
- json response = {
143
- {"jsonrpc", "2.0"},
144
- {"id", body.value("id", json(nullptr))},
145
- {"result", {
146
- {"tools", json::array({
147
- {
148
- {"name", "list_plugins"},
149
- {"description", "List loaded plugin instances"},
150
- {"inputSchema", {{"type", "object"}, {"properties", json::object()}}}
151
- },
152
- {
153
- {"name", "search_param"},
154
- {"description", "Search plugin parameters by keyword. Returns matching param IDs and values."},
155
- {"inputSchema", {
156
- {"type", "object"},
157
- {"properties", {
158
- {"plugin", {{"type", "string"}, {"description", "Plugin name"}}},
159
- {"keyword", {{"type", "string"}, {"description", "Search term (e.g. 'band 1', 'gain', 'cutoff')"}}}
160
- }},
161
- {"required", json::array({"plugin", "keyword"})}
162
- }}
163
- },
164
- {
165
- {"name", "get_params"},
166
- {"description", "Get current values for parameter IDs"},
167
- {"inputSchema", {
168
- {"type", "object"},
169
- {"properties", {
170
- {"plugin", {{"type", "string"}}},
171
- {"ids", {{"type", "array"}, {"items", {{"type", "integer"}}}}}
172
- }},
173
- {"required", json::array({"plugin", "ids"})}
174
- }}
175
- },
176
- {
177
- {"name", "set_params"},
178
- {"description", "Set parameter values by ID (batch). Values are 0.0-1.0 normalized."},
179
- {"inputSchema", {
180
- {"type", "object"},
181
- {"properties", {
182
- {"plugin", {{"type", "string"}}},
183
- {"values", {{"type", "object"}, {"description", "Map of param_index (string) to value (0.0-1.0)"}}}
184
- }},
185
- {"required", json::array({"plugin", "values"})}
186
- }}
187
- },
188
- {
189
- {"name", "get_analysis"},
190
- {"description", "Get real-time audio analysis (LUFS, frequency bands, stereo width)"},
191
- {"inputSchema", {{"type", "object"}, {"properties", json::object()}}}
192
- }
193
- })}
194
- }}
195
- };
196
- return response.dump();
197
- }
198
-
199
- std::string McpServer::handleToolsCall(const std::string& requestBody)
200
- {
201
- auto body = json::parse(requestBody);
202
- auto params = body["params"];
203
- std::string toolName = params["name"];
204
- auto args = params.value("arguments", json::object());
205
-
206
- std::string resultText;
207
- bool isError = false;
208
-
209
- if (toolName == "list_plugins") {
210
- resultText = toolListPlugins();
211
- }
212
- else if (toolName == "search_param") {
213
- resultText = toolSearchParam(
214
- args.value("plugin", ""),
215
- args.value("keyword", ""));
216
- }
217
- else if (toolName == "get_params") {
218
- resultText = toolGetParams(
219
- args.value("plugin", ""),
220
- args.value("ids", json::array()).dump());
221
- }
222
- else if (toolName == "set_params") {
223
- resultText = toolSetParams(
224
- args.value("plugin", ""),
225
- args.value("values", json::object()).dump());
226
- }
227
- else if (toolName == "get_analysis") {
228
- resultText = toolGetAnalysis();
229
- }
230
- else {
231
- resultText = "unknown tool: " + toolName;
232
- isError = true;
233
- }
234
-
235
- json response = {
236
- {"jsonrpc", "2.0"},
237
- {"id", body.value("id", json(nullptr))},
238
- {"result", {
239
- {"content", json::array({{{"type", "text"}, {"text", resultText}}})},
240
- {"isError", isError}
241
- }}
242
- };
243
- return response.dump();
244
- }
245
-
246
- // --- Tool Implementations ---
247
-
248
- std::string McpServer::toolListPlugins()
249
- {
250
- if (!pluginManager.isLoaded())
251
- return "[]";
252
-
253
- json result = json::array({pluginManager.getPluginName().toStdString()});
254
- return result.dump();
255
- }
256
-
257
- std::string McpServer::toolSearchParam(const std::string& /*plugin*/, const std::string& keyword)
258
- {
259
- if (!pluginManager.isLoaded())
260
- return "no plugin loaded";
261
-
262
- auto results = pluginManager.searchParams(juce::String(keyword));
263
-
264
- if (results.empty())
265
- return "no matches for '" + keyword + "'";
266
-
267
- // Compact output: [{id, name, value}]
268
- json output = json::array();
269
- for (auto& p : results)
270
- {
271
- output.push_back({
272
- {"i", p.index},
273
- {"n", p.name.toStdString()},
274
- {"v", std::round(p.value * 1000.0f) / 1000.0f},
275
- {"t", p.displayText.toStdString()}
276
- });
277
- }
278
- return output.dump();
279
- }
280
-
281
- std::string McpServer::toolGetParams(const std::string& /*plugin*/, const std::string& idsJson)
282
- {
283
- if (!pluginManager.isLoaded())
284
- return "no plugin loaded";
285
-
286
- auto idsArray = json::parse(idsJson);
287
- std::vector<int> ids;
288
- for (auto& id : idsArray)
289
- ids.push_back(id.get<int>());
290
-
291
- auto results = pluginManager.getParams(ids);
292
-
293
- // Compact: {id: value, id: value}
294
- json output = json::object();
295
- for (auto& [idx, val] : results)
296
- output[std::to_string(idx)] = std::round(val * 1000.0f) / 1000.0f;
297
-
298
- return output.dump();
299
- }
300
-
301
- std::string McpServer::toolSetParams(const std::string& /*plugin*/, const std::string& valuesJson)
302
- {
303
- if (!pluginManager.isLoaded())
304
- return "no plugin loaded";
305
-
306
- auto valuesObj = json::parse(valuesJson);
307
- std::map<int, float> values;
308
- for (auto& [key, val] : valuesObj.items())
309
- values[std::stoi(key)] = val.get<float>();
310
-
311
- bool ok = pluginManager.setParams(values);
312
- return ok ? "ok" : "error";
313
- }
314
-
315
- std::string McpServer::toolGetAnalysis()
316
- {
317
- // Sprint 4: will return real analysis
318
- return "silent";
319
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Source/McpServer.h DELETED
@@ -1,46 +0,0 @@
1
- #pragma once
2
-
3
- #include <thread>
4
- #include <atomic>
5
- #include <string>
6
- #include <memory>
7
-
8
- // Forward declarations
9
- namespace httplib { class Server; }
10
- class HostedPluginManager;
11
-
12
- class McpServer
13
- {
14
- public:
15
- McpServer(int port, HostedPluginManager& pluginManager);
16
- ~McpServer();
17
-
18
- void start();
19
- void stop();
20
- bool isRunning() const;
21
- int getPort() const { return port; }
22
-
23
- private:
24
- void setupRoutes();
25
-
26
- // MCP protocol handlers
27
- std::string handleInitialize(const std::string& requestBody);
28
- std::string handleToolsList(const std::string& requestBody);
29
- std::string handleToolsCall(const std::string& requestBody);
30
-
31
- // Tool implementations
32
- std::string toolListPlugins();
33
- std::string toolSearchParam(const std::string& plugin, const std::string& keyword);
34
- std::string toolGetParams(const std::string& plugin, const std::string& idsJson);
35
- std::string toolSetParams(const std::string& plugin, const std::string& valuesJson);
36
- std::string toolGetAnalysis();
37
-
38
- std::unique_ptr<httplib::Server> server;
39
- std::thread serverThread;
40
- std::atomic<bool> running{false};
41
- int port;
42
- std::string sessionId;
43
- bool initialized = false;
44
-
45
- HostedPluginManager& pluginManager;
46
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Source/PluginBridgeEditor.cpp DELETED
@@ -1,140 +0,0 @@
1
- #include "PluginBridgeEditor.h"
2
-
3
- PluginBridgeEditor::PluginBridgeEditor(PluginBridgeProcessor& p)
4
- : AudioProcessorEditor(&p), processorRef(p)
5
- {
6
- setSize(360, 220);
7
-
8
- titleLabel.setText("PluginBridge", juce::dontSendNotification);
9
- titleLabel.setFont(juce::FontOptions(22.0f));
10
- titleLabel.setJustificationType(juce::Justification::centred);
11
- titleLabel.setColour(juce::Label::textColourId, juce::Colours::white);
12
- addAndMakeVisible(titleLabel);
13
-
14
- statusLabel.setText("MCP: starting...", juce::dontSendNotification);
15
- statusLabel.setFont(juce::FontOptions(12.0f));
16
- statusLabel.setJustificationType(juce::Justification::centred);
17
- addAndMakeVisible(statusLabel);
18
-
19
- portLabel.setText("", juce::dontSendNotification);
20
- portLabel.setFont(juce::FontOptions(10.0f));
21
- portLabel.setJustificationType(juce::Justification::centred);
22
- portLabel.setColour(juce::Label::textColourId, juce::Colour(0xff888888));
23
- addAndMakeVisible(portLabel);
24
-
25
- pluginLabel.setText("No plugin loaded", juce::dontSendNotification);
26
- pluginLabel.setFont(juce::FontOptions(13.0f));
27
- pluginLabel.setJustificationType(juce::Justification::centred);
28
- pluginLabel.setColour(juce::Label::textColourId, juce::Colour(0xffaaaaaa));
29
- addAndMakeVisible(pluginLabel);
30
-
31
- pluginButton.onClick = [this]() { showPluginMenu(); };
32
- pluginButton.setColour(juce::TextButton::buttonColourId, juce::Colour(0xff2d2d44));
33
- pluginButton.setColour(juce::TextButton::textColourOffId, juce::Colours::white);
34
- addAndMakeVisible(pluginButton);
35
-
36
- startTimerHz(4);
37
- }
38
-
39
- PluginBridgeEditor::~PluginBridgeEditor()
40
- {
41
- stopTimer();
42
- }
43
-
44
- void PluginBridgeEditor::paint(juce::Graphics& g)
45
- {
46
- g.fillAll(juce::Colour(0xff12121f));
47
- g.setColour(juce::Colour(0xff1e1e35));
48
- g.fillRoundedRectangle(getLocalBounds().toFloat().reduced(4), 6.0f);
49
- }
50
-
51
- void PluginBridgeEditor::resized()
52
- {
53
- auto area = getLocalBounds().reduced(14);
54
- titleLabel.setBounds(area.removeFromTop(30));
55
- area.removeFromTop(4);
56
- statusLabel.setBounds(area.removeFromTop(18));
57
- portLabel.setBounds(area.removeFromTop(16));
58
- area.removeFromTop(10);
59
- pluginButton.setBounds(area.removeFromTop(30).reduced(20, 0));
60
- area.removeFromTop(8);
61
- pluginLabel.setBounds(area.removeFromTop(20));
62
- }
63
-
64
- void PluginBridgeEditor::timerCallback()
65
- {
66
- if (processorRef.isMcpServerRunning())
67
- {
68
- statusLabel.setText("MCP: running", juce::dontSendNotification);
69
- statusLabel.setColour(juce::Label::textColourId, juce::Colour(0xff4cef50));
70
- portLabel.setText("127.0.0.1:" + juce::String(processorRef.getMcpServerPort()),
71
- juce::dontSendNotification);
72
- }
73
- else
74
- {
75
- statusLabel.setText("MCP: offline", juce::dontSendNotification);
76
- statusLabel.setColour(juce::Label::textColourId, juce::Colour(0xffef5350));
77
- portLabel.setText("", juce::dontSendNotification);
78
- }
79
-
80
- auto& mgr = processorRef.getHostedPluginManager();
81
- if (mgr.isLoaded())
82
- {
83
- pluginLabel.setText(mgr.getPluginName() + " | " + juce::String(mgr.getParameterCount()) + " params",
84
- juce::dontSendNotification);
85
- pluginLabel.setColour(juce::Label::textColourId, juce::Colour(0xffffeb3b));
86
- pluginButton.setButtonText(mgr.getPluginName());
87
- }
88
- else
89
- {
90
- pluginLabel.setText("No plugin loaded", juce::dontSendNotification);
91
- pluginLabel.setColour(juce::Label::textColourId, juce::Colour(0xff888888));
92
- pluginButton.setButtonText("Select Plugin...");
93
- }
94
- }
95
-
96
- void PluginBridgeEditor::showPluginMenu()
97
- {
98
- auto files = processorRef.getInstalledPluginFiles();
99
-
100
- juce::PopupMenu menu;
101
-
102
- if (files.isEmpty())
103
- {
104
- menu.addItem(-1, "(No plugins found)", false);
105
- menu.showMenuAsync(juce::PopupMenu::Options().withTargetComponent(&pluginButton),
106
- [](int) {});
107
- return;
108
- }
109
-
110
- juce::PopupMenu vst3Menu;
111
- juce::PopupMenu auMenu;
112
- int itemId = 1;
113
-
114
- for (int i = 0; i < files.size(); ++i)
115
- {
116
- const auto& file = files[i];
117
- auto name = file.getFileNameWithoutExtension();
118
-
119
- if (file.getFileExtension() == ".vst3")
120
- vst3Menu.addItem(itemId, name);
121
- else
122
- auMenu.addItem(itemId, name);
123
-
124
- itemId++;
125
- }
126
-
127
- if (vst3Menu.getNumItems() > 0)
128
- menu.addSubMenu("VST3", vst3Menu);
129
- if (auMenu.getNumItems() > 0)
130
- menu.addSubMenu("Audio Units", auMenu);
131
-
132
- menu.showMenuAsync(juce::PopupMenu::Options().withTargetComponent(&pluginButton),
133
- [this, files](int result)
134
- {
135
- if (result > 0 && result <= files.size())
136
- {
137
- processorRef.loadPluginFromFile(files[result - 1]);
138
- }
139
- });
140
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Source/PluginBridgeEditor.h DELETED
@@ -1,29 +0,0 @@
1
- #pragma once
2
-
3
- #include <juce_gui_basics/juce_gui_basics.h>
4
- #include "PluginBridgeProcessor.h"
5
-
6
- class PluginBridgeEditor : public juce::AudioProcessorEditor,
7
- private juce::Timer
8
- {
9
- public:
10
- explicit PluginBridgeEditor(PluginBridgeProcessor&);
11
- ~PluginBridgeEditor() override;
12
-
13
- void paint(juce::Graphics&) override;
14
- void resized() override;
15
-
16
- private:
17
- void timerCallback() override;
18
- void showPluginMenu();
19
-
20
- PluginBridgeProcessor& processorRef;
21
-
22
- juce::Label titleLabel;
23
- juce::Label statusLabel;
24
- juce::Label portLabel;
25
- juce::Label pluginLabel;
26
- juce::TextButton pluginButton{"Select Plugin..."};
27
-
28
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginBridgeEditor)
29
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Source/PluginBridgeProcessor.cpp DELETED
@@ -1,130 +0,0 @@
1
- #include "PluginBridgeProcessor.h"
2
- #include "PluginBridgeEditor.h"
3
-
4
- PluginBridgeProcessor::PluginBridgeProcessor()
5
- : AudioProcessor(BusesProperties()
6
- .withInput("Input", juce::AudioChannelSet::stereo(), true)
7
- .withOutput("Output", juce::AudioChannelSet::stereo(), true)),
8
- mcpServer(kDefaultMcpPort, hostManager)
9
- {
10
- mcpServer.start();
11
- }
12
-
13
- PluginBridgeProcessor::~PluginBridgeProcessor()
14
- {
15
- mcpServer.stop();
16
- }
17
-
18
- void PluginBridgeProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
19
- {
20
- hostManager.prepareToPlay(sampleRate, samplesPerBlock);
21
- }
22
-
23
- void PluginBridgeProcessor::releaseResources()
24
- {
25
- hostManager.releaseResources();
26
- }
27
-
28
- void PluginBridgeProcessor::processBlock(juce::AudioBuffer<float>& buffer,
29
- juce::MidiBuffer& midiMessages)
30
- {
31
- juce::ScopedNoDenormals noDenormals;
32
-
33
- auto totalNumInputChannels = getTotalNumInputChannels();
34
- auto totalNumOutputChannels = getTotalNumOutputChannels();
35
- for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
36
- buffer.clear(i, 0, buffer.getNumSamples());
37
-
38
- hostManager.processBlock(buffer, midiMessages);
39
- }
40
-
41
- juce::AudioProcessorEditor* PluginBridgeProcessor::createEditor()
42
- {
43
- return new PluginBridgeEditor(*this);
44
- }
45
-
46
- void PluginBridgeProcessor::getStateInformation(juce::MemoryBlock& destData)
47
- {
48
- juce::ValueTree state("PluginBridgeState");
49
- state.setProperty("hostedPluginPath", hostManager.getLoadedPluginPath(), nullptr);
50
-
51
- juce::MemoryOutputStream stream(destData, false);
52
- state.writeToStream(stream);
53
- }
54
-
55
- void PluginBridgeProcessor::setStateInformation(const void* data, int sizeInBytes)
56
- {
57
- auto state = juce::ValueTree::readFromData(data, (size_t)sizeInBytes);
58
- if (state.isValid())
59
- {
60
- auto path = state.getProperty("hostedPluginPath").toString();
61
- if (path.isNotEmpty())
62
- {
63
- juce::File pluginFile(path);
64
- if (pluginFile.exists())
65
- loadPluginFromFile(pluginFile);
66
- }
67
- }
68
- }
69
-
70
- bool PluginBridgeProcessor::isMcpServerRunning() const
71
- {
72
- return mcpServer.isRunning();
73
- }
74
-
75
- int PluginBridgeProcessor::getMcpServerPort() const
76
- {
77
- return mcpServer.getPort();
78
- }
79
-
80
- void PluginBridgeProcessor::loadPluginFromFile(const juce::File& file)
81
- {
82
- hostManager.loadPlugin(file, getSampleRate(), getBlockSize());
83
- }
84
-
85
- juce::Array<juce::File> PluginBridgeProcessor::getInstalledPluginFiles() const
86
- {
87
- juce::Array<juce::File> results;
88
-
89
- // VST3 directories
90
- juce::Array<juce::File> vst3Dirs = {
91
- juce::File("/Library/Audio/Plug-Ins/VST3"),
92
- juce::File::getSpecialLocation(juce::File::userHomeDirectory)
93
- .getChildFile("Library/Audio/Plug-Ins/VST3")
94
- };
95
-
96
- for (auto& dir : vst3Dirs)
97
- {
98
- if (dir.isDirectory())
99
- {
100
- auto files = dir.findChildFiles(juce::File::findDirectories, false, "*.vst3");
101
- results.addArray(files);
102
- }
103
- }
104
-
105
- // AU directories
106
- juce::Array<juce::File> auDirs = {
107
- juce::File("/Library/Audio/Plug-Ins/Components"),
108
- juce::File::getSpecialLocation(juce::File::userHomeDirectory)
109
- .getChildFile("Library/Audio/Plug-Ins/Components")
110
- };
111
-
112
- for (auto& dir : auDirs)
113
- {
114
- if (dir.isDirectory())
115
- {
116
- auto files = dir.findChildFiles(juce::File::findDirectories, false, "*.component");
117
- results.addArray(files);
118
- }
119
- }
120
-
121
- // Sort alphabetically
122
- results.sort();
123
- return results;
124
- }
125
-
126
- // JUCE plugin entry point
127
- juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
128
- {
129
- return new PluginBridgeProcessor();
130
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Source/PluginBridgeProcessor.h DELETED
@@ -1,57 +0,0 @@
1
- #pragma once
2
-
3
- #include <juce_audio_processors/juce_audio_processors.h>
4
- #include <juce_dsp/juce_dsp.h>
5
- #include "McpServer.h"
6
- #include "HostedPluginManager.h"
7
-
8
- class PluginBridgeProcessor : public juce::AudioProcessor
9
- {
10
- public:
11
- PluginBridgeProcessor();
12
- ~PluginBridgeProcessor() override;
13
-
14
- // AudioProcessor interface
15
- void prepareToPlay(double sampleRate, int samplesPerBlock) override;
16
- void releaseResources() override;
17
- void processBlock(juce::AudioBuffer<float>&, juce::MidiBuffer&) override;
18
-
19
- // Editor
20
- juce::AudioProcessorEditor* createEditor() override;
21
- bool hasEditor() const override { return true; }
22
-
23
- // Plugin identity
24
- const juce::String getName() const override { return "PluginBridge"; }
25
- bool acceptsMidi() const override { return true; }
26
- bool producesMidi() const override { return true; }
27
- double getTailLengthSeconds() const override { return 0.0; }
28
-
29
- // Programs (unused)
30
- int getNumPrograms() override { return 1; }
31
- int getCurrentProgram() override { return 0; }
32
- void setCurrentProgram(int) override {}
33
- const juce::String getProgramName(int) override { return {}; }
34
- void changeProgramName(int, const juce::String&) override {}
35
-
36
- // State
37
- void getStateInformation(juce::MemoryBlock& destData) override;
38
- void setStateInformation(const void* data, int sizeInBytes) override;
39
-
40
- // Public access
41
- bool isMcpServerRunning() const;
42
- int getMcpServerPort() const;
43
- HostedPluginManager& getHostedPluginManager() { return hostManager; }
44
-
45
- // Load plugin (called on message thread)
46
- void loadPluginFromFile(const juce::File& file);
47
-
48
- // Plugin listing — just reads filenames, never loads binaries
49
- juce::Array<juce::File> getInstalledPluginFiles() const;
50
-
51
- private:
52
- HostedPluginManager hostManager;
53
- McpServer mcpServer;
54
- static constexpr int kDefaultMcpPort = 16620;
55
-
56
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginBridgeProcessor)
57
- };