livepilot 1.9.13 → 1.9.15
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 +51 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +7 -7
- package/bin/livepilot.js +32 -8
- package/installer/install.js +21 -2
- package/livepilot/.Codex-plugin/plugin.json +2 -2
- package/livepilot/.claude-plugin/plugin.json +2 -2
- package/livepilot/agents/livepilot-producer/AGENT.md +243 -49
- package/livepilot/skills/livepilot-core/SKILL.md +81 -6
- package/livepilot/skills/livepilot-core/references/m4l-devices.md +2 -2
- package/livepilot/skills/livepilot-core/references/overview.md +3 -3
- package/livepilot/skills/livepilot-core/references/sound-design.md +3 -2
- package/livepilot/skills/livepilot-release/SKILL.md +13 -13
- package/m4l_device/LivePilot_Analyzer.amxd +0 -0
- package/m4l_device/livepilot_bridge.js +6 -3
- package/mcp_server/__init__.py +1 -1
- package/mcp_server/curves.py +11 -3
- package/mcp_server/evaluation/__init__.py +1 -0
- package/mcp_server/evaluation/fabric.py +575 -0
- package/mcp_server/evaluation/feature_extractors.py +84 -0
- package/mcp_server/evaluation/policy.py +67 -0
- package/mcp_server/evaluation/tools.py +53 -0
- package/mcp_server/memory/__init__.py +11 -2
- package/mcp_server/memory/anti_memory.py +78 -0
- package/mcp_server/memory/promotion.py +94 -0
- package/mcp_server/memory/session_memory.py +108 -0
- package/mcp_server/memory/taste_memory.py +158 -0
- package/mcp_server/memory/technique_store.py +2 -1
- package/mcp_server/memory/tools.py +112 -0
- package/mcp_server/mix_engine/__init__.py +1 -0
- package/mcp_server/mix_engine/critics.py +299 -0
- package/mcp_server/mix_engine/models.py +152 -0
- package/mcp_server/mix_engine/planner.py +103 -0
- package/mcp_server/mix_engine/state_builder.py +316 -0
- package/mcp_server/mix_engine/tools.py +214 -0
- package/mcp_server/performance_engine/__init__.py +1 -0
- package/mcp_server/performance_engine/models.py +148 -0
- package/mcp_server/performance_engine/planner.py +267 -0
- package/mcp_server/performance_engine/safety.py +162 -0
- package/mcp_server/performance_engine/tools.py +183 -0
- package/mcp_server/project_brain/__init__.py +6 -0
- package/mcp_server/project_brain/arrangement_graph.py +64 -0
- package/mcp_server/project_brain/automation_graph.py +72 -0
- package/mcp_server/project_brain/builder.py +123 -0
- package/mcp_server/project_brain/capability_graph.py +64 -0
- package/mcp_server/project_brain/models.py +282 -0
- package/mcp_server/project_brain/refresh.py +80 -0
- package/mcp_server/project_brain/role_graph.py +103 -0
- package/mcp_server/project_brain/session_graph.py +51 -0
- package/mcp_server/project_brain/tools.py +144 -0
- package/mcp_server/reference_engine/__init__.py +1 -0
- package/mcp_server/reference_engine/gap_analyzer.py +239 -0
- package/mcp_server/reference_engine/models.py +105 -0
- package/mcp_server/reference_engine/profile_builder.py +149 -0
- package/mcp_server/reference_engine/tactic_router.py +117 -0
- package/mcp_server/reference_engine/tools.py +235 -0
- package/mcp_server/runtime/__init__.py +1 -0
- package/mcp_server/runtime/action_ledger.py +117 -0
- package/mcp_server/runtime/action_ledger_models.py +84 -0
- package/mcp_server/runtime/action_tools.py +57 -0
- package/mcp_server/runtime/capability_state.py +218 -0
- package/mcp_server/runtime/safety_kernel.py +339 -0
- package/mcp_server/runtime/safety_tools.py +42 -0
- package/mcp_server/runtime/tools.py +64 -0
- package/mcp_server/server.py +23 -1
- package/mcp_server/sound_design/__init__.py +1 -0
- package/mcp_server/sound_design/critics.py +297 -0
- package/mcp_server/sound_design/models.py +147 -0
- package/mcp_server/sound_design/planner.py +104 -0
- package/mcp_server/sound_design/tools.py +297 -0
- package/mcp_server/tools/_agent_os_engine.py +947 -0
- package/mcp_server/tools/_composition_engine.py +1530 -0
- package/mcp_server/tools/_conductor.py +199 -0
- package/mcp_server/tools/_conductor_budgets.py +222 -0
- package/mcp_server/tools/_evaluation_contracts.py +91 -0
- package/mcp_server/tools/_form_engine.py +416 -0
- package/mcp_server/tools/_motif_engine.py +351 -0
- package/mcp_server/tools/_planner_engine.py +516 -0
- package/mcp_server/tools/_research_engine.py +542 -0
- package/mcp_server/tools/_research_provider.py +185 -0
- package/mcp_server/tools/_snapshot_normalizer.py +49 -0
- package/mcp_server/tools/agent_os.py +440 -0
- package/mcp_server/tools/analyzer.py +18 -0
- package/mcp_server/tools/automation.py +25 -10
- package/mcp_server/tools/composition.py +563 -0
- package/mcp_server/tools/motif.py +104 -0
- package/mcp_server/tools/planner.py +144 -0
- package/mcp_server/tools/research.py +223 -0
- package/mcp_server/tools/tracks.py +18 -3
- package/mcp_server/tools/transport.py +10 -2
- package/mcp_server/transition_engine/__init__.py +6 -0
- package/mcp_server/transition_engine/archetypes.py +167 -0
- package/mcp_server/transition_engine/critics.py +340 -0
- package/mcp_server/transition_engine/models.py +90 -0
- package/mcp_server/transition_engine/tools.py +291 -0
- package/mcp_server/translation_engine/__init__.py +5 -0
- package/mcp_server/translation_engine/critics.py +297 -0
- package/mcp_server/translation_engine/models.py +27 -0
- package/mcp_server/translation_engine/tools.py +74 -0
- package/package.json +2 -2
- package/remote_script/LivePilot/__init__.py +1 -1
- package/remote_script/LivePilot/arrangement.py +12 -2
- package/requirements.txt +1 -1
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""Sound Design Engine MCP tools — 4 tools for timbral analysis and planning.
|
|
2
|
+
|
|
3
|
+
Each tool fetches data from Ableton via the shared connection,
|
|
4
|
+
then delegates to pure-computation modules.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from mcp.server.fastmcp import Context
|
|
10
|
+
|
|
11
|
+
from ..server import mcp
|
|
12
|
+
from .models import (
|
|
13
|
+
LayerStrategy,
|
|
14
|
+
PatchBlock,
|
|
15
|
+
PatchModel,
|
|
16
|
+
SoundDesignState,
|
|
17
|
+
TimbralGoalVector,
|
|
18
|
+
VALID_BLOCK_TYPES,
|
|
19
|
+
)
|
|
20
|
+
from .critics import run_all_sound_design_critics
|
|
21
|
+
from .planner import plan_sound_design_moves
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ── Helpers ──────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Native Ableton devices whose internal blocks are known
|
|
28
|
+
_NATIVE_BLOCK_MAP: dict[str, list[tuple[str, str]]] = {
|
|
29
|
+
# (block_type, label)
|
|
30
|
+
"Wavetable": [
|
|
31
|
+
("oscillator", "Wavetable"),
|
|
32
|
+
("filter", "Wavetable"),
|
|
33
|
+
("envelope", "Wavetable"),
|
|
34
|
+
("lfo", "Wavetable"),
|
|
35
|
+
],
|
|
36
|
+
"Operator": [
|
|
37
|
+
("oscillator", "Operator"),
|
|
38
|
+
("filter", "Operator"),
|
|
39
|
+
("envelope", "Operator"),
|
|
40
|
+
("lfo", "Operator"),
|
|
41
|
+
],
|
|
42
|
+
"Analog": [
|
|
43
|
+
("oscillator", "Analog"),
|
|
44
|
+
("filter", "Analog"),
|
|
45
|
+
("envelope", "Analog"),
|
|
46
|
+
("lfo", "Analog"),
|
|
47
|
+
],
|
|
48
|
+
"Drift": [
|
|
49
|
+
("oscillator", "Drift"),
|
|
50
|
+
("filter", "Drift"),
|
|
51
|
+
("envelope", "Drift"),
|
|
52
|
+
("lfo", "Drift"),
|
|
53
|
+
],
|
|
54
|
+
"Simpler": [
|
|
55
|
+
("oscillator", "Simpler"),
|
|
56
|
+
("filter", "Simpler"),
|
|
57
|
+
("envelope", "Simpler"),
|
|
58
|
+
("lfo", "Simpler"),
|
|
59
|
+
],
|
|
60
|
+
"Sampler": [
|
|
61
|
+
("oscillator", "Sampler"),
|
|
62
|
+
("filter", "Sampler"),
|
|
63
|
+
("envelope", "Sampler"),
|
|
64
|
+
("lfo", "Sampler"),
|
|
65
|
+
],
|
|
66
|
+
"Collision": [
|
|
67
|
+
("oscillator", "Collision"),
|
|
68
|
+
("filter", "Collision"),
|
|
69
|
+
("envelope", "Collision"),
|
|
70
|
+
("lfo", "Collision"),
|
|
71
|
+
],
|
|
72
|
+
"Tension": [
|
|
73
|
+
("oscillator", "Tension"),
|
|
74
|
+
("filter", "Tension"),
|
|
75
|
+
("envelope", "Tension"),
|
|
76
|
+
],
|
|
77
|
+
"Electric": [
|
|
78
|
+
("oscillator", "Electric"),
|
|
79
|
+
("effect", "Electric"),
|
|
80
|
+
],
|
|
81
|
+
"Saturator": [("saturation", "Saturator")],
|
|
82
|
+
"Overdrive": [("saturation", "Overdrive")],
|
|
83
|
+
"Pedal": [("saturation", "Pedal")],
|
|
84
|
+
"Auto Filter": [("filter", "Auto Filter"), ("lfo", "Auto Filter")],
|
|
85
|
+
"EQ Eight": [("filter", "EQ Eight")],
|
|
86
|
+
"EQ Three": [("filter", "EQ Three")],
|
|
87
|
+
"Chorus-Ensemble": [("spatial", "Chorus-Ensemble"), ("lfo", "Chorus-Ensemble")],
|
|
88
|
+
"Chorus": [("spatial", "Chorus"), ("lfo", "Chorus")],
|
|
89
|
+
"Phaser-Flanger": [("spatial", "Phaser-Flanger"), ("lfo", "Phaser-Flanger")],
|
|
90
|
+
"Phaser": [("spatial", "Phaser"), ("lfo", "Phaser")],
|
|
91
|
+
"Flanger": [("spatial", "Flanger"), ("lfo", "Flanger")],
|
|
92
|
+
"Reverb": [("spatial", "Reverb")],
|
|
93
|
+
"Delay": [("spatial", "Delay")],
|
|
94
|
+
"Echo": [("spatial", "Echo"), ("lfo", "Echo")],
|
|
95
|
+
"Corpus": [("effect", "Corpus")],
|
|
96
|
+
"Erosion": [("effect", "Erosion")],
|
|
97
|
+
"Frequency Shifter": [("effect", "Frequency Shifter")],
|
|
98
|
+
"Redux": [("effect", "Redux")],
|
|
99
|
+
"Grain Delay": [("spatial", "Grain Delay")],
|
|
100
|
+
"Spectral Resonator": [("effect", "Spectral Resonator")],
|
|
101
|
+
"Spectral Time": [("effect", "Spectral Time")],
|
|
102
|
+
"Hybrid Reverb": [("spatial", "Hybrid Reverb")],
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _build_patch_model(track_index: int, track_info: dict, devices: list[dict]) -> PatchModel:
|
|
107
|
+
"""Build a PatchModel from track info and device list."""
|
|
108
|
+
device_chain = [d.get("name", "Unknown") for d in devices]
|
|
109
|
+
blocks: list[PatchBlock] = []
|
|
110
|
+
opaque: list[str] = []
|
|
111
|
+
|
|
112
|
+
for d in devices:
|
|
113
|
+
name = d.get("name", "Unknown")
|
|
114
|
+
if name in _NATIVE_BLOCK_MAP:
|
|
115
|
+
for block_type, label in _NATIVE_BLOCK_MAP[name]:
|
|
116
|
+
blocks.append(PatchBlock(
|
|
117
|
+
block_type=block_type,
|
|
118
|
+
device_name=label,
|
|
119
|
+
controllable=True,
|
|
120
|
+
))
|
|
121
|
+
else:
|
|
122
|
+
# Opaque third-party or unknown device
|
|
123
|
+
opaque.append(name)
|
|
124
|
+
blocks.append(PatchBlock(
|
|
125
|
+
block_type="effect",
|
|
126
|
+
device_name=name,
|
|
127
|
+
controllable=False,
|
|
128
|
+
))
|
|
129
|
+
|
|
130
|
+
# Infer roles from track name
|
|
131
|
+
roles: list[str] = []
|
|
132
|
+
track_name = track_info.get("name", "").lower()
|
|
133
|
+
role_keywords = {
|
|
134
|
+
"bass": "bass",
|
|
135
|
+
"sub": "sub_anchor",
|
|
136
|
+
"kick": "transient_layer",
|
|
137
|
+
"drum": "transient_layer",
|
|
138
|
+
"perc": "transient_layer",
|
|
139
|
+
"pad": "texture_layer",
|
|
140
|
+
"texture": "texture_layer",
|
|
141
|
+
"lead": "lead",
|
|
142
|
+
"vocal": "lead",
|
|
143
|
+
"keys": "body_layer",
|
|
144
|
+
"piano": "body_layer",
|
|
145
|
+
"synth": "body_layer",
|
|
146
|
+
"wide": "width_layer",
|
|
147
|
+
"stereo": "width_layer",
|
|
148
|
+
}
|
|
149
|
+
for keyword, role in role_keywords.items():
|
|
150
|
+
if keyword in track_name and role not in roles:
|
|
151
|
+
roles.append(role)
|
|
152
|
+
if not roles:
|
|
153
|
+
roles.append("unknown")
|
|
154
|
+
|
|
155
|
+
return PatchModel(
|
|
156
|
+
track_index=track_index,
|
|
157
|
+
device_chain=device_chain,
|
|
158
|
+
roles=roles,
|
|
159
|
+
blocks=blocks,
|
|
160
|
+
opaque_blocks=opaque,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _build_layer_strategy(track_index: int, patch: PatchModel) -> LayerStrategy:
|
|
165
|
+
"""Build a basic LayerStrategy from the patch's inferred roles."""
|
|
166
|
+
layers = LayerStrategy()
|
|
167
|
+
for role in patch.roles:
|
|
168
|
+
if role == "sub_anchor" and layers.sub_anchor is None:
|
|
169
|
+
layers.sub_anchor = track_index
|
|
170
|
+
elif role == "body_layer" and layers.body_layer is None:
|
|
171
|
+
layers.body_layer = track_index
|
|
172
|
+
elif role == "transient_layer" and layers.transient_layer is None:
|
|
173
|
+
layers.transient_layer = track_index
|
|
174
|
+
elif role == "texture_layer" and layers.texture_layer is None:
|
|
175
|
+
layers.texture_layer = track_index
|
|
176
|
+
elif role == "width_layer" and layers.width_layer is None:
|
|
177
|
+
layers.width_layer = track_index
|
|
178
|
+
return layers
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _fetch_sound_design_data(ctx: Context, track_index: int) -> dict:
|
|
182
|
+
"""Fetch data needed to build a SoundDesignState from Ableton."""
|
|
183
|
+
ableton = ctx.lifespan_context["ableton"]
|
|
184
|
+
|
|
185
|
+
track_info = ableton.send_command(
|
|
186
|
+
"get_track_info", {"track_index": track_index}
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Get devices from track_info response (already included by Remote Script)
|
|
190
|
+
devices: list[dict] = track_info.get("devices", [])
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
"track_info": track_info,
|
|
194
|
+
"devices": devices,
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# ── MCP Tools ────────────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@mcp.tool()
|
|
202
|
+
def analyze_sound_design(ctx: Context, track_index: int) -> dict:
|
|
203
|
+
"""Build full sound design state and run all critics for a track.
|
|
204
|
+
|
|
205
|
+
Returns the complete timbral analysis including patch model,
|
|
206
|
+
layer strategy, all detected issues, and suggested moves.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
track_index: Index of the track to analyze.
|
|
210
|
+
"""
|
|
211
|
+
data = _fetch_sound_design_data(ctx, track_index)
|
|
212
|
+
patch = _build_patch_model(track_index, data["track_info"], data["devices"])
|
|
213
|
+
layers = _build_layer_strategy(track_index, patch)
|
|
214
|
+
state = SoundDesignState(
|
|
215
|
+
goal=TimbralGoalVector(),
|
|
216
|
+
patch=patch,
|
|
217
|
+
layers=layers,
|
|
218
|
+
)
|
|
219
|
+
issues = run_all_sound_design_critics(state)
|
|
220
|
+
moves = plan_sound_design_moves(issues, state)
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
"state": state.to_dict(),
|
|
224
|
+
"issues": [i.to_dict() for i in issues],
|
|
225
|
+
"suggested_moves": [m.to_dict() for m in moves],
|
|
226
|
+
"issue_count": len(issues),
|
|
227
|
+
"move_count": len(moves),
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@mcp.tool()
|
|
232
|
+
def get_sound_design_issues(ctx: Context, track_index: int) -> dict:
|
|
233
|
+
"""Run all sound design critics and return detected issues only.
|
|
234
|
+
|
|
235
|
+
Lighter than analyze_sound_design — skips move planning.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
track_index: Index of the track to analyze.
|
|
239
|
+
"""
|
|
240
|
+
data = _fetch_sound_design_data(ctx, track_index)
|
|
241
|
+
patch = _build_patch_model(track_index, data["track_info"], data["devices"])
|
|
242
|
+
layers = _build_layer_strategy(track_index, patch)
|
|
243
|
+
state = SoundDesignState(
|
|
244
|
+
goal=TimbralGoalVector(),
|
|
245
|
+
patch=patch,
|
|
246
|
+
layers=layers,
|
|
247
|
+
)
|
|
248
|
+
issues = run_all_sound_design_critics(state)
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
"issues": [i.to_dict() for i in issues],
|
|
252
|
+
"issue_count": len(issues),
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@mcp.tool()
|
|
257
|
+
def plan_sound_design_move(ctx: Context, track_index: int) -> dict:
|
|
258
|
+
"""Get ranked move suggestions based on current sound design issues.
|
|
259
|
+
|
|
260
|
+
Runs critics and planner, returns sorted moves with
|
|
261
|
+
estimated impact and risk scores.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
track_index: Index of the track to analyze.
|
|
265
|
+
"""
|
|
266
|
+
data = _fetch_sound_design_data(ctx, track_index)
|
|
267
|
+
patch = _build_patch_model(track_index, data["track_info"], data["devices"])
|
|
268
|
+
layers = _build_layer_strategy(track_index, patch)
|
|
269
|
+
state = SoundDesignState(
|
|
270
|
+
goal=TimbralGoalVector(),
|
|
271
|
+
patch=patch,
|
|
272
|
+
layers=layers,
|
|
273
|
+
)
|
|
274
|
+
issues = run_all_sound_design_critics(state)
|
|
275
|
+
moves = plan_sound_design_moves(issues, state)
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
"moves": [m.to_dict() for m in moves],
|
|
279
|
+
"move_count": len(moves),
|
|
280
|
+
"issue_count": len(issues),
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@mcp.tool()
|
|
285
|
+
def get_patch_model(ctx: Context, track_index: int) -> dict:
|
|
286
|
+
"""Get the structural patch model for a track's device chain.
|
|
287
|
+
|
|
288
|
+
Returns device chain, functional blocks, controllable vs opaque
|
|
289
|
+
blocks, and inferred musical roles.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
track_index: Index of the track to inspect.
|
|
293
|
+
"""
|
|
294
|
+
data = _fetch_sound_design_data(ctx, track_index)
|
|
295
|
+
patch = _build_patch_model(track_index, data["track_info"], data["devices"])
|
|
296
|
+
|
|
297
|
+
return patch.to_dict()
|