Skip to content

Commit f57f6d4

Browse files
mstitsclaude
andcommitted
docs: convert all diagrams to Mermaid
- ARCHITECTURE.md: module graph, render pipeline, snapshot lifecycle, MIDI routing, plugin hosting → all Mermaid. Reflects new CLAP module + bus inserts. - MAD_FORMAT.md: top-level file layout + TOOO chunk layout → Mermaid flowcharts. - PLUGINS.md: added plugin signal-path Mermaid showing per-channel inserts, bus inserts, global inserts, master limiter chain. - FEATURE_ROADMAP.md: progress pie (27 shipped / 4 scaffold / 8 deferred) + next- steps flow. - README.md: module breakdown updated to reflect CLAP + VST3-direct + mastering modules + no-JUCE. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1ac53ab commit f57f6d4

5 files changed

Lines changed: 150 additions & 96 deletions

File tree

README.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,12 @@ graph TD;
121121
subgraph IOLayer [I/O & File Management]
122122
IO_Bank[UnifiedSampleBank]
123123
IO_MAD[MADParser / MADWriter]
124-
IO_VST[ToooT_VST3 JUCE Bridge]
124+
IO_VST[ToooT_VST3 — Steinberg SDK direct]
125+
IO_CLAP[ToooT_CLAP — BSD-3 plugin host]
125126
UI_NI <==> IO_VST
127+
UI_NI <==> IO_CLAP
126128
IO_VST -.-> Audio_AUv3
129+
IO_CLAP -.-> Audio_AUv3
127130
IO_Bank -->|Raw PCM Pointer| Audio_Voice
128131
IO_MAD -.->|Deserializes| State_Seq
129132
end
@@ -137,11 +140,12 @@ graph TD;
137140

138141
### Module Breakdown
139142

140-
- **`ToooT_Core`**: The beating heart of the DAW. Contains the zero-allocation `AudioRenderNode`, pattern sequencer, envelope evaluators, and atomic data structures.
141-
- **`ToooT_UI`**: The modern, industrial "glassmorphism" SwiftUI frontend. Houses the GPU-accelerated pattern grid, the mixer, the JIT console, and the waveform editor.
142-
- **`ToooT_IO`**: Custom parsers (`MADParser`, `MADWriter`) providing bit-perfect backwards compatibility.
143-
- **`ToooT_Plugins`**: The AUv3 hosting layer.
144-
- **`ToooT_VST3`**: The Objective-C++ JUCE/Steinberg bridge for VST3 plugin hosting.
143+
- **`ToooT_Core`**: The beating heart of the DAW. Contains the zero-allocation `AudioRenderNode`, pattern sequencer, envelope evaluators, atomic data structures, `MasterMeter` (LUFS / true-peak / phase correlation), `MusicTheory`, `Arpeggiator`, `Arrangement`, `SessionGrid`, `Automation`, `Scenes`, `StarterContent`, `Fuzzer`, `StabilityMonitor`.
144+
- **`ToooT_UI`**: SwiftUI frontend. Houses the GPU-accelerated pattern grid, mixer, JIT console, waveform editor, **arrangement timeline**, **session clip-launch grid**, command palette, undo-history browser, crash-recovery sheet, video sync, JS scripting host, TTS via `AVSpeechSynthesizer`.
145+
- **`ToooT_IO`**: Custom parsers (`MADParser`, `MADWriter`), `MIDI2Manager` (MIDI 2.0 UMP + MPE dispatch), `SpatialManager` (PHASE 3D audio).
146+
- **`ToooT_Plugins`**: AUv3 hosting + bundled DSP (`ReverbPlugin`, `StereoWidePlugin`, `TruePeakLimiter`, `MultibandCompressor`, `LinearPhaseEQ`) + `MasteringExport` (TPDF dither + LUFS normalize) + `OfflineDSP` + `GPU_DSP` (Metal compute kernels).
147+
- **`ToooT_VST3`**: Objective-C++ bridge linking directly against Steinberg's VST3 SDK. No JUCE.
148+
- **`ToooT_CLAP` / `ToooT_CLAP_C`**: BSD-3-Clause CLAP plugin host. BSD-licensed ABI vendored; C loader wraps `dlopen` on `.clap` bundles; Swift layer manages instance lifecycle.
145149

