livepilot 1.1.1 → 1.2.0
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 +25 -0
- package/LivePilot audit report.md +87 -0
- package/README.md +1 -1
- package/installer/paths.js +2 -2
- package/mcp_server/tools/arrangement.py +10 -6
- package/mcp_server/tools/clips.py +28 -2
- package/mcp_server/tools/scenes.py +30 -2
- package/mcp_server/tools/tracks.py +62 -2
- package/package.json +2 -2
- package/plugin/plugin.json +2 -2
- package/plugin/skills/livepilot-core/SKILL.md +2 -2
- package/plugin/skills/livepilot-core/references/overview.md +15 -7
- package/remote_script/LivePilot/arrangement.py +4 -1
- package/remote_script/LivePilot/clips.py +41 -2
- package/remote_script/LivePilot/scenes.py +25 -1
- package/remote_script/LivePilot/tracks.py +95 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.0 — 2026-03-17
|
|
4
|
+
|
|
5
|
+
### New Tools (+8, total 99)
|
|
6
|
+
- **`create_group_track`** — group a range of tracks into a foldable group track
|
|
7
|
+
- **`set_group_fold`** — fold/unfold a group track to show/hide children
|
|
8
|
+
- **`freeze_track`** — freeze a track to reduce CPU load
|
|
9
|
+
- **`flatten_track`** — flatten a frozen track to audio permanently
|
|
10
|
+
- **`set_track_input_monitoring`** — set input monitoring state (In/Auto/Off)
|
|
11
|
+
- **`set_clip_warp_mode`** — set warp mode for audio clips (Beats/Tones/Texture/Re-Pitch/Complex/Complex Pro)
|
|
12
|
+
- **`set_scene_color`** — set scene color (0-69)
|
|
13
|
+
- **`set_scene_tempo`** — set per-scene tempo that triggers on launch
|
|
14
|
+
|
|
15
|
+
### Enhancements
|
|
16
|
+
- `get_clip_info` now returns `is_midi_clip`, `is_audio_clip`, and audio warp fields (`warping`, `warp_mode`)
|
|
17
|
+
- `get_track_info` now returns `is_foldable`, `is_grouped`, `fold_state`, and `current_monitoring_state`
|
|
18
|
+
|
|
19
|
+
## 1.1.2 — 2026-03-17
|
|
20
|
+
|
|
21
|
+
### Fixes
|
|
22
|
+
- Added missing `clip_index` validation to 4 arrangement MCP tools
|
|
23
|
+
- Standardized `json` import (removed `_json` alias)
|
|
24
|
+
- Added index range to arrangement clip error messages for consistency
|
|
25
|
+
- Made installer version-agnostic (`/^Live \d+/` regex instead of hardcoded "Live 12")
|
|
26
|
+
- Standardized project description across all config files
|
|
27
|
+
|
|
3
28
|
## 1.1.1 — 2026-03-17
|
|
4
29
|
|
|
5
30
|
### Fixes
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Deep audit of the **LivePilot** repository and comparison with other AI‑enabled DAW assistants
|
|
2
|
+
|
|
3
|
+
## Overview of LivePilot
|
|
4
|
+
|
|
5
|
+
**LivePilot** is an open‑source Model Context Protocol (MCP) server and companion plug‑in that connects large language models to *Ableton Live 12*. The project’s goal is to act as an **AI co‑pilot** for music production, sound design and mixing.
|
|
6
|
+
|
|
7
|
+
* **Purpose and capabilities:** The README explains that LivePilot exposes **91 tools across nine domains** (transport, tracks, clips, notes, devices, scenes, mixing, browser and arrangement)【665853537458845†L43-L70】. It allows a user to create tracks, program MIDI patterns, load instruments, adjust mixing parameters and even build full song structures via natural‑language requests【665853537458845†L15-L21】. The tool set covers nearly every aspect of a typical Ableton workflow: starting/stopping playback, setting tempo, creating MIDI or audio tracks, renaming and coloring them, creating/duplicating clips, reading and editing MIDI notes, loading devices and presets, changing levels/pans/sends, browsing Ableton’s library, editing arrangement clips and automation【665853537458845†L43-L70】. Each tool maps directly to an Ableton Live API call; the manual notes that there is **no abstraction layer** – commands are deterministic and reversible【608977690485721†L23-L27】.
|
|
8
|
+
* **Architecture:** A natural‑language prompt is processed by the AI client (e.g., Claude Code or Cursor) and sent over the MCP to the **LivePilot server** (Python with `fastmcp`)【665853537458845†L90-L97】. The server validates inputs and forwards them via a single‑client TCP connection to a **Remote Script** that runs inside Ableton’s Python environment【665853537458845†L90-L97】. The Remote Script executes commands on Ableton’s main thread (the same thread as the GUI), ensuring thread‑safe access to the Live Object Model【608977690485721†L29-L34】. Installation is streamlined via `npx -y github:dreamrec/LivePilot --install`, which copies the Remote Script to Ableton’s user library and sets up the MCP server【665853537458845†L22-L29】.
|
|
9
|
+
* **Plugin and producer agent:** LivePilot includes a Claude Code plug‑in that provides slash commands (`/session`, `/beat`, `/mix`, `/sounddesign`) and an **autonomous producer agent** that can build complete tracks from high‑level descriptions (“Make me a 126 BPM rominimal track…”)【665853537458845†L82-L89】. The core skill packaged with the plug‑in teaches the AI how to use LivePilot effectively – checking session state before issuing commands, verifying the results after writes and avoiding common mistakes【692402510651097†L69-L81】.
|
|
10
|
+
* **Documentation and user guidance:** The manual is comprehensive. It explains installation, connectivity, examples of natural‑language sessions, the details of each tool, production workflows, MIDI programming guidelines, sound design recipes, mixing tips and troubleshooting【608977690485721†L40-L47】. The getting‑started guide shows how to connect the server, then suggests an example conversation (“What’s in my session?” → “Set the tempo to 120 and create a MIDI track called DRUMS” → “Search for a drum kit and load it onto the DRUMS track” → “Create an 8‑beat clip and program a basic house kick pattern…”)【692402510651097†L39-L51】.
|
|
11
|
+
* **Compatibility:** LivePilot supports Ableton Live 12 (any edition) and requires Python 3.10 or newer and Node.js 18+【665853537458845†L96-L102】. Arrangement‑related tools work only in Live 12, while loading stock instruments and Max for Live devices requires the Suite edition【665853537458845†L96-L102】. CLI commands such as `--status`, `--doctor`, `--install` and `--version` simplify installation and diagnostics【665853537458845†L103-L106】.
|
|
12
|
+
|
|
13
|
+
### Project structure and quality
|
|
14
|
+
|
|
15
|
+
Because the repository cannot be cloned from the restricted environment, we rely on the README and manual to infer its structure. The repository contains at least the following components:
|
|
16
|
+
|
|
17
|
+
1. **Remote script (`remote_script/LivePilot`)** – a Python package that subclasses Ableton’s `ControlSurface` class and implements the command queue and handlers. It is installed into Ableton’s User Remote Scripts folder by the `--install` command【692402510651097†L18-L23】.
|
|
18
|
+
2. **MCP server (`mcp_server`)** – a Python server built on the `fastmcp` framework. It validates input parameters (ranges and types) before passing them to the remote script【608977690485721†L29-L34】. Using a single TCP client avoids concurrency issues, because Ableton’s Live Object Model is not thread‑safe【665853537458845†L90-L97】.
|
|
19
|
+
3. **CLI (JavaScript)** – the `npx` entry point uses Node.js to bootstrap the Python server and perform installation tasks. The minimal `requirements.txt` lists `fastmcp` as the only runtime dependency【45077298533394†L0-L2】.
|
|
20
|
+
4. **Docs and tests:** The repo contains a `docs/manual` folder with Markdown documentation, and it mentions a `tests` directory (run by `pytest`)【665853537458845†L110-L111】.
|
|
21
|
+
|
|
22
|
+
While we cannot inspect individual functions, the combination of Python and Node indicates a hybrid architecture. Using `fastmcp` suggests asynchronous I/O and structured validation. The repository emphasises deterministic operations and explicit `undo` functionality【692402510651097†L63-L67】, which points to careful state management. Including a detailed manual and CLI indicates strong attention to developer experience.
|
|
23
|
+
|
|
24
|
+
## Comparison with other AI‑enabled Ableton assistants
|
|
25
|
+
|
|
26
|
+
The AI‑DAW landscape has seen several community‑driven MCP servers that integrate language models with Ableton Live. Below is a comparison of LivePilot with four notable open‑source alternatives and one machine‑learning plugin collection (Magenta Studio). Star counts are ignored; only features matter. All projects implement the MCP for connecting an AI client to Ableton, but they differ in language, feature set and design philosophy.
|
|
27
|
+
|
|
28
|
+
### Feature comparison
|
|
29
|
+
|
|
30
|
+
The table summarises the core capabilities (✓ = present, – = absent or not mentioned). Note: only keywords/phrases are used in the cells; long prose is kept in the explanation below.
|
|
31
|
+
|
|
32
|
+
| Tool/Project | Track & Session management | MIDI/Clip editing | Device/Effect control | Arrangement & automation | Mixing & routing | Browser search | Voice/audio generation | State rollback/Undo | Low‑latency/UDP | Autonomous agent |
|
|
33
|
+
|---|---|---|---|---|---|---|---|---|---|---|
|
|
34
|
+
| **LivePilot (dreamrec)** | create/delete/duplicate tracks, rename, color, mute, solo, arm【665853537458845†L45-L61】 | add/get/modify/duplicate/transpose/quantize notes【665853537458845†L62-L66】 | load instruments/effects, tweak parameters, rack chains, get presets【665853537458845†L66-L69】 | arrangement clip creation & full MIDI note CRUD, cue points, recording, timeline navigation【665853537458845†L74-L81】 | set volume, pan, sends; routing & return tracks; master control【665853537458845†L72-L74】 | search Ableton library & load presets【665853537458845†L72-L75】 | – (not in current version) | deterministic operations with undo/redo commands【692402510651097†L63-L67】 | – | built‑in producer agent via plugin【665853537458845†L82-L89】 |
|
|
35
|
+
| **Ableton Copilot MCP (xiaolaa2)** | create/delete/duplicate/rename MIDI, audio & return tracks; set tempo, time signature and scale【477631886601640†L155-L160】 | add/remove/replace/duplicate notes, filter by pitch, transpose, adjust clip properties【477631886601640†L161-L166】 | browse library, load specific audio effects/instruments/VSTs, modify parameters, record audio into clips【477631886601640†L167-L171】 | – (no dedicated arrangement API mentioned) | – | – | – | robust state management with snapshot & rollback support【477631886601640†L173-L177】 | – | – |
|
|
36
|
+
| **AbletonMCP (ahujasid)** | create/modify MIDI & audio tracks, load and configure remote script【260789399880945†L10-L16】 | create & edit MIDI clips; add notes【260789399880945†L14-L16】【260789399880945†L61-L63】 | select/load instruments & effects from Ableton’s browser【260789399880945†L13-L16】【260789399880945†L61-L63】 | – | basic playback/transport control【260789399880945†L15-L16】 | – | – | – (standard `undo` only) | – | – |
|
|
37
|
+
| **Ableton Live MCP Server (Simon‑Kansara)** | generic track & session control via OSC【308870449054950†L16-L19】 | – | – | – | – | – | – | – | uses UDP/OSC for communication (requires separate daemon)【308870449054950†L30-L34】 | – |
|
|
38
|
+
| **Ableton MCP Extended (uisato)** | comprehensive session & track management including group/folding states【795384148544515†L29-L35】 | create/rename clips; add/delete/transpose/quantize notes; batch edits【795384148544515†L35-L37】 | load instruments/effects; set & batch‑set parameters【795384148544515†L38-L41】 | automation/envelope editing; high‑performance mode using UDP for real‑time control【795384148544515†L41-L54】【795384148544515†L82-L87】 | volume, panning, mute, solo, sends【795384148544515†L29-L35】 | browse Ableton library and import audio files【795384148544515†L44-L46】 | voice/text‑to‑speech integration via ElevenLabs; custom voice & SFX【795384148544515†L47-L50】 | – (standard `undo` not emphasised) | ultra‑low latency via UDP server【795384148544515†L82-L87】 | – |
|
|
39
|
+
| **Magenta Studio (Google)** | – | generates melodies/drums, extends or transforms patterns using ML models【262161962734483†L63-L84】 | – | – | – | – | – | – | – | – |
|
|
40
|
+
|
|
41
|
+
### Discussion
|
|
42
|
+
|
|
43
|
+
#### Breadth of control
|
|
44
|
+
|
|
45
|
+
LivePilot stands out for its broad coverage of Ableton’s API. It not only performs basic track and session operations but also supports **arrangement view editing** (creating arrangement clips, moving between cue points, starting/stopping recording, editing automation)【665853537458845†L74-L81】. Most competitors focus on session view; for example, Ableton Copilot MCP’s documentation highlights track/clip manipulation and device control but does not mention arrangement or automation【477631886601640†L155-L171】. AbletonMCP Extended adds some automation editing and envelope functionality【795384148544515†L41-L46】 but still doesn’t provide the same comprehensive list of arrangement tools.
|
|
46
|
+
|
|
47
|
+
#### Mixing and browser integration
|
|
48
|
+
|
|
49
|
+
LivePilot provides dedicated mixing tools (volume, pan, sends, routing, return tracks and master volume)【665853537458845†L72-L74】 and separate browser tools to search the entire Ableton library and load presets【665853537458845†L72-L75】. The other projects have limited mixing commands; AbletonMCP Extended includes volume/panning/mute/solo and send control【795384148544515†L29-L35】, but others do not emphasise mixing. Browser search appears in LivePilot and Extended; it is absent from Copilot MCP.
|
|
50
|
+
|
|
51
|
+
#### Safety and robustness
|
|
52
|
+
|
|
53
|
+
LivePilot emphasises deterministic operations and encourages the AI to verify the session state after every write. The `undo` tool is exposed, making all destructive operations reversible【692402510651097†L63-L67】. Ableton Copilot MCP goes further with **state rollback**; it snapshots the project state and can undo groups of operations, which is a robust safety net for complex tasks【477631886601640†L173-L177】. Competitors built on Python sockets generally rely on Ableton’s own undo (a single step), so LivePilot’s explicit undo tool is a strength but could be improved by adopting a snapshot approach.
|
|
54
|
+
|
|
55
|
+
#### Advanced features
|
|
56
|
+
|
|
57
|
+
* **Autonomous agent:** LivePilot’s Claude plug‑in includes a **producer agent** that builds complete tracks from high‑level descriptions【665853537458845†L82-L89】. None of the other open‑source projects advertise an autonomous composition agent, although some AI‑first DAW assistants outside the MCP ecosystem (e.g., commercial “Studio Co‑Pilot” or online generative services) may offer similar features.
|
|
58
|
+
* **Voice and audio generation:** Ableton MCP Extended uniquely integrates ElevenLabs for voice/speech generation and allows direct import of generated audio into Ableton sessions【795384148544515†L47-L50】. LivePilot lacks this type of generative audio; adding a similar feature could broaden its creative scope.
|
|
59
|
+
* **Low‑latency control:** The Extended project includes a UDP server for ultra‑low‑latency parameter control【795384148544515†L82-L87】. LivePilot uses a single TCP connection for thread safety【665853537458845†L90-L97】; while stable, it may introduce latency for real‑time performance. Offering a high‑performance mode or MIDI mapping could appeal to live performers.
|
|
60
|
+
* **State management:** Ableton Copilot MCP’s snapshot‑based rollback system is a notable innovation【477631886601640†L173-L177】. LivePilot currently supports standard undo/redo but not multi‑step rollback; implementing such a feature would increase safety.
|
|
61
|
+
* **Extensibility:** Both LivePilot and Ableton MCP Extended provide frameworks for creating custom tools. LivePilot’s skill file teaches the AI how to interact correctly【692402510651097†L69-L81】, and the Extended project demonstrates building custom controllers (XY mouse controller)【795384148544515†L82-L87】.
|
|
62
|
+
|
|
63
|
+
#### Installation and user experience
|
|
64
|
+
|
|
65
|
+
LivePilot provides a simple `npx` installer and CLI commands to install, check status and run diagnostics【665853537458845†L22-L27】【665853537458845†L103-L106】. Ableton Copilot MCP similarly offers one‑line installation via `npx @xiaolaa2/ableton-copilot-mcp --install-scripts`【477631886601640†L213-L216】. AbletonMCP and Extended require cloning or using `uv` and copying remote scripts manually【260789399880945†L21-L34】【795384148544515†L56-L66】, which is less streamlined. LivePilot’s documentation is thorough and includes a manual covering workflows, whereas competitors often rely on README‑level documentation or blog posts.
|
|
66
|
+
|
|
67
|
+
### Strengths and areas for improvement
|
|
68
|
+
|
|
69
|
+
#### Strengths of LivePilot
|
|
70
|
+
|
|
71
|
+
1. **Comprehensive toolset:** With 91 tools covering transport, track management, clip operations, note editing, devices, scenes, mixing, browser search and arrangement, LivePilot offers the broadest feature coverage among existing MCP servers【665853537458845†L43-L70】.
|
|
72
|
+
2. **Natural‑language workflows:** The manual demonstrates how to use conversation to build beats, search for instruments, program MIDI patterns and mix tracks【692402510651097†L39-L51】. This conversational approach, combined with the producer agent, makes LivePilot accessible to users with minimal technical knowledge.
|
|
73
|
+
3. **Deterministic operations and safety:** By mapping tools directly to Ableton’s API and exposing undo/redo, LivePilot aims to provide predictable outcomes and a clear safety net【608977690485721†L23-L27】【692402510651097†L63-L67】.
|
|
74
|
+
4. **Developer experience:** The `npx` installer, CLI commands (`--status`, `--doctor`), cross‑platform support (macOS and Windows) and extensive manual contribute to a polished user experience【665853537458845†L103-L106】.
|
|
75
|
+
5. **Plugin & agent integration:** The Claude plug‑in adds slash commands and an autonomous producer agent that can compose full tracks from high‑level prompts【665853537458845†L82-L89】. This is unique among open‑source MCP servers.
|
|
76
|
+
|
|
77
|
+
#### Areas for improvement
|
|
78
|
+
|
|
79
|
+
1. **State rollback and history:** Competing projects like Ableton Copilot MCP implement snapshot‑based rollback【477631886601640†L173-L177】. Incorporating multi‑step undo or versioned snapshots in LivePilot would improve safety during complex editing.
|
|
80
|
+
2. **Voice and audio generation:** Ableton MCP Extended integrates ElevenLabs for text‑to‑speech and can import generated vocals or sound effects【795384148544515†L47-L50】. Adding optional integration with generative audio tools (e.g., ElevenLabs, OpenAI TTS) could expand LivePilot’s creative capabilities.
|
|
81
|
+
3. **Low‑latency control:** The Extended project’s UDP server provides ultra‑low‑latency parameter control【795384148544515†L82-L87】. LivePilot could explore a similar performance mode or integration with hardware controllers to better serve live performers.
|
|
82
|
+
4. **Simplify cross‑language architecture:** LivePilot uses Node.js to bootstrap a Python server; although this is hidden from the user, consolidating components or providing a purely Python installation could reduce complexity and dependency on Node.
|
|
83
|
+
5. **Community engagement:** Many competing projects have active communities and Discord servers (e.g., AbletonMCP invites feedback on Discord【260789399880945†L8-L9】). Building a community around LivePilot would encourage contributions, gather feedback and accelerate development.
|
|
84
|
+
|
|
85
|
+
## Conclusion
|
|
86
|
+
|
|
87
|
+
LivePilot is an ambitious and well‑documented project that aims to be a **comprehensive AI co‑pilot** for Ableton Live 12. Its strength lies in its broad tool coverage, polished installation experience, deterministic operations and the inclusion of a plug‑in with an autonomous producer agent. Compared with other MCP servers, LivePilot covers more domains (especially arrangement and mixing) and provides a more polished user experience. Competing projects offer innovations such as state rollback, voice/audio generation and low‑latency control. Incorporating similar features, encouraging community engagement and potentially simplifying the architecture would enhance LivePilot’s position as the most intelligent and versatile AI assistant for Ableton Live.
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
[](https://github.com/dreamrec/LivePilot/actions/workflows/ci.yml)
|
|
12
12
|
[](https://github.com/dreamrec/LivePilot/stargazers)
|
|
13
13
|
|
|
14
|
-
**AI copilot for Ableton Live 12** —
|
|
14
|
+
**AI copilot for Ableton Live 12** — 99 MCP tools for music production, sound design, and mixing.
|
|
15
15
|
|
|
16
16
|
Control your entire Ableton session through natural language. Create tracks, program MIDI, load instruments, tweak parameters, arrange songs, and mix — all without leaving the keyboard.
|
|
17
17
|
|
package/installer/paths.js
CHANGED
|
@@ -26,7 +26,7 @@ function findAbletonPaths() {
|
|
|
26
26
|
try {
|
|
27
27
|
const entries = fs.readdirSync(prefsDir);
|
|
28
28
|
for (const entry of entries) {
|
|
29
|
-
if (
|
|
29
|
+
if (/^Live \d+/.test(entry)) {
|
|
30
30
|
candidates.push({
|
|
31
31
|
path: path.join(prefsDir, entry, "User Remote Scripts"),
|
|
32
32
|
description: `Ableton Preferences (${entry})`,
|
|
@@ -53,7 +53,7 @@ function findAbletonPaths() {
|
|
|
53
53
|
try {
|
|
54
54
|
const entries = fs.readdirSync(abletonAppData);
|
|
55
55
|
for (const entry of entries) {
|
|
56
|
-
if (
|
|
56
|
+
if (/^Live \d+/.test(entry)) {
|
|
57
57
|
candidates.push({
|
|
58
58
|
path: path.join(abletonAppData, entry, "Preferences", "User Remote Scripts"),
|
|
59
59
|
description: `Ableton AppData (${entry})`,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
19 tools matching the Remote Script arrangement domain.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
import json
|
|
6
|
+
import json
|
|
7
7
|
|
|
8
8
|
from fastmcp import Context
|
|
9
9
|
|
|
@@ -147,8 +147,9 @@ def add_arrangement_notes(
|
|
|
147
147
|
start_time in notes is relative to the clip start, not the song timeline.
|
|
148
148
|
"""
|
|
149
149
|
_validate_track_index(track_index)
|
|
150
|
+
_validate_clip_index(clip_index)
|
|
150
151
|
if isinstance(notes, str):
|
|
151
|
-
notes =
|
|
152
|
+
notes = json.loads(notes)
|
|
152
153
|
return _get_ableton(ctx).send_command("add_arrangement_notes", {
|
|
153
154
|
"track_index": track_index,
|
|
154
155
|
"clip_index": clip_index,
|
|
@@ -181,6 +182,7 @@ def set_arrangement_automation(
|
|
|
181
182
|
For parameter_type="send": send_index required (0=A, 1=B, ...).
|
|
182
183
|
"""
|
|
183
184
|
_validate_track_index(track_index)
|
|
185
|
+
_validate_clip_index(clip_index)
|
|
184
186
|
if parameter_type not in ("device", "volume", "panning", "send"):
|
|
185
187
|
raise ValueError("parameter_type must be 'device', 'volume', 'panning', or 'send'")
|
|
186
188
|
if parameter_type == "device":
|
|
@@ -189,7 +191,7 @@ def set_arrangement_automation(
|
|
|
189
191
|
if parameter_type == "send" and send_index is None:
|
|
190
192
|
raise ValueError("send_index required for parameter_type='send'")
|
|
191
193
|
if isinstance(points, str):
|
|
192
|
-
points =
|
|
194
|
+
points = json.loads(points)
|
|
193
195
|
if not points:
|
|
194
196
|
raise ValueError("points list cannot be empty")
|
|
195
197
|
params: dict = {
|
|
@@ -224,6 +226,7 @@ def transpose_arrangement_notes(
|
|
|
224
226
|
time_span: length of note range in beats (defaults to full clip)
|
|
225
227
|
"""
|
|
226
228
|
_validate_track_index(track_index)
|
|
229
|
+
_validate_clip_index(clip_index)
|
|
227
230
|
if not -127 <= semitones <= 127:
|
|
228
231
|
raise ValueError("semitones must be between -127 and 127")
|
|
229
232
|
params: dict = {
|
|
@@ -248,6 +251,7 @@ def set_arrangement_clip_name(
|
|
|
248
251
|
) -> dict:
|
|
249
252
|
"""Rename an arrangement clip by its index in the track's arrangement_clips list."""
|
|
250
253
|
_validate_track_index(track_index)
|
|
254
|
+
_validate_clip_index(clip_index)
|
|
251
255
|
if not name.strip():
|
|
252
256
|
raise ValueError("name cannot be empty")
|
|
253
257
|
return _get_ableton(ctx).send_command("set_arrangement_clip_name", {
|
|
@@ -333,7 +337,7 @@ def remove_arrangement_notes_by_id(
|
|
|
333
337
|
_validate_track_index(track_index)
|
|
334
338
|
_validate_clip_index(clip_index)
|
|
335
339
|
if isinstance(note_ids, str):
|
|
336
|
-
note_ids =
|
|
340
|
+
note_ids = json.loads(note_ids)
|
|
337
341
|
if not note_ids:
|
|
338
342
|
raise ValueError("note_ids list cannot be empty")
|
|
339
343
|
return _get_ableton(ctx).send_command("remove_arrangement_notes_by_id", {
|
|
@@ -355,7 +359,7 @@ def modify_arrangement_notes(
|
|
|
355
359
|
_validate_track_index(track_index)
|
|
356
360
|
_validate_clip_index(clip_index)
|
|
357
361
|
if isinstance(modifications, str):
|
|
358
|
-
modifications =
|
|
362
|
+
modifications = json.loads(modifications)
|
|
359
363
|
if not modifications:
|
|
360
364
|
raise ValueError("modifications list cannot be empty")
|
|
361
365
|
for mod in modifications:
|
|
@@ -388,7 +392,7 @@ def duplicate_arrangement_notes(
|
|
|
388
392
|
_validate_track_index(track_index)
|
|
389
393
|
_validate_clip_index(clip_index)
|
|
390
394
|
if isinstance(note_ids, str):
|
|
391
|
-
note_ids =
|
|
395
|
+
note_ids = json.loads(note_ids)
|
|
392
396
|
if not note_ids:
|
|
393
397
|
raise ValueError("note_ids list cannot be empty")
|
|
394
398
|
return _get_ableton(ctx).send_command("duplicate_arrangement_notes", {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"""Clip MCP tools — info, create, delete, duplicate, fire, stop, properties.
|
|
1
|
+
"""Clip MCP tools — info, create, delete, duplicate, fire, stop, properties, warp.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
11 tools matching the Remote Script clips domain.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from fastmcp import Context
|
|
@@ -185,3 +185,29 @@ def set_clip_launch(
|
|
|
185
185
|
if quantization is not None:
|
|
186
186
|
params["quantization"] = quantization
|
|
187
187
|
return _get_ableton(ctx).send_command("set_clip_launch", params)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
_VALID_WARP_MODES = {0, 1, 2, 3, 4, 6}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@mcp.tool()
|
|
194
|
+
def set_clip_warp_mode(
|
|
195
|
+
ctx: Context,
|
|
196
|
+
track_index: int,
|
|
197
|
+
clip_index: int,
|
|
198
|
+
mode: int,
|
|
199
|
+
warping: bool | None = None,
|
|
200
|
+
) -> dict:
|
|
201
|
+
"""Set warp mode for an audio clip (0=Beats, 1=Tones, 2=Texture, 3=Re-Pitch, 4=Complex, 6=Complex Pro)."""
|
|
202
|
+
_validate_track_index(track_index)
|
|
203
|
+
_validate_clip_index(clip_index)
|
|
204
|
+
if mode not in _VALID_WARP_MODES:
|
|
205
|
+
raise ValueError("Warp mode must be one of: 0=Beats, 1=Tones, 2=Texture, 3=Re-Pitch, 4=Complex, 6=Complex Pro")
|
|
206
|
+
params = {
|
|
207
|
+
"track_index": track_index,
|
|
208
|
+
"clip_index": clip_index,
|
|
209
|
+
"mode": mode,
|
|
210
|
+
}
|
|
211
|
+
if warping is not None:
|
|
212
|
+
params["warping"] = warping
|
|
213
|
+
return _get_ableton(ctx).send_command("set_clip_warp_mode", params)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"""Scene MCP tools — list, create, delete, duplicate, fire, rename.
|
|
1
|
+
"""Scene MCP tools — list, create, delete, duplicate, fire, rename, color, tempo.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
8 tools matching the Remote Script scenes domain.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from fastmcp import Context
|
|
@@ -61,3 +61,31 @@ def set_scene_name(ctx: Context, scene_index: int, name: str) -> dict:
|
|
|
61
61
|
"scene_index": scene_index,
|
|
62
62
|
"name": name,
|
|
63
63
|
})
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _validate_color_index(color_index: int):
|
|
67
|
+
if not 0 <= color_index <= 69:
|
|
68
|
+
raise ValueError("color_index must be between 0 and 69")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@mcp.tool()
|
|
72
|
+
def set_scene_color(ctx: Context, scene_index: int, color_index: int) -> dict:
|
|
73
|
+
"""Set scene color (0-69, Ableton's color palette)."""
|
|
74
|
+
_validate_scene_index(scene_index)
|
|
75
|
+
_validate_color_index(color_index)
|
|
76
|
+
return _get_ableton(ctx).send_command("set_scene_color", {
|
|
77
|
+
"scene_index": scene_index,
|
|
78
|
+
"color_index": color_index,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@mcp.tool()
|
|
83
|
+
def set_scene_tempo(ctx: Context, scene_index: int, tempo: float) -> dict:
|
|
84
|
+
"""Set scene tempo in BPM. Fires when the scene launches. Set to 0 to clear."""
|
|
85
|
+
_validate_scene_index(scene_index)
|
|
86
|
+
if tempo != 0 and (tempo < 20 or tempo > 999):
|
|
87
|
+
raise ValueError("Tempo must be 0 (clear) or between 20.0 and 999.0 BPM")
|
|
88
|
+
return _get_ableton(ctx).send_command("set_scene_tempo", {
|
|
89
|
+
"scene_index": scene_index,
|
|
90
|
+
"tempo": tempo,
|
|
91
|
+
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"""Track MCP tools — create, delete, rename, mute, solo, arm.
|
|
1
|
+
"""Track MCP tools — create, delete, rename, mute, solo, arm, group, freeze, monitor.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
17 tools matching the Remote Script tracks domain.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from fastmcp import Context
|
|
@@ -146,3 +146,63 @@ def stop_track_clips(ctx: Context, track_index: int) -> dict:
|
|
|
146
146
|
"""Stop all playing clips on a track."""
|
|
147
147
|
_validate_track_index(track_index)
|
|
148
148
|
return _get_ableton(ctx).send_command("stop_track_clips", {"track_index": track_index})
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@mcp.tool()
|
|
152
|
+
def create_group_track(
|
|
153
|
+
ctx: Context,
|
|
154
|
+
start_index: int,
|
|
155
|
+
end_index: int,
|
|
156
|
+
name: str | None = None,
|
|
157
|
+
color: int | None = None,
|
|
158
|
+
) -> dict:
|
|
159
|
+
"""Create a group track containing tracks from start_index to end_index (inclusive)."""
|
|
160
|
+
_validate_track_index(start_index)
|
|
161
|
+
_validate_track_index(end_index)
|
|
162
|
+
if end_index < start_index:
|
|
163
|
+
raise ValueError("end_index must be >= start_index")
|
|
164
|
+
params = {"start_index": start_index, "end_index": end_index}
|
|
165
|
+
if name is not None:
|
|
166
|
+
if not name.strip():
|
|
167
|
+
raise ValueError("Track name cannot be empty")
|
|
168
|
+
params["name"] = name
|
|
169
|
+
if color is not None:
|
|
170
|
+
_validate_color_index(color)
|
|
171
|
+
params["color_index"] = color
|
|
172
|
+
return _get_ableton(ctx).send_command("create_group_track", params)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@mcp.tool()
|
|
176
|
+
def set_group_fold(ctx: Context, track_index: int, folded: bool) -> dict:
|
|
177
|
+
"""Fold or unfold a group track to show/hide its children."""
|
|
178
|
+
_validate_track_index(track_index)
|
|
179
|
+
return _get_ableton(ctx).send_command("set_group_fold", {
|
|
180
|
+
"track_index": track_index,
|
|
181
|
+
"folded": folded,
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@mcp.tool()
|
|
186
|
+
def freeze_track(ctx: Context, track_index: int) -> dict:
|
|
187
|
+
"""Freeze a track to reduce CPU load. Frozen tracks render to audio cache."""
|
|
188
|
+
_validate_track_index(track_index)
|
|
189
|
+
return _get_ableton(ctx).send_command("freeze_track", {"track_index": track_index})
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@mcp.tool()
|
|
193
|
+
def flatten_track(ctx: Context, track_index: int) -> dict:
|
|
194
|
+
"""Flatten a frozen track to audio permanently. Use undo to revert."""
|
|
195
|
+
_validate_track_index(track_index)
|
|
196
|
+
return _get_ableton(ctx).send_command("flatten_track", {"track_index": track_index})
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@mcp.tool()
|
|
200
|
+
def set_track_input_monitoring(ctx: Context, track_index: int, state: int) -> dict:
|
|
201
|
+
"""Set input monitoring (0=In, 1=Auto, 2=Off). Only for regular tracks, not return tracks."""
|
|
202
|
+
_validate_track_index(track_index)
|
|
203
|
+
if state not in (0, 1, 2):
|
|
204
|
+
raise ValueError("Monitoring state must be 0=In, 1=Auto, or 2=Off")
|
|
205
|
+
return _get_ableton(ctx).send_command("set_track_input_monitoring", {
|
|
206
|
+
"track_index": track_index,
|
|
207
|
+
"state": state,
|
|
208
|
+
})
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livepilot",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"mcpName": "io.github.dreamrec/livepilot",
|
|
5
|
-
"description": "AI copilot for Ableton Live 12 —
|
|
5
|
+
"description": "AI copilot for Ableton Live 12 — 99 MCP tools for music production, sound design, and mixing",
|
|
6
6
|
"author": "Pilot Studio",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"type": "commonjs",
|
package/plugin/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livepilot",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "AI copilot for Ableton Live 12 —
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "AI copilot for Ableton Live 12 — 99 MCP tools for music production, sound design, and mixing",
|
|
5
5
|
"author": "Pilot Studio",
|
|
6
6
|
"skills": [
|
|
7
7
|
"skills/livepilot-core"
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: livepilot-core
|
|
3
|
-
description: Core discipline for controlling Ableton Live 12 through LivePilot's
|
|
3
|
+
description: Core discipline for controlling Ableton Live 12 through LivePilot's 99 MCP tools. Use whenever working with Ableton Live through MCP tools.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# LivePilot Core — Ableton Live 12 AI Copilot
|
|
7
7
|
|
|
8
|
-
LivePilot gives you
|
|
8
|
+
LivePilot gives you 99 MCP tools to control Ableton Live 12 in real-time: transport, tracks, clips, MIDI notes, devices, scenes, mixing, browser, and arrangement.
|
|
9
9
|
|
|
10
10
|
## Golden Rules
|
|
11
11
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# LivePilot Architecture & Tool Reference
|
|
2
2
|
|
|
3
|
-
LivePilot is an MCP server that controls Ableton Live 12 in real-time through
|
|
3
|
+
LivePilot is an MCP server that controls Ableton Live 12 in real-time through 99 tools across 9 domains. This document maps every tool to what it actually does in Ableton, so you know exactly which tool to reach for.
|
|
4
4
|
|
|
5
5
|
## Architecture
|
|
6
6
|
|
|
@@ -33,14 +33,15 @@ Claude Code ──MCP──► FastMCP Server ──TCP/9878──► Remote
|
|
|
33
33
|
| `get_recent_actions` | Returns log of recent commands sent to Ableton (newest first) | `limit` (1-50, default 20) |
|
|
34
34
|
| `get_session_diagnostics` | Analyzes session for issues: armed tracks, solo leftovers, unnamed tracks, empty clips | — |
|
|
35
35
|
|
|
36
|
-
### Tracks (
|
|
36
|
+
### Tracks (17) — Create, delete, configure, group, freeze tracks
|
|
37
37
|
|
|
38
38
|
| Tool | What it does | Key params |
|
|
39
39
|
|------|-------------|------------|
|
|
40
|
-
| `get_track_info` | Returns clips, devices, mixer state,
|
|
40
|
+
| `get_track_info` | Returns clips, devices, mixer state, group/fold info for one track | `track_index` (0-based) |
|
|
41
41
|
| `create_midi_track` | Creates a new MIDI track | `index` (-1=end), `name`, `color` (0-69) |
|
|
42
42
|
| `create_audio_track` | Creates a new audio track | `index` (-1=end), `name`, `color` (0-69) |
|
|
43
43
|
| `create_return_track` | Creates a new return track | — |
|
|
44
|
+
| `create_group_track` | Groups a range of tracks into a group track | `start_index`, `end_index`, `name`, `color` |
|
|
44
45
|
| `delete_track` | Deletes a track | `track_index` |
|
|
45
46
|
| `duplicate_track` | Duplicates track with all contents | `track_index` |
|
|
46
47
|
| `set_track_name` | Renames a track | `track_index`, `name` |
|
|
@@ -49,12 +50,16 @@ Claude Code ──MCP──► FastMCP Server ──TCP/9878──► Remote
|
|
|
49
50
|
| `set_track_solo` | Solos/unsolos | `track_index`, `soloed` (bool) |
|
|
50
51
|
| `set_track_arm` | Arms/disarms for recording | `track_index`, `armed` (bool) |
|
|
51
52
|
| `stop_track_clips` | Stops all playing clips on track | `track_index` |
|
|
53
|
+
| `set_group_fold` | Folds/unfolds a group track | `track_index`, `folded` (bool) |
|
|
54
|
+
| `freeze_track` | Freezes a track to reduce CPU | `track_index` |
|
|
55
|
+
| `flatten_track` | Flattens a frozen track to audio (use undo to revert) | `track_index` |
|
|
56
|
+
| `set_track_input_monitoring` | Sets input monitoring state | `track_index`, `state` (0=In, 1=Auto, 2=Off) |
|
|
52
57
|
|
|
53
|
-
### Clips (
|
|
58
|
+
### Clips (11) — Clip lifecycle, properties, warp
|
|
54
59
|
|
|
55
60
|
| Tool | What it does | Key params |
|
|
56
61
|
|------|-------------|------------|
|
|
57
|
-
| `get_clip_info` | Returns clip name, length, loop settings, playing state,
|
|
62
|
+
| `get_clip_info` | Returns clip name, length, loop settings, playing state, is_midi/is_audio, warp info | `track_index`, `clip_index` |
|
|
58
63
|
| `create_clip` | Creates empty MIDI clip | `track_index`, `clip_index`, `length` (beats) |
|
|
59
64
|
| `delete_clip` | Removes a clip from its slot | `track_index`, `clip_index` |
|
|
60
65
|
| `duplicate_clip` | Copies clip to next slot | `track_index`, `clip_index` |
|
|
@@ -64,6 +69,7 @@ Claude Code ──MCP──► FastMCP Server ──TCP/9878──► Remote
|
|
|
64
69
|
| `set_clip_color` | Sets clip color | `track_index`, `clip_index`, `color_index` (0-69) |
|
|
65
70
|
| `set_clip_loop` | Configures loop region | `track_index`, `clip_index`, `loop_start`, `loop_end`, `looping` |
|
|
66
71
|
| `set_clip_launch` | Sets launch mode and quantization | `track_index`, `clip_index`, `launch_mode`, `quantization` |
|
|
72
|
+
| `set_clip_warp_mode` | Sets warp mode for audio clips | `track_index`, `clip_index`, `mode` (0=Beats,1=Tones,2=Texture,3=Re-Pitch,4=Complex,6=Complex Pro) |
|
|
67
73
|
|
|
68
74
|
### Notes (8) — MIDI note manipulation (Live 12 API)
|
|
69
75
|
|
|
@@ -106,16 +112,18 @@ Claude Code ──MCP──► FastMCP Server ──TCP/9878──► Remote
|
|
|
106
112
|
| `set_chain_volume` | Sets volume of a rack chain | `track_index`, `device_index`, `chain_index`, `volume` |
|
|
107
113
|
| `get_device_presets` | Lists presets for a device (audio effects, instruments, MIDI effects) | `device_name` |
|
|
108
114
|
|
|
109
|
-
### Scenes (
|
|
115
|
+
### Scenes (8) — Scene management
|
|
110
116
|
|
|
111
117
|
| Tool | What it does | Key params |
|
|
112
118
|
|------|-------------|------------|
|
|
113
|
-
| `get_scenes_info` | Lists all scenes with names and
|
|
119
|
+
| `get_scenes_info` | Lists all scenes with names, tempo, and color | — |
|
|
114
120
|
| `create_scene` | Creates a new scene | `index` (-1=end) |
|
|
115
121
|
| `delete_scene` | Deletes a scene | `scene_index` |
|
|
116
122
|
| `duplicate_scene` | Duplicates a scene | `scene_index` |
|
|
117
123
|
| `fire_scene` | Launches all clips in a scene | `scene_index` |
|
|
118
124
|
| `set_scene_name` | Renames a scene | `scene_index`, `name` |
|
|
125
|
+
| `set_scene_color` | Sets scene color | `scene_index`, `color_index` (0-69) |
|
|
126
|
+
| `set_scene_tempo` | Sets tempo that triggers when scene fires (0 to clear) | `scene_index`, `tempo` (20-999 or 0) |
|
|
119
127
|
|
|
120
128
|
### Mixing (8) — Levels, panning, routing
|
|
121
129
|
|
|
@@ -583,7 +583,10 @@ def set_arrangement_clip_name(song, params):
|
|
|
583
583
|
track = get_track(song, track_index)
|
|
584
584
|
arr_clips = list(track.arrangement_clips)
|
|
585
585
|
if clip_index < 0 or clip_index >= len(arr_clips):
|
|
586
|
-
raise IndexError(
|
|
586
|
+
raise IndexError(
|
|
587
|
+
"Arrangement clip index %d out of range (0..%d)"
|
|
588
|
+
% (clip_index, len(arr_clips) - 1)
|
|
589
|
+
)
|
|
587
590
|
arr_clips[clip_index].name = name
|
|
588
591
|
return {"track_index": track_index, "clip_index": clip_index, "name": name}
|
|
589
592
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
LivePilot - Clip domain handlers (
|
|
2
|
+
LivePilot - Clip domain handlers (11 commands).
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from .router import register
|
|
@@ -13,7 +13,7 @@ def get_clip_info(song, params):
|
|
|
13
13
|
clip_index = int(params["clip_index"])
|
|
14
14
|
clip = get_clip(song, track_index, clip_index)
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
result = {
|
|
17
17
|
"track_index": track_index,
|
|
18
18
|
"clip_index": clip_index,
|
|
19
19
|
"name": clip.name,
|
|
@@ -21,6 +21,8 @@ def get_clip_info(song, params):
|
|
|
21
21
|
"length": clip.length,
|
|
22
22
|
"is_playing": clip.is_playing,
|
|
23
23
|
"is_recording": clip.is_recording,
|
|
24
|
+
"is_midi_clip": clip.is_midi_clip,
|
|
25
|
+
"is_audio_clip": clip.is_audio_clip,
|
|
24
26
|
"looping": clip.looping,
|
|
25
27
|
"loop_start": clip.loop_start,
|
|
26
28
|
"loop_end": clip.loop_end,
|
|
@@ -30,6 +32,13 @@ def get_clip_info(song, params):
|
|
|
30
32
|
"launch_quantization": clip.launch_quantization,
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
# Audio-clip-specific fields
|
|
36
|
+
if clip.is_audio_clip:
|
|
37
|
+
result["warping"] = clip.warping
|
|
38
|
+
result["warp_mode"] = clip.warp_mode
|
|
39
|
+
|
|
40
|
+
return result
|
|
41
|
+
|
|
33
42
|
|
|
34
43
|
@register("create_clip")
|
|
35
44
|
def create_clip(song, params):
|
|
@@ -170,3 +179,33 @@ def set_clip_launch(song, params):
|
|
|
170
179
|
"launch_mode": clip.launch_mode,
|
|
171
180
|
"launch_quantization": clip.launch_quantization,
|
|
172
181
|
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@register("set_clip_warp_mode")
|
|
185
|
+
def set_clip_warp_mode(song, params):
|
|
186
|
+
"""Set warp mode for an audio clip."""
|
|
187
|
+
track_index = int(params["track_index"])
|
|
188
|
+
clip_index = int(params["clip_index"])
|
|
189
|
+
clip = get_clip(song, track_index, clip_index)
|
|
190
|
+
|
|
191
|
+
if clip.is_midi_clip:
|
|
192
|
+
raise ValueError("Warp modes only apply to audio clips")
|
|
193
|
+
|
|
194
|
+
mode = int(params["mode"])
|
|
195
|
+
if mode not in (0, 1, 2, 3, 4, 6):
|
|
196
|
+
raise ValueError(
|
|
197
|
+
"Invalid warp mode %d. Valid: 0=Beats, 1=Tones, 2=Texture, "
|
|
198
|
+
"3=Re-Pitch, 4=Complex, 6=Complex Pro" % mode
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if "warping" in params:
|
|
202
|
+
clip.warping = bool(params["warping"])
|
|
203
|
+
|
|
204
|
+
clip.warp_mode = mode
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
"track_index": track_index,
|
|
208
|
+
"clip_index": clip_index,
|
|
209
|
+
"warp_mode": clip.warp_mode,
|
|
210
|
+
"warping": clip.warping,
|
|
211
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
LivePilot - Scene domain handlers (
|
|
2
|
+
LivePilot - Scene domain handlers (8 commands).
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from .router import register
|
|
@@ -73,3 +73,27 @@ def set_scene_name(song, params):
|
|
|
73
73
|
scene = get_scene(song, scene_index)
|
|
74
74
|
scene.name = str(params["name"])
|
|
75
75
|
return {"index": scene_index, "name": scene.name}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@register("set_scene_color")
|
|
79
|
+
def set_scene_color(song, params):
|
|
80
|
+
"""Set a scene's color."""
|
|
81
|
+
scene_index = int(params["scene_index"])
|
|
82
|
+
scene = get_scene(song, scene_index)
|
|
83
|
+
scene.color_index = int(params["color_index"])
|
|
84
|
+
return {"index": scene_index, "color_index": scene.color_index}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@register("set_scene_tempo")
|
|
88
|
+
def set_scene_tempo(song, params):
|
|
89
|
+
"""Set a scene's tempo (BPM). Set to 0 to clear the scene tempo."""
|
|
90
|
+
scene_index = int(params["scene_index"])
|
|
91
|
+
scene = get_scene(song, scene_index)
|
|
92
|
+
tempo = float(params["tempo"])
|
|
93
|
+
if tempo != 0 and (tempo < 20 or tempo > 999):
|
|
94
|
+
raise ValueError("Tempo must be 0 (clear) or between 20 and 999 BPM")
|
|
95
|
+
scene.tempo = tempo
|
|
96
|
+
return {
|
|
97
|
+
"index": scene_index,
|
|
98
|
+
"tempo": scene.tempo if scene.tempo > 0 else None,
|
|
99
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
LivePilot - Track domain handlers (
|
|
2
|
+
LivePilot - Track domain handlers (17 commands).
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from .router import register
|
|
@@ -80,17 +80,23 @@ def get_track_info(song, params):
|
|
|
80
80
|
"color_index": track.color_index,
|
|
81
81
|
"mute": track.mute,
|
|
82
82
|
"solo": track.solo,
|
|
83
|
+
"is_foldable": track.is_foldable,
|
|
84
|
+
"is_grouped": track.is_grouped,
|
|
83
85
|
"clip_slots": clips,
|
|
84
86
|
"devices": devices,
|
|
85
87
|
"mixer": mixer,
|
|
86
88
|
"sends": sends,
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
if track.is_foldable:
|
|
92
|
+
result["fold_state"] = bool(track.fold_state)
|
|
93
|
+
|
|
89
94
|
# Regular tracks have arm and input type; return tracks get null values
|
|
90
95
|
if track_index >= 0:
|
|
91
96
|
result["arm"] = track.arm
|
|
92
97
|
result["has_midi_input"] = track.has_midi_input
|
|
93
98
|
result["has_audio_input"] = track.has_audio_input
|
|
99
|
+
result["current_monitoring_state"] = track.current_monitoring_state
|
|
94
100
|
else:
|
|
95
101
|
result["arm"] = None
|
|
96
102
|
result["has_midi_input"] = None
|
|
@@ -227,3 +233,91 @@ def stop_track_clips(song, params):
|
|
|
227
233
|
track = get_track(song, track_index)
|
|
228
234
|
track.stop_all_clips()
|
|
229
235
|
return {"index": track_index, "stopped": True}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@register("create_group_track")
|
|
239
|
+
def create_group_track(song, params):
|
|
240
|
+
"""Create a group track containing the specified track range."""
|
|
241
|
+
start = int(params["start_index"])
|
|
242
|
+
end = int(params["end_index"])
|
|
243
|
+
tracks = list(song.tracks)
|
|
244
|
+
if start < 0 or start >= len(tracks):
|
|
245
|
+
raise IndexError(
|
|
246
|
+
"start_index %d out of range (0..%d)" % (start, len(tracks) - 1)
|
|
247
|
+
)
|
|
248
|
+
if end < start or end >= len(tracks):
|
|
249
|
+
raise IndexError(
|
|
250
|
+
"end_index %d out of range (%d..%d)" % (end, start, len(tracks) - 1)
|
|
251
|
+
)
|
|
252
|
+
# Select the range of tracks, then use the API
|
|
253
|
+
song.view.selected_track = tracks[start]
|
|
254
|
+
for i in range(start, end + 1):
|
|
255
|
+
tracks[i].is_part_of_selection = True
|
|
256
|
+
song.create_group_track()
|
|
257
|
+
# The new group track is inserted at start_index
|
|
258
|
+
new_tracks = list(song.tracks)
|
|
259
|
+
group_track = new_tracks[start]
|
|
260
|
+
if "name" in params:
|
|
261
|
+
group_track.name = str(params["name"])
|
|
262
|
+
if "color_index" in params:
|
|
263
|
+
group_track.color_index = int(params["color_index"])
|
|
264
|
+
return {
|
|
265
|
+
"index": start,
|
|
266
|
+
"name": group_track.name,
|
|
267
|
+
"is_foldable": group_track.is_foldable,
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@register("set_group_fold")
|
|
272
|
+
def set_group_fold(song, params):
|
|
273
|
+
"""Fold or unfold a group track."""
|
|
274
|
+
track_index = int(params["track_index"])
|
|
275
|
+
track = get_track(song, track_index)
|
|
276
|
+
if not track.is_foldable:
|
|
277
|
+
raise ValueError("Track %d is not a group track" % track_index)
|
|
278
|
+
track.fold_state = int(bool(params["folded"]))
|
|
279
|
+
return {
|
|
280
|
+
"index": track_index,
|
|
281
|
+
"folded": bool(track.fold_state),
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@register("freeze_track")
|
|
286
|
+
def freeze_track(song, params):
|
|
287
|
+
"""Freeze a track to reduce CPU load."""
|
|
288
|
+
track_index = int(params["track_index"])
|
|
289
|
+
track = get_track(song, track_index)
|
|
290
|
+
if track_index < 0:
|
|
291
|
+
raise ValueError("Cannot freeze a return track")
|
|
292
|
+
track.freeze()
|
|
293
|
+
return {"index": track_index, "frozen": True}
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@register("flatten_track")
|
|
297
|
+
def flatten_track(song, params):
|
|
298
|
+
"""Flatten a frozen track to audio (irreversible — use undo to revert)."""
|
|
299
|
+
track_index = int(params["track_index"])
|
|
300
|
+
track = get_track(song, track_index)
|
|
301
|
+
if track_index < 0:
|
|
302
|
+
raise ValueError("Cannot flatten a return track")
|
|
303
|
+
track.flatten()
|
|
304
|
+
return {"index": track_index, "flattened": True}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@register("set_track_input_monitoring")
|
|
308
|
+
def set_track_input_monitoring(song, params):
|
|
309
|
+
"""Set input monitoring state for a track (0=In, 1=Auto, 2=Off)."""
|
|
310
|
+
track_index = int(params["track_index"])
|
|
311
|
+
if track_index < 0:
|
|
312
|
+
raise ValueError("Cannot set input monitoring on a return track")
|
|
313
|
+
track = get_track(song, track_index)
|
|
314
|
+
state = int(params["state"])
|
|
315
|
+
if state not in (0, 1, 2):
|
|
316
|
+
raise ValueError(
|
|
317
|
+
"Invalid monitoring state %d. Valid: 0=In, 1=Auto, 2=Off" % state
|
|
318
|
+
)
|
|
319
|
+
track.current_monitoring_state = state
|
|
320
|
+
return {
|
|
321
|
+
"index": track_index,
|
|
322
|
+
"monitoring_state": track.current_monitoring_state,
|
|
323
|
+
}
|