RAM2118 commited on
Commit
2aa5050
·
verified ·
1 Parent(s): 58c47dc

Update CLAUDE.md — reflects out-of-process architecture, current status, lessons learned

Browse files
Files changed (1) hide show
  1. CLAUDE.md +90 -83
CLAUDE.md CHANGED
@@ -2,118 +2,125 @@
2
 
3
  ## What This Is
4
  A JUCE-based VST3/AU plugin that:
5
- 1. Hosts any third-party VST3/AU plugin inside itself (audio passes through)
6
  2. Exposes ALL hosted plugin parameters via a local MCP server (port 16620)
7
- 3. Provides real-time audio analysis (LUFS, FFT bands, stereo width)
8
  4. Any AI that speaks MCP (Claude Code, Codex CLI, Gemini CLI) can control any plugin
9
 
10
  ## Current Status
11
- - **Sprint 1:** ✅ Done — plugin shell + MCP server responding
12
- - **Sprint 2:** ✅ Done — hosts Pro-Q 4 (737 params), routes audio, MCP tools wired
13
- - **Sprint 3:** Next — full MCP client integration test from Claude Code
14
- - **Sprint 4:** Pending — real-time audio analysis
15
 
16
- ## Project Decisions (Confirmed)
17
- - **Platform:** macOS only (for now)
18
- - **License:** GPL (JUCE GPL mode — no commercial license needed, doesn't hamper project)
19
- - **VST3 install path:** `~/Library/Audio/Plug-Ins/VST3/`
20
- - **AU install path:** `~/Library/Audio/Plug-Ins/Components/`
21
- - **Test plugins:** FabFilter Pro-Q 4 (EQ, 737 params), LA-2A compressor (fewer params)
22
- - **Xcode:** CLI tools 2410+ (no full Xcode.app needed)
23
- - **CMake:** 3.28 via pip (system CMake 4.x incompatible with JUCE 8)
24
- - **DAW:** Ableton Live (primary), Logic Pro (secondary AU test)
25
- - **Build generator:** Unix Makefiles or Ninja (NOT Xcode generator — no Xcode.app)
26
 
27
- ## Architecture (Single Plugin Host, Not a Graph)
28
  ```
29
- PluginBridgeProcessor (AudioProcessor — the outer VST3/AU shell)
30
- ├── HostedPluginManager → loads ONE inner VST3/AU, enumerates params
31
- ├── McpServer (httplib) → JSON-RPC 2.0 on http://127.0.0.1:16620/mcp
32
- ├── AudioAnalyser → LUFS + 7-band FFT + stereo width in processBlock
33
- └── processBlock():
34
- buffer → hostManager.processBlock(buffer, midi)
35
- → output (audio routed through hosted plugin)
 
 
 
 
 
36
  ```
37
 
 
 
38
  ## Build System
39
  - **JUCE 8** via CMake (git submodule at `/JUCE`)
40
- - **cpp-httplib** — single header `libs/httplib.h` (MIT)
41
- - **nlohmann/json** — single header `libs/json.hpp` (MIT)
42
- - **LUFSMeter** — 4 files in `Source/LufsMeter/` (MIT, from klangfreund/LUFSMeter) — Sprint 4
43
- - **Target formats:** VST3 + AU (macOS only)
 
44
  - **Build:** `cd build && cmake .. -DCMAKE_C_COMPILER=/usr/bin/cc -DCMAKE_CXX_COMPILER=/usr/bin/c++ && cmake --build . --config Release`
45
- - **Install:** `COPY_PLUGIN_AFTER_BUILD=TRUE` copies to `~/Library/Audio/Plug-Ins/VST3/`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
  ## MCP Protocol
48
- - Transport: Streamable HTTP (NOT stdio — we're inside a DAW process)
49
  - Endpoint: `POST http://127.0.0.1:16620/mcp`
50
- - Health check: `GET http://127.0.0.1:16620/health`
51
  - Wire format: JSON-RPC 2.0
52
- - Handshake: `initialize` → `notifications/initialized` → then tools/list, tools/call
53
  - 5 tools: `list_plugins`, `search_param`, `get_params`, `set_params`, `get_analysis`
54
 
55
- ## Key JUCE 8 APIs (Verified — these WORK)
 
 
 
 
 
 
 
56
  - `formatManager.addFormat(new juce::VST3PluginFormat())` — explicit registration
57
  - `formatManager.addFormat(new juce::AudioUnitPluginFormat())` — explicit registration
58
  - `instance->enableAllBuses()` — MUST call before using
59
  - `plugin->getParameters()` → `Array<AudioProcessorParameter*>`
60
- - Cast to `juce::AudioPluginInstance::HostedParameter*` for `getParameterID()`
61
  - `param->beginChangeGesture()` / `setValueNotifyingHost(val)` / `endChangeGesture()`
62
- - `juce::dsp::FFT` for spectral analysis
63
- - `juce::PluginDirectoryScanner` for system plugin scan
64
- - `juce::KnownPluginList` for storing scan results
65
 
66
  ## Key JUCE 8 APIs (BROKEN — do NOT use)
67
- - ❌ `addDefaultFormats()` — deleted in JUCE 8, use explicit addFormat() calls
68
- - ❌ `addDefaultFormatsToManager()` — also deleted
69
  - ❌ `getParameter(int)` / `setParameter(int, float)` — deprecated, asserts
70
 
71
- ## Plugin Discovery UX
72
- - Click "Select Plugin..." button → first click triggers background scan
73
- - Button shows "Scanning..." while scanning (background thread, non-blocking)
74
- - After scan: popup menu grouped by manufacturer → plugin name
75
- - Subsequent clicks: instant menu (cached scan results)
76
- - NO file browser — clean DAW-style UX
77
-
78
- ## Design Laws (Non-Negotiable)
79
- - Never return all parameters — search first, fetch by ID
80
- - Delta only after param changes
81
- - Single-line compact responses (numbers only)
82
- - Batch operations (set 10 params in one call)
83
- - Silence detection (no analysis if RMS < -60dB)
84
- - Target: <2,000 tokens per full mixing session
85
 
86
- ## Thread Safety Rules
87
- - HTTP handlers run on httplib's thread pool — NOT audio thread
88
- - Plugin scan runs in background thread — NOT message thread (crashes DAW otherwise)
89
- - Use `CriticalSection` / `ScopedLock` for plugin hot-swap in processBlock
90
- - Cap httplib thread pool to 2 threads
91
- - `httplib::Server` lifecycle: constructor starts thread, destructor calls stop()+join()
92
-
93
- ## What NOT To Do
94
- - Don't use `AudioProcessorGraph` — we host ONE plugin, not a graph
95
- - Don't scan plugins on editor open — scan only on button click, in background thread
96
- - Don't use `addDefaultFormats()` — deleted in JUCE 8
97
- - Don't use stdio MCP transport — plugin is inside DAW, not a subprocess
98
- - Don't target Windows yet — macOS only for now
99
-
100
- ## Repo Structure
101
  ```
102
  pluginbridge/
103
- ├── CLAUDE.md ← this file
104
- ├── ROADMAP.md ← full technical roadmap
105
- ├── SYNC.md ← coordination between HF Claude and Claude Code
106
- ├── CMakeLists.txt ← JUCE 8 CMake (LANGUAGES C CXX)
107
- ├── .gitignore
108
- ├── JUCE/ ← git submodule
109
  ├── libs/
110
- │ ├── README.md ← download instructions
111
- │ ├── httplib.h ← cpp-httplib
112
- │ └── json.hpp ← nlohmann/json
113
- └── Source/
114
- ├── PluginBridgeProcessor.h/.cpp ← main AudioProcessor, owns HostedPluginManager + McpServer
115
- ├── PluginBridgeEditor.h/.cpp ← GUI: status + plugin selector popup menu
116
- ├── HostedPluginManager.h/.cpp ← loads VST3/AU, enumerates params, routes audio
117
- ├── McpServer.h/.cpp ← HTTP server, JSON-RPC dispatch, 5 tools
118
- └── AudioAnalyser.h/.cpp ← stub (Sprint 4)
 
 
 
 
 
 
 
 
 
 
119
  ```
 
 
 
 
 
 
 
 
 
2
 
3
  ## What This Is
4
  A JUCE-based VST3/AU plugin that:
5
+ 1. Hosts any third-party VST3/AU plugin in a **separate crash-safe process**
6
  2. Exposes ALL hosted plugin parameters via a local MCP server (port 16620)
7
+ 3. Audio routes through shared memory (zero added latency)
8
  4. Any AI that speaks MCP (Claude Code, Codex CLI, Gemini CLI) can control any plugin
9
 
10
  ## Current Status
11
+ - **Sprint 1:** ✅ Done — plugin shell + MCP server
12
+ - **Sprint 2:** ✅ Done — out-of-process plugin hosting, crash-safe, GUI working
13
+ - **Sprint 3:** Next — full MCP client integration test (search/get/set params via curl)
14
+ - **Sprint 4:** Pending — real-time audio analysis (LUFS, FFT)
15
 
16
+ ## Architecture: OUT-OF-PROCESS (Crash-Safe)
 
 
 
 
 
 
 
 
 
17
 
 
18
  ```
19
+ ┌─ Ableton Process ─────────────────┐ ┌─ PluginBridgeHelper Process ─────┐
20
+ │ │ │ │
21
+ │ PluginBridge.vst3 (thin shell) │ │ Standalone executable │
22
+ │ ├── McpServer (port 16620) │ │ ├── Loads third-party plugin │
23
+ │ ├── HelperConnection │ │ ├── processBlock via shared mem │
24
+ │ │ ├── Shared memory (audio) │◄───►│ ├── Shows plugin GUI (floating) │
25
+ │ │ ├── Semaphores (sync) │ │ └── Handles param get/set │
26
+ │ │ └── Unix socket (IPC/cmds) │◄───►│ │
27
+ │ └── processBlock(): │ │ If crash → only helper dies │
28
+ │ write shm → signal → wait │ │ Ableton stays alive │
29
+ │ read output from shm │ │ │
30
+ └────────────────────────────────────┘ └───────────────────────────────────┘
31
  ```
32
 
33
+ **Why out-of-process:** Some plugins (Ozone 12, Kickstart 2) call abort() or show NSAlert during loading, which crashes the entire host process. By running in a separate process, crashes are isolated — Ableton never dies.
34
+
35
  ## Build System
36
  - **JUCE 8** via CMake (git submodule at `/JUCE`)
37
+ - **Two build targets:**
38
+ - `PluginBridge` — VST3/AU plugin (loaded in DAW)
39
+ - `PluginBridgeHelper` — standalone executable (bundled inside .vst3/Contents/Resources/)
40
+ - **cpp-httplib** — `libs/httplib.h` (MIT, header-only)
41
+ - **nlohmann/json** — `libs/json.hpp` (MIT, header-only)
42
  - **Build:** `cd build && cmake .. -DCMAKE_C_COMPILER=/usr/bin/cc -DCMAKE_CXX_COMPILER=/usr/bin/c++ && cmake --build . --config Release`
43
+ - **CMake 3.28** required (via pip — system CMake 4.x incompatible with JUCE 8)
44
+
45
+ ## Project Decisions (Confirmed)
46
+ - **Platform:** macOS only
47
+ - **License:** GPL (JUCE GPL mode)
48
+ - **VST3 install:** `~/Library/Audio/Plug-Ins/VST3/`
49
+ - **Test plugins:** Pro-Q 4, LA-2A, Kickstart 2 (crash test)
50
+ - **Xcode CLI tools:** 2410+
51
+ - **DAW:** Ableton Live 12 (primary)
52
+
53
+ ## IPC Between Plugin ↔ Helper
54
+ - **Audio:** POSIX shared memory (`shm_open` + `mmap`) — zero-copy
55
+ - **Sync:** Named semaphores (`sem_open`) — ~microsecond wakeup
56
+ - **Commands:** Unix domain socket, newline-delimited JSON
57
+ - **Protocol:** See `Source/Shared/IPCProtocol.h` for all message formats
58
 
59
  ## MCP Protocol
 
60
  - Endpoint: `POST http://127.0.0.1:16620/mcp`
61
+ - Health: `GET http://127.0.0.1:16620/health`
62
  - Wire format: JSON-RPC 2.0
 
63
  - 5 tools: `list_plugins`, `search_param`, `get_params`, `set_params`, `get_analysis`
64
 
65
+ ## Plugin GUI Behavior
66
+ - GUI runs in Helper process as a **floating window** (always-on-top)
67
+ - Uses `NSApplicationActivationPolicyAccessory` — no Dock icon, no focus steal
68
+ - `orderFrontRegardless` — shows without switching away from Ableton
69
+ - Toggle via "Open GUI" / "Close GUI" button in PluginBridge's panel
70
+ - If plugin crashes → GUI disappears, PluginBridge shows "Plugin crashed - click Reload"
71
+
72
+ ## Key JUCE 8 APIs (Verified Working)
73
  - `formatManager.addFormat(new juce::VST3PluginFormat())` — explicit registration
74
  - `formatManager.addFormat(new juce::AudioUnitPluginFormat())` — explicit registration
75
  - `instance->enableAllBuses()` — MUST call before using
76
  - `plugin->getParameters()` → `Array<AudioProcessorParameter*>`
77
+ - `dynamic_cast<juce::AudioPluginInstance::HostedParameter*>(param)` for stable ID
78
  - `param->beginChangeGesture()` / `setValueNotifyingHost(val)` / `endChangeGesture()`
79
+ - `juce_add_gui_app()` for Helper target in CMake
 
 
80
 
81
  ## Key JUCE 8 APIs (BROKEN — do NOT use)
82
+ - ❌ `addDefaultFormats()` — deleted
83
+ - ❌ `juce::Thread::setCurrentThreadPriority()` — removed, use native pthread
84
  - ❌ `getParameter(int)` / `setParameter(int, float)` — deprecated, asserts
85
 
86
+ ## macOS-Specific Gotchas (Learned the Hard Way)
87
+ - ❌ `MSG_NOSIGNAL` — doesn't exist on macOS. Use `signal(SIGPIPE, SIG_IGN)` + `SO_NOSIGPIPE`
88
+ - ❌ Plugin scanning in background thread — some plugins show NSAlert → crash. Solution: don't scan at all, just list .vst3/.component files from disk
89
+ - ❌ `NSApplicationActivationPolicyRegular` — steals focus from DAW. Use `Accessory` (policy 1)
90
+ - ❌ `activateIgnoringOtherApps:YES` — switches away from Ableton. Use `orderFrontRegardless` instead
91
+ - ✅ `posix_spawn` — works for launching helper from within a plugin process
 
 
 
 
 
 
 
 
92
 
93
+ ## File Structure
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  ```
95
  pluginbridge/
96
+ ├── CMakeLists.txt (builds PluginBridge + PluginBridgeHelper)
97
+ ├── JUCE/ (git submodule)
 
 
 
 
98
  ├── libs/
99
+ │ ├── httplib.h
100
+ │ └── json.hpp
101
+ ├── Source/
102
+ │ ├── Plugin/ ← VST3/AU (runs inside Ableton)
103
+ │ │ ├── PluginBridgeProcessor.h/.cpp
104
+ │ │ ├── PluginBridgeEditor.h/.cpp
105
+ │ │ ├── HelperConnection.h/.cpp
106
+ │ │ └── McpServer.h/.cpp
107
+ │ ├── Helper/ ← Standalone exe (separate process)
108
+ │ │ ├── main.cpp
109
+ │ │ ├── HelperPluginHost.h/.cpp
110
+ │ │ └── HelperIPC.h/.cpp
111
+ │ └── Shared/ ← Used by both targets
112
+ │ ├── SharedAudioBuffer.h
113
+ │ ├── IPCProtocol.h
114
+ │ └── Constants.h
115
+ ├── CLAUDE.md
116
+ ├── ROADMAP.md
117
+ └── SYNC.md
118
  ```
119
+
120
+ ## What NOT To Do
121
+ - Don't load plugins in-process — crashes DAW with misbehaving plugins
122
+ - Don't use `PluginDirectoryScanner` — triggers plugin code execution → crash
123
+ - Don't use `addDefaultFormats()` — deleted in JUCE 8
124
+ - Don't steal focus when showing GUI — use Accessory policy
125
+ - Don't use `MSG_NOSIGNAL` — macOS doesn't have it
126
+ - Don't use `juce::Thread::setCurrentThreadPriority()` — removed in JUCE 8