livepilot 1.10.6 → 1.10.8
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 +168 -0
- package/README.md +12 -10
- package/bin/livepilot.js +168 -30
- package/installer/install.js +117 -11
- package/m4l_device/LivePilot_Analyzer.amxd +0 -0
- package/m4l_device/livepilot_bridge.js +215 -3
- package/mcp_server/__init__.py +1 -1
- package/mcp_server/atlas/__init__.py +132 -33
- package/mcp_server/atlas/tools.py +56 -15
- package/mcp_server/composer/layer_planner.py +27 -0
- package/mcp_server/composer/prompt_parser.py +15 -6
- package/mcp_server/connection.py +11 -3
- package/mcp_server/corpus/__init__.py +14 -4
- package/mcp_server/creative_constraints/tools.py +206 -33
- package/mcp_server/experiment/engine.py +7 -9
- package/mcp_server/hook_hunter/analyzer.py +62 -9
- package/mcp_server/hook_hunter/tools.py +60 -9
- package/mcp_server/m4l_bridge.py +68 -12
- package/mcp_server/musical_intelligence/detectors.py +32 -0
- package/mcp_server/performance_engine/tools.py +112 -29
- package/mcp_server/preview_studio/engine.py +89 -8
- package/mcp_server/preview_studio/tools.py +22 -6
- package/mcp_server/project_brain/automation_graph.py +71 -19
- package/mcp_server/project_brain/builder.py +2 -0
- package/mcp_server/project_brain/tools.py +55 -5
- package/mcp_server/reference_engine/profile_builder.py +129 -3
- package/mcp_server/reference_engine/tools.py +47 -6
- package/mcp_server/runtime/execution_router.py +66 -2
- package/mcp_server/runtime/mcp_dispatch.py +75 -3
- package/mcp_server/runtime/remote_commands.py +10 -2
- package/mcp_server/sample_engine/analyzer.py +131 -4
- package/mcp_server/sample_engine/critics.py +29 -8
- package/mcp_server/sample_engine/models.py +42 -4
- package/mcp_server/sample_engine/tools.py +48 -14
- package/mcp_server/semantic_moves/__init__.py +1 -0
- package/mcp_server/semantic_moves/compiler.py +9 -1
- package/mcp_server/semantic_moves/device_creation_compilers.py +47 -0
- package/mcp_server/semantic_moves/mix_compilers.py +170 -0
- package/mcp_server/semantic_moves/mix_moves.py +1 -1
- package/mcp_server/semantic_moves/models.py +5 -0
- package/mcp_server/semantic_moves/sound_design_compilers.py +22 -59
- package/mcp_server/semantic_moves/tools.py +15 -4
- package/mcp_server/semantic_moves/transition_compilers.py +12 -19
- package/mcp_server/server.py +75 -5
- package/mcp_server/services/singletons.py +68 -0
- package/mcp_server/session_continuity/models.py +4 -0
- package/mcp_server/session_continuity/tracker.py +14 -1
- package/mcp_server/song_brain/builder.py +110 -12
- package/mcp_server/song_brain/tools.py +77 -13
- package/mcp_server/sound_design/tools.py +112 -1
- package/mcp_server/splice_client/client.py +29 -8
- package/mcp_server/stuckness_detector/detector.py +90 -0
- package/mcp_server/stuckness_detector/tools.py +41 -0
- package/mcp_server/tools/_agent_os_engine/critics.py +24 -0
- package/mcp_server/tools/_composition_engine/__init__.py +2 -2
- package/mcp_server/tools/_composition_engine/harmony.py +90 -0
- package/mcp_server/tools/_composition_engine/sections.py +47 -4
- package/mcp_server/tools/_harmony_engine.py +52 -8
- package/mcp_server/tools/_research_engine.py +98 -19
- package/mcp_server/tools/_theory_engine.py +138 -9
- package/mcp_server/tools/agent_os.py +20 -3
- package/mcp_server/tools/analyzer.py +105 -6
- package/mcp_server/tools/clips.py +46 -1
- package/mcp_server/tools/composition.py +66 -23
- package/mcp_server/tools/devices.py +22 -1
- package/mcp_server/tools/harmony.py +115 -14
- package/mcp_server/tools/midi_io.py +23 -1
- package/mcp_server/tools/mixing.py +35 -1
- package/mcp_server/tools/motif.py +49 -3
- package/mcp_server/tools/research.py +24 -0
- package/mcp_server/tools/theory.py +108 -16
- package/mcp_server/tools/tracks.py +1 -1
- package/mcp_server/tools/transport.py +1 -1
- package/mcp_server/transition_engine/critics.py +18 -11
- package/mcp_server/translation_engine/tools.py +8 -4
- package/package.json +25 -3
- package/remote_script/LivePilot/__init__.py +77 -2
- package/remote_script/LivePilot/arrangement.py +12 -2
- package/remote_script/LivePilot/browser.py +16 -6
- package/remote_script/LivePilot/clips.py +69 -0
- package/remote_script/LivePilot/devices.py +10 -5
- package/remote_script/LivePilot/mixing.py +117 -0
- package/remote_script/LivePilot/notes.py +13 -2
- package/remote_script/LivePilot/router.py +13 -1
- package/remote_script/LivePilot/server.py +51 -13
- package/remote_script/LivePilot/version_detect.py +7 -4
- package/server.json +20 -0
- package/.claude-plugin/marketplace.json +0 -21
- package/.mcpbignore +0 -57
- package/AGENTS.md +0 -46
- package/CODE_OF_CONDUCT.md +0 -27
- package/CONTRIBUTING.md +0 -131
- package/SECURITY.md +0 -48
- package/livepilot/.Codex-plugin/plugin.json +0 -8
- package/livepilot/.claude-plugin/plugin.json +0 -8
- package/livepilot/agents/livepilot-producer/AGENT.md +0 -313
- package/livepilot/commands/arrange.md +0 -47
- package/livepilot/commands/beat.md +0 -77
- package/livepilot/commands/evaluate.md +0 -49
- package/livepilot/commands/memory.md +0 -22
- package/livepilot/commands/mix.md +0 -44
- package/livepilot/commands/perform.md +0 -42
- package/livepilot/commands/session.md +0 -13
- package/livepilot/commands/sounddesign.md +0 -43
- package/livepilot/skills/livepilot-arrangement/SKILL.md +0 -155
- package/livepilot/skills/livepilot-composition-engine/SKILL.md +0 -107
- package/livepilot/skills/livepilot-composition-engine/references/form-patterns.md +0 -97
- package/livepilot/skills/livepilot-composition-engine/references/transition-archetypes.md +0 -102
- package/livepilot/skills/livepilot-core/SKILL.md +0 -184
- package/livepilot/skills/livepilot-core/references/ableton-workflow-patterns.md +0 -831
- package/livepilot/skills/livepilot-core/references/automation-atlas.md +0 -272
- package/livepilot/skills/livepilot-core/references/device-atlas/00-index.md +0 -110
- package/livepilot/skills/livepilot-core/references/device-atlas/distortion-and-character.md +0 -687
- package/livepilot/skills/livepilot-core/references/device-atlas/drums-and-percussion.md +0 -753
- package/livepilot/skills/livepilot-core/references/device-atlas/dynamics-and-punch.md +0 -525
- package/livepilot/skills/livepilot-core/references/device-atlas/eq-and-filtering.md +0 -402
- package/livepilot/skills/livepilot-core/references/device-atlas/midi-tools.md +0 -963
- package/livepilot/skills/livepilot-core/references/device-atlas/movement-and-modulation.md +0 -874
- package/livepilot/skills/livepilot-core/references/device-atlas/space-and-depth.md +0 -571
- package/livepilot/skills/livepilot-core/references/device-atlas/spectral-and-weird.md +0 -714
- package/livepilot/skills/livepilot-core/references/device-atlas/synths-native.md +0 -953
- package/livepilot/skills/livepilot-core/references/device-knowledge/00-index.md +0 -34
- package/livepilot/skills/livepilot-core/references/device-knowledge/automation-as-music.md +0 -204
- package/livepilot/skills/livepilot-core/references/device-knowledge/chains-genre.md +0 -173
- package/livepilot/skills/livepilot-core/references/device-knowledge/creative-thinking.md +0 -211
- package/livepilot/skills/livepilot-core/references/device-knowledge/effects-distortion.md +0 -188
- package/livepilot/skills/livepilot-core/references/device-knowledge/effects-space.md +0 -162
- package/livepilot/skills/livepilot-core/references/device-knowledge/effects-spectral.md +0 -229
- package/livepilot/skills/livepilot-core/references/device-knowledge/instruments-synths.md +0 -243
- package/livepilot/skills/livepilot-core/references/m4l-devices.md +0 -352
- package/livepilot/skills/livepilot-core/references/memory-guide.md +0 -107
- package/livepilot/skills/livepilot-core/references/midi-recipes.md +0 -402
- package/livepilot/skills/livepilot-core/references/mixing-patterns.md +0 -578
- package/livepilot/skills/livepilot-core/references/overview.md +0 -290
- package/livepilot/skills/livepilot-core/references/sample-manipulation.md +0 -724
- package/livepilot/skills/livepilot-core/references/sound-design-deep.md +0 -140
- package/livepilot/skills/livepilot-core/references/sound-design.md +0 -393
- package/livepilot/skills/livepilot-devices/SKILL.md +0 -169
- package/livepilot/skills/livepilot-evaluation/SKILL.md +0 -156
- package/livepilot/skills/livepilot-evaluation/references/capability-modes.md +0 -118
- package/livepilot/skills/livepilot-evaluation/references/evaluation-contracts.md +0 -121
- package/livepilot/skills/livepilot-evaluation/references/memory-promotion.md +0 -110
- package/livepilot/skills/livepilot-mix-engine/SKILL.md +0 -123
- package/livepilot/skills/livepilot-mix-engine/references/mix-critics.md +0 -143
- package/livepilot/skills/livepilot-mix-engine/references/mix-moves.md +0 -105
- package/livepilot/skills/livepilot-mixing/SKILL.md +0 -157
- package/livepilot/skills/livepilot-notes/SKILL.md +0 -130
- package/livepilot/skills/livepilot-performance-engine/SKILL.md +0 -122
- package/livepilot/skills/livepilot-performance-engine/references/performance-safety.md +0 -98
- package/livepilot/skills/livepilot-release/SKILL.md +0 -130
- package/livepilot/skills/livepilot-sample-engine/SKILL.md +0 -105
- package/livepilot/skills/livepilot-sample-engine/references/sample-critics.md +0 -87
- package/livepilot/skills/livepilot-sample-engine/references/sample-philosophy.md +0 -51
- package/livepilot/skills/livepilot-sample-engine/references/sample-techniques.md +0 -131
- package/livepilot/skills/livepilot-sound-design-engine/SKILL.md +0 -168
- package/livepilot/skills/livepilot-sound-design-engine/references/patch-model.md +0 -119
- package/livepilot/skills/livepilot-sound-design-engine/references/sound-design-critics.md +0 -118
- package/livepilot/skills/livepilot-wonder/SKILL.md +0 -79
- package/m4l_device/LivePilot_Analyzer.maxpat +0 -2705
- package/manifest.json +0 -91
- package/mcp_server/splice_client/protos/app_pb2.pyi +0 -1153
- package/scripts/generate_tool_catalog.py +0 -131
- package/scripts/sync_metadata.py +0 -132
package/installer/install.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const fs = require("fs");
|
|
4
|
+
const os = require("os");
|
|
4
5
|
const path = require("path");
|
|
5
6
|
const { findAbletonPaths } = require("./paths");
|
|
6
7
|
|
|
@@ -10,6 +11,60 @@ const SOURCE_DIR = path.join(ROOT, "remote_script", "LivePilot");
|
|
|
10
11
|
// Files / dirs to skip during copy
|
|
11
12
|
const SKIP = new Set(["__pycache__", ".DS_Store"]);
|
|
12
13
|
|
|
14
|
+
// How many previous backups to keep on disk before auto-pruning (the upgrade
|
|
15
|
+
// path renames the old LivePilot dir to LivePilot.backup-<ts>/ so the user can
|
|
16
|
+
// recover a manual edit).
|
|
17
|
+
const BACKUP_RETENTION = 3;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Typed installer error. Wrappers (e.g. the --setup wizard) can catch this
|
|
21
|
+
* and decide whether to continue with later steps (recoverable) or abort the
|
|
22
|
+
* whole wizard (non-recoverable). The previous version called process.exit(1)
|
|
23
|
+
* mid-function, which silently short-circuited the setup wizard — callers
|
|
24
|
+
* had try/catch expecting exceptions, so later steps (bootstrap, M4L install,
|
|
25
|
+
* diagnostics) were skipped without warning.
|
|
26
|
+
*/
|
|
27
|
+
class InstallerAbort extends Error {
|
|
28
|
+
constructor(message, { recoverable = false } = {}) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = "InstallerAbort";
|
|
31
|
+
this.recoverable = recoverable;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validate that a user-supplied install destination is somewhere safe.
|
|
37
|
+
* Refuses to write outside of the user's home directory unless it matches
|
|
38
|
+
* one of the known Ableton Remote Scripts paths. This closes the path-
|
|
39
|
+
* traversal hole from `LIVEPILOT_INSTALL_PATH=/etc ...`.
|
|
40
|
+
*/
|
|
41
|
+
function _assertSafeInstallPath(resolvedPath, candidates) {
|
|
42
|
+
const home = os.homedir();
|
|
43
|
+
const allowedPrefixes = [
|
|
44
|
+
home,
|
|
45
|
+
// Systemwide Ableton install paths that live outside $HOME on some platforms
|
|
46
|
+
"/Applications/Ableton",
|
|
47
|
+
"C:\\ProgramData\\Ableton",
|
|
48
|
+
];
|
|
49
|
+
// The detected Ableton candidate paths are always considered safe
|
|
50
|
+
for (const c of candidates) {
|
|
51
|
+
if (path.resolve(c.path).startsWith(path.resolve(resolvedPath))) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (path.resolve(resolvedPath).startsWith(path.resolve(c.path))) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const safe = allowedPrefixes.some((p) => resolvedPath.startsWith(path.resolve(p)));
|
|
59
|
+
if (!safe) {
|
|
60
|
+
throw new InstallerAbort(
|
|
61
|
+
`LIVEPILOT_INSTALL_PATH=${resolvedPath} is outside permitted directories. ` +
|
|
62
|
+
`Refusing to install. Allowed roots: ${allowedPrefixes.join(", ")}`,
|
|
63
|
+
{ recoverable: false }
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
13
68
|
/**
|
|
14
69
|
* Recursively copy a directory, skipping __pycache__ and .DS_Store.
|
|
15
70
|
*/
|
|
@@ -28,22 +83,51 @@ function copyDirSync(src, dest) {
|
|
|
28
83
|
}
|
|
29
84
|
}
|
|
30
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Prune old LivePilot.backup-<ts>/ dirs, keeping the most recent N.
|
|
88
|
+
*/
|
|
89
|
+
function _pruneBackups(parentDir) {
|
|
90
|
+
try {
|
|
91
|
+
const entries = fs.readdirSync(parentDir, { withFileTypes: true });
|
|
92
|
+
const backups = entries
|
|
93
|
+
.filter((e) => e.isDirectory() && /^LivePilot\.backup-\d+$/.test(e.name))
|
|
94
|
+
.map((e) => e.name)
|
|
95
|
+
.sort(); // lexicographic — timestamps are monotonic, so this is age order
|
|
96
|
+
while (backups.length > BACKUP_RETENTION) {
|
|
97
|
+
const old = backups.shift();
|
|
98
|
+
try {
|
|
99
|
+
fs.rmSync(path.join(parentDir, old), { recursive: true, force: true });
|
|
100
|
+
} catch {
|
|
101
|
+
// best effort — don't let cleanup failure break an install
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
// best effort
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
31
109
|
/**
|
|
32
110
|
* Install the LivePilot Remote Script into Ableton's Remote Scripts folder.
|
|
111
|
+
*
|
|
112
|
+
* Throws InstallerAbort on recoverable failures (auto-detect missing) or
|
|
113
|
+
* non-recoverable ones (path-traversal attempt). Never calls process.exit.
|
|
114
|
+
* This lets the setup wizard continue with later steps on a recoverable
|
|
115
|
+
* failure.
|
|
33
116
|
*/
|
|
34
117
|
function install() {
|
|
35
118
|
const candidates = findAbletonPaths();
|
|
36
119
|
|
|
37
120
|
if (candidates.length === 0) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
121
|
+
throw new InstallerAbort(
|
|
122
|
+
"Could not auto-detect an Ableton Live Remote Scripts directory.\n\n" +
|
|
123
|
+
"Manual install:\n" +
|
|
124
|
+
" 1. Open Ableton Live > Preferences > File/Folder\n" +
|
|
125
|
+
" 2. Find the User Remote Scripts folder path\n" +
|
|
126
|
+
" 3. Copy the 'remote_script/LivePilot' folder into that directory\n" +
|
|
127
|
+
" 4. Restart Ableton Live\n" +
|
|
128
|
+
" 5. In Preferences > Link/Tempo/MIDI, set a Control Surface to 'LivePilot'",
|
|
129
|
+
{ recoverable: true }
|
|
130
|
+
);
|
|
47
131
|
}
|
|
48
132
|
|
|
49
133
|
// If multiple candidates exist, let the user choose via --install-path
|
|
@@ -51,7 +135,9 @@ function install() {
|
|
|
51
135
|
let target;
|
|
52
136
|
const explicitPath = process.env.LIVEPILOT_INSTALL_PATH;
|
|
53
137
|
if (explicitPath) {
|
|
54
|
-
|
|
138
|
+
const resolved = path.resolve(explicitPath);
|
|
139
|
+
_assertSafeInstallPath(resolved, candidates);
|
|
140
|
+
target = { path: resolved, description: "explicit (LIVEPILOT_INSTALL_PATH)" };
|
|
55
141
|
} else if (candidates.length > 1) {
|
|
56
142
|
console.log("Multiple Ableton Remote Scripts directories detected:");
|
|
57
143
|
candidates.forEach((c, i) => {
|
|
@@ -73,6 +159,25 @@ function install() {
|
|
|
73
159
|
// Ensure target base exists
|
|
74
160
|
fs.mkdirSync(targetBase, { recursive: true });
|
|
75
161
|
|
|
162
|
+
// Clear-then-copy upgrade path. Overlay-copying on top of an existing
|
|
163
|
+
// install leaves stale files when a module is removed/renamed upstream.
|
|
164
|
+
// Instead, rename the previous install to a timestamped backup, copy
|
|
165
|
+
// fresh, then prune old backups. The rename (not delete) preserves any
|
|
166
|
+
// local edits the user may have made.
|
|
167
|
+
if (fs.existsSync(destDir)) {
|
|
168
|
+
const ts = new Date().toISOString().replace(/[-:T.Z]/g, "").slice(0, 14);
|
|
169
|
+
const backup = path.join(targetBase, `LivePilot.backup-${ts}`);
|
|
170
|
+
try {
|
|
171
|
+
fs.renameSync(destDir, backup);
|
|
172
|
+
console.log("Existing install backed up to: %s", backup);
|
|
173
|
+
} catch (e) {
|
|
174
|
+
throw new InstallerAbort(
|
|
175
|
+
`Could not back up previous LivePilot install at ${destDir}: ${e.message}`,
|
|
176
|
+
{ recoverable: false }
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
76
181
|
console.log("Installing LivePilot Remote Script...");
|
|
77
182
|
console.log(" Source: %s", SOURCE_DIR);
|
|
78
183
|
console.log(" Target: %s", destDir);
|
|
@@ -80,6 +185,7 @@ function install() {
|
|
|
80
185
|
console.log("");
|
|
81
186
|
|
|
82
187
|
copyDirSync(SOURCE_DIR, destDir);
|
|
188
|
+
_pruneBackups(targetBase);
|
|
83
189
|
|
|
84
190
|
console.log("Done! Next steps:");
|
|
85
191
|
console.log(" 1. Restart Ableton Live (or press Cmd+, to open Preferences)");
|
|
@@ -111,4 +217,4 @@ function uninstall() {
|
|
|
111
217
|
}
|
|
112
218
|
}
|
|
113
219
|
|
|
114
|
-
module.exports = { install, uninstall };
|
|
220
|
+
module.exports = { install, uninstall, InstallerAbort };
|
|
Binary file
|
|
@@ -95,7 +95,7 @@ function anything() {
|
|
|
95
95
|
function dispatch(cmd, args) {
|
|
96
96
|
switch(cmd) {
|
|
97
97
|
case "ping":
|
|
98
|
-
send_response({"ok": true, "version": "1.10.
|
|
98
|
+
send_response({"ok": true, "version": "1.10.8"});
|
|
99
99
|
break;
|
|
100
100
|
case "get_params":
|
|
101
101
|
cmd_get_params(args);
|
|
@@ -183,6 +183,13 @@ function dispatch(cmd, args) {
|
|
|
183
183
|
case "get_plugin_presets":
|
|
184
184
|
cmd_get_plugin_presets(args);
|
|
185
185
|
break;
|
|
186
|
+
// ── BUG-A2 / A3: deep-LOM properties not on the automatable surface ──
|
|
187
|
+
case "simpler_set_warp":
|
|
188
|
+
cmd_simpler_set_warp(args);
|
|
189
|
+
break;
|
|
190
|
+
case "compressor_set_sidechain":
|
|
191
|
+
cmd_compressor_set_sidechain(args);
|
|
192
|
+
break;
|
|
186
193
|
default:
|
|
187
194
|
send_response({"error": "Unknown command: " + cmd});
|
|
188
195
|
}
|
|
@@ -641,8 +648,13 @@ function detect_key() {
|
|
|
641
648
|
detected_key = note_names[best_key];
|
|
642
649
|
detected_scale = best_scale;
|
|
643
650
|
|
|
644
|
-
// Send to UI
|
|
645
|
-
|
|
651
|
+
// Send to UI — use abbreviated scale ("min"/"maj") so text fits in the
|
|
652
|
+
// 72-pixel presentation widget, and pass a SINGLE symbol so Max's
|
|
653
|
+
// [route] + [prepend set] chain doesn't split atoms on the internal
|
|
654
|
+
// space. Max's [comment] displays whatever the `set` message carries.
|
|
655
|
+
var scale_abbr = (detected_scale === "minor") ? "min" : "maj";
|
|
656
|
+
var display = detected_key + " " + scale_abbr; // e.g., "D min"
|
|
657
|
+
outlet(1, "key", display);
|
|
646
658
|
}
|
|
647
659
|
|
|
648
660
|
function correlate(a, b) {
|
|
@@ -991,6 +1003,206 @@ function cmd_simpler_warp(args) {
|
|
|
991
1003
|
}
|
|
992
1004
|
}
|
|
993
1005
|
|
|
1006
|
+
// ── BUG-A2: Simpler warping property + warp_mode ─────────────────────
|
|
1007
|
+
//
|
|
1008
|
+
// Python's Remote Script ControlSurface API only exposes automatable
|
|
1009
|
+
// parameters. Simpler's `warping` and `warp_mode` live on the sample
|
|
1010
|
+
// child object (SimplerDevice.sample.*) — unreachable from the Python
|
|
1011
|
+
// side. Max JS LiveAPI can step INTO the sample child, so we do the
|
|
1012
|
+
// property write here and surface the result to the MCP server.
|
|
1013
|
+
//
|
|
1014
|
+
// args: [track_index, device_index, warp_on (0/1), warp_mode (-1 = leave alone, 0..6)]
|
|
1015
|
+
// warp_mode: 0=Beats, 1=Tones, 2=Texture, 3=Re-Pitch, 4=Complex, 6=Complex Pro
|
|
1016
|
+
//
|
|
1017
|
+
// Returns: {ok, warping, warp_mode} on success, {error} otherwise.
|
|
1018
|
+
function cmd_simpler_set_warp(args) {
|
|
1019
|
+
var track_idx = parseInt(args[0]);
|
|
1020
|
+
var device_idx = parseInt(args[1]);
|
|
1021
|
+
var warp_on = parseInt(args[2]);
|
|
1022
|
+
var warp_mode = args.length > 3 ? parseInt(args[3]) : -1;
|
|
1023
|
+
|
|
1024
|
+
var device_path = build_device_path(track_idx, device_idx);
|
|
1025
|
+
cursor_a.goto(device_path);
|
|
1026
|
+
if (cursor_a.id === 0) {
|
|
1027
|
+
send_response({"error": "Device not found at track " + track_idx + ", device " + device_idx});
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
if (cursor_a.get("class_name").toString() !== "OriginalSimpler") {
|
|
1031
|
+
send_response({"error": "Not a Simpler device (class is " + cursor_a.get("class_name") + ")"});
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// Step into the sample child — warping + warp_mode live there, not on
|
|
1036
|
+
// the device itself.
|
|
1037
|
+
try {
|
|
1038
|
+
cursor_a.goto(device_path + " sample");
|
|
1039
|
+
if (cursor_a.id === 0) {
|
|
1040
|
+
send_response({"error": "Simpler has no sample loaded (warping not applicable)"});
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
cursor_a.set("warping", warp_on ? 1 : 0);
|
|
1044
|
+
if (warp_on && warp_mode >= 0) {
|
|
1045
|
+
cursor_a.set("warp_mode", warp_mode);
|
|
1046
|
+
}
|
|
1047
|
+
// Read back so the caller can confirm the write landed
|
|
1048
|
+
var read_warping = parseInt(cursor_a.get("warping"));
|
|
1049
|
+
var read_warp_mode = parseInt(cursor_a.get("warp_mode"));
|
|
1050
|
+
send_response({
|
|
1051
|
+
"ok": true,
|
|
1052
|
+
"track_index": track_idx,
|
|
1053
|
+
"device_index": device_idx,
|
|
1054
|
+
"warping": read_warping,
|
|
1055
|
+
"warp_mode": read_warp_mode,
|
|
1056
|
+
});
|
|
1057
|
+
} catch(e) {
|
|
1058
|
+
send_response({"error": "simpler_set_warp failed: " + e.message});
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// ── BUG-A3: Compressor sidechain input routing ───────────────────────
|
|
1063
|
+
//
|
|
1064
|
+
// Sidechain INPUT ROUTING is exposed as LiveAPI properties on the
|
|
1065
|
+
// Compressor device in Live 11+: sidechain_input_routing_type and
|
|
1066
|
+
// sidechain_input_routing_channel. They don't appear in the automatable
|
|
1067
|
+
// parameter list so the Python Remote Script can't reach them; Max JS
|
|
1068
|
+
// LiveAPI can.
|
|
1069
|
+
//
|
|
1070
|
+
// args: [track_index, device_index, routing_type, routing_channel]
|
|
1071
|
+
// routing_type: string — e.g. "1-Audio From" / track name / "Ext. In"
|
|
1072
|
+
// routing_channel: string — "Post FX" / "Pre FX" / "Post Mixer" / ...
|
|
1073
|
+
//
|
|
1074
|
+
// Returns: {ok, sidechain: {type, channel}} on success.
|
|
1075
|
+
// Older Live versions without these properties return a clean error.
|
|
1076
|
+
function cmd_compressor_set_sidechain(args) {
|
|
1077
|
+
var track_idx = parseInt(args[0]);
|
|
1078
|
+
var device_idx = parseInt(args[1]);
|
|
1079
|
+
var routing_type = String(args[2] || "");
|
|
1080
|
+
var routing_channel = String(args[3] || "");
|
|
1081
|
+
|
|
1082
|
+
var path = build_device_path(track_idx, device_idx);
|
|
1083
|
+
cursor_a.goto(path);
|
|
1084
|
+
if (cursor_a.id === 0) {
|
|
1085
|
+
send_response({"error": "Device not found at track " + track_idx + ", device " + device_idx});
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
var class_name = String(cursor_a.get("class_name"));
|
|
1089
|
+
if (class_name !== "Compressor2" && class_name !== "Compressor") {
|
|
1090
|
+
send_response({"error": "Not a Compressor device (class is " + class_name + ")"});
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// Helper: read a LiveAPI property that returns a JSON-serialized dict
|
|
1095
|
+
// or list. Max's `get()` wraps results in a single-element array,
|
|
1096
|
+
// and complex properties come back as JSON strings.
|
|
1097
|
+
function read_json_prop(name) {
|
|
1098
|
+
try {
|
|
1099
|
+
var raw = cursor_a.get(name);
|
|
1100
|
+
if (raw === null || raw === undefined) return null;
|
|
1101
|
+
if (Object.prototype.toString.call(raw) === "[object Array]" && raw.length === 1) {
|
|
1102
|
+
raw = raw[0];
|
|
1103
|
+
}
|
|
1104
|
+
if (typeof raw === "string") {
|
|
1105
|
+
try { return JSON.parse(raw); } catch(e) { return raw; }
|
|
1106
|
+
}
|
|
1107
|
+
return raw;
|
|
1108
|
+
} catch(e) {
|
|
1109
|
+
return null;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
function find_by_name(list, name) {
|
|
1114
|
+
if (!list || !list.length || !name) return null;
|
|
1115
|
+
for (var i = 0; i < list.length; i++) {
|
|
1116
|
+
var entry = list[i];
|
|
1117
|
+
if (!entry) continue;
|
|
1118
|
+
var n = entry.display_name || entry.name;
|
|
1119
|
+
if (n === name) return entry;
|
|
1120
|
+
}
|
|
1121
|
+
return null;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
try {
|
|
1125
|
+
// Enable sidechain first — Live rejects routing writes on a
|
|
1126
|
+
// compressor with the sidechain disabled. Property is available
|
|
1127
|
+
// on Live 10+. Try/catch for legacy builds.
|
|
1128
|
+
try { cursor_a.set("sidechain_enabled", 1); } catch(e) {}
|
|
1129
|
+
|
|
1130
|
+
var debug = {};
|
|
1131
|
+
|
|
1132
|
+
// --- Routing TYPE (the source: "1-DRUMS", "Ext. In", "No Input", …)
|
|
1133
|
+
if (routing_type) {
|
|
1134
|
+
var types = read_json_prop("available_sidechain_input_routing_types");
|
|
1135
|
+
debug.requested_type = routing_type;
|
|
1136
|
+
debug.type_count = types && types.length ? types.length : 0;
|
|
1137
|
+
var t_match = find_by_name(types, routing_type);
|
|
1138
|
+
if (t_match && t_match.identifier !== undefined) {
|
|
1139
|
+
// LOM expects a RoutingType object; Max JS accepts a
|
|
1140
|
+
// JSON-encoded {identifier: N} for the `set`.
|
|
1141
|
+
cursor_a.set(
|
|
1142
|
+
"sidechain_input_routing_type",
|
|
1143
|
+
JSON.stringify({identifier: t_match.identifier})
|
|
1144
|
+
);
|
|
1145
|
+
debug.set_type = "ok (identifier=" + t_match.identifier + ")";
|
|
1146
|
+
} else {
|
|
1147
|
+
debug.set_type = "FAIL: no matching type";
|
|
1148
|
+
if (types) {
|
|
1149
|
+
debug.available_types = [];
|
|
1150
|
+
for (var i = 0; i < types.length; i++) {
|
|
1151
|
+
debug.available_types.push(types[i].display_name || types[i].name || "?");
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// --- Routing CHANNEL (the tap point: "Post FX", "Pre FX", …)
|
|
1158
|
+
if (routing_channel) {
|
|
1159
|
+
var channels = read_json_prop("available_sidechain_input_routing_channels");
|
|
1160
|
+
debug.requested_channel = routing_channel;
|
|
1161
|
+
debug.channel_count = channels && channels.length ? channels.length : 0;
|
|
1162
|
+
var c_match = find_by_name(channels, routing_channel);
|
|
1163
|
+
if (c_match && c_match.identifier !== undefined) {
|
|
1164
|
+
cursor_a.set(
|
|
1165
|
+
"sidechain_input_routing_channel",
|
|
1166
|
+
JSON.stringify({identifier: c_match.identifier})
|
|
1167
|
+
);
|
|
1168
|
+
debug.set_channel = "ok (identifier=" + c_match.identifier + ")";
|
|
1169
|
+
} else {
|
|
1170
|
+
debug.set_channel = "FAIL: no matching channel";
|
|
1171
|
+
if (channels) {
|
|
1172
|
+
debug.available_channels = [];
|
|
1173
|
+
for (var j = 0; j < channels.length; j++) {
|
|
1174
|
+
debug.available_channels.push(channels[j].display_name || channels[j].name || "?");
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// Read back canonical display_name for confirmation
|
|
1181
|
+
var cur_type = read_json_prop("sidechain_input_routing_type");
|
|
1182
|
+
var cur_channel = read_json_prop("sidechain_input_routing_channel");
|
|
1183
|
+
var read_type_name = (cur_type && cur_type.display_name) || "";
|
|
1184
|
+
var read_channel_name = (cur_channel && cur_channel.display_name) || "";
|
|
1185
|
+
|
|
1186
|
+
send_response({
|
|
1187
|
+
"ok": true,
|
|
1188
|
+
"track_index": track_idx,
|
|
1189
|
+
"device_index": device_idx,
|
|
1190
|
+
"sidechain": {
|
|
1191
|
+
"type": read_type_name,
|
|
1192
|
+
"channel": read_channel_name,
|
|
1193
|
+
"enabled": 1
|
|
1194
|
+
},
|
|
1195
|
+
"debug": debug
|
|
1196
|
+
});
|
|
1197
|
+
} catch(e) {
|
|
1198
|
+
send_response({
|
|
1199
|
+
"error": "compressor_set_sidechain failed: " + e.message
|
|
1200
|
+
+ " (this Live build may not expose sidechain_input_routing_* —"
|
|
1201
|
+
+ " user must set routing manually)"
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
994
1206
|
// ── Phase 2: Warp Markers ─────────────────────────────────────────────
|
|
995
1207
|
|
|
996
1208
|
function cmd_get_warp_markers(args) {
|
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.8"
|