|
Download SYNC.md from RAM2118/pluginbridge: direct link, hf CLI and curl.
- Browser
- Download file 9.77 kB
-
https://huggingface.co/RAM2118/pluginbridge/resolve/main/SYNC.md
- Command line
-
hf download hf://RAM2118/pluginbridge/SYNC.md
-
curl -L -o SYNC.md https://huggingface.co/RAM2118/pluginbridge/resolve/main/SYNC.md
9.77 kB
| # 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 | |
| ``` | |