146150
---
147151

docs/ARCHITECTURE.md

Lines changed: 90 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,36 @@
22

33
## Module graph
44

5-
```
6-
ProjectToooTApp (executable)
7-
8-
9-
ToooT_UI ─────────────────────► ToooT_IO
10-
│ │ │ │
11-
│ │ ▼ ▼
12-
│ │ ToooT_Plugins ► ToooT_Core
13-
│ │
14-
│ └───► ToooT_VST3 (Obj-C++)
15-
16-
└── host process: AudioHost, Timeline, TrackerWorkspace, Metal grid, JIT shell
5+
```mermaid
6+
graph TD
7+
App[ProjectToooTApp<br/>executable]
8+
UI[ToooT_UI<br/>SwiftUI, Metal, AudioHost, Timeline]
9+
Core[ToooT_Core<br/>AudioRenderNode, SynthVoice, EngineSharedState]
10+
IO[ToooT_IO<br/>MADParser/Writer, MIDI2Manager, SpatialManager]
11+
Plugins[ToooT_Plugins<br/>Reverb, StereoWide, MultibandComp, LinearPhaseEQ, TruePeakLimiter]
12+
VST3[ToooT_VST3<br/>Steinberg SDK, Obj-C++]
13+
CLAP[ToooT_CLAP<br/>BSD-3 plugin host]
14+
CLAPC[ToooT_CLAP_C<br/>C ABI + dlopen loader]
15+
16+
App --> UI
17+
UI --> Core
18+
UI --> IO
19+
UI --> Plugins
20+
UI --> CLAP
21+
IO --> Core
22+
Plugins --> Core
23+
Plugins --> VST3
24+
CLAP --> CLAPC
1725
```
1826

1927
| Module | Role |
2028
|---|---|
21-
| `ToooT_Core` | Zero-allocation render loop: `AudioRenderNode`, `SynthVoice`, `RenderResources`, atomic snapshot bridge, `EngineSharedState`, `AtomicRingBuffer`, `UnifiedSampleBank`. |
22-
| `ToooT_IO` | Parsers / writers (`MADParser`, `MADWriter`, `FormatTranspiler`), `MIDI2Manager` (UMP in+out, clock), `SpatialManager` (PHASE). |
23-
| `ToooT_Plugins` | Bundled DSP units: `ReverbPlugin`, `StereoWidePlugin`, `BaseEffect`. `AUv3HostManager` for system plugin discovery. Offline/pattern DSP helpers. |
24-
| `ToooT_VST3` | Obj-C++ wrapper (`JUCEVST3Host`). Gated behind `TOOOT_VST3_SDK_AVAILABLE` — ships as an inert stub unless the Steinberg SDK is vendored. |
25-
| `ToooT_UI` | SwiftUI workbench, Metal pattern grid, Piano Roll, envelope / automation / mixer / spatial views, JIT shell, `AudioHost` (the real engine wiring), `Timeline` (MainActor sync loop). |
29+
| `ToooT_Core` | Zero-allocation render loop: `AudioRenderNode`, `SynthVoice`, `RenderResources`, atomic snapshot bridge, `EngineSharedState`, `AtomicRingBuffer`, `UnifiedSampleBank`, `MasterMeter`, music theory + arrangement + session models. |
30+
| `ToooT_IO` | Parsers / writers (`MADParser`, `MADWriter`, `FormatTranspiler`), `MIDI2Manager` (UMP in+out, MPE dispatch, clock), `SpatialManager` (PHASE). |
31+
| `ToooT_Plugins` | Bundled DSP units: `ReverbPlugin`, `StereoWidePlugin`, `TruePeakLimiter`, `MultibandCompressor`, `LinearPhaseEQ`, `MasteringExport`, `AUv3HostManager`, `OfflineDSP` + `GPU_DSP`. |
32+
| `ToooT_VST3` | Obj-C++ bridge that links directly against Steinberg's VST3 SDK. Gated behind `TOOOT_VST3_SDK_AVAILABLE`. |
33+
| `ToooT_CLAP` / `ToooT_CLAP_C` | BSD-3-Clause CLAP host. `_C` carries the minimal ABI header + dlopen loader; the Swift side does discovery + instance management. |
34+
| `ToooT_UI` | SwiftUI workbench, Metal pattern grid, Piano Roll, arrangement + session views, JIT shell, `AudioHost` (engine wiring), `Timeline` (MainActor sync loop). |
2635

2736
## Thread model
2837

@@ -32,72 +41,71 @@ Three threads are load-bearing:
3241
|---|---|---|
3342
| Audio I/O (CoreAudio) | `nonisolated` — entered via `renderBlock` | No heap allocation, no locking, no Swift ARC traffic, no `@MainActor` calls. Reads snapshot via a single `Atomic<UInt>` exchange. |
3443
| UI | `@MainActor` | Never dereferences audio-thread pointers directly. Reads playback state via `EngineSharedState` snapshot fields written by the render thread (naturally atomic on arm64 aligned word stores). |
35-
| Background | default actor | MIDI clock timer (`DispatchSource.userInteractive`), recording tap drain, async export. |
44+
| Background | default actor | MIDI clock timer (`DispatchSource.userInteractive`), recording tap drain, async export, autosave. |
3645

3746
The single legal write path from UI to engine is `AudioRenderNode.swapSnapshot(_:SongSnapshot)`, which performs an atomic pointer exchange and queues the old snapshot for main-thread deallocation via `processDeallocations`.
3847

3948
## Render pipeline (per audio buffer)
4049

41-
```
42-
AUInternalRenderBlock
43-
44-
├─ 1. Load snapshot (Atomic.load, retained for the block scope)
45-
├─ 2. Drain event ringbuf (MIDI note-on/off from MIDI2Manager → voice.trigger)
46-
47-
├─ 3. Per-tick loop while samplesProcessedInBlock < frames:
48-
│ a. processTickSequencer (shared by realtime + offline, per L33)
49-
│ – advance row, dispatch pattern effects, build activeChannelIndices
50-
│ b. for ch in activeChannelIndices:
51-
│ – voice.process (fast path: vDSP_vramp+vclip+vlint+vma;
52-
│ slow path: scalar Hermite for loop/pingpong)
53-
│ – sidechain peak track (channelVolume gates ducking)
54-
│ – PDC delay buffer (per-channel circular)
55-
│ – spatialPush → SpatialManager (channels 0–31, PHASE stream nodes)
56-
│ – vDSP_vsma into sumL/sumR (with ducking multiplier)
57-
│ c. metronome tone sum
58-
59-
├─ 4. masterVolume * 0.5 (L26 — both renderBlock + renderOffline)
60-
├─ 5. Master safety limiter (1ms attack / 100ms release)
61-
│ — or soft clip (tanh) when limiter disabled
62-
├─ 6. peakLevel for UI meters
63-
└─ 7. memcpy sumL/sumR → output bus
50+
```mermaid
51+
flowchart TD
52+
A[AUInternalRenderBlock called] --> B[Load snapshot<br/>Atomic.load + retain]
53+
B --> C[Drain MIDI event ringbuffer<br/>dispatch note-on/off to voices]
54+
C --> D{Per-tick loop<br/>while samples < frames}
55+
D -->|tick| E[processTickSequencer<br/>advance row + dispatch effects<br/>build activeChannelIndices]
56+
E --> F[For each active channel:<br/>voice.process → scratchL/R/mono<br/>vDSP_vlint fast path or scalar Hermite]
57+
F --> G[Sidechain peak track<br/>PDC delay buffer<br/>spatialPush → PHASE]
58+
G --> H[vDSP_vsma into sumL/sumR<br/>+ aux bus accumulation]
59+
H --> I[Metronome tone sum]
60+
I --> D
61+
D -->|done| J[Run bus insert chains<br/>vsma bus outputs into master]
62+
J --> K[masterVolume × 0.5<br/>Master limiter or soft-clip]
63+
K --> L[MasterMeter: LUFS + true-peak + phase correlation]
64+
L --> M[memcpy sumL/sumR → ioData]
6465
```
6566

