RAM2118 commited on
Commit
985bfc7
Β·
verified Β·
1 Parent(s): 0a2f47b

Add refined roadmap after deep research of all referenced repos

Browse files
Files changed (1) hide show
  1. ROADMAP.md +469 -0
ROADMAP.md ADDED
@@ -0,0 +1,469 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # PluginBridge β€” Refined Roadmap (Post-Research)
2
+ > Updated after deep-diving all referenced GitHub repos + MCP spec
3
+ > Status: Research complete. Ready to build.
4
+
5
+ ---
6
+
7
+ ## Research Verdicts
8
+
9
+ | Repo | Verdict | Use As |
10
+ |---|---|---|
11
+ | `getdunne/juce-plugin-wrapper` | ❌ Don't fork β€” dead (2021), JUCE 6, GPL-3, hardcoded plugin, no param exposure | **Reference only** β€” copy bus-sync pattern |
12
+ | JUCE AudioPluginHost | βœ… Gold standard | **Primary reference** β€” all hosting patterns extracted |
13
+ | `cpp-httplib` | βœ… Perfect fit | **Use directly** β€” header-only, background thread, MIT |
14
+ | `klangfreund/LUFSMeter` | βœ… Drop-in | **Embed directly** β€” MIT, 4 files, clean API |
15
+ | `adamstark/Sound-Analyser` / Gist | ⚠️ GPL β€” can't use code directly | **Reference only** β€” reimplement spectral math with JUCE FFT |
16
+ | `josmithiii/mcp-servers-jos` | βœ… Shows MCP structure | **Reference** β€” but uses stdio/TypeScript, we need HTTP/C++ |
17
+
18
+ ---
19
+
20
+ ## Critical Architecture Decisions (Changed From Original Plan)
21
+
22
+ ### 1. Don't Fork juce-plugin-wrapper β†’ Build Fresh with CMake
23
+
24
+ **Why:** The wrapper is JUCE 6, Projucer-only, GPL-3, and missing everything we need (parameter enumeration, runtime plugin selection, FX mode). Starting fresh with JUCE 8 + CMake is faster than upgrading a 4-year-old skeleton.
25
+
26
+ **What to copy from it:** The `getBusesPropertiesFromProcessor()` + `prepareToPlay()` bus-sync pattern (~30 lines). That's it.
27
+
28
+ ### 2. Plugin Architecture: NOT a Graph β€” Single Hosted Instance
29
+
30
+ The original plan says "host any plugin." The AudioPluginHost uses `AudioProcessorGraph` for routing multiple plugins. **PluginBridge doesn't need this.** We host ONE plugin at a time (the one on the track). Architecture:
31
+
32
+ ```
33
+ PluginBridgeProcessor (our AudioProcessor β€” the outer shell)
34
+ β”œβ”€β”€ hostedPlugin: AudioPluginInstance* (the inner VST3/AU)
35
+ β”œβ”€β”€ httpServer: httplib::Server (background thread, port 16620)
36
+ β”œβ”€β”€ analyser: AudioAnalyser (LUFS + FFT in processBlock)
37
+ └── processBlock():
38
+ buffer β†’ analyser.process(buffer)
39
+ β†’ hostedPlugin->processBlock(buffer, midi)
40
+ β†’ output
41
+ ```
42
+
43
+ No graph. No AudioProcessorPlayer. No AudioDeviceManager. The DAW drives our `processBlock` β€” we just intercept + forward.
44
+
45
+ ### 3. MCP Transport: Streamable HTTP (Not stdio)
46
+
47
+ **Why:** Plugin runs inside a DAW process β€” can't spawn as a subprocess. Must expose HTTP endpoint.
48
+
49
+ **Protocol:** JSON-RPC 2.0 over HTTP POST to `http://127.0.0.1:16620/mcp`
50
+
51
+ **Required handshake:** `initialize` β†’ `notifications/initialized` before any tool calls.
52
+
53
+ ### 4. Plugin Discovery: File Path Based (Not System Scan)
54
+
55
+ Don't scan the entire system on load. Instead:
56
+ - User loads PluginBridge on a track
57
+ - PluginBridge GUI has a "Load Plugin" button β†’ native file picker
58
+ - User selects a `.vst3` or `.component` file
59
+ - We call `formatManager.createPluginInstanceAsync(desc, sr, bs, callback)`
60
+ - Plugin state (which inner plugin to load) persists via `getStateInformation()`
61
+
62
+ This avoids the AudioPluginHost's `KnownPluginList` dependency entirely.
63
+
64
+ ### 5. Spectral Analysis: Use JUCE DSP FFT (Not Gist/KissFFT)
65
+
66
+ **Why:** Gist is GPL. JUCE has `juce::dsp::FFT` built-in (optimized, uses vDSP on macOS). Spectral features (centroid, crest, band energy) are each 5-10 lines of math.
67
+
68
+ ### 6. True Peak: Add via 4Γ— Oversampling
69
+
70
+ LUFSMeter doesn't include True Peak. Add it using `juce::dsp::Oversampling<float>` (4Γ— oversample β†’ peak detect β†’ report in dBTP).
71
+
72
+ ---
73
+
74
+ ## Revised Phase 1 β€” Core Plugin + MCP Server
75
+
76
+ ### File Structure
77
+ ```
78
+ PluginBridge/
79
+ β”œβ”€β”€ CMakeLists.txt (JUCE 8 CMake)
80
+ β”œβ”€β”€ libs/
81
+ β”‚ β”œβ”€β”€ httplib.h (cpp-httplib, single header)
82
+ β”‚ └── json.hpp (nlohmann/json, single header)
83
+ β”œβ”€β”€ Source/
84
+ β”‚ β”œβ”€β”€ PluginBridgeProcessor.h/.cpp (main AudioProcessor)
85
+ β”‚ β”œβ”€β”€ PluginBridgeEditor.h/.cpp (GUI β€” load button, param list)
86
+ β”‚ β”œβ”€β”€ HostedPluginManager.h/.cpp (load/unload inner plugin, param enumeration)
87
+ β”‚ β”œβ”€β”€ McpServer.h/.cpp (HTTP server + JSON-RPC + tool dispatch)
88
+ β”‚ └── AudioAnalyser.h/.cpp (Phase 2 β€” LUFS + FFT)
89
+ └── Resources/
90
+ └── (icons, etc.)
91
+ ```
92
+
93
+ ### Key Classes
94
+
95
+ #### `HostedPluginManager`
96
+ ```cpp
97
+ class HostedPluginManager {
98
+ public:
99
+ void loadPlugin(const File& pluginFile, double sampleRate, int blockSize);
100
+ void unloadPlugin();
101
+
102
+ AudioPluginInstance* getPlugin() const;
103
+ bool isLoaded() const;
104
+
105
+ // Parameter surface for MCP
106
+ struct ParamInfo {
107
+ int index;
108
+ String id; // stable HostedAudioProcessorParameter ID
109
+ String name;
110
+ float value; // 0.0–1.0 normalized
111
+ String displayText; // formatted value string
112
+ bool automatable;
113
+ };
114
+
115
+ std::vector<ParamInfo> searchParams(const String& keyword) const;
116
+ std::vector<std::pair<int, float>> getParams(const std::vector<int>& indices) const;
117
+ bool setParams(const std::map<int, float>& values); // batch set
118
+ StringArray getLoadedPluginNames() const;
119
+
120
+ private:
121
+ AudioPluginFormatManager formatManager;
122
+ std::unique_ptr<AudioPluginInstance> hostedPlugin;
123
+ CriticalSection pluginLock; // protects hot-swap
124
+ };
125
+ ```
126
+
127
+ #### `McpServer`
128
+ ```cpp
129
+ class McpServer {
130
+ public:
131
+ McpServer(HostedPluginManager& mgr, AudioAnalyser& analyser);
132
+ ~McpServer();
133
+
134
+ void start(int port = 16620);
135
+ void stop();
136
+ bool isRunning() const;
137
+
138
+ private:
139
+ void handleInitialize(const nlohmann::json& req, nlohmann::json& res);
140
+ void handleToolsList(const nlohmann::json& req, nlohmann::json& res);
141
+ void handleToolsCall(const nlohmann::json& req, nlohmann::json& res);
142
+
143
+ // Tool implementations
144
+ nlohmann::json toolListPlugins();
145
+ nlohmann::json toolSearchParam(const std::string& plugin, const std::string& keyword);
146
+ nlohmann::json toolGetParams(const std::string& plugin, const std::vector<int>& ids);
147
+ nlohmann::json toolSetParams(const std::string& plugin, const std::map<int, float>& values);
148
+ nlohmann::json toolGetAnalysis();
149
+
150
+ httplib::Server server;
151
+ std::thread serverThread;
152
+ HostedPluginManager& pluginManager;
153
+ AudioAnalyser& analyser;
154
+ String sessionId;
155
+ bool initialized = false;
156
+ };
157
+ ```
158
+
159
+ #### `PluginBridgeProcessor::processBlock`
160
+ ```cpp
161
+ void PluginBridgeProcessor::processBlock(AudioBuffer<float>& buffer, MidiBuffer& midi)
162
+ {
163
+ // 1. Pre-analysis (before processing β€” captures input signal)
164
+ // analyser.captureInput(buffer); // Phase 2
165
+
166
+ // 2. Forward to hosted plugin
167
+ if (auto* plugin = hostManager.getPlugin())
168
+ {
169
+ ScopedLock sl(hostManager.getPluginLock());
170
+ plugin->processBlock(buffer, midi);
171
+ }
172
+
173
+ // 3. Post-analysis (after processing β€” captures output signal)
174
+ // analyser.captureOutput(buffer); // Phase 2
175
+ }
176
+ ```
177
+
178
+ ### MCP Protocol Implementation
179
+
180
+ Single endpoint: `POST http://127.0.0.1:16620/mcp`
181
+
182
+ ```cpp
183
+ // In McpServer::start()
184
+ server.Post("/mcp", [this](const httplib::Request& req, httplib::Response& res) {
185
+ auto body = nlohmann::json::parse(req.body);
186
+ nlohmann::json response;
187
+
188
+ std::string method = body["method"];
189
+
190
+ if (method == "initialize")
191
+ handleInitialize(body, response);
192
+ else if (method == "notifications/initialized")
193
+ return; // notification β€” no response
194
+ else if (method == "tools/list")
195
+ handleToolsList(body, response);
196
+ else if (method == "tools/call")
197
+ handleToolsCall(body, response);
198
+ else {
199
+ response = {
200
+ {"jsonrpc", "2.0"},
201
+ {"id", body["id"]},
202
+ {"error", {{"code", -32601}, {"message", "Method not found"}}}
203
+ };
204
+ }
205
+
206
+ res.set_content(response.dump(), "application/json");
207
+ });
208
+ ```
209
+
210
+ ### Tool Definitions (returned by `tools/list`)
211
+ ```json
212
+ {
213
+ "tools": [
214
+ {
215
+ "name": "list_plugins",
216
+ "description": "List all loaded plugin instances",
217
+ "inputSchema": { "type": "object", "properties": {} }
218
+ },
219
+ {
220
+ "name": "search_param",
221
+ "description": "Search plugin parameters by keyword. Returns matching param IDs and current values.",
222
+ "inputSchema": {
223
+ "type": "object",
224
+ "properties": {
225
+ "plugin": { "type": "string", "description": "Plugin name from list_plugins" },
226
+ "keyword": { "type": "string", "description": "Search term (e.g. 'band 1', 'cutoff', 'gain')" }
227
+ },
228
+ "required": ["plugin", "keyword"]
229
+ }
230
+ },
231
+ {
232
+ "name": "get_params",
233
+ "description": "Get current values for specific parameter IDs",
234
+ "inputSchema": {
235
+ "type": "object",
236
+ "properties": {
237
+ "plugin": { "type": "string" },
238
+ "ids": { "type": "array", "items": { "type": "integer" } }
239
+ },
240
+ "required": ["plugin", "ids"]
241
+ }
242
+ },
243
+ {
244
+ "name": "set_params",
245
+ "description": "Set parameter values by ID. Batch operation.",
246
+ "inputSchema": {
247
+ "type": "object",
248
+ "properties": {
249
+ "plugin": { "type": "string" },
250
+ "values": { "type": "object", "description": "Map of param_id (int) β†’ value (0.0–1.0)" }
251
+ },
252
+ "required": ["plugin", "values"]
253
+ }
254
+ },
255
+ {
256
+ "name": "get_analysis",
257
+ "description": "Get real-time audio analysis. Returns LUFS, frequency balance, stereo width.",
258
+ "inputSchema": { "type": "object", "properties": {} }
259
+ }
260
+ ]
261
+ }
262
+ ```
263
+
264
+ ---
265
+
266
+ ## Revised Phase 2 β€” Audio Analysis
267
+
268
+ ### Architecture
269
+ ```cpp
270
+ class AudioAnalyser {
271
+ public:
272
+ void prepareToPlay(double sampleRate, int blockSize);
273
+ void processBlock(const AudioBuffer<float>& buffer); // called from audio thread
274
+
275
+ // Thread-safe queries (called from HTTP thread)
276
+ String getCompactAnalysis() const; // β†’ "-14.2 LUFS | bass:+3dB | stereo:0.8"
277
+
278
+ private:
279
+ // LUFS (from LUFSMeter β€” MIT, embed directly)
280
+ Ebu128LoudnessMeter lufsMeter;
281
+
282
+ // True Peak (JUCE oversampling)
283
+ juce::dsp::Oversampling<float> oversampler{2, 2, juce::dsp::Oversampling<float>::filterHalfBandPolyphaseIIR};
284
+ std::atomic<float> truePeak{0.0f};
285
+
286
+ // FFT (JUCE built-in)
287
+ juce::dsp::FFT fft{10}; // 1024-point
288
+ juce::dsp::WindowingFunction<float> window{1024, juce::dsp::WindowingFunction<float>::hann};
289
+
290
+ // Ring buffer (host blocks β†’ analysis frames)
291
+ std::array<float, 2048> ringBuffer{};
292
+ int writePos = 0;
293
+
294
+ // Band energy (7 bands)
295
+ struct BandEnergy {
296
+ std::atomic<float> sub_bass; // 20-60 Hz
297
+ std::atomic<float> bass; // 60-250 Hz
298
+ std::atomic<float> low_mid; // 250-500 Hz
299
+ std::atomic<float> mid; // 500-2k Hz
300
+ std::atomic<float> high_mid; // 2k-4k Hz
301
+ std::atomic<float> highs; // 4k-8k Hz
302
+ std::atomic<float> brilliance; // 8k-20k Hz
303
+ } bands;
304
+
305
+ // Stereo
306
+ std::atomic<float> stereoWidth{0.0f};
307
+ std::atomic<float> correlation{0.0f};
308
+
309
+ // Spectral centroid
310
+ std::atomic<float> centroid{0.0f};
311
+
312
+ // Silence gate
313
+ std::atomic<bool> isSilent{true};
314
+ static constexpr float silenceThresholdDb = -60.0f;
315
+ };
316
+ ```
317
+
318
+ ### `getCompactAnalysis()` output format
319
+ ```
320
+ "-14.2 LUFS | TP:-1.1 | bass:+3dB | mids:ok | highs:-2dB | stereo:0.8 | bright"
321
+ ```
322
+
323
+ Rules:
324
+ - Only report bands that deviate >Β±2dB from flat reference
325
+ - "ok" for bands within Β±2dB
326
+ - Stereo width as 0.0 (mono) to 1.0 (wide)
327
+ - "bright"/"dark"/"balanced" from spectral centroid
328
+ - Returns `"silent"` if RMS < -60dB (silence gate)
329
+ - Max ~60 tokens per call
330
+
331
+ ---
332
+
333
+ ## Implementation Order (What to Build First)
334
+
335
+ ### Sprint 1 (Week 1): Minimal Viable Plugin
336
+ 1. Create JUCE 8 CMake project (VST3 + AU targets)
337
+ 2. `PluginBridgeProcessor` β€” empty shell that passes audio through
338
+ 3. Build & load in Ableton β€” verify audio passthrough works
339
+ 4. Add `httplib.h` + background server thread
340
+ 5. Verify `curl http://localhost:16620/mcp` returns a response from inside Ableton
341
+
342
+ **Done when:** Plugin loads in Ableton, passes audio, responds to HTTP.
343
+
344
+ ### Sprint 2 (Week 2): Plugin Hosting
345
+ 6. `HostedPluginManager` β€” load a VST3 by file path
346
+ 7. GUI file picker β†’ load Pro-Q 4
347
+ 8. Audio routing through hosted plugin (copy bus-sync from juce-plugin-wrapper)
348
+ 9. Parameter enumeration via `getParameters()` + `HostedAudioProcessorParameter`
349
+ 10. Verify all Pro-Q 4 params visible in debug log
350
+
351
+ **Done when:** Pro-Q 4 loads inside PluginBridge, processes audio, params enumerated.
352
+
353
+ ### Sprint 3 (Week 3): MCP Tools
354
+ 11. Implement JSON-RPC dispatch (initialize β†’ tools/list β†’ tools/call)
355
+ 12. `list_plugins` tool
356
+ 13. `search_param` tool (fuzzy keyword match on param names)
357
+ 14. `get_params` tool
358
+ 15. `set_params` tool (with `beginChangeGesture`/`endChangeGesture`)
359
+ 16. Add to `.mcp.json`, test from Claude Code
360
+ 17. Token test: set a Pro-Q 4 band from Claude
361
+
362
+ **Done when:** Claude sets Pro-Q 4 Band 1 Gain via MCP. Full loop < 500 tokens.
363
+
364
+ ### Sprint 4 (Week 4): Audio Analysis
365
+ 18. Embed `Ebu128LoudnessMeter` (4 files, MIT)
366
+ 19. Add JUCE FFT + Hann window + ring buffer accumulator
367
+ 20. Implement 7-band energy calculation
368
+ 21. Add M/S stereo width
369
+ 22. Add True Peak via 4Γ— oversampling
370
+ 23. Implement `get_analysis` tool
371
+ 24. Silence gate
372
+ 25. Token test: full analysis < 60 tokens
373
+
374
+ **Done when:** `get_analysis()` returns meaningful data on a playing track.
375
+
376
+ ---
377
+
378
+ ## Build Requirements
379
+
380
+ | Dependency | Source | License | Size |
381
+ |---|---|---|---|
382
+ | JUCE 8 | git submodule | dual GPL/commercial | ~200MB |
383
+ | cpp-httplib | `httplib.h` drop-in | MIT | 1 file, 800KB |
384
+ | nlohmann/json | `json.hpp` drop-in | MIT | 1 file, 850KB |
385
+ | LUFSMeter core | 4 files copied | MIT | ~20KB |
386
+ | CMake 3.22+ | system | β€” | β€” |
387
+
388
+ ### CMakeLists.txt skeleton
389
+ ```cmake
390
+ cmake_minimum_required(VERSION 3.22)
391
+ project(PluginBridge VERSION 0.1.0)
392
+
393
+ add_subdirectory(JUCE)
394
+
395
+ juce_add_plugin(PluginBridge
396
+ COMPANY_NAME "PluginBridge"
397
+ PLUGIN_MANUFACTURER_CODE Plbr
398
+ PLUGIN_CODE Plbr
399
+ FORMATS VST3 AU
400
+ PRODUCT_NAME "PluginBridge"
401
+ IS_SYNTH FALSE
402
+ NEEDS_MIDI_INPUT TRUE
403
+ NEEDS_MIDI_OUTPUT TRUE
404
+ IS_MIDI_EFFECT FALSE
405
+ EDITOR_WANTS_KEYBOARD_FOCUS FALSE
406
+ COPY_PLUGIN_AFTER_BUILD TRUE
407
+ )
408
+
409
+ target_sources(PluginBridge PRIVATE
410
+ Source/PluginBridgeProcessor.cpp
411
+ Source/PluginBridgeEditor.cpp
412
+ Source/HostedPluginManager.cpp
413
+ Source/McpServer.cpp
414
+ Source/AudioAnalyser.cpp
415
+ Source/LufsMeter/Ebu128LoudnessMeter.cpp
416
+ Source/LufsMeter/SecondOrderIIRFilter.cpp
417
+ )
418
+
419
+ target_include_directories(PluginBridge PRIVATE
420
+ libs/ # httplib.h, json.hpp
421
+ Source/LufsMeter/
422
+ )
423
+
424
+ target_compile_definitions(PluginBridge PUBLIC
425
+ JUCE_PLUGINHOST_VST3=1
426
+ JUCE_PLUGINHOST_AU=1
427
+ JUCE_WEB_BROWSER=0
428
+ JUCE_USE_CURL=0
429
+ DONT_SET_USING_JUCE_NAMESPACE=1
430
+ )
431
+
432
+ target_link_libraries(PluginBridge PRIVATE
433
+ juce::juce_audio_utils
434
+ juce::juce_audio_processors
435
+ juce::juce_dsp
436
+ juce::juce_gui_basics
437
+ juce::juce_recommended_config_flags
438
+ juce::juce_recommended_lto_flags
439
+ juce::juce_recommended_warning_flags
440
+ )
441
+ ```
442
+
443
+ ---
444
+
445
+ ## Risk Register
446
+
447
+ | Risk | Likelihood | Impact | Mitigation |
448
+ |---|---|---|---|
449
+ | VST3 hosting crashes on some plugins | Medium | High | Crash isolation: catch exceptions in processBlock, unload plugin on repeated crashes |
450
+ | Port 16620 conflict | Low | Medium | Fallback: try 16621-16625, report active port in plugin GUI |
451
+ | `createPluginInstanceAsync` fails for some AU | Medium | Medium | Fall back to sync `createPluginInstance()` with timeout |
452
+ | DAW sandbox blocks localhost HTTP | Low | High | Test in Ableton/Logic/Reaper early β€” if blocked, fall back to Unix domain socket |
453
+ | Parameter IDs not stable across plugin versions | Medium | Low | Always use `search_param` by name first, not cached IDs |
454
+ | Multiple PluginBridge instances (multiple tracks) | Certain | Medium | Each instance gets its own port (16620 + instance_index). `list_plugins` shows all. |
455
+ | Thread safety: HTTP handler reads param while audio writes | Certain | High | Use `std::atomic<float>` for param cache, never call `getValue()` from HTTP thread directly |
456
+
457
+ ---
458
+
459
+ ## What Changed From Original Roadmap
460
+
461
+ | Original Plan | Revised | Why |
462
+ |---|---|---|
463
+ | Fork juce-plugin-wrapper | Build fresh (CMake, JUCE 8) | Dead repo, GPL, JUCE 6, missing everything |
464
+ | Borrow AudioPluginHost settings file | Runtime file picker | Simpler, no dependency on external app |
465
+ | Use Gist/Sound-Analyser for FFT | Use JUCE `dsp::FFT` | GPL license conflict |
466
+ | Single port for all instances | Port-per-instance | Multiple tracks need independent access |
467
+ | cpp-httplib default thread pool | Cap at 2-4 threads | Reduce OS thread pressure in plugin host |
468
+ | Phase 3 "MCP + JUCE already exists" | Implement JSON-RPC from scratch in C++ | josmithiii repo is TypeScript/stdio β€” not usable |
469
+ | 5 tools | 5 tools (unchanged) | API design validated βœ… |