File size: 9,773 Bytes
a8f4396
cc0ab22
 
 
a8f4396
0cf3d62
7c45edf
a8f4396
29b4c18
 
a8f4396
8afcba7
7c45edf
0cf3d62
 
 
a8f4396
cc0ab22
a8f4396
 
cc0ab22
 
29b4c18
 
 
 
a21f3f0
29b4c18
 
 
 
 
 
 
 
 
a21f3f0
 
29b4c18
 
 
 
 
 
 
 
 
 
 
 
a21f3f0
 
29b4c18
 
a21f3f0
29b4c18
 
 
 
a21f3f0
29b4c18
 
a21f3f0
29b4c18
 
 
 
 
 
 
 
 
 
 
 
 
a21f3f0
 
29b4c18
a21f3f0
29b4c18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a21f3f0
29b4c18
a21f3f0
29b4c18
a21f3f0
29b4c18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a21f3f0
 
29b4c18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a21f3f0
29b4c18
 
 
 
 
 
 
 
 
 
 
 
 
a21f3f0
 
29b4c18
 
 
 
 
 
 
 
 
 
 
a21f3f0
29b4c18
 
 
 
a21f3f0
 
 
29b4c18
 
 
a21f3f0
 
29b4c18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cc0ab22
 
 
 
a8f4396
ac96bfc
 
8afcba7
ac96bfc
0548359
 
 
a8f4396
ac96bfc
715eca0
7c45edf
 
29b4c18
5198ce4
 
 
 
7c45edf
5198ce4
7c45edf
a8f4396
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# 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
```