66-
`RenderBlockWrapper` (in `AudioHost.swift`) wraps `renderBlock` with per-channel AUv3 insert chains (4 per channel + 1 instrument slot), then the global StereoWide + Reverb inserts.
67+
`RenderBlockWrapper` (in `AudioHost.swift`) wraps `renderBlock` with per-channel AUv3 insert chains (4 per channel + 1 instrument slot), per-bus AUv3 insert chains (4 per bus), then the global StereoWide + Reverb inserts.
6768

6869
## Snapshot lifecycle
6970

7071
`SongSnapshot` is a value type with raw pointers into `SequencerData`. `SnapshotBox` wraps it so Unmanaged retain/release can be used. `_snapshotPtr: Atomic<UInt>` holds the bitPattern of the current box.
7172

72-
Swap flow:
73-
1. UI builds a new `SongSnapshot` (same-shape, possibly updated `events` / `instruments` pointers).
74-
2. `swapSnapshot` calls `Unmanaged.passRetained(newBox).toOpaque()``_snapshotPtr.exchange`.
75-
3. The old pointer tag is pushed into `deallocationQueue` (a lock-free ring buffer).
76-
4. Main thread drains `deallocationQueue` via `processDeallocations()` and releases the retained box.
77-
78-
The audio thread reads the snapshot with `retain()` / `release()` around the block to keep it alive across a potential swap mid-render.
73+
```mermaid
74+
sequenceDiagram
75+
participant UI as UI thread
76+
participant Atomic as _snapshotPtr<br/>(Atomic&lt;UInt&gt;)
77+
participant Queue as deallocationQueue
78+
participant Audio as Audio thread
79+
80+
UI->>UI: Build new SongSnapshot + SnapshotBox
81+
UI->>Atomic: exchange(new raw pointer)
82+
Atomic-->>UI: returns old raw pointer
83+
UI->>Queue: push(old)
84+
Audio->>Atomic: load (acquire)
85+
Atomic-->>Audio: current raw
86+
Audio->>Audio: retain → process block → release
87+
UI->>Queue: processDeallocations()<br/>release old box
88+
```
7989

