livepilot 1.10.4 → 1.10.5
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/.claude-plugin/marketplace.json +3 -3
- package/AGENTS.md +3 -3
- package/CHANGELOG.md +98 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +6 -6
- package/livepilot/.Codex-plugin/plugin.json +2 -2
- package/livepilot/.claude-plugin/plugin.json +2 -2
- package/livepilot/skills/livepilot-core/SKILL.md +4 -4
- package/livepilot/skills/livepilot-core/references/overview.md +3 -3
- package/livepilot/skills/livepilot-evaluation/references/capability-modes.md +1 -1
- package/livepilot/skills/livepilot-release/SKILL.md +5 -5
- package/m4l_device/LivePilot_Analyzer.amxd +0 -0
- package/m4l_device/livepilot_bridge.js +1 -1
- package/manifest.json +3 -3
- package/mcp_server/__init__.py +1 -1
- package/mcp_server/sample_engine/tools.py +370 -18
- package/mcp_server/tools/analyzer.py +161 -1
- package/package.json +2 -2
- package/remote_script/LivePilot/__init__.py +1 -1
- package/requirements.txt +6 -0
- package/livepilot.mcpb +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
|
|
3
3
|
"name": "dreamrec-LivePilot",
|
|
4
|
-
"description": "Agentic MCP production system for Ableton Live 12 —
|
|
4
|
+
"description": "Agentic MCP production system for Ableton Live 12 — 320 tools, 43 domains",
|
|
5
5
|
"owner": {
|
|
6
6
|
"name": "dreamrec",
|
|
7
7
|
"email": "dreamrec@users.noreply.github.com"
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"plugins": [
|
|
10
10
|
{
|
|
11
11
|
"name": "livepilot",
|
|
12
|
-
"description": "Agentic production system for Ableton Live 12 —
|
|
13
|
-
"version": "1.10.
|
|
12
|
+
"description": "Agentic production system for Ableton Live 12 — 320 tools, 43 domains, device atlas, spectral perception, technique memory, sample intelligence, auto-composition, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
|
|
13
|
+
"version": "1.10.5",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Pilot Studio"
|
|
16
16
|
},
|
package/AGENTS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# LivePilot v1.10.
|
|
1
|
+
# LivePilot v1.10.5 — Ableton Live 12
|
|
2
2
|
|
|
3
3
|
## Project
|
|
4
4
|
- **Repo:** This directory (LivePilot)
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
## Key Rules
|
|
23
23
|
- ALL Live Object Model (LOM) calls must execute on Ableton's main thread via schedule_message queue
|
|
24
24
|
- Live 12 minimum — use modern note API (add_new_notes, get_notes_extended, apply_note_modifications)
|
|
25
|
-
-
|
|
25
|
+
- 320 tools across 43 domains: transport, tracks, clips, notes, devices, scenes, mixing, browser, arrangement, memory, analyzer, automation, theory, generative, harmony, midi_io, perception, agent_os, composition, motif, research, planner, project_brain, runtime, evaluation, mix_engine, sound_design, transition_engine, reference_engine, translation_engine, performance_engine, song_brain, preview_studio, hook_hunter, stuckness_detector, wonder_mode, session_continuity, creative_constraints, device_forge, sample_engine, atlas, composer
|
|
26
26
|
- JSON over TCP, newline-delimited, port 9878
|
|
27
27
|
- Structured errors with codes: INDEX_ERROR, NOT_FOUND, INVALID_PARAM, STATE_ERROR, TIMEOUT, INTERNAL
|
|
28
28
|
|
|
@@ -43,4 +43,4 @@ When modifying .amxd attributes that Max editor won't persist (e.g., `openinpres
|
|
|
43
43
|
4. Structure: 24-byte `ampf` header + `ptch` chunk + `mx@c` header + JSON patcher + frozen deps
|
|
44
44
|
|
|
45
45
|
## Tool Count
|
|
46
|
-
Currently
|
|
46
|
+
Currently 320 tools. If adding/removing tools, update: README.md, package.json description, livepilot/.Codex-plugin/plugin.json, livepilot/.claude-plugin/plugin.json, server.json, livepilot/skills/livepilot-core/SKILL.md, livepilot/skills/livepilot-core/references/overview.md, AGENTS.md, CLAUDE.md, CHANGELOG.md, tests/test_tools_contract.py, docs/manual/index.md, docs/manual/tool-reference.md
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,103 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.10.5 — Splice online catalog unblocked + Simpler sample-loading fixes (April 14 2026)
|
|
4
|
+
|
|
5
|
+
The Splice integration was **never working online** in previous releases. The
|
|
6
|
+
`SpliceGRPCClient` existed in the codebase but silently fell back to a
|
|
7
|
+
SQLite-only path that returned only locally-downloaded samples (2 files on the
|
|
8
|
+
test user's machine). The bug was a missing `grpcio` dependency in the venv
|
|
9
|
+
combined with `sources.py` never checking for the gRPC client. Once unblocked,
|
|
10
|
+
a single query returns 19,690+ catalog hits. The "Beatles × Boards of Canada"
|
|
11
|
+
session that surfaced these bugs is archived at
|
|
12
|
+
`docs/2026-04-14-bugs-discovered.md` with 13 bugs categorized P0–P3.
|
|
13
|
+
|
|
14
|
+
Tool count: **317 → 320** (three new Splice catalog tools added).
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **`get_splice_credits`** — query the Splice user's subscription tier and
|
|
18
|
+
remaining credit balance. Returns `{connected, username, plan,
|
|
19
|
+
credits_remaining, credit_floor, can_download}`. Graceful degradation when
|
|
20
|
+
Splice desktop isn't running or grpcio is missing.
|
|
21
|
+
- **`splice_catalog_hunt`** — search Splice's ONLINE catalog via gRPC (not
|
|
22
|
+
just local downloads). Supports query, bpm_min/max, key, sample_type,
|
|
23
|
+
genre filters. Returns full sample metadata including `file_hash` for
|
|
24
|
+
downloads. This is the tool that unblocks 19,690+ results previously
|
|
25
|
+
inaccessible.
|
|
26
|
+
- **`splice_download_sample`** — download a sample by `file_hash` (costs 1
|
|
27
|
+
credit), with automatic credit-floor safety check. Optionally copies the
|
|
28
|
+
downloaded file into `~/Music/Ableton/User Library/Samples/Splice/` so
|
|
29
|
+
Ableton's browser indexes it, returning a `browser_uri` ready for
|
|
30
|
+
`load_browser_item`.
|
|
31
|
+
- **Smart warped-loop defaults** in `load_sample_to_simpler` and
|
|
32
|
+
`replace_simpler_sample`: when the filename contains a BPM marker (e.g.
|
|
33
|
+
`86bpm`), Simpler's `S Start` is set to 0, `S Length` to 100%, and
|
|
34
|
+
`S Loop On` to 1 so the full musical loop plays. Previously these tools
|
|
35
|
+
used crop defaults designed for one-shots, which chopped warped loops.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
- **P0-2 — Splice online catalog is finally reachable.** `grpcio>=1.60.0`
|
|
39
|
+
and `protobuf>=4.25.0` are now REQUIRED dependencies (added to
|
|
40
|
+
`requirements.txt`). `search_samples(source="splice")` now uses the gRPC
|
|
41
|
+
client from `ctx.lifespan_context["splice_client"]` when connected and
|
|
42
|
+
only falls back to SQLite when the gRPC path is unavailable. Before this
|
|
43
|
+
fix, a query like `"mellotron"` returned 0 hits; after, it returns 851.
|
|
44
|
+
Queries like `"lofi chord"` 80-92 BPM return 19,690 hits.
|
|
45
|
+
- **P0-1 — Simpler sample replacement is verified.** Both
|
|
46
|
+
`replace_simpler_sample` and `load_sample_to_simpler` now verify by
|
|
47
|
+
reading the device name back after the replace. If the name doesn't
|
|
48
|
+
match the requested filename stem, the tool returns a clear error
|
|
49
|
+
instead of silently shipping a wrong sample (the previous behavior
|
|
50
|
+
caused the test session to play a kick drum named as a vocal for two
|
|
51
|
+
consecutive rebuilds). The error message recommends
|
|
52
|
+
`load_browser_item` as a more reliable alternative.
|
|
53
|
+
- **P1-1 — Simpler `Snap` is automatically turned OFF** after sample load.
|
|
54
|
+
With Snap ON, the Sample Start position gets snapped to a zero-crossing
|
|
55
|
+
outside the newly loaded sample's data, causing silent playback. This was
|
|
56
|
+
the root cause of every "sample loaded but doesn't play" symptom in
|
|
57
|
+
previous sessions. The fix also applies to `replace_simpler_sample`.
|
|
58
|
+
- **P2-6 — Warped-loop sample defaults** no longer crop arbitrary sections.
|
|
59
|
+
When `load_sample_to_simpler` or `replace_simpler_sample` detects a BPM
|
|
60
|
+
marker in the filename, it applies loop-appropriate defaults instead of
|
|
61
|
+
one-shot-appropriate defaults.
|
|
62
|
+
|
|
63
|
+
### Removed
|
|
64
|
+
Nothing removed — all additions are additive.
|
|
65
|
+
|
|
66
|
+
### Verified
|
|
67
|
+
- `tests/test_tools_contract.py::test_total_tool_count` — 320 tools
|
|
68
|
+
(up from 317)
|
|
69
|
+
- `tests/test_tools_contract.py::test_sample_engine_tools_registered` —
|
|
70
|
+
includes `get_splice_credits`, `splice_catalog_hunt`,
|
|
71
|
+
`splice_download_sample`
|
|
72
|
+
- Live gRPC round-trip: searched 3 queries against Splice online, found
|
|
73
|
+
21,488 combined catalog hits, downloaded 3 samples (credits 100 → 97),
|
|
74
|
+
copied into User Library, loaded onto 3 Ableton tracks via
|
|
75
|
+
`load_browser_item` — all verified via `get_track_info` device name
|
|
76
|
+
matching.
|
|
77
|
+
|
|
78
|
+
### Known limitations
|
|
79
|
+
- **Unlimited downloads inside Splice Sounds.vst3 are not yet drivable**
|
|
80
|
+
programmatically. The gRPC download path always decrements monthly
|
|
81
|
+
credits (100/month on most subscription tiers) regardless of
|
|
82
|
+
`SoundsStatus: subscribed`. The Splice Sounds VST3 uses a separate HTTPS
|
|
83
|
+
API that LivePilot cannot drive through Ableton's plugin boundary.
|
|
84
|
+
Treat this as a research item — see P2-7 in
|
|
85
|
+
`docs/2026-04-14-bugs-discovered.md`.
|
|
86
|
+
- **The M4L bridge `.amxd` still reports 1.10.4** in its ping response.
|
|
87
|
+
Source code is at 1.10.5 but the frozen JS inside the .amxd wasn't
|
|
88
|
+
re-exported. For users installing via `npm install -g livepilot@1.10.5`
|
|
89
|
+
this is cosmetic — no bridge commands changed. If publishing a new
|
|
90
|
+
`.mcpb`, re-freeze or binary-patch the version bytes first (see
|
|
91
|
+
`feedback_amxd_freeze_drift.md`).
|
|
92
|
+
|
|
93
|
+
### Why a new patch version
|
|
94
|
+
The P0-2 fix (missing grpcio dependency) is a correctness bug: users
|
|
95
|
+
installed previous versions believed the Splice integration worked, when
|
|
96
|
+
in fact every "online" search returned only locally-downloaded files.
|
|
97
|
+
This is a silent incorrect-behavior bug — the tool returned 0-2 results
|
|
98
|
+
confidently without any warning. Users deserve a clearly-communicated
|
|
99
|
+
fix release and the ability to `npm install -g livepilot@1.10.5`.
|
|
100
|
+
|
|
3
101
|
## 1.10.4 — Bridge ping sync (April 14 2026)
|
|
4
102
|
|
|
5
103
|
A pure ship-fix release. The frozen JS inside `LivePilot_Analyzer.amxd` was
|
package/CONTRIBUTING.md
CHANGED
|
@@ -98,7 +98,7 @@ Prefix with `fix:`, `feat:`, `docs:`, `refactor:`, `test:`, or `chore:`.
|
|
|
98
98
|
|
|
99
99
|
## Tool Count Discipline
|
|
100
100
|
|
|
101
|
-
Currently **
|
|
101
|
+
Currently **320 tools**. If you add or remove a `@mcp.tool()` decorator, update all of these files:
|
|
102
102
|
|
|
103
103
|
- `README.md`
|
|
104
104
|
- `CLAUDE.md`
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
19
19
|
An agentic production system for Ableton Live 12.<br>
|
|
20
|
-
|
|
20
|
+
320 tools. 43 domains. Device atlas. Splice integration. Auto-composition. Spectral perception. Technique memory.
|
|
21
21
|
</p>
|
|
22
22
|
|
|
23
23
|
<br>
|
|
@@ -79,7 +79,7 @@ Most MCP servers are tool collections — they execute commands. LivePilot is an
|
|
|
79
79
|
│ └─────────────────┼──────────────────┘ │
|
|
80
80
|
│ ▼ │
|
|
81
81
|
│ ┌─────────────────┐ │
|
|
82
|
-
│ │
|
|
82
|
+
│ │ 320 MCP Tools │ │
|
|
83
83
|
│ │ 43 domains │ │
|
|
84
84
|
│ └────────┬────────┘ │
|
|
85
85
|
│ │ │
|
|
@@ -120,7 +120,7 @@ Most MCP servers are tool collections — they execute commands. LivePilot is an
|
|
|
120
120
|
|
|
121
121
|
## The Intelligence Layer
|
|
122
122
|
|
|
123
|
-
12 engines sit on top of the
|
|
123
|
+
12 engines sit on top of the 320 tools. They give the AI musical judgment, not just musical execution.
|
|
124
124
|
|
|
125
125
|
### SongBrain — What the Song Is
|
|
126
126
|
|
|
@@ -172,7 +172,7 @@ Every engine follows: **measure before → act → measure after → compare**.
|
|
|
172
172
|
|
|
173
173
|
## Tools
|
|
174
174
|
|
|
175
|
-
|
|
175
|
+
320 tools across 43 domains. Highlights below — [full catalog here](docs/manual/tool-catalog.md).
|
|
176
176
|
|
|
177
177
|
<br>
|
|
178
178
|
|
|
@@ -360,7 +360,7 @@ The V2 intelligence layer. These tools analyze, diagnose, plan, evaluate, and le
|
|
|
360
360
|
| Creative Constraints | 5 | constraint activation, reference-inspired variants |
|
|
361
361
|
| Preview Studio | 5 | variant creation, preview rendering, comparison, commit |
|
|
362
362
|
|
|
363
|
-
> **[View all
|
|
363
|
+
> **[View all 320 tools →](docs/manual/tool-catalog.md)**
|
|
364
364
|
|
|
365
365
|
<br>
|
|
366
366
|
|
|
@@ -585,7 +585,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for architecture details, code guidelines
|
|
|
585
585
|
|
|
586
586
|
| Document | What's inside |
|
|
587
587
|
|----------|---------------|
|
|
588
|
-
| [Manual](docs/manual/index.md) | Complete reference: architecture, all
|
|
588
|
+
| [Manual](docs/manual/index.md) | Complete reference: architecture, all 320 tools, workflows |
|
|
589
589
|
| [Intelligence Layer](docs/manual/intelligence.md) | How the 12 engines connect — conductor, moves, preview, evaluation |
|
|
590
590
|
| [Device Atlas](docs/manual/device-atlas.md) | 1305 devices indexed — search, suggest, chain building |
|
|
591
591
|
| [Samples & Slicing](docs/manual/samples.md) | 3-source search, fitness critics, slice workflows |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livepilot",
|
|
3
|
-
"version": "1.10.
|
|
4
|
-
"description": "Agentic production system for Ableton Live 12 —
|
|
3
|
+
"version": "1.10.5",
|
|
4
|
+
"description": "Agentic production system for Ableton Live 12 — 320 tools, 43 domains, device atlas, sample intelligence, auto-composition, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Pilot Studio"
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livepilot",
|
|
3
|
-
"version": "1.10.
|
|
4
|
-
"description": "Agentic production system for Ableton Live 12 —
|
|
3
|
+
"version": "1.10.5",
|
|
4
|
+
"description": "Agentic production system for Ableton Live 12 — 320 tools, 43 domains, device atlas, sample intelligence, auto-composition, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Pilot Studio"
|
|
7
7
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: livepilot-core
|
|
3
|
-
description: Core discipline for LivePilot — agentic production system for Ableton Live 12.
|
|
3
|
+
description: Core discipline for LivePilot — agentic production system for Ableton Live 12. 320 tools across 43 domains. This skill should be used whenever working with Ableton Live through MCP tools. Provides golden rules, tool speed tiers, error handling protocol, and pointers to domain and engine skills.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# LivePilot Core — Ableton Live 12
|
|
7
7
|
|
|
8
|
-
Agentic production system for Ableton Live 12.
|
|
8
|
+
Agentic production system for Ableton Live 12. 320 tools across 43 domains, three layers:
|
|
9
9
|
|
|
10
10
|
- **Device Atlas** — 1305 devices indexed (81 enriched with sonic intelligence, 683 drum kits). Consult `atlas_search` or `atlas_suggest` before loading any device. Never guess a device name.
|
|
11
11
|
- **M4L Analyzer** — Real-time audio analysis on the master bus (8-band spectrum, RMS/peak, key detection). Optional — all core tools work without it.
|
|
@@ -39,7 +39,7 @@ Agentic production system for Ableton Live 12. 317 tools across 43 domains, thre
|
|
|
39
39
|
## Tool Speed Tiers
|
|
40
40
|
|
|
41
41
|
### Instant (<1s) — Use freely
|
|
42
|
-
All
|
|
42
|
+
All 320 tools plus M4L perception tools.
|
|
43
43
|
|
|
44
44
|
### Fast (1-5s) — Use freely
|
|
45
45
|
`analyze_loudness` · `analyze_mix` · `analyze_sound_design`
|
|
@@ -128,7 +128,7 @@ Deep production knowledge in `references/`:
|
|
|
128
128
|
|
|
129
129
|
| File | Content |
|
|
130
130
|
|------|---------|
|
|
131
|
-
| `references/overview.md` | All
|
|
131
|
+
| `references/overview.md` | All 320 tools with params and ranges |
|
|
132
132
|
| `references/device-atlas/` | 280+ device corpus with URIs and presets |
|
|
133
133
|
| `references/midi-recipes.md` | Drum patterns, chord voicings, humanization |
|
|
134
134
|
| `references/sound-design.md` | Synth recipes, device chain patterns |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# LivePilot v1.10.
|
|
1
|
+
# LivePilot v1.10.5 — Architecture & Tool Reference
|
|
2
2
|
|
|
3
|
-
Agentic production system for Ableton Live 12.
|
|
3
|
+
Agentic production system for Ableton Live 12. 320 tools across 43 domains. Device atlas (1305 devices, 81 enriched), spectral perception (M4L analyzer), technique memory, automation intelligence (16 curve types, 15 recipes), music theory (Krumhansl-Schmuckler, species counterpoint), generative algorithms (Euclidean rhythm, tintinnabuli, phase shift, additive process), neo-Riemannian harmony (PRL transforms, Tonnetz), MIDI file I/O, Splice online catalog search and download via gRPC (v1.10.5 unblocked 19,690+ catalog hits previously inaccessible).
|
|
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 320 Tools — What Each One Does
|
|
36
36
|
|
|
37
37
|
### Transport (12) — Playback, tempo, global state, diagnostics
|
|
38
38
|
|
|
@@ -104,7 +104,7 @@ Call `get_capability_state` at the start of any evaluation session. The response
|
|
|
104
104
|
{
|
|
105
105
|
"mode": "normal",
|
|
106
106
|
"analyzer_connected": true,
|
|
107
|
-
"bridge_version": "1.10.
|
|
107
|
+
"bridge_version": "1.10.5",
|
|
108
108
|
"spectral_cache_age_ms": 1200,
|
|
109
109
|
"flucoma_available": false,
|
|
110
110
|
"session_connected": true
|
|
@@ -28,20 +28,20 @@ Run this checklist EVERY time the user says "update everything", "push", "releas
|
|
|
28
28
|
|
|
29
29
|
## 2. Tool Count (must ALL match)
|
|
30
30
|
|
|
31
|
-
Current: **
|
|
32
|
-
Core (no M4L): **
|
|
31
|
+
Current: **320 tools across 43 domains**.
|
|
32
|
+
Core (no M4L): **289**. Bridge (M4L): **30** (backed by 28 bridge commands).
|
|
33
33
|
|
|
34
34
|
Verify: `grep -rc "@mcp.tool" mcp_server/tools/ | grep -v ":0" | awk -F: '{sum+=$2} END{print sum}'`
|
|
35
35
|
|
|
36
36
|
Files that reference tool count:
|
|
37
|
-
- [ ] `README.md` — header ("
|
|
37
|
+
- [ ] `README.md` — header ("320 tools. 43 domains"), bridge section ("281 core...36 bridge")
|
|
38
38
|
- [ ] `package.json` → `"description"`
|
|
39
39
|
- [ ] `server.json` → `"description"`
|
|
40
40
|
- [ ] `manifest.json` → `"description"`
|
|
41
41
|
- [ ] `livepilot/.Codex-plugin/plugin.json` → `"description"` (primary Codex manifest)
|
|
42
42
|
- [ ] `livepilot/.claude-plugin/plugin.json` → `"description"` (must match Codex plugin)
|
|
43
43
|
- [ ] `.claude-plugin/marketplace.json` → `"description"`
|
|
44
|
-
- [ ] `CLAUDE.md` → "
|
|
44
|
+
- [ ] `CLAUDE.md` → "320 tools across 43 domains"
|
|
45
45
|
- [ ] `CONTRIBUTING.md` → tool count in intro
|
|
46
46
|
- [ ] `livepilot/skills/livepilot-core/SKILL.md` — tool/domain count
|
|
47
47
|
- [ ] `livepilot/skills/livepilot-core/references/overview.md` — tool/domain count
|
|
@@ -93,7 +93,7 @@ Current: **43 domains**: transport, tracks, clips, notes, devices, scenes, mixin
|
|
|
93
93
|
|
|
94
94
|
- [ ] `README.md` — features match current capabilities, "Coming" section is accurate
|
|
95
95
|
- [ ] `docs/manual/getting-started.md` — install instructions current
|
|
96
|
-
- [ ] `docs/manual/tool-reference.md` — all 43 domains listed, all
|
|
96
|
+
- [ ] `docs/manual/tool-reference.md` — all 43 domains listed, all 320 tools present
|
|
97
97
|
- [ ] `docs/TOOL_REFERENCE.md` — all 43 domains present with correct counts
|
|
98
98
|
- [ ] `docs/M4L_BRIDGE.md` — architecture accurate, core/bridge counts correct
|
|
99
99
|
|
|
Binary file
|
package/manifest.json
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"manifest_version": "0.3",
|
|
3
3
|
"name": "livepilot",
|
|
4
4
|
"display_name": "LivePilot — AI for Ableton Live",
|
|
5
|
-
"version": "1.10.
|
|
6
|
-
"description": "Agentic production system for Ableton Live 12. Make beats, mix tracks, design sounds, and arrange songs with
|
|
7
|
-
"long_description": "LivePilot is an agentic production system for Ableton Live 12.
|
|
5
|
+
"version": "1.10.5",
|
|
6
|
+
"description": "Agentic production system for Ableton Live 12. Make beats, mix tracks, design sounds, and arrange songs with 320 AI-powered tools.",
|
|
7
|
+
"long_description": "LivePilot is an agentic production system for Ableton Live 12. 320 tools across 43 domains — device atlas (1305 devices), sample intelligence (Splice + browser + filesystem), auto-composition, spectral perception, technique memory, and 12 creative engines.\n\n**What it does:**\n- Creates MIDI clips with notes, chords, and rhythms\n- Loads instruments and effects via Device Atlas (1305 devices indexed)\n- Searches samples across Splice, Ableton browser, and filesystem\n- Plans compositions from text prompts with genre-aware layering\n- Slices samples with intent-based MIDI generation\n- Mixes with volume, panning, sends, and automation\n- Analyzes your mix with real-time spectral data (M4L bridge)\n- Diagnoses stuck sessions and generates creative rescue variants\n- Remembers your production style across sessions\n\n**How it works:**\nLivePilot installs a Remote Script in Ableton that communicates with the AI over a local TCP connection. Everything runs on your machine — no audio leaves your computer.",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Pilot Studio",
|
|
10
10
|
"url": "https://github.com/dreamrec/LivePilot"
|
package/mcp_server/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""LivePilot MCP Server — bridges MCP protocol to Ableton Live."""
|
|
2
|
-
__version__ = "1.10.
|
|
2
|
+
__version__ = "1.10.5"
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
"""Sample Engine MCP tools —
|
|
1
|
+
"""Sample Engine MCP tools — intelligence-layer tools.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Wraps analyzer, critics, planner, technique library, and (as of v1.10.5)
|
|
4
|
+
direct Splice online catalog hunt/download via the gRPC client.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import os
|
|
9
10
|
from typing import Optional
|
|
10
11
|
|
|
11
12
|
from fastmcp import Context
|
|
@@ -155,7 +156,7 @@ def evaluate_sample_fit(
|
|
|
155
156
|
|
|
156
157
|
|
|
157
158
|
@mcp.tool()
|
|
158
|
-
def search_samples(
|
|
159
|
+
async def search_samples(
|
|
159
160
|
ctx: Context,
|
|
160
161
|
query: str,
|
|
161
162
|
material_type: Optional[str] = None,
|
|
@@ -169,6 +170,12 @@ def search_samples(
|
|
|
169
170
|
Searches all enabled sources in parallel and ranks results.
|
|
170
171
|
Splice results include rich metadata (key, BPM, genre, tags, pack info).
|
|
171
172
|
|
|
173
|
+
When the Splice desktop app is running AND grpcio is installed, this
|
|
174
|
+
searches Splice's ONLINE catalog (19,690+ hits for a generic query)
|
|
175
|
+
and returns un-downloaded items alongside local files. When gRPC is
|
|
176
|
+
unavailable, it falls back to the local SQLite index and only returns
|
|
177
|
+
already-downloaded samples.
|
|
178
|
+
|
|
172
179
|
query: search text like "dark vocal", "breakbeat", "foley metal"
|
|
173
180
|
material_type: filter by type (vocal, drum_loop, texture, etc.)
|
|
174
181
|
key: prefer samples in this key (e.g., "Cm", "F#")
|
|
@@ -188,21 +195,73 @@ def search_samples(
|
|
|
188
195
|
except ValueError:
|
|
189
196
|
pass
|
|
190
197
|
|
|
191
|
-
# Splice search
|
|
198
|
+
# Splice search — prefer gRPC online catalog when available, fall back
|
|
199
|
+
# to local SQLite index. See docs/2026-04-14-bugs-discovered.md — P0-2.
|
|
192
200
|
if source in (None, "splice"):
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
201
|
+
grpc_client = None
|
|
202
|
+
try:
|
|
203
|
+
grpc_client = ctx.lifespan_context.get("splice_client")
|
|
204
|
+
except AttributeError:
|
|
205
|
+
grpc_client = None
|
|
206
|
+
|
|
207
|
+
used_grpc = False
|
|
208
|
+
if grpc_client is not None and getattr(grpc_client, "connected", False):
|
|
209
|
+
try:
|
|
210
|
+
grpc_result = await grpc_client.search_samples(
|
|
211
|
+
query=query,
|
|
212
|
+
key=(key or "").lower().rstrip("m") if key else "",
|
|
213
|
+
bpm_min=int(bpm_min) if bpm_min else 0,
|
|
214
|
+
bpm_max=int(bpm_max) if bpm_max else 0,
|
|
215
|
+
per_page=max_results,
|
|
216
|
+
page=1,
|
|
217
|
+
purchased_only=False,
|
|
218
|
+
)
|
|
219
|
+
for s in grpc_result.samples[:max_results]:
|
|
220
|
+
results.append({
|
|
221
|
+
"source": "splice",
|
|
222
|
+
"name": s.filename,
|
|
223
|
+
"file_path": s.local_path or None,
|
|
224
|
+
"uri": None,
|
|
225
|
+
"freesound_id": None,
|
|
226
|
+
"relevance_score": 0,
|
|
227
|
+
"source_priority": 1,
|
|
228
|
+
"splice_catalog": True,
|
|
229
|
+
"downloaded": bool(s.local_path),
|
|
230
|
+
"file_hash": s.file_hash,
|
|
231
|
+
"metadata": {
|
|
232
|
+
"key": s.audio_key,
|
|
233
|
+
"bpm": s.bpm,
|
|
234
|
+
"tags": ",".join(s.tags) if s.tags else "",
|
|
235
|
+
"genre": s.genre or None,
|
|
236
|
+
"sample_type": s.sample_type,
|
|
237
|
+
"material_type": "vocal" if "vocal" in (s.tags or []) else "unknown",
|
|
238
|
+
"pack": s.provider_name,
|
|
239
|
+
"pack_uuid": s.pack_uuid,
|
|
240
|
+
"duration": s.duration_ms / 1000.0 if s.duration_ms else 0.0,
|
|
241
|
+
"is_premium": s.is_premium,
|
|
242
|
+
"chord_type": s.chord_type,
|
|
243
|
+
},
|
|
244
|
+
})
|
|
245
|
+
used_grpc = True
|
|
246
|
+
except Exception:
|
|
247
|
+
used_grpc = False
|
|
248
|
+
|
|
249
|
+
# Also query local index (if not already covered by gRPC) to surface
|
|
250
|
+
# downloaded-only samples that might not appear in catalog results.
|
|
251
|
+
if not used_grpc:
|
|
252
|
+
splice = SpliceSource()
|
|
253
|
+
if splice.enabled:
|
|
254
|
+
splice_results = splice.search(
|
|
255
|
+
query=query,
|
|
256
|
+
max_results=max_results,
|
|
257
|
+
key=key,
|
|
258
|
+
bpm_min=bpm_min,
|
|
259
|
+
bpm_max=bpm_max,
|
|
260
|
+
)
|
|
261
|
+
for candidate in splice_results:
|
|
262
|
+
d = candidate.to_dict()
|
|
263
|
+
d["source_priority"] = 1
|
|
264
|
+
results.append(d)
|
|
206
265
|
|
|
207
266
|
# Browser search
|
|
208
267
|
if source in (None, "browser"):
|
|
@@ -543,3 +602,296 @@ def plan_slice_workflow(
|
|
|
543
602
|
plan["style_hint"] = style_hint
|
|
544
603
|
|
|
545
604
|
return plan
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
# ── v1.10.5 Splice online catalog tools ───────────────────────────────────
|
|
608
|
+
#
|
|
609
|
+
# These expose the SpliceGRPCClient's catalog capabilities as first-class MCP
|
|
610
|
+
# tools so the agent can drive hunt→download→load without a standalone helper
|
|
611
|
+
# script. See docs/2026-04-14-bugs-discovered.md — P0-2.
|
|
612
|
+
#
|
|
613
|
+
# Prerequisites:
|
|
614
|
+
# - Splice desktop app running (port.conf present in ~/Library/Application
|
|
615
|
+
# Support/com.splice.Splice/)
|
|
616
|
+
# - grpcio and protobuf installed (added to requirements.txt in v1.10.5)
|
|
617
|
+
#
|
|
618
|
+
# Credit model (as of 2026-04-14):
|
|
619
|
+
# - Even with `SoundsStatus: subscribed`, the gRPC `DownloadSample` endpoint
|
|
620
|
+
# always decrements a monthly credit counter (default 100/month on most
|
|
621
|
+
# subscription plans).
|
|
622
|
+
# - The "unlimited downloads in Ableton" the Splice marketing references
|
|
623
|
+
# only applies to the Splice Sounds.vst3 plugin, which uses a different
|
|
624
|
+
# HTTPS API that these tools cannot drive.
|
|
625
|
+
# - `CREDIT_HARD_FLOOR = 5` in client.py reserves 5 credits as a safety
|
|
626
|
+
# margin — downloads will refuse below the floor.
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
_SPLICE_USER_LIB_DEST = "~/Music/Ableton/User Library/Samples/Splice"
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
@mcp.tool()
|
|
633
|
+
async def get_splice_credits(ctx: Context) -> dict:
|
|
634
|
+
"""Get the user's current Splice credit balance and subscription tier.
|
|
635
|
+
|
|
636
|
+
Returns: {
|
|
637
|
+
"connected": bool, # whether Splice desktop gRPC is reachable
|
|
638
|
+
"username": str,
|
|
639
|
+
"plan": str, # e.g. "subscribed", "free"
|
|
640
|
+
"credits_remaining": int,
|
|
641
|
+
"credit_floor": int, # safety reserve (typically 5)
|
|
642
|
+
"can_download": bool, # credits_remaining > credit_floor
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
Returns connected=False (with zero credits) when the Splice desktop app
|
|
646
|
+
isn't running or grpcio isn't installed.
|
|
647
|
+
"""
|
|
648
|
+
from ..splice_client.client import CREDIT_HARD_FLOOR
|
|
649
|
+
|
|
650
|
+
client = None
|
|
651
|
+
try:
|
|
652
|
+
client = ctx.lifespan_context.get("splice_client")
|
|
653
|
+
except AttributeError:
|
|
654
|
+
pass
|
|
655
|
+
|
|
656
|
+
if client is None or not getattr(client, "connected", False):
|
|
657
|
+
return {
|
|
658
|
+
"connected": False,
|
|
659
|
+
"username": "",
|
|
660
|
+
"plan": "",
|
|
661
|
+
"credits_remaining": 0,
|
|
662
|
+
"credit_floor": CREDIT_HARD_FLOOR,
|
|
663
|
+
"can_download": False,
|
|
664
|
+
"hint": (
|
|
665
|
+
"Splice gRPC not connected. Ensure Splice desktop app is "
|
|
666
|
+
"running and grpcio+protobuf are installed in the LivePilot "
|
|
667
|
+
"venv (pip install grpcio protobuf)."
|
|
668
|
+
),
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
try:
|
|
672
|
+
info = await client.get_credits()
|
|
673
|
+
except Exception as exc:
|
|
674
|
+
return {
|
|
675
|
+
"connected": False,
|
|
676
|
+
"error": f"get_credits failed: {exc}",
|
|
677
|
+
"credit_floor": CREDIT_HARD_FLOOR,
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
remaining = int(info.credits)
|
|
681
|
+
return {
|
|
682
|
+
"connected": True,
|
|
683
|
+
"username": info.username,
|
|
684
|
+
"plan": info.plan,
|
|
685
|
+
"credits_remaining": remaining,
|
|
686
|
+
"credit_floor": CREDIT_HARD_FLOOR,
|
|
687
|
+
"can_download": remaining > CREDIT_HARD_FLOOR,
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
@mcp.tool()
|
|
692
|
+
async def splice_catalog_hunt(
|
|
693
|
+
ctx: Context,
|
|
694
|
+
query: str,
|
|
695
|
+
bpm_min: int = 0,
|
|
696
|
+
bpm_max: int = 0,
|
|
697
|
+
key: str = "",
|
|
698
|
+
sample_type: str = "",
|
|
699
|
+
genre: str = "",
|
|
700
|
+
per_page: int = 10,
|
|
701
|
+
page: int = 1,
|
|
702
|
+
) -> dict:
|
|
703
|
+
"""Search Splice's ONLINE catalog via gRPC.
|
|
704
|
+
|
|
705
|
+
Unlike `search_samples` which can fall back to the local SQLite index,
|
|
706
|
+
this tool ONLY queries the online catalog — if Splice isn't connected
|
|
707
|
+
it returns an error instead of local-only results. Use this when you
|
|
708
|
+
specifically want fresh catalog content.
|
|
709
|
+
|
|
710
|
+
query: free-text search ("mellotron", "lofi chord", "soul vocal")
|
|
711
|
+
bpm_min: minimum BPM (0 = no lower bound)
|
|
712
|
+
bpm_max: maximum BPM (0 = no upper bound)
|
|
713
|
+
key: musical key (e.g. "cm", "f#", "a")
|
|
714
|
+
sample_type: "loop", "oneshot", or "" for any
|
|
715
|
+
genre: genre filter (e.g. "hip hop", "ambient")
|
|
716
|
+
per_page: results per page (1-50)
|
|
717
|
+
page: page number (1-indexed)
|
|
718
|
+
|
|
719
|
+
Returns: {
|
|
720
|
+
"connected": bool,
|
|
721
|
+
"total_hits": int, # total catalog matches
|
|
722
|
+
"samples": [...], # sample metadata with file_hash for download
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
Each sample entry contains `file_hash` which you can pass to
|
|
726
|
+
`splice_download_sample` to trigger a download.
|
|
727
|
+
"""
|
|
728
|
+
client = None
|
|
729
|
+
try:
|
|
730
|
+
client = ctx.lifespan_context.get("splice_client")
|
|
731
|
+
except AttributeError:
|
|
732
|
+
pass
|
|
733
|
+
|
|
734
|
+
if client is None or not getattr(client, "connected", False):
|
|
735
|
+
return {
|
|
736
|
+
"connected": False,
|
|
737
|
+
"error": "Splice gRPC not connected",
|
|
738
|
+
"hint": (
|
|
739
|
+
"Ensure Splice desktop app is running. Also verify grpcio "
|
|
740
|
+
"and protobuf are installed: `pip install grpcio protobuf`."
|
|
741
|
+
),
|
|
742
|
+
"samples": [],
|
|
743
|
+
"total_hits": 0,
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
try:
|
|
747
|
+
result = await client.search_samples(
|
|
748
|
+
query=query,
|
|
749
|
+
key=key.lower().rstrip("m") if key else "",
|
|
750
|
+
chord_type="minor" if key and key.lower().endswith("m") else "",
|
|
751
|
+
bpm_min=int(bpm_min),
|
|
752
|
+
bpm_max=int(bpm_max),
|
|
753
|
+
sample_type=sample_type,
|
|
754
|
+
genre=genre,
|
|
755
|
+
per_page=max(1, min(per_page, 50)),
|
|
756
|
+
page=max(1, int(page)),
|
|
757
|
+
purchased_only=False,
|
|
758
|
+
)
|
|
759
|
+
except Exception as exc:
|
|
760
|
+
return {
|
|
761
|
+
"connected": False,
|
|
762
|
+
"error": f"Splice search failed: {exc}",
|
|
763
|
+
"samples": [],
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
samples_out = []
|
|
767
|
+
for s in result.samples:
|
|
768
|
+
samples_out.append({
|
|
769
|
+
"file_hash": s.file_hash,
|
|
770
|
+
"filename": s.filename,
|
|
771
|
+
"key": s.audio_key,
|
|
772
|
+
"chord_type": s.chord_type,
|
|
773
|
+
"bpm": s.bpm,
|
|
774
|
+
"duration_sec": round((s.duration_ms or 0) / 1000.0, 2),
|
|
775
|
+
"genre": s.genre,
|
|
776
|
+
"sample_type": s.sample_type,
|
|
777
|
+
"tags": list(s.tags) if s.tags else [],
|
|
778
|
+
"pack": s.provider_name,
|
|
779
|
+
"pack_uuid": s.pack_uuid,
|
|
780
|
+
"is_premium": bool(s.is_premium),
|
|
781
|
+
"is_downloaded": bool(s.local_path),
|
|
782
|
+
"local_path": s.local_path or None,
|
|
783
|
+
"preview_url": s.preview_url,
|
|
784
|
+
})
|
|
785
|
+
|
|
786
|
+
return {
|
|
787
|
+
"connected": True,
|
|
788
|
+
"query": query,
|
|
789
|
+
"total_hits": result.total_hits,
|
|
790
|
+
"returned": len(samples_out),
|
|
791
|
+
"samples": samples_out,
|
|
792
|
+
"matching_tags": dict(result.matching_tags) if result.matching_tags else {},
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
@mcp.tool()
|
|
797
|
+
async def splice_download_sample(
|
|
798
|
+
ctx: Context,
|
|
799
|
+
file_hash: str,
|
|
800
|
+
copy_to_user_library: bool = True,
|
|
801
|
+
) -> dict:
|
|
802
|
+
"""Download a Splice sample by file_hash (costs 1 credit).
|
|
803
|
+
|
|
804
|
+
Use `splice_catalog_hunt` first to find samples and get their file_hash.
|
|
805
|
+
This tool will:
|
|
806
|
+
1. Check credit balance against the safety floor (refuses if < 5)
|
|
807
|
+
2. Trigger the download via the Splice desktop gRPC
|
|
808
|
+
3. Poll until the file appears on disk (up to 30s)
|
|
809
|
+
4. Optionally copy the file into `~/Music/Ableton/User Library/Samples/
|
|
810
|
+
Splice/` so Ableton's browser indexes it — this makes the sample
|
|
811
|
+
loadable via `load_browser_item` with a `query:UserLibrary#Samples:...`
|
|
812
|
+
URI.
|
|
813
|
+
|
|
814
|
+
Returns: {
|
|
815
|
+
"ok": bool,
|
|
816
|
+
"local_path": str, # Splice's own download path
|
|
817
|
+
"user_library_path": str, # if copy_to_user_library=True
|
|
818
|
+
"browser_uri": str, # ready for load_browser_item
|
|
819
|
+
"credits_remaining": int,
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
Note: even with an "unlimited" subscription, this gRPC path always
|
|
823
|
+
decrements credits (typically 100/month allotment). The unlimited
|
|
824
|
+
downloads inside Ableton's Splice Sounds VST3 use a different API
|
|
825
|
+
that LivePilot cannot drive programmatically yet.
|
|
826
|
+
"""
|
|
827
|
+
import shutil
|
|
828
|
+
|
|
829
|
+
client = None
|
|
830
|
+
try:
|
|
831
|
+
client = ctx.lifespan_context.get("splice_client")
|
|
832
|
+
except AttributeError:
|
|
833
|
+
pass
|
|
834
|
+
|
|
835
|
+
if client is None or not getattr(client, "connected", False):
|
|
836
|
+
return {
|
|
837
|
+
"ok": False,
|
|
838
|
+
"error": "Splice gRPC not connected",
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
# Credit safety check
|
|
842
|
+
try:
|
|
843
|
+
can, remaining = await client.can_afford(1, budget=10)
|
|
844
|
+
except Exception as exc:
|
|
845
|
+
return {"ok": False, "error": f"Credit check failed: {exc}"}
|
|
846
|
+
if not can:
|
|
847
|
+
return {
|
|
848
|
+
"ok": False,
|
|
849
|
+
"error": (
|
|
850
|
+
f"Credit safety floor hit (remaining={remaining}, "
|
|
851
|
+
f"hard floor=5). Skipping download."
|
|
852
|
+
),
|
|
853
|
+
"credits_remaining": remaining,
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
# Trigger download
|
|
857
|
+
try:
|
|
858
|
+
local_path = await client.download_sample(file_hash, timeout=30.0)
|
|
859
|
+
except Exception as exc:
|
|
860
|
+
return {"ok": False, "error": f"Download failed: {exc}"}
|
|
861
|
+
|
|
862
|
+
if not local_path:
|
|
863
|
+
return {
|
|
864
|
+
"ok": False,
|
|
865
|
+
"error": "Download did not complete within 30s timeout",
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
response: dict = {
|
|
869
|
+
"ok": True,
|
|
870
|
+
"local_path": local_path,
|
|
871
|
+
"filename": os.path.basename(local_path),
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
# Copy into User Library so Ableton's browser indexes it
|
|
875
|
+
if copy_to_user_library:
|
|
876
|
+
dest_dir = os.path.expanduser(_SPLICE_USER_LIB_DEST)
|
|
877
|
+
try:
|
|
878
|
+
os.makedirs(dest_dir, exist_ok=True)
|
|
879
|
+
dest_path = os.path.join(dest_dir, os.path.basename(local_path))
|
|
880
|
+
if not os.path.exists(dest_path):
|
|
881
|
+
shutil.copy2(local_path, dest_path)
|
|
882
|
+
response["user_library_path"] = dest_path
|
|
883
|
+
# URI format Ableton uses for user_library samples
|
|
884
|
+
response["browser_uri"] = (
|
|
885
|
+
f"query:UserLibrary#Samples:Splice:{os.path.basename(local_path)}"
|
|
886
|
+
)
|
|
887
|
+
except Exception as exc:
|
|
888
|
+
response["copy_warning"] = f"Failed to copy to User Library: {exc}"
|
|
889
|
+
|
|
890
|
+
# Post-credit count
|
|
891
|
+
try:
|
|
892
|
+
info = await client.get_credits()
|
|
893
|
+
response["credits_remaining"] = int(info.credits)
|
|
894
|
+
except Exception:
|
|
895
|
+
pass
|
|
896
|
+
|
|
897
|
+
return response
|
|
@@ -277,6 +277,128 @@ async def get_clip_file_path(
|
|
|
277
277
|
return await bridge.send_command("get_clip_file_path", track_index, clip_index)
|
|
278
278
|
|
|
279
279
|
|
|
280
|
+
import os # for filename parsing in smart-defaults helper
|
|
281
|
+
import re
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
# ── Sample loading helpers (P0-1, P1-1, P2-6 fixes) ────────────────────────
|
|
285
|
+
#
|
|
286
|
+
# Critical bug 2026-04-14 (see docs/2026-04-14-bugs-discovered.md):
|
|
287
|
+
#
|
|
288
|
+
# The M4L bridge's `replace_simpler_sample` command can report success even
|
|
289
|
+
# when the sample is still the bootstrap placeholder. The Simpler's display
|
|
290
|
+
# name also does NOT refresh after a replace. After loading, Simpler's Snap
|
|
291
|
+
# parameter is ON by default which causes the Sample Start position to
|
|
292
|
+
# snap to a location outside the new sample's valid audio — resulting in
|
|
293
|
+
# silent playback.
|
|
294
|
+
#
|
|
295
|
+
# The fixes below:
|
|
296
|
+
# 1. After replace, verify by reading the actual device name via
|
|
297
|
+
# get_track_info and comparing to the expected filename stem. If the
|
|
298
|
+
# name doesn't match, return a clear error so the caller doesn't
|
|
299
|
+
# silently ship the wrong audio.
|
|
300
|
+
# 2. Auto-set Snap=0 to disarm the zero-crossing snap that breaks playback.
|
|
301
|
+
# 3. For WARPED LOOPS (detected by "NNbpm" in the filename), set
|
|
302
|
+
# S Start=0, S Length=1, S Loop On=1 so the full loop plays in its
|
|
303
|
+
# musical phrasing. For ONE-SHOTS, leave defaults alone.
|
|
304
|
+
|
|
305
|
+
_BPM_IN_FILENAME_RE = re.compile(r"(\d{2,3})\s*bpm", re.IGNORECASE)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _is_warped_loop(file_path: str) -> bool:
|
|
309
|
+
"""Return True if the filename contains a BPM marker (likely a tempo-locked loop)."""
|
|
310
|
+
stem = os.path.splitext(os.path.basename(file_path))[0]
|
|
311
|
+
return bool(_BPM_IN_FILENAME_RE.search(stem))
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _filename_stem(file_path: str) -> str:
|
|
315
|
+
return os.path.splitext(os.path.basename(file_path))[0]
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
async def _simpler_post_load_hygiene(
|
|
319
|
+
bridge,
|
|
320
|
+
ableton,
|
|
321
|
+
track_index: int,
|
|
322
|
+
device_index: int,
|
|
323
|
+
file_path: str,
|
|
324
|
+
) -> dict:
|
|
325
|
+
"""Apply post-load hygiene to a newly loaded Simpler and verify success.
|
|
326
|
+
|
|
327
|
+
Steps:
|
|
328
|
+
1. Read track info to verify the device's actual name matches the
|
|
329
|
+
expected sample stem. If it doesn't, return an error.
|
|
330
|
+
2. Set Snap=0 (Off) — required so sample playback works.
|
|
331
|
+
3. If filename indicates a warped loop, set S Start=0, S Length=1,
|
|
332
|
+
S Loop On=1 so the loop plays fully instead of being cropped.
|
|
333
|
+
4. Return a verified response dict.
|
|
334
|
+
"""
|
|
335
|
+
expected_stem = _filename_stem(file_path)
|
|
336
|
+
|
|
337
|
+
# Step 1: verify device name matches expected file
|
|
338
|
+
try:
|
|
339
|
+
track_info = ableton.send_command(
|
|
340
|
+
"get_track_info", {"track_index": track_index}
|
|
341
|
+
)
|
|
342
|
+
except Exception as exc:
|
|
343
|
+
return {"error": f"Verification read failed: {exc}"}
|
|
344
|
+
|
|
345
|
+
devices = track_info.get("devices", []) or []
|
|
346
|
+
if device_index < 0 or device_index >= len(devices):
|
|
347
|
+
return {
|
|
348
|
+
"error": (
|
|
349
|
+
f"Device index {device_index} out of range after load "
|
|
350
|
+
f"(track has {len(devices)} devices)"
|
|
351
|
+
),
|
|
352
|
+
"verified": False,
|
|
353
|
+
}
|
|
354
|
+
device = devices[device_index]
|
|
355
|
+
actual_name = str(device.get("name") or "")
|
|
356
|
+
verified = expected_stem in actual_name or actual_name in expected_stem
|
|
357
|
+
if not verified:
|
|
358
|
+
return {
|
|
359
|
+
"error": (
|
|
360
|
+
f"Sample verification FAILED — Simpler name '{actual_name}' "
|
|
361
|
+
f"does not match requested file '{expected_stem}'. The bridge "
|
|
362
|
+
f"reported success but the actual sample is different. "
|
|
363
|
+
f"Try `load_browser_item` with a user_library URI instead."
|
|
364
|
+
),
|
|
365
|
+
"verified": False,
|
|
366
|
+
"actual_device_name": actual_name,
|
|
367
|
+
"expected_stem": expected_stem,
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
# Step 2: turn Snap OFF — required for reliable playback after replace
|
|
371
|
+
hygiene_params: list[dict] = [
|
|
372
|
+
{"name_or_index": "Snap", "value": 0},
|
|
373
|
+
]
|
|
374
|
+
|
|
375
|
+
# Step 3: smart defaults for warped loops
|
|
376
|
+
if _is_warped_loop(file_path):
|
|
377
|
+
hygiene_params.extend([
|
|
378
|
+
{"name_or_index": "S Start", "value": 0.0},
|
|
379
|
+
{"name_or_index": "S Length", "value": 1.0},
|
|
380
|
+
{"name_or_index": "S Loop On", "value": 1},
|
|
381
|
+
])
|
|
382
|
+
|
|
383
|
+
try:
|
|
384
|
+
ableton.send_command("batch_set_parameters", {
|
|
385
|
+
"track_index": track_index,
|
|
386
|
+
"device_index": device_index,
|
|
387
|
+
"parameters": hygiene_params,
|
|
388
|
+
})
|
|
389
|
+
except Exception:
|
|
390
|
+
# non-fatal — verification already succeeded
|
|
391
|
+
pass
|
|
392
|
+
|
|
393
|
+
return {
|
|
394
|
+
"verified": True,
|
|
395
|
+
"device_name": actual_name,
|
|
396
|
+
"track_index": track_index,
|
|
397
|
+
"device_index": device_index,
|
|
398
|
+
"warped_loop_defaults_applied": _is_warped_loop(file_path),
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
|
|
280
402
|
@mcp.tool()
|
|
281
403
|
async def replace_simpler_sample(
|
|
282
404
|
ctx: Context,
|
|
@@ -292,6 +414,17 @@ async def replace_simpler_sample(
|
|
|
292
414
|
manually first or use find_and_load_device to load a preset that already
|
|
293
415
|
contains a sample.
|
|
294
416
|
|
|
417
|
+
**Prefer `load_browser_item(track, uri)` when possible** — see P0-1 in
|
|
418
|
+
docs/2026-04-14-bugs-discovered.md. The M4L bridge's replace path can
|
|
419
|
+
silently keep the bootstrap placeholder in some conditions; this tool
|
|
420
|
+
now verifies by reading back the device name and will return an error
|
|
421
|
+
if the replace didn't actually take effect.
|
|
422
|
+
|
|
423
|
+
Also auto-applies post-load hygiene:
|
|
424
|
+
- Sets Simpler Snap=0 (required for playback after replace)
|
|
425
|
+
- For warped loops (filename contains 'NNbpm'), sets S Start=0,
|
|
426
|
+
S Length=1, S Loop On=1
|
|
427
|
+
|
|
295
428
|
Use get_clip_file_path to get the path of a resampled clip, then pass
|
|
296
429
|
it here to load it into Simpler for slicing.
|
|
297
430
|
Requires LivePilot Analyzer on master track.
|
|
@@ -299,6 +432,7 @@ async def replace_simpler_sample(
|
|
|
299
432
|
cache = _get_spectral(ctx)
|
|
300
433
|
_require_analyzer(cache)
|
|
301
434
|
bridge = _get_m4l(ctx)
|
|
435
|
+
ableton = ctx.lifespan_context["ableton"]
|
|
302
436
|
result = await bridge.send_command(
|
|
303
437
|
"replace_simpler_sample", track_index, device_index, file_path
|
|
304
438
|
)
|
|
@@ -312,6 +446,16 @@ async def replace_simpler_sample(
|
|
|
312
446
|
"error": "Sample may not have loaded. Ensure the Simpler already "
|
|
313
447
|
"has a sample loaded — replace_sample silently fails on empty Simplers."
|
|
314
448
|
}
|
|
449
|
+
|
|
450
|
+
# Verify by reading back the device name — guards against the silent
|
|
451
|
+
# failure mode where the bridge reports success but keeps the placeholder.
|
|
452
|
+
hygiene = await _simpler_post_load_hygiene(
|
|
453
|
+
bridge, ableton, track_index, device_index, file_path
|
|
454
|
+
)
|
|
455
|
+
if not hygiene.get("verified"):
|
|
456
|
+
return hygiene
|
|
457
|
+
|
|
458
|
+
result.update(hygiene)
|
|
315
459
|
return result
|
|
316
460
|
|
|
317
461
|
|
|
@@ -327,10 +471,18 @@ async def load_sample_to_simpler(
|
|
|
327
471
|
This is the full workflow for programmatic sample loading:
|
|
328
472
|
1. Loads a dummy sample via the browser (creates Simpler with a sample)
|
|
329
473
|
2. Replaces the dummy with your audio file
|
|
330
|
-
3.
|
|
474
|
+
3. Applies post-load hygiene (Snap=0, loop defaults for warped loops)
|
|
475
|
+
4. Verifies by reading back the device name — returns an error if
|
|
476
|
+
the Simpler still has the bootstrap placeholder (P0-1 guard)
|
|
331
477
|
|
|
332
478
|
Use this instead of replace_simpler_sample when the track has no Simpler
|
|
333
479
|
or the Simpler is empty. Works with any audio file path.
|
|
480
|
+
|
|
481
|
+
**For files that exist in Ableton's browser index** (Samples, User Library,
|
|
482
|
+
Packs), PREFER `load_browser_item(track, uri)` — it goes through Ableton's
|
|
483
|
+
native loading path and is more reliable. This tool is a workaround for
|
|
484
|
+
files that aren't browser-indexed.
|
|
485
|
+
|
|
334
486
|
Requires LivePilot Analyzer on master track.
|
|
335
487
|
"""
|
|
336
488
|
cache = _get_spectral(ctx)
|
|
@@ -380,6 +532,14 @@ async def load_sample_to_simpler(
|
|
|
380
532
|
if not result.get("sample_loaded"):
|
|
381
533
|
return {"error": "Sample replacement failed after bootstrap"}
|
|
382
534
|
|
|
535
|
+
# Step 4: Verify by reading back the device name (P0-1 guard)
|
|
536
|
+
hygiene = await _simpler_post_load_hygiene(
|
|
537
|
+
bridge, ableton, track_index, actual_device_index, file_path
|
|
538
|
+
)
|
|
539
|
+
if not hygiene.get("verified"):
|
|
540
|
+
return hygiene
|
|
541
|
+
|
|
542
|
+
result.update(hygiene)
|
|
383
543
|
result["method"] = "bootstrap_and_replace"
|
|
384
544
|
result["device_index"] = actual_device_index # additive — for step-result binding
|
|
385
545
|
result["track_index"] = track_index
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livepilot",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.5",
|
|
4
4
|
"mcpName": "io.github.dreamrec/livepilot",
|
|
5
|
-
"description": "Agentic production system for Ableton Live 12 —
|
|
5
|
+
"description": "Agentic production system for Ableton Live 12 — 320 tools, 43 domains. Device atlas (1305 devices), sample engine (Splice + browser + filesystem), auto-composition, spectral perception, technique memory, creative intelligence (12 engines)",
|
|
6
6
|
"author": "Pilot Studio",
|
|
7
7
|
"license": "BSL-1.1",
|
|
8
8
|
"type": "commonjs",
|
|
@@ -5,7 +5,7 @@ Entry point for the ControlSurface. Ableton calls create_instance(c_instance)
|
|
|
5
5
|
when this script is selected in Preferences > Link, Tempo & MIDI.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = "1.10.
|
|
8
|
+
__version__ = "1.10.5"
|
|
9
9
|
|
|
10
10
|
from _Framework.ControlSurface import ControlSurface
|
|
11
11
|
from .server import LivePilotServer
|
package/requirements.txt
CHANGED
|
@@ -8,6 +8,12 @@ pyloudnorm>=0.1.0
|
|
|
8
8
|
soundfile>=0.12.0
|
|
9
9
|
scipy>=1.11.0
|
|
10
10
|
mutagen>=1.47.0
|
|
11
|
+
# v1.10.5 Splice online catalog integration — required, not optional.
|
|
12
|
+
# Without these, SpliceGRPCClient silently disables itself and search_samples
|
|
13
|
+
# falls back to the SQLite sounds.db which only returns locally downloaded
|
|
14
|
+
# samples (see docs/2026-04-14-bugs-discovered.md — P0-2).
|
|
15
|
+
grpcio>=1.60.0
|
|
16
|
+
protobuf>=4.25.0
|
|
11
17
|
|
|
12
18
|
# Development / testing (not required for runtime)
|
|
13
19
|
# pip install pytest pytest-asyncio
|
package/livepilot.mcpb
DELETED
|
Binary file
|