livepilot 1.9.24 → 1.10.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.
Files changed (165) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/AGENTS.md +3 -3
  3. package/CHANGELOG.md +73 -0
  4. package/CONTRIBUTING.md +1 -1
  5. package/README.md +56 -19
  6. package/bin/livepilot.js +87 -0
  7. package/installer/codex.js +147 -0
  8. package/livepilot/.Codex-plugin/plugin.json +2 -2
  9. package/livepilot/.claude-plugin/plugin.json +2 -2
  10. package/livepilot/skills/livepilot-core/SKILL.md +21 -4
  11. package/livepilot/skills/livepilot-core/references/device-knowledge/00-index.md +34 -0
  12. package/livepilot/skills/livepilot-core/references/device-knowledge/automation-as-music.md +204 -0
  13. package/livepilot/skills/livepilot-core/references/device-knowledge/chains-genre.md +173 -0
  14. package/livepilot/skills/livepilot-core/references/device-knowledge/creative-thinking.md +211 -0
  15. package/livepilot/skills/livepilot-core/references/device-knowledge/effects-distortion.md +188 -0
  16. package/livepilot/skills/livepilot-core/references/device-knowledge/effects-space.md +162 -0
  17. package/livepilot/skills/livepilot-core/references/device-knowledge/effects-spectral.md +229 -0
  18. package/livepilot/skills/livepilot-core/references/device-knowledge/instruments-synths.md +243 -0
  19. package/livepilot/skills/livepilot-core/references/overview.md +13 -9
  20. package/livepilot/skills/livepilot-core/references/sample-manipulation.md +724 -0
  21. package/livepilot/skills/livepilot-core/references/sound-design-deep.md +140 -0
  22. package/livepilot/skills/livepilot-devices/SKILL.md +16 -2
  23. package/livepilot/skills/livepilot-evaluation/references/capability-modes.md +1 -1
  24. package/livepilot/skills/livepilot-release/SKILL.md +5 -5
  25. package/livepilot/skills/livepilot-sample-engine/SKILL.md +104 -0
  26. package/livepilot/skills/livepilot-sample-engine/references/sample-critics.md +87 -0
  27. package/livepilot/skills/livepilot-sample-engine/references/sample-philosophy.md +51 -0
  28. package/livepilot/skills/livepilot-sample-engine/references/sample-techniques.md +131 -0
  29. package/livepilot/skills/livepilot-sound-design-engine/SKILL.md +45 -0
  30. package/livepilot/skills/livepilot-wonder/SKILL.md +15 -0
  31. package/livepilot.mcpb +0 -0
  32. package/m4l_device/livepilot_bridge.js +1 -1
  33. package/manifest.json +2 -2
  34. package/mcp_server/__init__.py +1 -1
  35. package/mcp_server/atlas/__init__.py +357 -0
  36. package/mcp_server/atlas/device_atlas.json +44067 -0
  37. package/mcp_server/atlas/enrichments/__init__.py +111 -0
  38. package/mcp_server/atlas/enrichments/audio_effects/auto_filter.yaml +162 -0
  39. package/mcp_server/atlas/enrichments/audio_effects/beat_repeat.yaml +183 -0
  40. package/mcp_server/atlas/enrichments/audio_effects/channel_eq.yaml +126 -0
  41. package/mcp_server/atlas/enrichments/audio_effects/chorus_ensemble.yaml +149 -0
  42. package/mcp_server/atlas/enrichments/audio_effects/color_limiter.yaml +109 -0
  43. package/mcp_server/atlas/enrichments/audio_effects/compressor.yaml +159 -0
  44. package/mcp_server/atlas/enrichments/audio_effects/convolution_reverb.yaml +143 -0
  45. package/mcp_server/atlas/enrichments/audio_effects/convolution_reverb_pro.yaml +178 -0
  46. package/mcp_server/atlas/enrichments/audio_effects/delay.yaml +151 -0
  47. package/mcp_server/atlas/enrichments/audio_effects/drum_buss.yaml +142 -0
  48. package/mcp_server/atlas/enrichments/audio_effects/dynamic_tube.yaml +147 -0
  49. package/mcp_server/atlas/enrichments/audio_effects/echo.yaml +167 -0
  50. package/mcp_server/atlas/enrichments/audio_effects/eq_eight.yaml +148 -0
  51. package/mcp_server/atlas/enrichments/audio_effects/eq_three.yaml +121 -0
  52. package/mcp_server/atlas/enrichments/audio_effects/erosion.yaml +103 -0
  53. package/mcp_server/atlas/enrichments/audio_effects/filter_delay.yaml +173 -0
  54. package/mcp_server/atlas/enrichments/audio_effects/gate.yaml +130 -0
  55. package/mcp_server/atlas/enrichments/audio_effects/gated_delay.yaml +133 -0
  56. package/mcp_server/atlas/enrichments/audio_effects/glue_compressor.yaml +142 -0
  57. package/mcp_server/atlas/enrichments/audio_effects/grain_delay.yaml +141 -0
  58. package/mcp_server/atlas/enrichments/audio_effects/hybrid_reverb.yaml +160 -0
  59. package/mcp_server/atlas/enrichments/audio_effects/limiter.yaml +97 -0
  60. package/mcp_server/atlas/enrichments/audio_effects/multiband_dynamics.yaml +174 -0
  61. package/mcp_server/atlas/enrichments/audio_effects/overdrive.yaml +119 -0
  62. package/mcp_server/atlas/enrichments/audio_effects/pedal.yaml +145 -0
  63. package/mcp_server/atlas/enrichments/audio_effects/phaser_flanger.yaml +161 -0
  64. package/mcp_server/atlas/enrichments/audio_effects/redux.yaml +114 -0
  65. package/mcp_server/atlas/enrichments/audio_effects/reverb.yaml +190 -0
  66. package/mcp_server/atlas/enrichments/audio_effects/roar.yaml +159 -0
  67. package/mcp_server/atlas/enrichments/audio_effects/saturator.yaml +146 -0
  68. package/mcp_server/atlas/enrichments/audio_effects/shifter.yaml +154 -0
  69. package/mcp_server/atlas/enrichments/audio_effects/spectral_resonator.yaml +141 -0
  70. package/mcp_server/atlas/enrichments/audio_effects/spectral_time.yaml +164 -0
  71. package/mcp_server/atlas/enrichments/audio_effects/vector_delay.yaml +140 -0
  72. package/mcp_server/atlas/enrichments/audio_effects/vinyl_distortion.yaml +141 -0
  73. package/mcp_server/atlas/enrichments/instruments/analog.yaml +222 -0
  74. package/mcp_server/atlas/enrichments/instruments/bass.yaml +202 -0
  75. package/mcp_server/atlas/enrichments/instruments/collision.yaml +150 -0
  76. package/mcp_server/atlas/enrichments/instruments/drift.yaml +167 -0
  77. package/mcp_server/atlas/enrichments/instruments/electric.yaml +137 -0
  78. package/mcp_server/atlas/enrichments/instruments/emit.yaml +163 -0
  79. package/mcp_server/atlas/enrichments/instruments/meld.yaml +164 -0
  80. package/mcp_server/atlas/enrichments/instruments/operator.yaml +197 -0
  81. package/mcp_server/atlas/enrichments/instruments/poli.yaml +192 -0
  82. package/mcp_server/atlas/enrichments/instruments/sampler.yaml +218 -0
  83. package/mcp_server/atlas/enrichments/instruments/simpler.yaml +217 -0
  84. package/mcp_server/atlas/enrichments/instruments/tension.yaml +156 -0
  85. package/mcp_server/atlas/enrichments/instruments/tree_tone.yaml +162 -0
  86. package/mcp_server/atlas/enrichments/instruments/vector_fm.yaml +165 -0
  87. package/mcp_server/atlas/enrichments/instruments/vector_grain.yaml +166 -0
  88. package/mcp_server/atlas/enrichments/instruments/wavetable.yaml +162 -0
  89. package/mcp_server/atlas/enrichments/midi_effects/arpeggiator.yaml +156 -0
  90. package/mcp_server/atlas/enrichments/midi_effects/bouncy_notes.yaml +93 -0
  91. package/mcp_server/atlas/enrichments/midi_effects/chord.yaml +147 -0
  92. package/mcp_server/atlas/enrichments/midi_effects/melodic_steps.yaml +97 -0
  93. package/mcp_server/atlas/enrichments/midi_effects/note_echo.yaml +108 -0
  94. package/mcp_server/atlas/enrichments/midi_effects/note_length.yaml +97 -0
  95. package/mcp_server/atlas/enrichments/midi_effects/pitch.yaml +76 -0
  96. package/mcp_server/atlas/enrichments/midi_effects/random.yaml +117 -0
  97. package/mcp_server/atlas/enrichments/midi_effects/rhythmic_steps.yaml +103 -0
  98. package/mcp_server/atlas/enrichments/midi_effects/scale.yaml +83 -0
  99. package/mcp_server/atlas/enrichments/midi_effects/step_arp.yaml +112 -0
  100. package/mcp_server/atlas/enrichments/midi_effects/velocity.yaml +119 -0
  101. package/mcp_server/atlas/enrichments/utility/amp.yaml +159 -0
  102. package/mcp_server/atlas/enrichments/utility/cabinet.yaml +109 -0
  103. package/mcp_server/atlas/enrichments/utility/corpus.yaml +150 -0
  104. package/mcp_server/atlas/enrichments/utility/resonators.yaml +131 -0
  105. package/mcp_server/atlas/enrichments/utility/spectrum.yaml +63 -0
  106. package/mcp_server/atlas/enrichments/utility/tuner.yaml +51 -0
  107. package/mcp_server/atlas/enrichments/utility/utility.yaml +136 -0
  108. package/mcp_server/atlas/enrichments/utility/vocoder.yaml +160 -0
  109. package/mcp_server/atlas/scanner.py +236 -0
  110. package/mcp_server/atlas/tools.py +224 -0
  111. package/mcp_server/composer/__init__.py +1 -0
  112. package/mcp_server/composer/engine.py +452 -0
  113. package/mcp_server/composer/layer_planner.py +427 -0
  114. package/mcp_server/composer/prompt_parser.py +329 -0
  115. package/mcp_server/composer/tools.py +201 -0
  116. package/mcp_server/connection.py +53 -8
  117. package/mcp_server/corpus/__init__.py +377 -0
  118. package/mcp_server/device_forge/__init__.py +1 -0
  119. package/mcp_server/device_forge/builder.py +377 -0
  120. package/mcp_server/device_forge/models.py +142 -0
  121. package/mcp_server/device_forge/templates.py +483 -0
  122. package/mcp_server/device_forge/tools.py +162 -0
  123. package/mcp_server/m4l_bridge.py +1 -0
  124. package/mcp_server/preview_studio/tools.py +4 -4
  125. package/mcp_server/runtime/capability_probe.py +21 -2
  126. package/mcp_server/runtime/execution_router.py +4 -0
  127. package/mcp_server/runtime/live_version.py +102 -0
  128. package/mcp_server/runtime/remote_commands.py +9 -4
  129. package/mcp_server/runtime/tools.py +18 -4
  130. package/mcp_server/sample_engine/__init__.py +1 -0
  131. package/mcp_server/sample_engine/analyzer.py +216 -0
  132. package/mcp_server/sample_engine/critics.py +390 -0
  133. package/mcp_server/sample_engine/models.py +193 -0
  134. package/mcp_server/sample_engine/moves.py +127 -0
  135. package/mcp_server/sample_engine/planner.py +186 -0
  136. package/mcp_server/sample_engine/sources.py +540 -0
  137. package/mcp_server/sample_engine/techniques.py +908 -0
  138. package/mcp_server/sample_engine/tools.py +442 -0
  139. package/mcp_server/semantic_moves/__init__.py +3 -0
  140. package/mcp_server/semantic_moves/device_creation_moves.py +237 -0
  141. package/mcp_server/semantic_moves/sample_compilers.py +372 -0
  142. package/mcp_server/server.py +51 -0
  143. package/mcp_server/sound_design/critics.py +89 -1
  144. package/mcp_server/splice_client/__init__.py +1 -0
  145. package/mcp_server/splice_client/client.py +347 -0
  146. package/mcp_server/splice_client/models.py +96 -0
  147. package/mcp_server/splice_client/protos/__init__.py +1 -0
  148. package/mcp_server/splice_client/protos/app_pb2.py +319 -0
  149. package/mcp_server/splice_client/protos/app_pb2.pyi +1153 -0
  150. package/mcp_server/splice_client/protos/app_pb2_grpc.py +1946 -0
  151. package/mcp_server/tools/arrangement.py +69 -0
  152. package/mcp_server/tools/automation.py +15 -2
  153. package/mcp_server/tools/devices.py +117 -6
  154. package/mcp_server/tools/notes.py +37 -4
  155. package/mcp_server/wonder_mode/diagnosis.py +5 -0
  156. package/mcp_server/wonder_mode/engine.py +85 -1
  157. package/package.json +12 -2
  158. package/remote_script/LivePilot/__init__.py +8 -1
  159. package/remote_script/LivePilot/arrangement.py +114 -0
  160. package/remote_script/LivePilot/browser.py +56 -1
  161. package/remote_script/LivePilot/devices.py +236 -6
  162. package/remote_script/LivePilot/mixing.py +8 -3
  163. package/remote_script/LivePilot/server.py +5 -1
  164. package/remote_script/LivePilot/transport.py +3 -0
  165. package/remote_script/LivePilot/version_detect.py +78 -0