8090
## Memory ownership
8191

8292
- `UnifiedSampleBank` owns one giant PCM slab (256 MiB default). Samples have no per-region retain count; `SampleRegion.offset+length` indexes the slab.
83-
- `RenderResources` owns all render-thread scratch buffers (per-channel delay, voices, mixing sums, envelope scratch). Allocated once, lives for the life of `AudioEngine`.
93+
- `RenderResources` owns all render-thread scratch buffers (per-channel delay, voices, mixing sums, envelope scratch, per-thread voice scratch pool for the concurrent offline render). Allocated once, lives for the life of `AudioEngine`.
8494
- `EngineSharedState` is a plain C struct of `Int32` / `Float`. The only cross-thread state. All writes from UI must go through `Atomic<T>` wrappers in the `Synchronization` framework.
8595

8696
## MIDI routing
8797

88-
```
89-
CoreMIDI device ──► MIDIInputPortCreateWithProtocol(._2_0)
90-
└─► MIDI2Manager.dispatchUMP ──► AtomicRingBuffer<TrackerEvent>
91-
92-
└─► AudioRenderNode drains
93-
on each render block
94-
95-
SynthVoice.trigger / noteOff (internal) ──► AudioRenderNode.midiOut(n,v,c)
96-
└─► AudioHost wires this to
97-
AudioEngine.midiManager
98-
└─► MIDI2Manager.sendNoteOn (MIDI 1.0)
99-
or sendUMPNoteOn (MIDI 2.0)
100-
└─► CoreMIDI destination
98+
```mermaid
99+
flowchart LR
100+
Device[CoreMIDI device] --> Port[MIDIInputPortCreateWithProtocol<br/>._2_0 UMP]
101+
Port --> Dispatch[MIDI2Manager.dispatchUMP]
102+
Dispatch -->|per-note bend/pressure| Ring[AtomicRingBuffer TrackerEvent]
103+
Ring --> Drain[AudioRenderNode drains<br/>on each render block]
104+
105+
Voice[SynthVoice trigger/noteOff] --> MidiOut[AudioRenderNode.midiOut]
106+
MidiOut --> Host[AudioHost wires to AudioEngine.midiManager]
107+
Host --> Send[MIDI2Manager sendNoteOn / sendUMPNoteOn]
108+
Send --> Dest[CoreMIDI destination]
101109
```
102110

103111
The MIDI clock (`0xF8`, 24 ppqn) is driven by a `DispatchSource.userInteractive` timer in `MIDI2Manager.startClock(bpm:)`, not by the audio thread.
@@ -110,9 +118,25 @@ Positions are updated from the UI via `SpatialManager.updateVoicePosition(channe
110118

111119
## Plugin hosting
112120

121+
```mermaid
122+
flowchart TD
123+
Host[AudioHost] --> AU[AUv3 per-channel<br/>4 inserts + 1 instrument]
124+
Host --> CLAP[CLAP per-channel<br/>dlopen bundle → clap_entry → factory]
125+
Host --> VST3[VST3 direct Steinberg SDK<br/>gated behind SDK_AVAILABLE]
126+
Host --> BUS[Aux-bus inserts<br/>4 slots per bus × 4 buses]
127+
128+
AU --> Wrapper[RenderBlockWrapper]
129+
CLAP --> Wrapper
130+
VST3 --> Wrapper
131+
BUS --> Wrapper
132+
Wrapper --> Master[Master sum + safety limiter]
133+
```
134+
113135
- **AUv3 inserts**: `AudioHost.loadPlugin(component:for:)` instantiates an `AUAudioUnit`, takes its `internalRenderBlock`, and stores it in `RenderBlockWrapper.pluginBlocks[ch*4 + slot]`. Up to 4 inserts per channel + 1 instrument. The per-channel loop in `coreAudioRenderCallback` walks these in order.
114-
- **Bundled inserts**: `StereoWidePlugin`, `ReverbPlugin` are created as `AUAudioUnit` subclasses (`ToooTBaseEffect`) and kept alive on `AudioHost` (freeing them while their block is registered would crash the IO thread).
115-
- **VST3**: `JUCEVST3Host` gates everything behind `TOOOT_VST3_SDK_AVAILABLE`. Without the SDK, `loadPluginAtPath:` fails, `sdkAvailable` returns `NO`, and `AudioHost.loadVST3Plugin` refuses to install the render block — guaranteeing a stub VST3 never silently replaces a working AUv3 instrument.
136+
- **Bus inserts**: Same pattern but on bus outputs — `busInsertBlocks[bus * 4 + slot]`. Bus buffers are wrapped in pre-allocated `AudioBufferList`s (mData points at `res.busL[b]`/`busR[b]` — stable for the lifetime of `RenderResources`).
137+
- **Bundled inserts**: `StereoWidePlugin`, `ReverbPlugin`, `TruePeakLimiter`, `MultibandCompressor`, `LinearPhaseEQ` are created as `AUAudioUnit` subclasses (`ToooTBaseEffect`) and kept alive on `AudioHost` (freeing them while their block is registered would crash the IO thread).
138+
- **CLAP**: `CLAPHostManager` enumerates `.clap` bundles; `CLAPPluginInstance` manages lifecycle (`init``activate``start_processing``process``stop_processing``deactivate``destroy`).
139+
- **VST3**: `VST3Host` gates everything behind `TOOOT_VST3_SDK_AVAILABLE`. Without the SDK, `loadPluginAtPath:` fails, `sdkAvailable` returns `NO`, and `AudioHost.loadVST3Plugin` refuses to install the render block — guaranteeing a stub VST3 never silently replaces a working AUv3 instrument.
116140

117141
## File format
118142

@@ -129,7 +153,7 @@ Positions are updated from the UI via `SpatialManager.updateVoicePosition(channe
129153
| 1301 | `numPat * 64 * numChn * 5` | Pattern cells (note, inst, vol, effect, param) |
130154
| after patterns | `numInstruments * 232` | Instrument headers |
131155
| after headers | variable | Int16 PCM sample data |
132-
| after samples | optional | `TOOO` chunk: `[4b tag][4b LE len][JSON plugin states]` |
156+
| after samples | optional | `TOOO` chunk: `[4b tag][4b LE len][JSON plugin states + scenes + arrangement + session + automation]` |
133157

134158
Instrument header (232 bytes): 32-byte name, sample length (LE 32-bit), loop start/length, finetune nibble at byte 24 (MOD-compatible) and byte 44 (MAD-extended, two's complement), stereo flag, loop type.
135159

docs/FEATURE_ROADMAP.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@ Honest assessment of what ToooT has today vs what the last 30 years of pro DAWs
44

55
The goal is parity with the best, not a toy. Every gap below is addressable; none of them are blocked on research. Things marked ✅ already work.
66

7+
## Progress snapshot
8+
9+
```mermaid
10+
pie title 39-item roadmap status
11+
"Shipped" : 27
12+
"Scaffold / Partial" : 4
13+
"Deferred with plan" : 8
14+
```
15+
16+
```mermaid
17+
flowchart LR
18+
Today[Today<br/>27 items shipped<br/>232 UAT passes<br/>11 stress scenarios OK] --> Next[Next release<br/>ship arrangement UI<br/>ship session UI<br/>HN / Reddit launch]
19+
Next --> Later[Later<br/>ARA2 + AAF skipped per OSS rule<br/>iPad skipped per platform rule<br/>CRDT collab is long-term]
20+
```
21+
722
## Shipped
823

924
- ✅ Zero-allocation real-time render loop (Swift 6 strict concurrency, `vDSP`/`Accelerate` throughout)

docs/MAD_FORMAT.md

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,19 @@ Native ToooT project file. All multi-byte values are little-endian.
44

55
## Top-level layout
66

7-
```
8-
+0x000 4 signature: "MADK" | "MADG" | "Tooo"
9-
+0x004 32 song title (ASCII, zero-padded, null-terminated)
10-
+0x024 239 reserved / padding
11-
+0x113 3 reserved
12-
+0x116 8 reserved
13-
+0x11E 3 reserved
14-
+0x121 3 reserved
15-
+0x124 1 numPatterns
16-
+0x125 1 numChannels
17-
+0x126 1 reserved
18-
+0x127 1 numInstruments
19-
+0x128 5 reserved
20-
+0x129 999 orderList[999] (UInt8 per position; unused entries = 0)
21-
+0x514 pattern data (5 bytes per cell)
22-
layout: patterns × 64 rows × numChannels × 5 bytes
23-
cell: [note, instrument, vol, effect, effectParam]
24-
after instrument headers (232 bytes each × numInstruments)
25-
after sample data (Int16 PCM, mono or interleaved stereo)
26-
after optional "TOOO" plugin-state trailer
7+
```mermaid
8+
flowchart TD
9+
A["+0x000 · 4 bytes<br/>signature: MADK / MADG / Tooo"]
10+
B["+0x004 · 32 bytes<br/>song title (ASCII zero-padded)"]
11+
C["+0x024 · 256 bytes<br/>reserved padding"]
12+
D["+0x124 · header<br/>numPatterns · numChannels · numInstruments"]
13+
E["+0x129 · 999 bytes<br/>orderList UInt8 × 999"]
14+
F["+0x514<br/>pattern data<br/>patterns × 64 rows × numChannels × 5 bytes<br/>cell: note/inst/vol/effect/param"]
15+
G["after patterns<br/>instrument headers 232 bytes × numInstruments"]
16+
H["after headers<br/>sample data (Int16 PCM)"]
17+
I["optional trailer<br/>TOOO chunk: plugin states + scenes + arrangement"]
18+
19+
A --> B --> C --> D --> E --> F --> G --> H --> I
2720
```
2821

2922
## Pattern cell
@@ -65,10 +58,12 @@ Conversion to the engine's `Float32` representation uses `vDSP_vflt16` + `vDSP_v
6558

6659
After the last sample, an optional chunk may appear:
6760

68-
```
69-
+0 4 "TOOO"
70-
+4 4 chunkLength (UInt32 LE)
71-
+8 N JSON body: {"pluginID": "base64(state)"}
61+
```mermaid
62+
flowchart LR
63+
Tag["+0 · 4 bytes<br/>TOOO magic"]
64+
Len["+4 · 4 bytes<br/>chunkLength (UInt32 LE)"]
65+
Body["+8 · N bytes<br/>JSON body<br/>{pluginID: base64(state), scene.N: ..., arrangement: ...}"]
66+
Tag --> Len --> Body
7267
```
7368

7469
Plugin IDs are `channelIndex_slotIndex` (AUv3 inserts), `channelIndex_inst` (instrument slot), or the reserved keys `StereoWide` / `ProReverb` for global inserts. Bodies are `PropertyListSerialization` XML plists, base64-encoded.

0 commit comments

Comments
 (0)