|
Download FAILED-APPROACHES.md from RAM2118/pluginbridge: direct link, hf CLI and curl.
- Browser
- Download file 11 kB
-
https://huggingface.co/RAM2118/pluginbridge/resolve/main/FAILED-APPROACHES.md
- Command line
-
hf download hf://RAM2118/pluginbridge/FAILED-APPROACHES.md
-
curl -L -o FAILED-APPROACHES.md https://huggingface.co/RAM2118/pluginbridge/resolve/main/FAILED-APPROACHES.md
11 kB
| # PluginBridge β Failed Approaches & Architecture Decisions Log | |
| > Everything we tried that didn't work, why it failed, and what we did instead. | |
| > Use this to avoid repeating mistakes or to revisit with better solutions later. | |
| --- | |
| ## 1. Out-of-Process GUI via Floating Window (Sprint 2, v0.2) | |
| **What we tried:** | |
| Host the plugin in a separate Helper process. Show the plugin's GUI as a floating window from the Helper process, overlaying Ableton. | |
| **What worked:** | |
| - Audio routing via shared memory β | |
| - Crash safety (Helper crashes, Ableton survives) β | |
| - GUI showed correctly when Ableton was in WINDOWED mode β | |
| **What broke:** | |
| When Ableton was in macOS FULLSCREEN, the plugin GUI opened in a separate Space (different virtual desktop), not over Ableton. | |
| **Fixes attempted (all failed in fullscreen):** | |
| | Attempt | Code | Result | | |
| |---|---|---| | |
| | `NSApplicationActivationPolicyAccessory` | Set in `main.cpp initialise()` | No Dock icon β but broke fullscreen behavior β | | |
| | `setLevel: 3` (NSFloatingWindowLevel) | In `showAsFloatingPanel()` | Not high enough β fullscreen apps are above level 3 β | | |
| | `setLevel: 25` (NSStatusWindowLevel) | In `showAsFloatingPanel()` | Still opens in separate Space β | | |
| | `setCollectionBehavior: canJoinAllSpaces + fullScreenAuxiliary` (257) | Before `setVisible(true)` | Still separate Space β | | |
| | `setCollectionBehavior` + `stationary` + `ignoresCycle` | Full flags (1<<0 \| 1<<4 \| 1<<6 \| 1<<8) | Still separate Space β | | |
| | `orderFrontRegardless` | After setting level | Window appears but wrong Space β | | |
| | `setHidesOnDeactivate: 0` | Combined with above | No effect on Space problem β | | |
| | `setAlwaysOnTop(true)` (JUCE method) | Reverted to original approach | Was original working code but didn't fix fullscreen either β | | |
| **Root cause:** | |
| macOS fullscreen creates a dedicated compositor Space. Apple prevents other processes from injecting windows into a fullscreen app's Space. No combination of window level, collection behavior, or activation policy reliably works for cross-process window display in fullscreen. This is a macOS platform limitation, not a code bug. | |
| **What we did instead:** | |
| Switched to in-process hosting (v0.3). Plugin GUI is embedded inside PluginBridge's own editor. Ableton manages the window. No separate process window needed. | |
| --- | |
| ## 2. X Button (Close) Not Working on Helper Window (Sprint 2, v0.2) | |
| **What happened:** | |
| The Helper's floating window had minimize and close (X) buttons visible, but clicking X did nothing. | |
| **Root cause:** | |
| When the Helper ran as `NSApplicationActivationPolicyAccessory`, macOS doesn't deliver close-button clicks to accessory-policy processes the same way. The native title bar's close button was visible but non-functional. | |
| **Fix by Claude Code (commit 0632681):** | |
| Removed `makeAccessoryProcess()`, added `showAsFloatingPanel()` with objc_msgSend level tricks. This fixed the X button but broke the fullscreen behavior (see #1 above). | |
| **What we did instead:** | |
| In v0.3 (in-process hosting), there's no separate window at all. The plugin editor is a child component inside PluginBridge's editor. No close button needed β the DAW manages the window lifecycle. | |
| --- | |
| ## 3. Plugin Crashes Crashing Ableton (v0.3 initial) | |
| **What happened:** | |
| After moving to in-process hosting, loading Kickstart 2 crashed Ableton (because the plugin crash happens inside Ableton's process). | |
| **Root cause:** | |
| `loadPlugin()` called `createPluginInstance()` directly without first testing if the plugin was safe. Some plugins (Kickstart 2, Ozone 12, Gullfoss) call `abort()` or show `NSAlert` during loading, which kills the host process. | |
| **Fix attempted that was missing:** | |
| The design called for Helper-based test loading BEFORE in-process load, but it was left as a `// TODO` stub. | |
| **What we did:** | |
| Implemented `testPluginSafety()` β spawns `PluginBridgeHelper --test "/path"` before loading. If Helper survives (exit 0) β safe to load in-process. If Helper crashes β add to blocklist β show "Not compatible" β Ableton stays alive. | |
| --- | |
| ## 4. In-Process Hosting = No Crash Safety for Audio Processing | |
| **Current limitation (v0.3):** | |
| If a plugin passes the test-load but crashes during `processBlock()` later (rare but possible), Ableton will still crash. The Helper test only validates loading, not runtime stability. | |
| **Potential future solutions:** | |
| - Wrap `processBlock()` in a signal handler (fragile, not recommended) | |
| - Use `setjmp`/`longjmp` around processBlock (undefined behavior with C++) | |
| - Move back to out-of-process audio routing BUT keep GUI in-process (hybrid approach) | |
| - Accept the risk β plugins that crash during audio processing are very rare | |
| **Status:** Accepted risk. Most crashes happen during loading, not during audio processing. | |
| --- | |
| ## 5. Plugin Scanning via PluginDirectoryScanner (Early research, never shipped) | |
| **What was planned:** | |
| Use JUCE's `PluginDirectoryScanner` to scan all installed plugins on first run. | |
| **Why it was rejected:** | |
| Some plugins execute code during scanning (show NSAlert dialogs, call abort()). This would crash the scanner β or worse, crash Ableton if scanning happened in-process. | |
| **What we did instead:** | |
| Read `.vst3` and `.component` filenames from disk (no code execution). Only actually load a plugin when the user selects it (after Helper test). | |
| --- | |
| ## 6. `addDefaultFormats()` β Deleted in JUCE 8 | |
| **What happened:** | |
| Early code used `formatManager.addDefaultFormats()` which doesn't exist in JUCE 8. | |
| **Fix:** | |
| Explicitly register formats: | |
| ```cpp | |
| formatManager.addFormat(new juce::VST3PluginFormat()); | |
| formatManager.addFormat(new juce::AudioUnitPluginFormat()); | |
| ``` | |
| --- | |
| ## 7. `MSG_NOSIGNAL` β Doesn't Exist on macOS | |
| **What happened:** | |
| Socket code used `MSG_NOSIGNAL` flag which is Linux-only. | |
| **Fix:** | |
| ```cpp | |
| signal(SIGPIPE, SIG_IGN); // Global | |
| int nosigpipe = 1; | |
| setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); // Per-socket | |
| ``` | |
| --- | |
| ## 8. `juce::Thread::setCurrentThreadPriority()` β Removed in JUCE 8 | |
| **What happened:** | |
| Audio thread priority setting via JUCE API no longer exists. | |
| **Fix:** | |
| Use native pthread: | |
| ```cpp | |
| struct sched_param param; | |
| param.sched_priority = sched_get_priority_max(SCHED_FIFO); | |
| pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m); | |
| ``` | |
| --- | |
| ## 9. `create_audio_track()` Serialization Error (Ableton LOM, not PluginBridge) | |
| **Context:** From the broader AI Music Production system, not PluginBridge-specific. | |
| **What happened:** | |
| `song.create_audio_track()` in Ableton's LOM returns a "not JSON serializable" error. | |
| **Reality:** | |
| The track IS created. The error is in the return value serialization, not the action. | |
| **Workaround:** | |
| Always check `len(song.tracks)` to confirm track was created, ignore the error message. | |
| --- | |
| ## Architecture Evolution Summary | |
| ``` | |
| v0.1 (Sprint 1): Plugin shell + MCP server only (no hosting) | |
| v0.2 (Sprint 2): Out-of-process hosting via shared memory | |
| β Crash-safe audio | |
| β GUI in separate window β fullscreen broken | |
| v0.3 (Sprint 3): In-process hosting (like SnappySnap) | |
| β GUI embedded in Ableton's window | |
| β Helper tests plugin safety before loading | |
| β No crash safety during audio processBlock (accepted risk) | |
| ``` | |
| --- | |
| ## 10. CAContext + CALayerHost β Cross-Process GUI Embedding (v0.4, 2026-05) | |
| **What we tried:** | |
| After the floating-window fullscreen failure (see #1), we tried the macOS private API for sharing a `CALayer` tree across processes: | |
| - **Helper (server):** `CAContext contextWithCGSConnection:options:` β wraps the plugin's GUI layer and exposes it as a `uint32_t contextId` | |
| - **Plugin (client):** `CALayerHost.contextId = contextId` β a `CALayer` subclass that renders the remote context inline in JUCE's NSView | |
| The contextId was sent over the existing IPC socket. No Mach port bootstrapping needed. | |
| Also fixed a secondary bug: the Helper's NSWindow was never ordered front. Fix was to position it off-screen at `(-32000, -32000)` and call `orderFront:`. | |
| **What happened:** | |
| - Build succeeded, API calls compiled and ran without errors | |
| - `CALayerHost` was created and added as a sublayer to JUCE's NSView | |
| - The plugin GUI never appeared inside the PluginBridge editor window | |
| - Debug showed: `contextId` was valid (non-zero), `CALayerHost` was attached, but nothing rendered | |
| **Why it failed:** | |
| Ableton Live runs inside the macOS app sandbox / process isolation model. `CAContext` cross-process layer sharing requires both processes to be in the same WindowServer session with compatible entitlements. Ableton's process environment appears to block this β the compositor receives the layer tree but doesn't render it into a foreign process's view hierarchy. | |
| This is the same fundamental limitation as the floating-window approach: Apple does not provide a reliable, supported API for one process to embed rendering from another process's view hierarchy. | |
| **Attempts made:** | |
| | Attempt | Result | | |
| |---|---| | |
| | `CAContext` + off-screen NSWindow + `orderFront:` at (-32000,-32000) | API worked, nothing rendered β | | |
| | `CALayerHost` added as sublayer to JUCE NSView root layer | Layer hierarchy correct, blank β | | |
| | Explicit `setNeedsDisplay` / `setNeedsLayout` on CALayerHost | No effect β | | |
| | Sending contextId immediately after `createEditor()` | Same result β | | |
| **Files changed (then reverted):** | |
| - `Source/Shared/PrivateCA.h` β declared `CAContext`, `CALayerHost`, `CGSMainConnectionID` | |
| - `Source/Helper/HelperPluginHost.mm` β `showGui()` using `CAContext` | |
| - `Source/Plugin/PluginBridgeEditor.mm` β `connectRemoteLayer()` using `CALayerHost` | |
| **What we did instead:** | |
| Moved to the **hybrid safety-scan architecture** (v0.5): | |
| - Plugins that pass a safety scan are loaded **in-process** β their `createEditor()` runs inside PluginBridge's process, producing a standard JUCE `AudioProcessorEditor*` that embeds natively | |
| - Plugins that fail the scan are loaded only in Helper for audio-only mode (no GUI) | |
| - No cross-process GUI required at all | |
| This trades crash isolation for reliability: safe plugins (99% of the library) get full GUI; only crash-prone plugins (iZotope Ozone, Neutron, a few others) are audio-only. | |
| --- | |
| ## Revisit Candidates (Future) | |
| | Problem | Possible Future Solution | Difficulty | | |
| |---|---|---| | |
| | Fullscreen floating window from separate process | Wait for Apple to provide a supported API, or use private `CGSOrderWindow` API | Hard, fragile | | |
| | Runtime crash safety (processBlock) | Hybrid: audio in Helper, GUI in-process. Two instances synced via IPC. | Very hard | | |
| | Blocklist is manual (per-machine) | Share blocklist via cloud / community database | Medium | | |
| | Test-load adds 2-3 second delay first time | Cache results aggressively, pre-test in background on startup | Medium | | |
| | Some plugins crash in createEditor() not createInstance() | Test GUI creation in Helper too (spawn with --test-gui flag) | Medium | | |