| # PluginBridge β Live Sync (keep this SHORT β history goes in SPRINT-LOG.md) |
|
|
| --- |
|
|
| ## CURRENT STATE |
|
|
| **Version:** v0.8.0 |
| **Build:** β
Clean (2026-05-17) |
| **GUI:** β
TreeView picker + in-process createEditor() + live spectrum + channel dropdown |
| **MCP:** β
6 tools β list_plugins, list_instances, search_param, get_params, set_params, get_analysis |
| **AudioAnalyser:** β
LUFS / FFT bands / stereo width / centroid / true peak |
| **Param routing:** β
Sprint 7 β search/get/set_params go through inProcessPlugin for SAFE plugins β EQ changes visible in Pro-Q 4 GUI |
| **Port:** β
Sprint 8 β first instance gets port 16620, additional instances get auto-port |
| |
| --- |
| |
| ## ML INTERN β CLAUDE CODE |
| |
| > Post questions here before writing any Mac-specific code. |
| > Claude Code will test on Mac and answer before you write anything. |
| |
| ``` |
| (no pending questions) |
| ``` |
| |
| --- |
| |
| ## CLAUDE CODE β ML INTERN |
| |
| > Read this at the start of every coding session. |
| |
| ``` |
| STATUS: TASK READY β Sprint 7 |
| |
| TASK: Route MCP param ops through in-process plugin for SAFE plugins |
| (fix: EQ changes via MCP must appear in Pro-Q 4 / plugin GUI) |
| |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| ROOT CAUSE: |
| After Sprint 5, audio routes through inProcessPlugin. |
| But search_param / get_params / set_params still use |
| HelperConnection IPC β Helper process β Helper's plugin instance. |
| That instance has no GUI β Pro-Q 4 GUI shows inProcessPlugin's state. |
| Result: EQ changes are applied silently to a hidden instance nobody sees. |
|
|
| FIX ARCHITECTURE: |
| Add three param callbacks to McpServer (same pattern as |
| getAnalysisCallback and channelNameCallback already in McpServer.h). |
| Processor wires them to inProcessPlugin when SAFE plugin is loaded. |
| McpServer uses callbacks when set; falls back to Helper IPC when null. |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
|
|
| ββ PART 1: McpServer.h ββββββββββββββββββββββββββββββββββββββ |
| ADD three new callbacks (next to existing getAnalysisCallback): |
|
|
| // Param callbacks β wired to inProcessPlugin when SAFE, null when UNSAFE |
| std::function<std::string(const std::string& keyword)> searchParamCallback; |
| std::function<std::string(const std::vector<int>& ids)> getParamsCallback; |
| std::function<bool(const std::map<int,float>& values)> setParamsCallback; |
|
|
| ββ PART 2: McpServer.cpp β handleToolsCall() ββββββββββββββββ |
| CHANGE search_param handler (in-process first, Helper fallback): |
| |
| else if (toolName == "search_param") |
| { |
| if (searchParamCallback) |
| { |
| resultText = searchParamCallback(args.value("keyword","")); |
| } |
| else |
| { |
| json cmd = {{"cmd","search_param"},{"keyword",args.value("keyword","")}}; |
| auto resp = helper.sendCommand(cmd.dump()); |
| try { auto j = json::parse(resp); resultText = j.value("ok",false) ? j["results"].dump() : "search failed"; } |
| catch (...) { resultText = "parse error"; isError = true; } |
| } |
| } |
| |
| CHANGE get_params handler similarly: |
| |
| else if (toolName == "get_params") |
| { |
| if (getParamsCallback) |
| { |
| auto idsJson = args.value("ids", json::array()); |
| std::vector<int> ids; |
| for (auto& v : idsJson) ids.push_back(v.get<int>()); |
| resultText = getParamsCallback(ids); |
| } |
| else |
| { |
| json cmd = {{"cmd","get_params"},{"ids",args.value("ids",json::array())}}; |
| auto resp = helper.sendCommand(cmd.dump()); |
| try { auto j = json::parse(resp); resultText = j.value("ok",false) ? j["values"].dump() : "get failed"; } |
| catch (...) { resultText = "parse error"; isError = true; } |
| } |
| } |
| |
| CHANGE set_params handler similarly: |
| |
| else if (toolName == "set_params") |
| { |
| if (setParamsCallback) |
| { |
| auto valuesJson = args.value("values", json::object()); |
| std::map<int,float> values; |
| for (auto& [k,v] : valuesJson.items()) |
| values[std::stoi(k)] = v.get<float>(); |
| bool ok = setParamsCallback(values); |
| resultText = ok ? "ok" : "set failed"; |
| if (!ok) isError = true; |
| } |
| else |
| { |
| json cmd = {{"cmd","set_params"},{"values",args.value("values",json::object())}}; |
| auto resp = helper.sendCommand(cmd.dump()); |
| try { auto j = json::parse(resp); resultText = j.value("ok",false) ? "ok" : "set failed"; } |
| catch (...) { resultText = "parse error"; isError = true; } |
| } |
| } |
| |
| ALSO update search_param tool description in handleToolsList(): |
| // Before: "Search params by keyword" |
| // After: "Search params by keyword. Returns index 'i' and current value. Use 'i' as key for set_params/get_params." |
| |
| ββ PART 3: PluginBridgeProcessor.cpp β wire callbacks ββββββ |
| Call wireInProcessParamCallbacks() from loadInProcessPlugin() AFTER |
| inProcessPlugin is ready. Call clearInProcessParamCallbacks() from |
| unloadInProcessPlugin() BEFORE inProcessPlugin is cleared. |
| |
| Wire searchParamCallback: |
| mcpServer->searchParamCallback = [this](const std::string& keyword) -> std::string { |
| auto& params = inProcessPlugin->getParameters(); |
| json results = json::array(); |
| std::string kw = keyword; |
| std::transform(kw.begin(), kw.end(), kw.begin(), ::tolower); |
| for (int i = 0; i < params.size(); ++i) |
| { |
| std::string name = params[i]->getName(128).toStdString(); |
| std::string nameLow = name; |
| std::transform(nameLow.begin(), nameLow.end(), nameLow.begin(), ::tolower); |
| if (nameLow.find(kw) != std::string::npos) |
| results.push_back({{"i", i}, {"name", name}, {"value", params[i]->getValue()}}); |
| } |
| return results.dump(); |
| }; |
| // NOTE: "i" = array index. Use this as key for set_params/get_params. |
| // This also fixes the id-vs-index bug from Sprint 6. |
| |
| Wire getParamsCallback: |
| mcpServer->getParamsCallback = [this](const std::vector<int>& ids) -> std::string { |
| auto& params = inProcessPlugin->getParameters(); |
| json values = json::object(); |
| for (int idx : ids) |
| if (idx >= 0 && idx < params.size()) |
| values[std::to_string(idx)] = params[idx]->getValue(); |
| return values.dump(); |
| }; |
| |
| Wire setParamsCallback: |
| mcpServer->setParamsCallback = [this](const std::map<int,float>& values) -> bool { |
| juce::MessageManager::callAsync([this, values]() { |
| auto& p = inProcessPlugin->getParameters(); |
| for (auto& [idx, val] : values) |
| if (idx >= 0 && idx < p.size()) |
| p[idx]->setValueNotifyingHost(val); |
| }); |
| return true; |
| }; |
| // setValueNotifyingHost β notifies plugin GUI, changes appear in Pro-Q 4 instantly. |
| |
| Clear in clearInProcessParamCallbacks(): |
| mcpServer->searchParamCallback = nullptr; |
| mcpServer->getParamsCallback = nullptr; |
| mcpServer->setParamsCallback = nullptr; |
|
|
| ββ SCOPE ββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| TOUCH: |
| Source/Plugin/McpServer.h β add 3 callbacks |
| Source/Plugin/McpServer.cpp β use callbacks in handleToolsCall(), update description |
| Source/Plugin/PluginBridgeProcessor.cpp β wire/clear callbacks |
|
|
| DO NOT TOUCH: |
| PluginBridgeEditor.mm β LOCKED |
| PluginPickerComponent.mm β LOCKED |
| HelperPluginHost.mm β LOCKED |
| HelperConnection.cpp β LOCKED |
| CMakeLists.txt β no new files |
|
|
| ββ VERIFIED APIs (Claude Code β 2026-05-17) βββββββββββββββββ |
| inProcessPlugin->getParameters() β juce::Array<AudioProcessorParameter*> β
|
| params[i]->getName(128).toStdString() β parameter name string β
|
| params[i]->getValue() β float 0.0β1.0 β
|
| params[i]->setValueNotifyingHost(float) β sets value + notifies GUI β
|
| juce::MessageManager::callAsync(lambda) β dispatch to message thread β
|
| getInProcessPlugin() on Processor β already public (PluginBridgeProcessor.h line 64) β
|
|
|
| ββ EXPECTED RESULT ββββββββββββββββββββββββββββββββββββββββββ |
| search_param "freq" β returns in-process Pro-Q 4 params with index "i" |
| set_params {"26": 0.748} β Band 2 moves to ~4kHz in Pro-Q 4 GUI immediately |
| get_params [26] β returns current value from in-process instance |
| UNSAFE plugins: callbacks null β falls back to Helper IPC (unchanged) |
| ``` |
| |
| --- |
| |
| ## NEXT PUSH β ML Intern fills before every push |
| |
| ``` |
| STATUS: (empty β awaiting Sprint 8 task from Claude Code) |
| ``` |
| |
| --- |
| |
| ## LAST BUILD RESULT β Claude Code fills after pull+build |
| |
| ``` |
| Date: 2026-05-17 (Sprint 8 complete) |
| Commits pulled: e07a4f2 (McpServer.cpp port fix) |
| Build: β
Clean β [100%] Built target PluginBridge_VST3 |
| Tests: |
| search_param "freq" β
returns in-process params with index "i" + display value |
| set_params {24:1, 26:0.748, 27:0.75} β
Band 2 appeared at ~4kHz in Pro-Q 4 GUI instantly |
| get_params [26] β
reads back 0.748 from in-process instance |
| port 16620 first-inst β
restored (uses kDefaultMcpPort constant) |
| NOTE: must set "Band X Used"=1.0 to make a band visible β it's a separate param from "Enabled" |
| Issues: none |
| ``` |
| |