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.
Files changed (105) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/AGENTS.md +3 -3
  3. package/CHANGELOG.md +51 -0
  4. package/CONTRIBUTING.md +1 -1
  5. package/README.md +7 -7
  6. package/bin/livepilot.js +32 -8
  7. package/installer/install.js +21 -2
  8. package/livepilot/.Codex-plugin/plugin.json +2 -2
  9. package/livepilot/.claude-plugin/plugin.json +2 -2
  10. package/livepilot/agents/livepilot-producer/AGENT.md +243 -49
  11. package/livepilot/skills/livepilot-core/SKILL.md +81 -6
  12. package/livepilot/skills/livepilot-core/references/m4l-devices.md +2 -2
  13. package/livepilot/skills/livepilot-core/references/overview.md +3 -3
  14. package/livepilot/skills/livepilot-core/references/sound-design.md +3 -2
  15. package/livepilot/skills/livepilot-release/SKILL.md +13 -13
  16. package/m4l_device/LivePilot_Analyzer.amxd +0 -0
  17. package/m4l_device/livepilot_bridge.js +6 -3
  18. package/mcp_server/__init__.py +1 -1
  19. package/mcp_server/curves.py +11 -3
  20. package/mcp_server/evaluation/__init__.py +1 -0
  21. package/mcp_server/evaluation/fabric.py +575 -0
  22. package/mcp_server/evaluation/feature_extractors.py +84 -0
  23. package/mcp_server/evaluation/policy.py +67 -0
  24. package/mcp_server/evaluation/tools.py +53 -0
  25. package/mcp_server/memory/__init__.py +11 -2
  26. package/mcp_server/memory/anti_memory.py +78 -0
  27. package/mcp_server/memory/promotion.py +94 -0
  28. package/mcp_server/memory/session_memory.py +108 -0
  29. package/mcp_server/memory/taste_memory.py +158 -0
  30. package/mcp_server/memory/technique_store.py +2 -1
  31. package/mcp_server/memory/tools.py +112 -0
  32. package/mcp_server/mix_engine/__init__.py +1 -0
  33. package/mcp_server/mix_engine/critics.py +299 -0
  34. package/mcp_server/mix_engine/models.py +152 -0
  35. package/mcp_server/mix_engine/planner.py +103 -0
  36. package/mcp_server/mix_engine/state_builder.py +316 -0
  37. package/mcp_server/mix_engine/tools.py +214 -0
  38. package/mcp_server/performance_engine/__init__.py +1 -0
  39. package/mcp_server/performance_engine/models.py +148 -0
  40. package/mcp_server/performance_engine/planner.py +267 -0
  41. package/mcp_server/performance_engine/safety.py +162 -0
  42. package/mcp_server/performance_engine/tools.py +183 -0
  43. package/mcp_server/project_brain/__init__.py +6 -0
  44. package/mcp_server/project_brain/arrangement_graph.py +64 -0
  45. package/mcp_server/project_brain/automation_graph.py +72 -0
  46. package/mcp_server/project_brain/builder.py +123 -0
  47. package/mcp_server/project_brain/capability_graph.py +64 -0
  48. package/mcp_server/project_brain/models.py +282 -0
  49. package/mcp_server/project_brain/refresh.py +80 -0
  50. package/mcp_server/project_brain/role_graph.py +103 -0
  51. package/mcp_server/project_brain/session_graph.py +51 -0
  52. package/mcp_server/project_brain/tools.py +144 -0
  53. package/mcp_server/reference_engine/__init__.py +1 -0
  54. package/mcp_server/reference_engine/gap_analyzer.py +239 -0
  55. package/mcp_server/reference_engine/models.py +105 -0
  56. package/mcp_server/reference_engine/profile_builder.py +149 -0
  57. package/mcp_server/reference_engine/tactic_router.py +117 -0
  58. package/mcp_server/reference_engine/tools.py +235 -0
  59. package/mcp_server/runtime/__init__.py +1 -0
  60. package/mcp_server/runtime/action_ledger.py +117 -0
  61. package/mcp_server/runtime/action_ledger_models.py +84 -0
  62. package/mcp_server/runtime/action_tools.py +57 -0
  63. package/mcp_server/runtime/capability_state.py +218 -0
  64. package/mcp_server/runtime/safety_kernel.py +339 -0
  65. package/mcp_server/runtime/safety_tools.py +42 -0
  66. package/mcp_server/runtime/tools.py +64 -0
  67. package/mcp_server/server.py +23 -1
  68. package/mcp_server/sound_design/__init__.py +1 -0
  69. package/mcp_server/sound_design/critics.py +297 -0
  70. package/mcp_server/sound_design/models.py +147 -0
  71. package/mcp_server/sound_design/planner.py +104 -0
  72. package/mcp_server/sound_design/tools.py +297 -0
  73. package/mcp_server/tools/_agent_os_engine.py +947 -0
  74. package/mcp_server/tools/_composition_engine.py +1530 -0
  75. package/mcp_server/tools/_conductor.py +199 -0
  76. package/mcp_server/tools/_conductor_budgets.py +222 -0
  77. package/mcp_server/tools/_evaluation_contracts.py +91 -0
  78. package/mcp_server/tools/_form_engine.py +416 -0
  79. package/mcp_server/tools/_motif_engine.py +351 -0
  80. package/mcp_server/tools/_planner_engine.py +516 -0
  81. package/mcp_server/tools/_research_engine.py +542 -0
  82. package/mcp_server/tools/_research_provider.py +185 -0
  83. package/mcp_server/tools/_snapshot_normalizer.py +49 -0
  84. package/mcp_server/tools/agent_os.py +440 -0
  85. package/mcp_server/tools/analyzer.py +18 -0
  86. package/mcp_server/tools/automation.py +25 -10
  87. package/mcp_server/tools/composition.py +563 -0
  88. package/mcp_server/tools/motif.py +104 -0
  89. package/mcp_server/tools/planner.py +144 -0
  90. package/mcp_server/tools/research.py +223 -0
  91. package/mcp_server/tools/tracks.py +18 -3
  92. package/mcp_server/tools/transport.py +10 -2
  93. package/mcp_server/transition_engine/__init__.py +6 -0
  94. package/mcp_server/transition_engine/archetypes.py +167 -0
  95. package/mcp_server/transition_engine/critics.py +340 -0
  96. package/mcp_server/transition_engine/models.py +90 -0
  97. package/mcp_server/transition_engine/tools.py +291 -0
  98. package/mcp_server/translation_engine/__init__.py +5 -0
  99. package/mcp_server/translation_engine/critics.py +297 -0
  100. package/mcp_server/translation_engine/models.py +27 -0
  101. package/mcp_server/translation_engine/tools.py +74 -0
  102. package/package.json +2 -2
  103. package/remote_script/LivePilot/__init__.py +1 -1
  104. package/remote_script/LivePilot/arrangement.py +12 -2
  105. 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()