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.
- package/.claude-plugin/marketplace.json +3 -3
- package/AGENTS.md +3 -3
- package/CHANGELOG.md +73 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +56 -19
- package/bin/livepilot.js +87 -0
- package/installer/codex.js +147 -0
- package/livepilot/.Codex-plugin/plugin.json +2 -2
- package/livepilot/.claude-plugin/plugin.json +2 -2
- package/livepilot/skills/livepilot-core/SKILL.md +21 -4
- package/livepilot/skills/livepilot-core/references/device-knowledge/00-index.md +34 -0
- package/livepilot/skills/livepilot-core/references/device-knowledge/automation-as-music.md +204 -0
- package/livepilot/skills/livepilot-core/references/device-knowledge/chains-genre.md +173 -0
- package/livepilot/skills/livepilot-core/references/device-knowledge/creative-thinking.md +211 -0
- package/livepilot/skills/livepilot-core/references/device-knowledge/effects-distortion.md +188 -0
- package/livepilot/skills/livepilot-core/references/device-knowledge/effects-space.md +162 -0
- package/livepilot/skills/livepilot-core/references/device-knowledge/effects-spectral.md +229 -0
- package/livepilot/skills/livepilot-core/references/device-knowledge/instruments-synths.md +243 -0
- package/livepilot/skills/livepilot-core/references/overview.md +13 -9
- package/livepilot/skills/livepilot-core/references/sample-manipulation.md +724 -0
- package/livepilot/skills/livepilot-core/references/sound-design-deep.md +140 -0
- package/livepilot/skills/livepilot-devices/SKILL.md +16 -2
- package/livepilot/skills/livepilot-evaluation/references/capability-modes.md +1 -1
- package/livepilot/skills/livepilot-release/SKILL.md +5 -5
- package/livepilot/skills/livepilot-sample-engine/SKILL.md +104 -0
- package/livepilot/skills/livepilot-sample-engine/references/sample-critics.md +87 -0
- package/livepilot/skills/livepilot-sample-engine/references/sample-philosophy.md +51 -0
- package/livepilot/skills/livepilot-sample-engine/references/sample-techniques.md +131 -0
- package/livepilot/skills/livepilot-sound-design-engine/SKILL.md +45 -0
- package/livepilot/skills/livepilot-wonder/SKILL.md +15 -0
- package/livepilot.mcpb +0 -0
- package/m4l_device/livepilot_bridge.js +1 -1
- package/manifest.json +2 -2
- package/mcp_server/__init__.py +1 -1
- package/mcp_server/atlas/__init__.py +357 -0
- package/mcp_server/atlas/device_atlas.json +44067 -0
- package/mcp_server/atlas/enrichments/__init__.py +111 -0
- package/mcp_server/atlas/enrichments/audio_effects/auto_filter.yaml +162 -0
- package/mcp_server/atlas/enrichments/audio_effects/beat_repeat.yaml +183 -0
- package/mcp_server/atlas/enrichments/audio_effects/channel_eq.yaml +126 -0
- package/mcp_server/atlas/enrichments/audio_effects/chorus_ensemble.yaml +149 -0
- package/mcp_server/atlas/enrichments/audio_effects/color_limiter.yaml +109 -0
- package/mcp_server/atlas/enrichments/audio_effects/compressor.yaml +159 -0
- package/mcp_server/atlas/enrichments/audio_effects/convolution_reverb.yaml +143 -0
- package/mcp_server/atlas/enrichments/audio_effects/convolution_reverb_pro.yaml +178 -0
- package/mcp_server/atlas/enrichments/audio_effects/delay.yaml +151 -0
- package/mcp_server/atlas/enrichments/audio_effects/drum_buss.yaml +142 -0
- package/mcp_server/atlas/enrichments/audio_effects/dynamic_tube.yaml +147 -0
- package/mcp_server/atlas/enrichments/audio_effects/echo.yaml +167 -0
- package/mcp_server/atlas/enrichments/audio_effects/eq_eight.yaml +148 -0
- package/mcp_server/atlas/enrichments/audio_effects/eq_three.yaml +121 -0
- package/mcp_server/atlas/enrichments/audio_effects/erosion.yaml +103 -0
- package/mcp_server/atlas/enrichments/audio_effects/filter_delay.yaml +173 -0
- package/mcp_server/atlas/enrichments/audio_effects/gate.yaml +130 -0
- package/mcp_server/atlas/enrichments/audio_effects/gated_delay.yaml +133 -0
- package/mcp_server/atlas/enrichments/audio_effects/glue_compressor.yaml +142 -0
- package/mcp_server/atlas/enrichments/audio_effects/grain_delay.yaml +141 -0
- package/mcp_server/atlas/enrichments/audio_effects/hybrid_reverb.yaml +160 -0
- package/mcp_server/atlas/enrichments/audio_effects/limiter.yaml +97 -0
- package/mcp_server/atlas/enrichments/audio_effects/multiband_dynamics.yaml +174 -0
- package/mcp_server/atlas/enrichments/audio_effects/overdrive.yaml +119 -0
- package/mcp_server/atlas/enrichments/audio_effects/pedal.yaml +145 -0
- package/mcp_server/atlas/enrichments/audio_effects/phaser_flanger.yaml +161 -0
- package/mcp_server/atlas/enrichments/audio_effects/redux.yaml +114 -0
- package/mcp_server/atlas/enrichments/audio_effects/reverb.yaml +190 -0
- package/mcp_server/atlas/enrichments/audio_effects/roar.yaml +159 -0
- package/mcp_server/atlas/enrichments/audio_effects/saturator.yaml +146 -0
- package/mcp_server/atlas/enrichments/audio_effects/shifter.yaml +154 -0
- package/mcp_server/atlas/enrichments/audio_effects/spectral_resonator.yaml +141 -0
- package/mcp_server/atlas/enrichments/audio_effects/spectral_time.yaml +164 -0
- package/mcp_server/atlas/enrichments/audio_effects/vector_delay.yaml +140 -0
- package/mcp_server/atlas/enrichments/audio_effects/vinyl_distortion.yaml +141 -0
- package/mcp_server/atlas/enrichments/instruments/analog.yaml +222 -0
- package/mcp_server/atlas/enrichments/instruments/bass.yaml +202 -0
- package/mcp_server/atlas/enrichments/instruments/collision.yaml +150 -0
- package/mcp_server/atlas/enrichments/instruments/drift.yaml +167 -0
- package/mcp_server/atlas/enrichments/instruments/electric.yaml +137 -0
- package/mcp_server/atlas/enrichments/instruments/emit.yaml +163 -0
- package/mcp_server/atlas/enrichments/instruments/meld.yaml +164 -0
- package/mcp_server/atlas/enrichments/instruments/operator.yaml +197 -0
- package/mcp_server/atlas/enrichments/instruments/poli.yaml +192 -0
- package/mcp_server/atlas/enrichments/instruments/sampler.yaml +218 -0
- package/mcp_server/atlas/enrichments/instruments/simpler.yaml +217 -0
- package/mcp_server/atlas/enrichments/instruments/tension.yaml +156 -0
- package/mcp_server/atlas/enrichments/instruments/tree_tone.yaml +162 -0
- package/mcp_server/atlas/enrichments/instruments/vector_fm.yaml +165 -0
- package/mcp_server/atlas/enrichments/instruments/vector_grain.yaml +166 -0
- package/mcp_server/atlas/enrichments/instruments/wavetable.yaml +162 -0
- package/mcp_server/atlas/enrichments/midi_effects/arpeggiator.yaml +156 -0
- package/mcp_server/atlas/enrichments/midi_effects/bouncy_notes.yaml +93 -0
- package/mcp_server/atlas/enrichments/midi_effects/chord.yaml +147 -0
- package/mcp_server/atlas/enrichments/midi_effects/melodic_steps.yaml +97 -0
- package/mcp_server/atlas/enrichments/midi_effects/note_echo.yaml +108 -0
- package/mcp_server/atlas/enrichments/midi_effects/note_length.yaml +97 -0
- package/mcp_server/atlas/enrichments/midi_effects/pitch.yaml +76 -0
- package/mcp_server/atlas/enrichments/midi_effects/random.yaml +117 -0
- package/mcp_server/atlas/enrichments/midi_effects/rhythmic_steps.yaml +103 -0
- package/mcp_server/atlas/enrichments/midi_effects/scale.yaml +83 -0
- package/mcp_server/atlas/enrichments/midi_effects/step_arp.yaml +112 -0
- package/mcp_server/atlas/enrichments/midi_effects/velocity.yaml +119 -0
- package/mcp_server/atlas/enrichments/utility/amp.yaml +159 -0
- package/mcp_server/atlas/enrichments/utility/cabinet.yaml +109 -0
- package/mcp_server/atlas/enrichments/utility/corpus.yaml +150 -0
- package/mcp_server/atlas/enrichments/utility/resonators.yaml +131 -0
- package/mcp_server/atlas/enrichments/utility/spectrum.yaml +63 -0
- package/mcp_server/atlas/enrichments/utility/tuner.yaml +51 -0
- package/mcp_server/atlas/enrichments/utility/utility.yaml +136 -0
- package/mcp_server/atlas/enrichments/utility/vocoder.yaml +160 -0
- package/mcp_server/atlas/scanner.py +236 -0
- package/mcp_server/atlas/tools.py +224 -0
- package/mcp_server/composer/__init__.py +1 -0
- package/mcp_server/composer/engine.py +452 -0
- package/mcp_server/composer/layer_planner.py +427 -0
- package/mcp_server/composer/prompt_parser.py +329 -0
- package/mcp_server/composer/tools.py +201 -0
- package/mcp_server/connection.py +53 -8
- package/mcp_server/corpus/__init__.py +377 -0
- package/mcp_server/device_forge/__init__.py +1 -0
- package/mcp_server/device_forge/builder.py +377 -0
- package/mcp_server/device_forge/models.py +142 -0
- package/mcp_server/device_forge/templates.py +483 -0
- package/mcp_server/device_forge/tools.py +162 -0
- package/mcp_server/m4l_bridge.py +1 -0
- package/mcp_server/preview_studio/tools.py +4 -4
- package/mcp_server/runtime/capability_probe.py +21 -2
- package/mcp_server/runtime/execution_router.py +4 -0
- package/mcp_server/runtime/live_version.py +102 -0
- package/mcp_server/runtime/remote_commands.py +9 -4
- package/mcp_server/runtime/tools.py +18 -4
- package/mcp_server/sample_engine/__init__.py +1 -0
- package/mcp_server/sample_engine/analyzer.py +216 -0
- package/mcp_server/sample_engine/critics.py +390 -0
- package/mcp_server/sample_engine/models.py +193 -0
- package/mcp_server/sample_engine/moves.py +127 -0
- package/mcp_server/sample_engine/planner.py +186 -0
- package/mcp_server/sample_engine/sources.py +540 -0
- package/mcp_server/sample_engine/techniques.py +908 -0
- package/mcp_server/sample_engine/tools.py +442 -0
- package/mcp_server/semantic_moves/__init__.py +3 -0
- package/mcp_server/semantic_moves/device_creation_moves.py +237 -0
- package/mcp_server/semantic_moves/sample_compilers.py +372 -0
- package/mcp_server/server.py +51 -0
- package/mcp_server/sound_design/critics.py +89 -1
- package/mcp_server/splice_client/__init__.py +1 -0
- package/mcp_server/splice_client/client.py +347 -0
- package/mcp_server/splice_client/models.py +96 -0
- package/mcp_server/splice_client/protos/__init__.py +1 -0
- package/mcp_server/splice_client/protos/app_pb2.py +319 -0
- package/mcp_server/splice_client/protos/app_pb2.pyi +1153 -0
- package/mcp_server/splice_client/protos/app_pb2_grpc.py +1946 -0
- package/mcp_server/tools/arrangement.py +69 -0
- package/mcp_server/tools/automation.py +15 -2
- package/mcp_server/tools/devices.py +117 -6
- package/mcp_server/tools/notes.py +37 -4
- package/mcp_server/wonder_mode/diagnosis.py +5 -0
- package/mcp_server/wonder_mode/engine.py +85 -1
- package/package.json +12 -2
- package/remote_script/LivePilot/__init__.py +8 -1
- package/remote_script/LivePilot/arrangement.py +114 -0
- package/remote_script/LivePilot/browser.py +56 -1
- package/remote_script/LivePilot/devices.py +236 -6
- package/remote_script/LivePilot/mixing.py +8 -3
- package/remote_script/LivePilot/server.py +5 -1
- package/remote_script/LivePilot/transport.py +3 -0
- 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)
|
package/mcp_server/server.py
CHANGED
|
@@ -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
|
|
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."""
|