livepilot 1.5.0 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +2 -2
- package/m4l_device/livepilot_bridge.js +1 -1
- package/mcp_server/__init__.py +1 -1
- package/mcp_server/curves.py +741 -0
- package/mcp_server/server.py +1 -0
- package/mcp_server/tools/automation.py +488 -0
- package/package.json +2 -2
- package/plugin/agents/livepilot-producer/AGENT.md +32 -2
- package/plugin/plugin.json +2 -2
- package/plugin/skills/livepilot-core/SKILL.md +29 -5
- package/plugin/skills/livepilot-core/references/automation-atlas.md +272 -0
- package/plugin/skills/livepilot-core/references/overview.md +20 -3
- package/plugin/skills/livepilot-release/SKILL.md +101 -0
- package/remote_script/LivePilot/__init__.py +3 -2
- package/remote_script/LivePilot/clip_automation.py +220 -0
- package/remote_script/LivePilot/server.py +3 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# Automation Atlas — LivePilot v1.6
|
|
2
|
+
|
|
3
|
+
Complete knowledge corpus for musically intelligent automation. Load this reference when working with automation tools.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Curve Theory
|
|
8
|
+
|
|
9
|
+
### Why exponential for filters
|
|
10
|
+
Filter frequency perception is logarithmic (octaves). 100Hz → 200Hz is the same perceptual distance as 1kHz → 2kHz. An exponential curve in the normalized 0–1 range maps to perceptually even movement across the frequency spectrum. Linear automation on a filter sounds like it's rushing through the highs and crawling through the lows. Use `exponential` with `factor` 2.0–3.0 for filter cutoff.
|
|
11
|
+
|
|
12
|
+
### Why logarithmic for volume
|
|
13
|
+
Human loudness perception follows a logarithmic curve (Weber-Fechner law). -6dB = half perceived loudness, not half amplitude. A logarithmic fade-in sounds smooth; a linear fade-in sounds like nothing happens then suddenly gets loud. Use `logarithmic` with `factor` 2.5–3.5 for volume fades.
|
|
14
|
+
|
|
15
|
+
### Why S-curve for crossfades
|
|
16
|
+
Natural acceleration/deceleration. Avoids the "hole in the middle" of linear crossfades where combined energy dips. The smoothstep function (3t² - 2t³) ensures the rate of change is zero at both endpoints, so the transition feels organic. Use `s_curve` for any A→B crossfade.
|
|
17
|
+
|
|
18
|
+
### Why spike for throws
|
|
19
|
+
Dub production technique — instant send level spike creates a single reflection that decays naturally through the reverb/delay tail. Any other shape creates unnatural sustained reflections. The exponential decay (peak × e^(-decay×t)) models how real acoustic energy dissipates.
|
|
20
|
+
|
|
21
|
+
### Pan is linear
|
|
22
|
+
Stereo position perception is roughly linear. A pan pot moving at constant speed sounds even. No need for perceptual correction on pan automation.
|
|
23
|
+
|
|
24
|
+
### Resonance is dangerous
|
|
25
|
+
Filter resonance is non-linear — subtle changes at low values, dramatic and potentially destructive at high values. Self-oscillation begins above ~0.85 on most filters. Never automate resonance above 0.85 without explicit user intent. Use `breathing` recipe with reduced amplitude (0.05–0.10) for subtle resonance movement.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. The Perception-Action Loop (5 levels)
|
|
30
|
+
|
|
31
|
+
**MANDATORY for all automation work.** Never write automation blind — always perceive first.
|
|
32
|
+
|
|
33
|
+
### Level 1: Reactive
|
|
34
|
+
Single read, single action. The simplest loop.
|
|
35
|
+
1. `get_master_spectrum` → read frequency content
|
|
36
|
+
2. Identify one issue (e.g., "mud below 200Hz")
|
|
37
|
+
3. Apply one automation (e.g., HP filter sweep)
|
|
38
|
+
4. `get_master_spectrum` → verify improvement
|
|
39
|
+
|
|
40
|
+
### Level 2: Diagnostic
|
|
41
|
+
Multi-step investigation using EQ as a microscope. See Section 3 (Diagnostic Filter Technique).
|
|
42
|
+
|
|
43
|
+
### Level 3: Verification
|
|
44
|
+
Act → measure → adjust cycle. For every automation written:
|
|
45
|
+
1. Read spectrum BEFORE
|
|
46
|
+
2. Write automation
|
|
47
|
+
3. Read spectrum AFTER
|
|
48
|
+
4. Compare: did the problem frequency range improve?
|
|
49
|
+
5. If not, `clear_clip_automation` → adjust parameters → try again
|
|
50
|
+
6. Log the successful parameters in memory for reuse
|
|
51
|
+
|
|
52
|
+
### Level 4: Cross-Track
|
|
53
|
+
Solo each track, build spectral map, write complementary automation.
|
|
54
|
+
1. For each track: solo → `get_master_spectrum` → record fingerprint
|
|
55
|
+
2. Find frequency overlaps between tracks (masking)
|
|
56
|
+
3. Write complementary automation: as kick's filter opens, bass's filter narrows
|
|
57
|
+
4. Verify no new masking was introduced
|
|
58
|
+
5. Store the cross-track map in memory
|
|
59
|
+
|
|
60
|
+
### Level 5: Full Pipeline
|
|
61
|
+
Per-track analysis across entire session.
|
|
62
|
+
1. Run Level 4 for all tracks
|
|
63
|
+
2. Build session-wide spectral map
|
|
64
|
+
3. Write automation considering all interactions
|
|
65
|
+
4. Verify global mix spectrum
|
|
66
|
+
5. Iterate until balanced
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 3. Diagnostic Filter Technique
|
|
71
|
+
|
|
72
|
+
Using EQ Eight as a measurement instrument. Step-by-step:
|
|
73
|
+
|
|
74
|
+
1. **Load EQ Eight** on target track via `find_and_load_device`
|
|
75
|
+
2. **Configure band 1** as narrow bandpass: Q=8, gain=0dB, frequency=100Hz
|
|
76
|
+
- Use `set_device_parameter` to set Filter Type to Band Pass, Q to 8.0
|
|
77
|
+
3. **Solo the track** via `set_track_solo`
|
|
78
|
+
4. **Read spectrum** at 100Hz: `get_master_spectrum` → note the `sub` value
|
|
79
|
+
5. **Sweep frequency** through key ranges:
|
|
80
|
+
- 100Hz, 200Hz, 300Hz, 500Hz, 1kHz, 2kHz, 5kHz, 10kHz
|
|
81
|
+
- At each: `set_device_parameter` (frequency) → `get_master_spectrum` → record
|
|
82
|
+
6. **Build frequency map**: `{100Hz: 0.18, 200Hz: 0.25, 300Hz: 0.09, ...}`
|
|
83
|
+
7. **Identify problems**: resonance buildup, mud, harshness, dead zones
|
|
84
|
+
8. **Remove diagnostic EQ** — always clean up: `delete_device`
|
|
85
|
+
9. **Un-solo the track**: `set_track_solo` (toggle off)
|
|
86
|
+
10. **Write targeted automation** addressing what was found
|
|
87
|
+
|
|
88
|
+
### What to look for:
|
|
89
|
+
- **Mud** (200-400Hz): values > 0.20 suggest buildup → HP filter automation
|
|
90
|
+
- **Resonance** (narrow peak in 300-800Hz): ringing → notch filter or gentle sweep
|
|
91
|
+
- **Harshness** (2-5kHz): values > 0.25 suggest brightness → LP filter or shelf automation
|
|
92
|
+
- **Dead zone** (any range with 0.00): frequency hole → boost or different device
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 4. Genre Recipes
|
|
97
|
+
|
|
98
|
+
### Techno
|
|
99
|
+
- `filter_sweep_up` on LP cutoff, 32 bars, factor 2.0 — the classic build
|
|
100
|
+
- `sidechain_pump` on pad volume via Utility gain, 1 beat loop
|
|
101
|
+
- `stutter` on vocals before drop, 0.5 beats, frequency 16
|
|
102
|
+
- `stereo_narrow` on master bus, 8 bars before drop → instant widen at drop
|
|
103
|
+
- Long transitions: combine HP filter + reverb send + stereo width, all exponential
|
|
104
|
+
|
|
105
|
+
### Dub
|
|
106
|
+
- `dub_throw` on Send A at each snare position, 1 beat duration
|
|
107
|
+
- `tape_stop` on clip transpose for transitions, 0.5 beats
|
|
108
|
+
- `washout` on delay feedback, 4 bars → cut at phrase boundary
|
|
109
|
+
- `breathing` on filter cutoff, 4 bars — everything breathes in dub
|
|
110
|
+
- Tip: never automate delay feedback above 0.85 (infinite feedback risk)
|
|
111
|
+
|
|
112
|
+
### Ambient
|
|
113
|
+
- `breathing` on filter cutoff, 4 bars, amplitude 0.10 — subtle is key
|
|
114
|
+
- `perlin` noise on reverb mix, 8 bars, amplitude 0.15, seed varies
|
|
115
|
+
- `brownian` drift on wavetable position, 16 bars, volatility 0.05
|
|
116
|
+
- Polyrhythmic automation: 3-beat filter + 5-beat reverb + 7-beat pan = 105-beat cycle
|
|
117
|
+
- Less is more. Ambient automation should be felt, not heard.
|
|
118
|
+
|
|
119
|
+
### Hip Hop
|
|
120
|
+
- `tape_stop` on clip transpose, 0.5 beats — classic vinyl stop
|
|
121
|
+
- `vinyl_crackle` on Redux bit depth, 16 bars — lo-fi character
|
|
122
|
+
- `sidechain_pump` on bass under kick, 0.5 beat — tight pocket
|
|
123
|
+
- `fade_out` on filter for transitions between sections
|
|
124
|
+
- Keep automation sparse — hip hop is about groove, not modulation
|
|
125
|
+
|
|
126
|
+
### IDM / Experimental
|
|
127
|
+
- `stutter` on volume, 0.5 beats, frequency 16 — micro-editing
|
|
128
|
+
- `euclidean` on filter cutoff: 5 hits across 8 steps — rhythmic intelligence
|
|
129
|
+
- `stochastic` on grain parameters, narrowing 0.7 — controlled chaos
|
|
130
|
+
- `spring` on filter cutoff for overshoot character
|
|
131
|
+
- Layer multiple polyrhythmic automations for generative evolution
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 5. Sound Design Automation
|
|
136
|
+
|
|
137
|
+
### Wavetable position
|
|
138
|
+
Exponential sweep for timbral morphing over 8–16 bars. The timbral change is often perceptually logarithmic, so exponential automation creates even-sounding evolution.
|
|
139
|
+
|
|
140
|
+
### Grain size
|
|
141
|
+
Sine modulation at 0.5–2Hz for alive textures. The slow oscillation prevents the grainular artifacts from sounding static. Use `breathing` recipe adapted: center 0.5, amplitude 0.15.
|
|
142
|
+
|
|
143
|
+
### Reverb decay
|
|
144
|
+
Link inversely to volume: quieter passages get longer tails for natural space. As volume drops, reverb decay increases. Automate both with complementary curves.
|
|
145
|
+
|
|
146
|
+
### Delay feedback
|
|
147
|
+
Spike for throws (dub technique). NEVER exceed 0.9 on feedback — infinite feedback creates dangerous signal buildup. Use `dub_throw` recipe with peak capped at 0.85.
|
|
148
|
+
|
|
149
|
+
### Filter resonance
|
|
150
|
+
Subtle sine modulation creates vocal/crying quality. Watch for self-oscillation above 0.85. Use amplitude 0.05–0.10 for subtle character, 0.15–0.25 for expressive.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 6. Arrangement Techniques
|
|
155
|
+
|
|
156
|
+
### The Build (16–32 bars before drop)
|
|
157
|
+
Combine multiple automations, all exponential:
|
|
158
|
+
1. HP filter: 0→0.6 (remove low end gradually)
|
|
159
|
+
2. Volume: 0.7→1.0 (subtle lift)
|
|
160
|
+
3. Reverb send: 0→0.5 (add wash)
|
|
161
|
+
4. Stereo width: 1.0→0.0 (collapse to mono)
|
|
162
|
+
5. At drop: instant step back to defaults
|
|
163
|
+
|
|
164
|
+
### The Drop (instant)
|
|
165
|
+
Step automation restoring all build parameters to default values. No curves — immediate snap. Use `steps` curve with a single value at time 0.
|
|
166
|
+
|
|
167
|
+
### The Strip (8–16 bars)
|
|
168
|
+
Gradual HP filter rise removing elements one by one:
|
|
169
|
+
1. First: HP filter on bass track (removes bass)
|
|
170
|
+
2. Then: HP filter on drums (removes kick)
|
|
171
|
+
3. Then: HP filter on pads (removes mids)
|
|
172
|
+
4. Only high frequencies remain → transition point
|
|
173
|
+
|
|
174
|
+
### The Crossfade (2–8 bars)
|
|
175
|
+
S-curve volume automation between two sections:
|
|
176
|
+
- Outgoing track: s_curve from 1.0 to 0.0
|
|
177
|
+
- Incoming track: s_curve from 0.0 to 1.0
|
|
178
|
+
- S-curve prevents the energy dip of linear crossfades
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 7. Micro-Editing and Humanization
|
|
183
|
+
|
|
184
|
+
### Velocity vs volume
|
|
185
|
+
Velocity is timbre — it changes how the sound is generated (harder hits, brighter tone). Volume automation is loudness — it changes amplitude after generation. They are different tools.
|
|
186
|
+
|
|
187
|
+
### Per-note filter accent
|
|
188
|
+
Automate filter cutoff to spike on accented notes. Use `spike` with duration matching the note length. Creates timbral accents without touching velocity.
|
|
189
|
+
|
|
190
|
+
### Spatial punctuation
|
|
191
|
+
Send spikes on specific hits create spatial depth variation. Snare → reverb throw on beat 2 and 4. Hi-hat → delay throw on off-beats. Use `dub_throw` recipe.
|
|
192
|
+
|
|
193
|
+
### Humanization via Perlin noise
|
|
194
|
+
Tiny pitch/filter variations make programmed music feel human:
|
|
195
|
+
- `perlin` on detune: amplitude 0.02, frequency 0.5
|
|
196
|
+
- `perlin` on filter cutoff: amplitude 0.05, frequency 0.3
|
|
197
|
+
- Different seeds per track — each voice drifts independently
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 8. Polyrhythmic Automation
|
|
202
|
+
|
|
203
|
+
### The concept
|
|
204
|
+
Unlinked automation envelopes with different loop lengths create long, non-repeating cycles:
|
|
205
|
+
- 4-beat clip loop + 3-beat filter envelope + 5-beat reverb envelope
|
|
206
|
+
- Total cycle: LCM(4, 3, 5) = 60 beats before exact repetition
|
|
207
|
+
- 60 beats ≈ 15 bars at 4/4 — feels evolving, never static
|
|
208
|
+
|
|
209
|
+
### Prime number rule
|
|
210
|
+
Use prime-number beat lengths for maximum non-repetition:
|
|
211
|
+
- 3, 5, 7, 11, 13 beats
|
|
212
|
+
- LCM of primes = their product (all coprime)
|
|
213
|
+
- 3 × 5 × 7 = 105 beats ≈ 26 bars before exact repeat
|
|
214
|
+
|
|
215
|
+
### Implementation
|
|
216
|
+
1. Create clip with N-beat loop (your base rhythm)
|
|
217
|
+
2. `apply_automation_shape` with `duration` = M beats (M ≠ N, preferably prime)
|
|
218
|
+
3. The automation loops at M beats, the clip loops at N beats
|
|
219
|
+
4. Add another automation with duration = P beats (another prime)
|
|
220
|
+
5. Total cycle = LCM(N, M, P) beats
|
|
221
|
+
|
|
222
|
+
### Use cases
|
|
223
|
+
Essential for ambient, installation, generative work. Creates the sense of "always evolving" that distinguishes produced music from loops.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## 9. Cross-Track Spectral Mapping
|
|
228
|
+
|
|
229
|
+
### Process
|
|
230
|
+
1. **Solo each track** one at a time
|
|
231
|
+
2. **Read spectrum**: `get_master_spectrum` → record {sub, low, mid, high, presence, air}
|
|
232
|
+
3. **Build spectral fingerprint** for each track:
|
|
233
|
+
```
|
|
234
|
+
Kick: sub=0.45, low=0.30, mid=0.05, high=0.02
|
|
235
|
+
Bass: sub=0.35, low=0.40, mid=0.15, high=0.03
|
|
236
|
+
Pad: sub=0.05, low=0.15, mid=0.35, high=0.25
|
|
237
|
+
```
|
|
238
|
+
4. **Find overlaps**: kick and bass both strong in sub/low = masking
|
|
239
|
+
5. **Write complementary automation**: as kick opens, bass narrows
|
|
240
|
+
6. **Verify**: un-solo all, check master spectrum for improvement
|
|
241
|
+
|
|
242
|
+
### Complementary automation patterns
|
|
243
|
+
- **Kick + Bass**: sidechain pump on bass volume, or complementary filter sweeps
|
|
244
|
+
- **Vocals + Pads**: automate pad filter to duck in vocal frequency range (1–4kHz)
|
|
245
|
+
- **Multiple synths**: different breathing rates (frequency 0.3, 0.5, 0.7) to weave in/out
|
|
246
|
+
|
|
247
|
+
### Store findings
|
|
248
|
+
Use `memory_learn` to save spectral maps and successful automation combinations. Build a knowledge base about the session's sounds for consistent decisions.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## 10. Golden Rules
|
|
253
|
+
|
|
254
|
+
1. **Always use Utility gain for volume automation** — preserve the mixer fader for mixing. Automate the Utility device's Gain parameter, not track volume.
|
|
255
|
+
|
|
256
|
+
2. **Exponential for filters, logarithmic for volume** — never linear for either. This is perceptual science, not preference.
|
|
257
|
+
|
|
258
|
+
3. **Subtle automation (5–15% range) for organic feel; dramatic (full range) for transitions.** Most automation should be felt, not heard. Save the big sweeps for builds and drops.
|
|
259
|
+
|
|
260
|
+
4. **ALWAYS verify with `get_master_spectrum` after writing automation.** The feedback loop is mandatory, not optional.
|
|
261
|
+
|
|
262
|
+
5. **Use `clear_clip_automation` before rewriting.** Don't stack conflicting curves — clear first, then write fresh.
|
|
263
|
+
|
|
264
|
+
6. **Use the diagnostic filter technique before guessing at problem frequencies.** Measurement beats intuition.
|
|
265
|
+
|
|
266
|
+
7. **Store spectral findings in memory.** Build a knowledge base about this session's sounds. Cross-session awareness makes every future decision better.
|
|
267
|
+
|
|
268
|
+
8. **Delay feedback NEVER above 0.9.** Infinite feedback creates dangerous signal buildup. Cap at 0.85 for safety, even in experimental contexts.
|
|
269
|
+
|
|
270
|
+
9. **density 16–32 for most curves.** 16 points per bar gives smooth results for gentle curves. 32 for fast modulations or detailed shapes. Above 64 is rarely worth the overhead.
|
|
271
|
+
|
|
272
|
+
10. **When in doubt, use a recipe.** The 15 built-in recipes encode production wisdom. Start with the recipe, then customize if needed.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# LivePilot v1.
|
|
1
|
+
# LivePilot v1.6.0 — Architecture & Tool Reference
|
|
2
2
|
|
|
3
|
-
LivePilot is an agentic production system for Ableton Live 12. It combines
|
|
3
|
+
LivePilot is an agentic production system for Ableton Live 12. It combines 135 MCP tools with a device knowledge corpus, real-time audio analysis, automation intelligence, and persistent technique memory.
|
|
4
4
|
|
|
5
5
|
## Architecture
|
|
6
6
|
|
|
@@ -32,7 +32,7 @@ A flat tool list lets the AI press buttons. LivePilot's three layers give it con
|
|
|
32
32
|
|
|
33
33
|
This turns "set EQ band 3 to -4 dB" into "cut 400 Hz by 4 dB, then read the spectrum to confirm the mud is actually reduced."
|
|
34
34
|
|
|
35
|
-
## The
|
|
35
|
+
## The 135 Tools — What Each One Does
|
|
36
36
|
|
|
37
37
|
### Transport (12) — Playback, tempo, global state, diagnostics
|
|
38
38
|
|
|
@@ -227,6 +227,23 @@ This turns "set EQ band 3 to -4 dB" into "cut 400 Hz by 4 dB, then read the spec
|
|
|
227
227
|
| `stop_scrub` | Stop preview | `track_index`, `clip_index` |
|
|
228
228
|
| `get_display_values` | Human-readable parameter values ("440 Hz", "-6 dB") | `track_index`, `device_index` |
|
|
229
229
|
|
|
230
|
+
### Automation (8) — Clip automation CRUD + intelligent curve generation
|
|
231
|
+
|
|
232
|
+
| Tool | What it does | Key params |
|
|
233
|
+
|------|-------------|------------|
|
|
234
|
+
| `get_clip_automation` | Lists all automation envelopes on a session clip | `track_index`, `clip_index` |
|
|
235
|
+
| `set_clip_automation` | Writes automation points to a clip envelope | `track_index`, `clip_index`, `parameter_type`, `points` |
|
|
236
|
+
| `clear_clip_automation` | Clears automation envelopes (specific or all) | `track_index`, `clip_index`, `parameter_type` (optional) |
|
|
237
|
+
| `apply_automation_shape` | Generates and applies a curve to a clip in one call | `track_index`, `clip_index`, `parameter_type`, `curve_type`, `duration`, `density` |
|
|
238
|
+
| `apply_automation_recipe` | Applies a named recipe (filter_sweep_up, dub_throw, etc.) | `track_index`, `clip_index`, `parameter_type`, `recipe`, `duration` |
|
|
239
|
+
| `get_automation_recipes` | Lists all 15 recipes with descriptions and targets | — |
|
|
240
|
+
| `generate_automation_curve` | Previews curve points without writing them | `curve_type`, `duration`, `density`, curve-specific params |
|
|
241
|
+
| `analyze_for_automation` | Spectral analysis + device-aware automation suggestions | `track_index` |
|
|
242
|
+
|
|
243
|
+
**16 curve types:** linear, exponential, logarithmic, s_curve, sine, sawtooth, spike, square, steps, perlin, brownian, spring, bezier, easing, euclidean, stochastic
|
|
244
|
+
|
|
245
|
+
**15 recipes:** filter_sweep_up, filter_sweep_down, dub_throw, tape_stop, build_rise, sidechain_pump, fade_in, fade_out, tremolo, auto_pan, stutter, breathing, washout, vinyl_crackle, stereo_narrow
|
|
246
|
+
|
|
230
247
|
## Units & Ranges Quick Reference
|
|
231
248
|
|
|
232
249
|
| Concept | Unit/Range | Notes |
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: livepilot-release
|
|
3
|
+
description: Release checklist for LivePilot — run before ANY push, publish, or "update everything" request. Covers every file, channel, and artifact that references version numbers, tool counts, or project state.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# LivePilot Release Checklist
|
|
7
|
+
|
|
8
|
+
Run this checklist EVERY time the user says "update everything", "push", "release", "make sure everything is current", or similar.
|
|
9
|
+
|
|
10
|
+
## 1. Version Strings (must ALL match)
|
|
11
|
+
|
|
12
|
+
- [ ] `package.json` → `"version"`
|
|
13
|
+
- [ ] `package-lock.json` → `"version"` (run `npm install --package-lock-only` if stale)
|
|
14
|
+
- [ ] `server.json` → `"version"` (TWO locations: top-level and package)
|
|
15
|
+
- [ ] `plugin/plugin.json` → `"version"`
|
|
16
|
+
- [ ] `mcp_server/__init__.py` → `__version__`
|
|
17
|
+
- [ ] `remote_script/LivePilot/__init__.py` → version in log message
|
|
18
|
+
- [ ] `m4l_device/livepilot_bridge.js` → version in ping response
|
|
19
|
+
- [ ] `CHANGELOG.md` → latest version header
|
|
20
|
+
- [ ] `docs/social-banner.html` → version display
|
|
21
|
+
|
|
22
|
+
**How to check:** `grep -rn "1\.[0-9]\.[0-9]" package.json server.json plugin/plugin.json mcp_server/__init__.py remote_script/LivePilot/__init__.py m4l_device/livepilot_bridge.js CHANGELOG.md docs/social-banner.html`
|
|
23
|
+
|
|
24
|
+
## 2. Tool Count (must ALL match)
|
|
25
|
+
|
|
26
|
+
- [ ] `README.md` — every occurrence
|
|
27
|
+
- [ ] `package.json` → `"description"`
|
|
28
|
+
- [ ] `server.json` → `"description"`
|
|
29
|
+
- [ ] `plugin/plugin.json` → `"description"`
|
|
30
|
+
- [ ] `CLAUDE.md`
|
|
31
|
+
- [ ] `plugin/skills/livepilot-core/SKILL.md` — header and domain breakdown
|
|
32
|
+
- [ ] `plugin/skills/livepilot-core/references/overview.md`
|
|
33
|
+
- [ ] `docs/manual/index.md`
|
|
34
|
+
- [ ] `docs/manual/tool-reference.md`
|
|
35
|
+
- [ ] `docs/TOOL_REFERENCE.md`
|
|
36
|
+
- [ ] `docs/M4L_BRIDGE.md`
|
|
37
|
+
- [ ] `docs/social-banner.html`
|
|
38
|
+
- [ ] `mcp_server/tools/analyzer.py` → module docstring
|
|
39
|
+
- [ ] `tests/test_tools_contract.py` → expected count
|
|
40
|
+
|
|
41
|
+
**How to check:** `grep -rn "127\|104\|113" --include="*.md" --include="*.json" --include="*.py" --include="*.html" --include="*.js" . | grep -v node_modules | grep -v .git | grep -v __pycache__`
|
|
42
|
+
|
|
43
|
+
## 3. Domain Count
|
|
44
|
+
|
|
45
|
+
- [ ] All files above that mention "10 domains" should say "11 domains"
|
|
46
|
+
- [ ] Domain lists should include: transport, tracks, clips, notes, devices, scenes, mixing, browser, arrangement, memory, analyzer
|
|
47
|
+
|
|
48
|
+
## 4. npm Registry
|
|
49
|
+
|
|
50
|
+
- [ ] `npm view livepilot version` matches local version
|
|
51
|
+
- [ ] If not: `npm publish`
|
|
52
|
+
- [ ] Verify badge will update: badge URL in README.md points to shields.io/npm/v/livepilot
|
|
53
|
+
|
|
54
|
+
## 5. GitHub
|
|
55
|
+
|
|
56
|
+
- [ ] Repo description matches current tool count and features
|
|
57
|
+
- [ ] Topics are current (should include: ai, mcp, ableton, livepilot, max-for-live, audio-analysis)
|
|
58
|
+
- [ ] Latest release matches current version (`gh release list`)
|
|
59
|
+
- [ ] Release notes are current
|
|
60
|
+
- [ ] Old stale releases cleaned up
|
|
61
|
+
- [ ] Git tags: only relevant versions exist (`git tag -l`)
|
|
62
|
+
- [ ] No co-author or unwanted metadata in commit messages
|
|
63
|
+
|
|
64
|
+
## 6. Plugin Cache
|
|
65
|
+
|
|
66
|
+
- [ ] `~/.claude/plugins/cache/dreamrec-LivePilot/livepilot/` has current version directory
|
|
67
|
+
- [ ] Old version directories removed
|
|
68
|
+
|
|
69
|
+
## 7. Social/Promotional Assets
|
|
70
|
+
|
|
71
|
+
- [ ] `docs/social-banner.html` — tool count, version, domain list
|
|
72
|
+
- [ ] `docs/social-banner.png` — regenerated from HTML (must match)
|
|
73
|
+
- [ ] GitHub repo social preview image (Settings > Social preview)
|
|
74
|
+
|
|
75
|
+
## 8. Documentation Content
|
|
76
|
+
|
|
77
|
+
- [ ] `README.md` — features match current capabilities
|
|
78
|
+
- [ ] `docs/manual/getting-started.md` — install instructions current, mentions M4L Analyzer
|
|
79
|
+
- [ ] `docs/manual/tool-reference.md` — all tools listed
|
|
80
|
+
- [ ] `docs/M4L_BRIDGE.md` — architecture accurate
|
|
81
|
+
- [ ] `docs/TOOL_REFERENCE.md` — all tools listed with correct params
|
|
82
|
+
|
|
83
|
+
## 9. Derived Artifacts
|
|
84
|
+
|
|
85
|
+
- [ ] `m4l_device/LivePilot_Analyzer.amxd` — frozen JS matches source? All commands present?
|
|
86
|
+
- [ ] Distributable zip on Desktop — rebuilt with latest?
|
|
87
|
+
- [ ] Private backup repo — synced and pushed?
|
|
88
|
+
- [ ] `LivePilot-v*.INSTALL.txt` — updated?
|
|
89
|
+
|
|
90
|
+
## 10. Code Consistency
|
|
91
|
+
|
|
92
|
+
- [ ] `@mcp.tool()` count matches documented tool count: `grep -r "@mcp.tool" mcp_server/tools/ | wc -l`
|
|
93
|
+
- [ ] No dead imports or unused code in recently changed files
|
|
94
|
+
- [ ] Remote script version matches MCP server version
|
|
95
|
+
|
|
96
|
+
## Quick Verify Command
|
|
97
|
+
|
|
98
|
+
Run this one-liner to catch most issues:
|
|
99
|
+
```bash
|
|
100
|
+
echo "=== Versions ===" && grep -h '"version"' package.json server.json plugin/plugin.json | head -5 && grep __version__ mcp_server/__init__.py && echo "=== Tool count ===" && grep -rc "@mcp.tool" mcp_server/tools/ | tail -1 && echo "=== npm ===" && npm view livepilot version 2>/dev/null && echo "=== GitHub release ===" && gh release list --limit 1 && echo "=== Tags ===" && git tag -l
|
|
101
|
+
```
|
|
@@ -16,7 +16,8 @@ from . import scenes # noqa: F401 — registers scene handlers
|
|
|
16
16
|
from . import mixing # noqa: F401 — registers mixing handlers
|
|
17
17
|
from . import browser # noqa: F401 — registers browser handlers
|
|
18
18
|
from . import arrangement # noqa: F401 — registers arrangement handlers
|
|
19
|
-
from . import diagnostics
|
|
19
|
+
from . import diagnostics # noqa: F401 — registers diagnostics handler
|
|
20
|
+
from . import clip_automation # noqa: F401 — registers clip automation handlers
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def create_instance(c_instance):
|
|
@@ -31,7 +32,7 @@ class LivePilot(ControlSurface):
|
|
|
31
32
|
ControlSurface.__init__(self, c_instance)
|
|
32
33
|
self._server = LivePilotServer(self)
|
|
33
34
|
self._server.start()
|
|
34
|
-
self.log_message("LivePilot v1.
|
|
35
|
+
self.log_message("LivePilot v1.6.1 initialized")
|
|
35
36
|
self.show_message("LivePilot: Listening on port 9878")
|
|
36
37
|
|
|
37
38
|
def disconnect(self):
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""Clip automation envelope handlers.
|
|
2
|
+
|
|
3
|
+
Provides CRUD access to session clip automation envelopes.
|
|
4
|
+
Uses the same LOM API as arrangement automation (AutomationEnvelope)
|
|
5
|
+
but targets session clips via track.clip_slots[i].clip.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .router import register
|
|
9
|
+
from .utils import get_track
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@register("get_clip_automation")
|
|
13
|
+
def get_clip_automation(song, params):
|
|
14
|
+
"""List automation envelopes on a session clip."""
|
|
15
|
+
track_index = params["track_index"]
|
|
16
|
+
clip_index = params["clip_index"]
|
|
17
|
+
|
|
18
|
+
track = get_track(song, track_index)
|
|
19
|
+
clip_slot = list(track.clip_slots)[clip_index]
|
|
20
|
+
if not clip_slot.has_clip:
|
|
21
|
+
return {"error": {"code": "NOT_FOUND",
|
|
22
|
+
"message": "No clip at slot %d" % clip_index}}
|
|
23
|
+
|
|
24
|
+
clip = clip_slot.clip
|
|
25
|
+
envelopes = []
|
|
26
|
+
|
|
27
|
+
# Check mixer parameters: volume, panning, sends
|
|
28
|
+
mixer = track.mixer_device
|
|
29
|
+
for param_name, param in [
|
|
30
|
+
("Volume", mixer.volume),
|
|
31
|
+
("Pan", mixer.panning),
|
|
32
|
+
]:
|
|
33
|
+
env = clip.automation_envelope(param)
|
|
34
|
+
if env is not None:
|
|
35
|
+
envelopes.append({
|
|
36
|
+
"parameter_name": param_name,
|
|
37
|
+
"parameter_type": "mixer",
|
|
38
|
+
"has_envelope": True,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
# Check send parameters
|
|
42
|
+
sends = list(mixer.sends)
|
|
43
|
+
for i, send in enumerate(sends):
|
|
44
|
+
env = clip.automation_envelope(send)
|
|
45
|
+
if env is not None:
|
|
46
|
+
envelopes.append({
|
|
47
|
+
"parameter_name": "Send %s" % chr(65 + i),
|
|
48
|
+
"parameter_type": "send",
|
|
49
|
+
"send_index": i,
|
|
50
|
+
"has_envelope": True,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
# Check device parameters
|
|
54
|
+
devices = list(track.devices)
|
|
55
|
+
for di, device in enumerate(devices):
|
|
56
|
+
dev_params = list(device.parameters)
|
|
57
|
+
for pi, param in enumerate(dev_params):
|
|
58
|
+
try:
|
|
59
|
+
env = clip.automation_envelope(param)
|
|
60
|
+
if env is not None:
|
|
61
|
+
envelopes.append({
|
|
62
|
+
"parameter_name": param.name,
|
|
63
|
+
"parameter_type": "device",
|
|
64
|
+
"device_index": di,
|
|
65
|
+
"device_name": device.name,
|
|
66
|
+
"parameter_index": pi,
|
|
67
|
+
"has_envelope": True,
|
|
68
|
+
})
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
"track_index": track_index,
|
|
74
|
+
"clip_index": clip_index,
|
|
75
|
+
"clip_name": clip.name,
|
|
76
|
+
"envelope_count": len(envelopes),
|
|
77
|
+
"envelopes": envelopes,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@register("set_clip_automation")
|
|
82
|
+
def set_clip_automation(song, params):
|
|
83
|
+
"""Write automation points to a session clip envelope.
|
|
84
|
+
|
|
85
|
+
parameter_type: "device", "volume", "panning", "send"
|
|
86
|
+
points: [{time, value, duration?}] — time relative to clip start
|
|
87
|
+
"""
|
|
88
|
+
track_index = params["track_index"]
|
|
89
|
+
clip_index = params["clip_index"]
|
|
90
|
+
parameter_type = params["parameter_type"]
|
|
91
|
+
points = params["points"]
|
|
92
|
+
device_index = params.get("device_index")
|
|
93
|
+
parameter_index = params.get("parameter_index")
|
|
94
|
+
send_index = params.get("send_index")
|
|
95
|
+
|
|
96
|
+
track = get_track(song, track_index)
|
|
97
|
+
clip_slot = list(track.clip_slots)[clip_index]
|
|
98
|
+
if not clip_slot.has_clip:
|
|
99
|
+
return {"error": {"code": "NOT_FOUND",
|
|
100
|
+
"message": "No clip at slot %d" % clip_index}}
|
|
101
|
+
|
|
102
|
+
clip = clip_slot.clip
|
|
103
|
+
|
|
104
|
+
# Resolve the target parameter
|
|
105
|
+
if parameter_type == "volume":
|
|
106
|
+
parameter = track.mixer_device.volume
|
|
107
|
+
elif parameter_type == "panning":
|
|
108
|
+
parameter = track.mixer_device.panning
|
|
109
|
+
elif parameter_type == "send":
|
|
110
|
+
if send_index is None:
|
|
111
|
+
return {"error": {"code": "INVALID_PARAM",
|
|
112
|
+
"message": "send_index required for send automation"}}
|
|
113
|
+
sends = list(track.mixer_device.sends)
|
|
114
|
+
if send_index >= len(sends):
|
|
115
|
+
return {"error": {"code": "INDEX_ERROR",
|
|
116
|
+
"message": "send_index %d out of range" % send_index}}
|
|
117
|
+
parameter = sends[send_index]
|
|
118
|
+
elif parameter_type == "device":
|
|
119
|
+
if device_index is None or parameter_index is None:
|
|
120
|
+
return {"error": {"code": "INVALID_PARAM",
|
|
121
|
+
"message": "device_index and parameter_index required"}}
|
|
122
|
+
devices = list(track.devices)
|
|
123
|
+
if device_index >= len(devices):
|
|
124
|
+
return {"error": {"code": "INDEX_ERROR",
|
|
125
|
+
"message": "device_index %d out of range" % device_index}}
|
|
126
|
+
dev_params = list(devices[device_index].parameters)
|
|
127
|
+
if parameter_index >= len(dev_params):
|
|
128
|
+
return {"error": {"code": "INDEX_ERROR",
|
|
129
|
+
"message": "parameter_index %d out of range" % parameter_index}}
|
|
130
|
+
parameter = dev_params[parameter_index]
|
|
131
|
+
else:
|
|
132
|
+
return {"error": {"code": "INVALID_PARAM",
|
|
133
|
+
"message": "parameter_type must be device/volume/panning/send"}}
|
|
134
|
+
|
|
135
|
+
# Get or create envelope
|
|
136
|
+
song.begin_undo_step()
|
|
137
|
+
try:
|
|
138
|
+
envelope = clip.automation_envelope(parameter)
|
|
139
|
+
if envelope is None:
|
|
140
|
+
envelope = clip.create_automation_envelope(parameter)
|
|
141
|
+
|
|
142
|
+
# Write points
|
|
143
|
+
written = 0
|
|
144
|
+
for pt in points:
|
|
145
|
+
time = float(pt["time"])
|
|
146
|
+
value = float(pt["value"])
|
|
147
|
+
duration = float(pt.get("duration", 0.125))
|
|
148
|
+
# Clamp value to parameter range
|
|
149
|
+
value = max(parameter.min, min(parameter.max, value))
|
|
150
|
+
envelope.insert_step(time, duration, value)
|
|
151
|
+
written += 1
|
|
152
|
+
finally:
|
|
153
|
+
song.end_undo_step()
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
"track_index": track_index,
|
|
157
|
+
"clip_index": clip_index,
|
|
158
|
+
"parameter_name": parameter.name,
|
|
159
|
+
"parameter_type": parameter_type,
|
|
160
|
+
"points_written": written,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@register("clear_clip_automation")
|
|
165
|
+
def clear_clip_automation(song, params):
|
|
166
|
+
"""Clear automation envelopes from a session clip.
|
|
167
|
+
|
|
168
|
+
If parameter_type is provided, clears only that parameter's envelope.
|
|
169
|
+
If omitted, clears ALL envelopes on the clip.
|
|
170
|
+
"""
|
|
171
|
+
track_index = params["track_index"]
|
|
172
|
+
clip_index = params["clip_index"]
|
|
173
|
+
parameter_type = params.get("parameter_type")
|
|
174
|
+
|
|
175
|
+
track = get_track(song, track_index)
|
|
176
|
+
clip_slot = list(track.clip_slots)[clip_index]
|
|
177
|
+
if not clip_slot.has_clip:
|
|
178
|
+
return {"error": {"code": "NOT_FOUND",
|
|
179
|
+
"message": "No clip at slot %d" % clip_index}}
|
|
180
|
+
|
|
181
|
+
clip = clip_slot.clip
|
|
182
|
+
|
|
183
|
+
song.begin_undo_step()
|
|
184
|
+
try:
|
|
185
|
+
if parameter_type is None:
|
|
186
|
+
# Clear all envelopes
|
|
187
|
+
clip.clear_all_envelopes()
|
|
188
|
+
return {
|
|
189
|
+
"track_index": track_index,
|
|
190
|
+
"clip_index": clip_index,
|
|
191
|
+
"cleared": "all",
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# Clear specific parameter
|
|
195
|
+
if parameter_type == "volume":
|
|
196
|
+
parameter = track.mixer_device.volume
|
|
197
|
+
elif parameter_type == "panning":
|
|
198
|
+
parameter = track.mixer_device.panning
|
|
199
|
+
elif parameter_type == "send":
|
|
200
|
+
send_index = params.get("send_index", 0)
|
|
201
|
+
parameter = list(track.mixer_device.sends)[send_index]
|
|
202
|
+
elif parameter_type == "device":
|
|
203
|
+
device_index = params.get("device_index", 0)
|
|
204
|
+
parameter_index = params.get("parameter_index", 0)
|
|
205
|
+
device = list(track.devices)[device_index]
|
|
206
|
+
parameter = list(device.parameters)[parameter_index]
|
|
207
|
+
else:
|
|
208
|
+
return {"error": {"code": "INVALID_PARAM",
|
|
209
|
+
"message": "Unknown parameter_type"}}
|
|
210
|
+
|
|
211
|
+
clip.clear_envelope(parameter)
|
|
212
|
+
finally:
|
|
213
|
+
song.end_undo_step()
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
"track_index": track_index,
|
|
217
|
+
"clip_index": clip_index,
|
|
218
|
+
"cleared": parameter_type,
|
|
219
|
+
"parameter_name": parameter.name,
|
|
220
|
+
}
|
|
@@ -52,6 +52,9 @@ WRITE_COMMANDS = frozenset([
|
|
|
52
52
|
"modify_arrangement_notes", "duplicate_arrangement_notes",
|
|
53
53
|
"transpose_arrangement_notes", "set_arrangement_automation",
|
|
54
54
|
"set_arrangement_clip_name",
|
|
55
|
+
# clip automation
|
|
56
|
+
"set_clip_automation",
|
|
57
|
+
"clear_clip_automation",
|
|
55
58
|
])
|
|
56
59
|
|
|
57
60
|
|