@@ -0,0 +1,372 @@
1
+ """Compilers for sample-domain semantic moves.
2
+
3
+ These compile sample manipulation intents into concrete tool call sequences
4
+ using the session kernel to find appropriate tracks and devices.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from .compiler import CompiledPlan, CompiledStep, register_compiler
10
+ from .models import SemanticMove
11
+ from . import resolvers
12
+
13
+
14
+ def _compile_sample_chop_rhythm(move: SemanticMove, kernel: dict) -> CompiledPlan:
15
+ """Compile 'sample_chop_rhythm': load, slice, and chop a sample for rhythm."""
16
+ steps = []
17
+ descriptions = []
18
+ warnings = []
19
+
20
+ # Find drum/percussion tracks to layer alongside
21
+ drums = resolvers.find_tracks_by_role(kernel, ["drums", "percussion"])
22
+
23
+ # Create a new track for the chopped sample
24
+ steps.append(CompiledStep(
25
+ tool="create_midi_track",
26
+ params={"name": "Chop"},
27
+ description="Create track for chopped sample",
28
+ ))
29
+ descriptions.append("Create chop track")
30
+
31
+ # Load into Simpler — track index will be last + 1
32
+ tracks = kernel.get("session_info", {}).get("tracks", [])
33
+ new_idx = len(tracks)
34
+
35
+ steps.append(CompiledStep(
36
+ tool="load_sample_to_simpler",
37
+ params={"track_index": new_idx},
38
+ description="Load sample into Simpler for slicing",
39
+ ))
40
+
41
+ steps.append(CompiledStep(
42
+ tool="set_simpler_playback_mode",
43
+ params={"track_index": new_idx, "mode": "slice"},
44
+ description="Switch to slice mode for rhythmic chopping",
45
+ ))
46
+ descriptions.append("Slice sample")
47
+
48
+ # Balance against existing drums
49
+ if drums:
50
+ steps.append(CompiledStep(
51
+ tool="set_track_volume",
52
+ params={"track_index": new_idx, "volume": 0.55},
53
+ description="Set chop volume below main drums",
54
+ ))
55
+ else:
56
+ warnings.append("No drum tracks found — chop will be the primary rhythm")
57
+
58
+ steps.append(CompiledStep(
59
+ tool="get_track_meters",
60
+ params={"include_stereo": True},
61
+ description="Verify chopped sample producing audio",
62
+ ))
63
+
64
+ return CompiledPlan(
65
+ move_id=move.move_id,
66
+ intent=move.intent,
67
+ steps=steps,
68
+ risk_level="medium",
69
+ summary="; ".join(descriptions) if descriptions else "Chop sample for rhythm",
70
+ requires_approval=True,
71
+ warnings=warnings,
72
+ )
73
+
74
+
75
+ def _compile_sample_texture_layer(move: SemanticMove, kernel: dict) -> CompiledPlan:
76
+ """Compile 'sample_texture_layer': load and filter a sample as background texture."""
77
+ steps = []
78
+ descriptions = []
79
+
80
+ tracks = kernel.get("session_info", {}).get("tracks", [])
81
+ new_idx = len(tracks)
82
+
83
+ steps.append(CompiledStep(
84
+ tool="create_midi_track",
85
+ params={"name": "Texture"},
86
+ description="Create track for texture layer",
87
+ ))
88
+
89
+ steps.append(CompiledStep(
90
+ tool="load_sample_to_simpler",
91
+ params={"track_index": new_idx},
92
+ description="Load textural sample into Simpler",
93
+ ))
94
+ descriptions.append("Load texture sample")
95
+
96
+ # Low volume for background placement
97
+ steps.append(CompiledStep(
98
+ tool="set_track_volume",
99
+ params={"track_index": new_idx, "volume": 0.35},
100
+ description="Set texture low in mix for background presence",
101
+ ))
102
+ descriptions.append("Set background level")
103
+
104
+ # Add reverb send for spatial depth
105
+ steps.append(CompiledStep(
106
+ tool="set_track_send",
107
+ params={"track_index": new_idx, "send_index": 0, "value": 0.40},
108
+ description="Heavy reverb for spatial depth on texture",
109
+ ))
110
+ descriptions.append("Add reverb depth")
111
+
112
+ steps.append(CompiledStep(
113
+ tool="get_track_meters",
114
+ params={"include_stereo": True},
115
+ description="Verify texture layer producing audio at low level",
116
+ ))
117
+
118
+ return CompiledPlan(
119
+ move_id=move.move_id,
120
+ intent=move.intent,
121
+ steps=steps,
122
+ risk_level="low",
123
+ summary="; ".join(descriptions),
124
+ requires_approval=(kernel.get("mode", "improve") != "explore"),
125
+ )
126
+
127
+
128
+ def _compile_sample_vocal_ghost(move: SemanticMove, kernel: dict) -> CompiledPlan:
129
+ """Compile 'sample_vocal_ghost': reverse, pitch, and wash a vocal sample."""
130
+ steps = []
131
+ descriptions = []
132
+
133
+ tracks = kernel.get("session_info", {}).get("tracks", [])
134
+ new_idx = len(tracks)
135
+
136
+ steps.append(CompiledStep(
137
+ tool="create_midi_track",
138
+ params={"name": "Ghost Vox"},
139
+ description="Create track for ghost vocal",
140
+ ))
141
+
142
+ steps.append(CompiledStep(
143
+ tool="load_sample_to_simpler",
144
+ params={"track_index": new_idx},
145
+ description="Load vocal sample into Simpler",
146
+ ))
147
+
148
+ steps.append(CompiledStep(
149
+ tool="reverse_simpler",
150
+ params={"track_index": new_idx},
151
+ description="Reverse vocal for ghostly character",
152
+ ))
153
+ descriptions.append("Reverse vocal")
154
+
155
+ # Heavy reverb wash
156
+ steps.append(CompiledStep(
157
+ tool="set_track_send",
158
+ params={"track_index": new_idx, "send_index": 0, "value": 0.55},
159
+ description="Heavy reverb wash for ghostly depth",
160
+ ))
161
+ descriptions.append("Reverb wash")
162
+
163
+ # Low volume — ghosts live in the background
164
+ steps.append(CompiledStep(
165
+ tool="set_track_volume",
166
+ params={"track_index": new_idx, "volume": 0.30},
167
+ description="Set ghost vocal low in mix",
168
+ ))
169
+ descriptions.append("Background level")
170
+
171
+ steps.append(CompiledStep(
172
+ tool="get_track_meters",
173
+ params={"include_stereo": True},
174
+ description="Verify ghost vocal producing audio with reverb tail",
175
+ ))
176
+
177
+ return CompiledPlan(
178
+ move_id=move.move_id,
179
+ intent=move.intent,
180
+ steps=steps,
181
+ risk_level="medium",
182
+ summary="; ".join(descriptions),
183
+ requires_approval=True,
184
+ )
185
+
186
+
187
+ def _compile_sample_break_layer(move: SemanticMove, kernel: dict) -> CompiledPlan:
188
+ """Compile 'sample_break_layer': slice a break and layer over existing drums."""
189
+ steps = []
190
+ descriptions = []
191
+ warnings = []
192
+
193
+ drums = resolvers.find_tracks_by_role(kernel, ["drums", "percussion"])
194
+ if not drums:
195
+ warnings.append("No existing drum tracks — break will be the primary rhythm")
196
+
197
+ tracks = kernel.get("session_info", {}).get("tracks", [])
198
+ new_idx = len(tracks)
199
+
200
+ steps.append(CompiledStep(
201
+ tool="create_midi_track",
202
+ params={"name": "Break"},
203
+ description="Create track for breakbeat layer",
204
+ ))
205
+
206
+ steps.append(CompiledStep(
207
+ tool="load_sample_to_simpler",
208
+ params={"track_index": new_idx},
209
+ description="Load breakbeat into Simpler",
210
+ ))
211
+
212
+ steps.append(CompiledStep(
213
+ tool="set_simpler_playback_mode",
214
+ params={"track_index": new_idx, "mode": "slice"},
215
+ description="Slice break by transients for individual hits",
216
+ ))
217
+ descriptions.append("Slice break")
218
+
219
+ # Sit below main drums
220
+ steps.append(CompiledStep(
221
+ tool="set_track_volume",
222
+ params={"track_index": new_idx, "volume": 0.45},
223
+ description="Set break layer below main drums",
224
+ ))
225
+ descriptions.append("Balance break level")
226
+
227
+ steps.append(CompiledStep(
228
+ tool="get_track_meters",
229
+ params={"include_stereo": True},
230
+ description="Verify break layer producing audio alongside drums",
231
+ ))
232
+
233
+ return CompiledPlan(
234
+ move_id=move.move_id,
235
+ intent=move.intent,
236
+ steps=steps,
237
+ risk_level="medium",
238
+ summary="; ".join(descriptions) if descriptions else "Layer breakbeat",
239
+ requires_approval=True,
240
+ warnings=warnings,
241
+ )
242
+
243
+
244
+ def _compile_sample_resample_destroy(move: SemanticMove, kernel: dict) -> CompiledPlan:
245
+ """Compile 'sample_resample_destroy': warp and mangle a sample destructively.
246
+
247
+ SAFETY: This is a high-risk move — always requires approval.
248
+ Only adjusts device params when a known device is confirmed present.
249
+ """
250
+ steps = []
251
+ descriptions = []
252
+ warnings = ["High-risk: destructive processing — consider duplicating track first"]
253
+
254
+ tracks = kernel.get("session_info", {}).get("tracks", [])
255
+ new_idx = len(tracks)
256
+
257
+ steps.append(CompiledStep(
258
+ tool="create_midi_track",
259
+ params={"name": "Destroy"},
260
+ description="Create track for destructive resampling",
261
+ ))
262
+
263
+ steps.append(CompiledStep(
264
+ tool="load_sample_to_simpler",
265
+ params={"track_index": new_idx},
266
+ description="Load sample for destruction",
267
+ ))
268
+ descriptions.append("Load source")
269
+
270
+ steps.append(CompiledStep(
271
+ tool="warp_simpler",
272
+ params={"track_index": new_idx},
273
+ description="Apply extreme warp for time-stretch artifacts",
274
+ ))
275
+ descriptions.append("Warp for artifacts")
276
+
277
+ # Use volume + send instead of blindly setting device params
278
+ steps.append(CompiledStep(
279
+ tool="set_track_send",
280
+ params={"track_index": new_idx, "send_index": 0, "value": 0.30},
281
+ description="Add reverb send for destroyed texture depth",
282
+ ))
283
+
284
+ steps.append(CompiledStep(
285
+ tool="set_track_volume",
286
+ params={"track_index": new_idx, "volume": 0.50},
287
+ description="Set destroyed sample at moderate level",
288
+ ))
289
+ descriptions.append("Set level")
290
+
291
+ steps.append(CompiledStep(
292
+ tool="get_track_meters",
293
+ params={"include_stereo": True},
294
+ description="Verify destroyed sample producing audio",
295
+ ))
296
+
297
+ return CompiledPlan(
298
+ move_id=move.move_id,
299
+ intent=move.intent,
300
+ steps=steps,
301
+ risk_level="high",
302
+ summary="; ".join(descriptions),
303
+ requires_approval=True,
304
+ warnings=warnings,
305
+ )
306
+
307
+
308
+ def _compile_sample_one_shot_accent(move: SemanticMove, kernel: dict) -> CompiledPlan:
309
+ """Compile 'sample_one_shot_accent': load a one-shot for rhythmic punctuation."""
310
+ steps = []
311
+ descriptions = []
312
+
313
+ tracks = kernel.get("session_info", {}).get("tracks", [])
314
+ new_idx = len(tracks)
315
+
316
+ steps.append(CompiledStep(
317
+ tool="create_midi_track",
318
+ params={"name": "Accent"},
319
+ description="Create track for one-shot accent",
320
+ ))
321
+
322
+ steps.append(CompiledStep(
323
+ tool="load_sample_to_simpler",
324
+ params={"track_index": new_idx},
325
+ description="Load one-shot into Simpler",
326
+ ))
327
+
328
+ steps.append(CompiledStep(
329
+ tool="set_simpler_playback_mode",
330
+ params={"track_index": new_idx, "mode": "one_shot"},
331
+ description="One-shot mode for trigger playback",
332
+ ))
333
+ descriptions.append("One-shot mode")
334
+
335
+ steps.append(CompiledStep(
336
+ tool="crop_simpler",
337
+ params={"track_index": new_idx},
338
+ description="Tight crop around the transient",
339
+ ))
340
+ descriptions.append("Crop to transient")
341
+
342
+ # Accent should be punchy but not dominating
343
+ steps.append(CompiledStep(
344
+ tool="set_track_volume",
345
+ params={"track_index": new_idx, "volume": 0.60},
346
+ description="Set accent at punchy but balanced level",
347
+ ))
348
+
349
+ steps.append(CompiledStep(
350
+ tool="get_track_meters",
351
+ params={"include_stereo": True},
352
+ description="Verify one-shot accent triggers cleanly",
353
+ ))
354
+
355
+ return CompiledPlan(
356
+ move_id=move.move_id,
357
+ intent=move.intent,
358
+ steps=steps,
359
+ risk_level="low",
360
+ summary="; ".join(descriptions) if descriptions else "One-shot accent",
361
+ requires_approval=(kernel.get("mode", "improve") != "explore"),
362
+ )
363
+
364
+
365
+ # ── Register ────────────────────────────────────────────────────────────────
366
+
367
+ register_compiler("sample_chop_rhythm", _compile_sample_chop_rhythm)
368
+ register_compiler("sample_texture_layer", _compile_sample_texture_layer)
369
+ register_compiler("sample_vocal_ghost", _compile_sample_vocal_ghost)
370
+ register_compiler("sample_break_layer", _compile_sample_break_layer)
371
+ register_compiler("sample_resample_destroy", _compile_sample_resample_destroy)
372
+ register_compiler("sample_one_shot_accent", _compile_sample_one_shot_accent)
@@ -40,6 +40,40 @@ def _identify_port_holder(port: int) -> str | None:
40
40
  return None
41
41
 
42
42
 
43
+ def _master_has_livepilot_analyzer(ableton: AbletonConnection) -> bool:
44
+ """Check whether the analyzer device is currently on the master track."""
45
+ try:
46
+ track = ableton.send_command("get_master_track")
47
+ except Exception:
48
+ return False
49
+
50
+ devices = track.get("devices", []) if isinstance(track, dict) else []
51
+ for device in devices:
52
+ normalized = " ".join(
53
+ str(device.get("name") or "").replace("_", " ").replace("-", " ").lower().split()
54
+ )
55
+ if normalized == "livepilot analyzer":
56
+ return True
57
+ return False
58
+
59
+
60
+ async def _warm_analyzer_bridge(
61
+ ableton: AbletonConnection,
62
+ spectral: SpectralCache,
63
+ timeout: float = 3.0,
64
+ ) -> None:
65
+ """Give the analyzer stream a short startup window before first use."""
66
+ if not _master_has_livepilot_analyzer(ableton):
67
+ return
68
+
69
+ loop = asyncio.get_running_loop()
70
+ deadline = loop.time() + max(timeout, 0.0)
71
+ while loop.time() < deadline:
72
+ if spectral.is_connected:
73
+ return
74
+ await asyncio.sleep(0.05)
75
+
76
+
43
77
  @asynccontextmanager
44
78
  async def lifespan(server):
45
79
  """Create and yield the shared AbletonConnection + M4L bridge."""
@@ -80,6 +114,8 @@ async def lifespan(server):
80
114
  }
81
115
 
82
116
  try:
117
+ if bridge_state["transport"] is not None:
118
+ await _warm_analyzer_bridge(ableton, spectral)
83
119
  yield {
84
120
  "ableton": ableton,
85
121
  "spectral": spectral,
@@ -140,6 +176,10 @@ from .stuckness_detector import tools as stuckness_tools # noqa: F401, E40
140
176
  from .wonder_mode import tools as wonder_mode_tools # noqa: F401, E402
141
177
  from .session_continuity import tools as session_cont_tools # noqa: F401, E402
142
178
  from .creative_constraints import tools as constraints_tools # noqa: F401, E402
179
+ from .device_forge import tools as device_forge_tools # noqa: F401, E402
180
+ from .sample_engine import tools as sample_engine_tools # noqa: F401, E402
181
+ from .atlas import tools as atlas_tools # noqa: F401, E402
182
+ from .composer import tools as composer_tools # noqa: F401, E402
143
183
 
144
184
 
145
185
  # ---------------------------------------------------------------------------
@@ -170,6 +210,14 @@ def _coerce_schema_property(prop: dict) -> None:
170
210
  # Recurse into array items so list[int]/list[float] params also accept strings
171
211
  if "items" in prop and isinstance(prop["items"], dict):
172
212
  _coerce_schema_property(prop["items"])
213
+ if "properties" in prop and isinstance(prop["properties"], dict):
214
+ for nested in prop["properties"].values():
215
+ if isinstance(nested, dict):
216
+ _coerce_schema_property(nested)
217
+ if "$defs" in prop and isinstance(prop["$defs"], dict):
218
+ for nested in prop["$defs"].values():
219
+ if isinstance(nested, dict):
220
+ _coerce_schema_property(nested)
173
221
 
174
222
 
175
223
  def _get_all_tools():
@@ -202,6 +250,9 @@ def _patch_tool_schemas() -> None:
202
250
  if name == "ctx":
203
251
  continue # skip the Context parameter
204
252
  _coerce_schema_property(prop)
253
+ for definition in tool.parameters.get("$defs", {}).values():
254
+ if isinstance(definition, dict):
255
+ _coerce_schema_property(definition)
205
256
 
206
257
 
207
258
  _patch_tool_schemas()
@@ -281,17 +281,105 @@ def run_layer_overlap_critic(
281
281
  return issues
282
282
 
283
283
 
284
+ # ── Corpus Intelligence Critic ──────────────────────────────────────
285
+
286
+
287
+ def run_corpus_critic(
288
+ patch: PatchModel,
289
+ goal: TimbralGoalVector,
290
+ ) -> list[SoundDesignIssue]:
291
+ """Use the device-knowledge corpus to flag missed opportunities.
292
+
293
+ Checks each device in the chain against the corpus for known
294
+ techniques, parameter sweet spots, and creative possibilities
295
+ that the current patch doesn't exploit.
296
+ """
297
+ try:
298
+ from ..corpus import get_corpus
299
+ except ImportError:
300
+ return []
301
+
302
+ corpus = get_corpus()
303
+ if not corpus.emotional_recipes and not corpus.device_knowledge:
304
+ return [] # Corpus not loaded
305
+
306
+ issues: list[SoundDesignIssue] = []
307
+
308
+ # Check if any device in the chain has corpus knowledge
309
+ for block in patch.blocks:
310
+ dk = corpus.get_device(block.device_name)
311
+ if dk and dk.techniques and block.block_type == "oscillator":
312
+ # Oscillator with known techniques — suggest if patch is simple
313
+ has_character_block = any(
314
+ b.block_type in ("saturation", "spectral")
315
+ for b in patch.blocks
316
+ )
317
+ if not has_character_block and len(dk.techniques) > 2:
318
+ issues.append(SoundDesignIssue(
319
+ issue_type="corpus_technique_available",
320
+ critic="corpus",
321
+ severity=0.25,
322
+ confidence=0.6,
323
+ affected_blocks=[block.device_name],
324
+ evidence=(
325
+ f"Corpus has {len(dk.techniques)} known techniques "
326
+ f"for {block.device_name} but chain lacks character "
327
+ f"processing (saturation/spectral). First technique: "
328
+ f"{dk.techniques[0][:80]}"
329
+ ),
330
+ recommended_moves=["modulation_injection", "filter_contour"],
331
+ ))
332
+
333
+ # Check if goal maps to a known emotional recipe
334
+ emotion_map = {
335
+ "warmth": ("warmth", goal.warmth),
336
+ "brightness": ("euphoria", goal.brightness),
337
+ "instability": ("tension", goal.instability),
338
+ "softness": ("melancholy", goal.softness),
339
+ }
340
+ for quality, (emotion_key, goal_value) in emotion_map.items():
341
+ if goal_value > 0.3:
342
+ recipe = corpus.suggest_for_emotion(emotion_key)
343
+ if recipe and recipe.techniques:
344
+ # Check if any corpus technique device is in the chain
345
+ chain_names_lower = {d.lower() for d in patch.device_chain}
346
+ recipe_devices = set()
347
+ for tech in recipe.techniques:
348
+ # Extract bold device names from technique strings
349
+ for match in re.finditer(r"\*\*(.+?)\*\*", tech):
350
+ recipe_devices.add(match.group(1).lower())
351
+
352
+ missing = recipe_devices - chain_names_lower
353
+ if missing and len(missing) <= 3:
354
+ issues.append(SoundDesignIssue(
355
+ issue_type="corpus_emotion_opportunity",
356
+ critic="corpus",
357
+ severity=0.2,
358
+ confidence=0.5,
359
+ affected_blocks=list(missing)[:3],
360
+ evidence=(
361
+ f"Goal wants {quality}={goal_value:.2f}. "
362
+ f"Corpus '{recipe.emotion}' recipe suggests "
363
+ f"devices not in chain: {', '.join(list(missing)[:3])}"
364
+ ),
365
+ recommended_moves=["filter_contour", "modulation_injection"],
366
+ ))
367
+
368
+ return issues
369
+
370
+
284
371
  # ── Run all critics ──────────────────────────────────────────────────
285
372
 
286
373
 
287
374
  def run_all_sound_design_critics(
288
375
  state: SoundDesignState,
289
376
  ) -> list[SoundDesignIssue]:
290
- """Run all five critics and aggregate issues."""
377
+ """Run all six critics and aggregate issues."""
291
378
  issues: list[SoundDesignIssue] = []
292
379
  issues.extend(run_static_timbre_critic(state.patch, state.goal))
293
380
  issues.extend(run_weak_identity_critic(state.patch))
294
381
  issues.extend(run_masking_role_critic(state.patch, state.layers))
295
382
  issues.extend(run_modulation_flatness_critic(state.patch))
296
383
  issues.extend(run_layer_overlap_critic(state.layers))
384
+ issues.extend(run_corpus_critic(state.patch, state.goal))
297
385
  return issues
@@ -0,0 +1 @@
1
+ """Splice gRPC client — connect to Splice desktop's local API for sample search and download."""