pluginbridge / SYNC.md
Sitar118
docs: Sprint 8 βœ… β€” port 16620 restored, build clean
7c45edf
|
Raw
History Blame Contribute Delete
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