livepilot 1.9.23 → 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 (191) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/AGENTS.md +3 -3
  3. package/CHANGELOG.md +119 -0
  4. package/CONTRIBUTING.md +1 -1
  5. package/README.md +144 -13
  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 +19 -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/hook_hunter/analyzer.py +23 -0
  124. package/mcp_server/hook_hunter/models.py +1 -0
  125. package/mcp_server/hook_hunter/tools.py +4 -2
  126. package/mcp_server/m4l_bridge.py +1 -0
  127. package/mcp_server/memory/taste_graph.py +68 -1
  128. package/mcp_server/memory/tools.py +15 -4
  129. package/mcp_server/musical_intelligence/detectors.py +14 -1
  130. package/mcp_server/musical_intelligence/tools.py +11 -8
  131. package/mcp_server/persistence/__init__.py +1 -0
  132. package/mcp_server/persistence/base_store.py +82 -0
  133. package/mcp_server/persistence/project_store.py +106 -0
  134. package/mcp_server/persistence/taste_store.py +122 -0
  135. package/mcp_server/preview_studio/models.py +1 -0
  136. package/mcp_server/preview_studio/tools.py +56 -13
  137. package/mcp_server/runtime/capability.py +66 -0
  138. package/mcp_server/runtime/capability_probe.py +137 -0
  139. package/mcp_server/runtime/execution_router.py +143 -0
  140. package/mcp_server/runtime/live_version.py +102 -0
  141. package/mcp_server/runtime/remote_commands.py +87 -0
  142. package/mcp_server/runtime/tools.py +18 -4
  143. package/mcp_server/sample_engine/__init__.py +1 -0
  144. package/mcp_server/sample_engine/analyzer.py +216 -0
  145. package/mcp_server/sample_engine/critics.py +390 -0
  146. package/mcp_server/sample_engine/models.py +193 -0
  147. package/mcp_server/sample_engine/moves.py +127 -0
  148. package/mcp_server/sample_engine/planner.py +186 -0
  149. package/mcp_server/sample_engine/sources.py +540 -0
  150. package/mcp_server/sample_engine/techniques.py +908 -0
  151. package/mcp_server/sample_engine/tools.py +442 -0
  152. package/mcp_server/semantic_moves/__init__.py +3 -0
  153. package/mcp_server/semantic_moves/device_creation_moves.py +237 -0
  154. package/mcp_server/semantic_moves/mix_moves.py +41 -41
  155. package/mcp_server/semantic_moves/performance_moves.py +13 -13
  156. package/mcp_server/semantic_moves/sample_compilers.py +372 -0
  157. package/mcp_server/semantic_moves/sound_design_moves.py +15 -15
  158. package/mcp_server/semantic_moves/tools.py +18 -17
  159. package/mcp_server/semantic_moves/transition_moves.py +16 -16
  160. package/mcp_server/server.py +51 -0
  161. package/mcp_server/services/__init__.py +1 -0
  162. package/mcp_server/services/motif_service.py +67 -0
  163. package/mcp_server/session_continuity/tracker.py +29 -1
  164. package/mcp_server/song_brain/builder.py +28 -1
  165. package/mcp_server/song_brain/models.py +4 -0
  166. package/mcp_server/song_brain/tools.py +20 -2
  167. package/mcp_server/sound_design/critics.py +89 -1
  168. package/mcp_server/splice_client/__init__.py +1 -0
  169. package/mcp_server/splice_client/client.py +347 -0
  170. package/mcp_server/splice_client/models.py +96 -0
  171. package/mcp_server/splice_client/protos/__init__.py +1 -0
  172. package/mcp_server/splice_client/protos/app_pb2.py +319 -0
  173. package/mcp_server/splice_client/protos/app_pb2.pyi +1153 -0
  174. package/mcp_server/splice_client/protos/app_pb2_grpc.py +1946 -0
  175. package/mcp_server/tools/arrangement.py +69 -0
  176. package/mcp_server/tools/automation.py +15 -2
  177. package/mcp_server/tools/devices.py +117 -6
  178. package/mcp_server/tools/notes.py +37 -4
  179. package/mcp_server/wonder_mode/diagnosis.py +5 -0
  180. package/mcp_server/wonder_mode/engine.py +85 -1
  181. package/mcp_server/wonder_mode/tools.py +6 -1
  182. package/package.json +12 -2
  183. package/remote_script/LivePilot/__init__.py +8 -1
  184. package/remote_script/LivePilot/arrangement.py +114 -0
  185. package/remote_script/LivePilot/browser.py +56 -1
  186. package/remote_script/LivePilot/devices.py +236 -6
  187. package/remote_script/LivePilot/mixing.py +8 -3
  188. package/remote_script/LivePilot/server.py +5 -1
  189. package/remote_script/LivePilot/transport.py +3 -0
  190. package/remote_script/LivePilot/version_detect.py +78 -0
  191. package/scripts/sync_metadata.py +132 -0
@@ -0,0 +1,483 @@
1
+ """gen~ DSP building block library — GenExpr code as reusable templates.
2
+
3
+ Each template is a complete, working gen~ codebox program with parameters.
4
+ Templates are organized by category and can be used directly with
5
+ generate_m4l_effect or as starting points for custom devices.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from .models import GenExprParam, GenExprTemplate, UNIT_STYLE_HERTZ, UNIT_STYLE_PERCENT, UNIT_STYLE_FLOAT
11
+
12
+ TEMPLATES: dict[str, GenExprTemplate] = {}
13
+
14
+
15
+ def _register(t: GenExprTemplate) -> GenExprTemplate:
16
+ TEMPLATES[t.template_id] = t
17
+ return t
18
+
19
+
20
+ def get_template(template_id: str) -> GenExprTemplate | None:
21
+ return TEMPLATES.get(template_id)
22
+
23
+
24
+ def list_templates(category: str = "") -> list[dict]:
25
+ items = list(TEMPLATES.values())
26
+ if category:
27
+ items = [t for t in items if t.category == category]
28
+ return [t.to_dict() for t in items]
29
+
30
+
31
+ def list_categories() -> list[str]:
32
+ return sorted({t.category for t in TEMPLATES.values()})
33
+
34
+
35
+ # ═══════════════════════════════════════════════════════════════════════
36
+ # UTILITY
37
+ # ═══════════════════════════════════════════════════════════════════════
38
+
39
+ _register(GenExprTemplate(
40
+ template_id="passthrough",
41
+ name="Passthrough",
42
+ description="Clean audio passthrough — useful as a starting skeleton",
43
+ category="utility",
44
+ code="out1 = in1;",
45
+ params=[],
46
+ ))
47
+
48
+ # ═══════════════════════════════════════════════════════════════════════
49
+ # CHAOS
50
+ # ═══════════════════════════════════════════════════════════════════════
51
+
52
+ _register(GenExprTemplate(
53
+ template_id="lorenz_attractor",
54
+ name="Lorenz Attractor",
55
+ description="Three coupled differential equations producing chaotic modulation. "
56
+ "Smooth but unpredictable — great for filter cutoff, pan, and send modulation.",
57
+ category="chaos",
58
+ code="""\
59
+ Param sigma(10);
60
+ Param rho(28);
61
+ Param beta(2.667);
62
+ Param speed(0.001);
63
+
64
+ History x(0.1);
65
+ History y(0);
66
+ History z(0);
67
+
68
+ dx = sigma * (y - x);
69
+ dy = x * (rho - z) - y;
70
+ dz = x * y - beta * z;
71
+
72
+ x += dx * speed;
73
+ y += dy * speed;
74
+ z += dz * speed;
75
+
76
+ out1 = x * 0.02;""",
77
+ params=[
78
+ GenExprParam(name="sigma", default=10, min_val=0.1, max_val=50),
79
+ GenExprParam(name="rho", default=28, min_val=0.1, max_val=100),
80
+ GenExprParam(name="beta", default=2.667, min_val=0.1, max_val=10),
81
+ GenExprParam(name="speed", default=0.001, min_val=0.0001, max_val=0.01),
82
+ ],
83
+ ))
84
+
85
+ _register(GenExprTemplate(
86
+ template_id="henon_map",
87
+ name="Henon Map",
88
+ description="Discrete chaotic map — produces noise textures at some parameters, "
89
+ "pitched tones at others. The edge-of-chaos zone is musically rich.",
90
+ category="chaos",
91
+ code="""\
92
+ Param a(1.4);
93
+ Param b(0.3);
94
+ Param speed(0.01);
95
+
96
+ History x(0.1);
97
+ History y(0);
98
+
99
+ nx = 1 - a * x * x + y;
100
+ ny = b * x;
101
+
102
+ x = nx;
103
+ y = ny;
104
+
105
+ out1 = x * speed;""",
106
+ params=[
107
+ GenExprParam(name="a", default=1.4, min_val=0.1, max_val=2.0),
108
+ GenExprParam(name="b", default=0.3, min_val=0.0, max_val=1.0),
109
+ GenExprParam(name="speed", default=0.01, min_val=0.001, max_val=0.1),
110
+ ],
111
+ ))
112
+
113
+ # ═══════════════════════════════════════════════════════════════════════
114
+ # SYNTHESIS
115
+ # ═══════════════════════════════════════════════════════════════════════
116
+
117
+ _register(GenExprTemplate(
118
+ template_id="karplus_strong",
119
+ name="Karplus-Strong String",
120
+ description="Plucked string physical model — noise burst excitation through "
121
+ "a filtered delay line. Classic digital waveguide synthesis.",
122
+ category="synthesis",
123
+ code="""\
124
+ Param freq(220);
125
+ Param damping(0.996);
126
+ Param brightness(0.5);
127
+
128
+ delaySamples = samplerate / max(freq, 20);
129
+ History h1(0);
130
+
131
+ trigger = in1 > 0.5;
132
+ excitation = noise() * trigger;
133
+
134
+ delayed = delay(h1, delaySamples);
135
+ filtered = delayed * brightness + h1 * (1 - brightness);
136
+ h1 = excitation + filtered * damping;
137
+
138
+ out1 = h1;""",
139
+ params=[
140
+ GenExprParam(name="freq", default=220, min_val=20, max_val=20000, unit_style=UNIT_STYLE_HERTZ),
141
+ GenExprParam(name="damping", default=0.996, min_val=0.9, max_val=0.9999),
142
+ GenExprParam(name="brightness", default=0.5, min_val=0.0, max_val=1.0),
143
+ ],
144
+ ))
145
+
146
+ _register(GenExprTemplate(
147
+ template_id="karplus_strong_reverb",
148
+ name="Karplus-Strong with Reverb Feedback",
149
+ description="KS variant with allpass diffusion in the feedback loop instead of simple "
150
+ "lowpass. Produces sustained, evolving, richly harmonic tones.",
151
+ category="synthesis",
152
+ code="""\
153
+ Param freq(220);
154
+ Param damping(0.995);
155
+ Param diffusion(0.5);
156
+
157
+ delaySamples = samplerate / max(freq, 20);
158
+ History h1(0);
159
+ History ap1(0);
160
+ History ap2(0);
161
+
162
+ trigger = in1 > 0.5;
163
+ excitation = noise() * trigger;
164
+
165
+ delayed = delay(h1, delaySamples);
166
+
167
+ // Two allpass stages for diffusion
168
+ ap1_in = delayed + ap1 * diffusion;
169
+ ap1_out = ap1 * -diffusion + delay(ap1_in, 37);
170
+ ap1 = ap1_in;
171
+
172
+ ap2_in = ap1_out + ap2 * diffusion;
173
+ ap2_out = ap2 * -diffusion + delay(ap2_in, 113);
174
+ ap2 = ap2_in;
175
+
176
+ filtered = (ap2_out + delayed) * 0.5;
177
+ h1 = excitation + filtered * damping;
178
+
179
+ out1 = h1;""",
180
+ params=[
181
+ GenExprParam(name="freq", default=220, min_val=20, max_val=20000, unit_style=UNIT_STYLE_HERTZ),
182
+ GenExprParam(name="damping", default=0.995, min_val=0.9, max_val=0.9999),
183
+ GenExprParam(name="diffusion", default=0.5, min_val=0.0, max_val=0.9),
184
+ ],
185
+ ))
186
+
187
+ _register(GenExprTemplate(
188
+ template_id="phase_distortion",
189
+ name="Phase Distortion Synth",
190
+ description="CZ-style phase distortion synthesis — a phasor's shape is warped "
191
+ "before reading a cosine table, creating rich timbral evolution.",
192
+ category="synthesis",
193
+ code="""\
194
+ Param freq(440);
195
+ Param distortion(0.5);
196
+
197
+ History phase(0);
198
+
199
+ phase += freq / samplerate;
200
+ phase = wrap(phase, 0, 1);
201
+
202
+ // Distort the phase curve
203
+ warped = phase + distortion * sin(phase * TWOPI) * 0.5;
204
+ warped = wrap(warped, 0, 1);
205
+
206
+ out1 = cos(warped * TWOPI) * 0.5;""",
207
+ params=[
208
+ GenExprParam(name="freq", default=440, min_val=20, max_val=20000, unit_style=UNIT_STYLE_HERTZ),
209
+ GenExprParam(name="distortion", default=0.5, min_val=0.0, max_val=1.0),
210
+ ],
211
+ ))
212
+
213
+ # ═══════════════════════════════════════════════════════════════════════
214
+ # DISTORTION
215
+ # ═══════════════════════════════════════════════════════════════════════
216
+
217
+ _register(GenExprTemplate(
218
+ template_id="wavefolder",
219
+ name="Wavefolder",
220
+ description="Buchla-style wavefolder — folds the waveform back on itself, "
221
+ "generating rich harmonic series. Multiple fold stages for complexity.",
222
+ category="distortion",
223
+ code="""\
224
+ Param drive(2);
225
+ Param symmetry(0.5);
226
+ Param mix(0.75);
227
+
228
+ input = in1 * drive;
229
+
230
+ // Three fold stages
231
+ folded = fold(input + symmetry - 0.5, -1, 1);
232
+ folded = fold(folded * 1.5, -1, 1);
233
+ folded = fold(folded * 1.2, -1, 1);
234
+
235
+ out1 = in1 * (1 - mix) + folded * mix;""",
236
+ params=[
237
+ GenExprParam(name="drive", default=2, min_val=1, max_val=20),
238
+ GenExprParam(name="symmetry", default=0.5, min_val=0.0, max_val=1.0),
239
+ GenExprParam(name="mix", default=0.75, min_val=0.0, max_val=1.0, unit_style=UNIT_STYLE_PERCENT),
240
+ ],
241
+ ))
242
+
243
+ _register(GenExprTemplate(
244
+ template_id="bitcrusher",
245
+ name="Bitcrusher",
246
+ description="Sample-rate and bit-depth reducer — from subtle aliasing to full "
247
+ "digital destruction. Modulate rate for glitch textures.",
248
+ category="distortion",
249
+ code="""\
250
+ Param rate(0.5);
251
+ Param bits(8);
252
+
253
+ // Sample rate reduction via sample-and-hold
254
+ reduced = latch(in1, phasor(samplerate * max(rate, 0.01)));
255
+
256
+ // Bit depth reduction
257
+ levels = pow(2, max(bits, 1));
258
+ crushed = floor(reduced * levels + 0.5) / levels;
259
+
260
+ out1 = crushed;""",
261
+ params=[
262
+ GenExprParam(name="rate", default=0.5, min_val=0.01, max_val=1.0),
263
+ GenExprParam(name="bits", default=8, min_val=1, max_val=16),
264
+ ],
265
+ ))
266
+
267
+ # ═══════════════════════════════════════════════════════════════════════
268
+ # MODULATION
269
+ # ═══════════════════════════════════════════════════════════════════════
270
+
271
+ _register(GenExprTemplate(
272
+ template_id="ring_modulator",
273
+ name="Ring Modulator",
274
+ description="Classic ring modulation — input multiplied by a carrier oscillator. "
275
+ "Use sub-audio rates for tremolo, audio rates for metallic sidebands.",
276
+ category="modulation",
277
+ code="""\
278
+ Param freq(100);
279
+ Param depth(1.0);
280
+ Param mix(0.5);
281
+
282
+ carrier = cycle(freq);
283
+ modulated = in1 * (1 - depth + depth * carrier);
284
+
285
+ out1 = in1 * (1 - mix) + modulated * mix;""",
286
+ params=[
287
+ GenExprParam(name="freq", default=100, min_val=0.1, max_val=20000, unit_style=UNIT_STYLE_HERTZ),
288
+ GenExprParam(name="depth", default=1.0, min_val=0.0, max_val=1.0),
289
+ GenExprParam(name="mix", default=0.5, min_val=0.0, max_val=1.0, unit_style=UNIT_STYLE_PERCENT),
290
+ ],
291
+ ))
292
+
293
+ _register(GenExprTemplate(
294
+ template_id="chorus",
295
+ name="Chorus",
296
+ description="Multi-voice micro-delay chorus — three slightly detuned delay taps "
297
+ "create width and shimmer without muddiness.",
298
+ category="modulation",
299
+ code="""\
300
+ Param rate(0.5);
301
+ Param depth(0.003);
302
+ Param mix(0.5);
303
+
304
+ base_delay = 0.01 * samplerate;
305
+ mod1 = cycle(rate) * depth * samplerate;
306
+ mod2 = cycle(rate * 1.1) * depth * samplerate * 0.8;
307
+ mod3 = cycle(rate * 0.9) * depth * samplerate * 1.2;
308
+
309
+ d1 = delay(in1, base_delay + mod1);
310
+ d2 = delay(in1, base_delay + mod2);
311
+ d3 = delay(in1, base_delay + mod3);
312
+
313
+ wet = (d1 + d2 + d3) / 3;
314
+
315
+ out1 = in1 * (1 - mix) + wet * mix;""",
316
+ params=[
317
+ GenExprParam(name="rate", default=0.5, min_val=0.05, max_val=5, unit_style=UNIT_STYLE_HERTZ),
318
+ GenExprParam(name="depth", default=0.003, min_val=0.0, max_val=0.02),
319
+ GenExprParam(name="mix", default=0.5, min_val=0.0, max_val=1.0, unit_style=UNIT_STYLE_PERCENT),
320
+ ],
321
+ ))
322
+
323
+ # ═══════════════════════════════════════════════════════════════════════
324
+ # DELAY
325
+ # ═══════════════════════════════════════════════════════════════════════
326
+
327
+ _register(GenExprTemplate(
328
+ template_id="feedback_delay",
329
+ name="Feedback Delay",
330
+ description="Simple delay line with filtered feedback — the backbone of "
331
+ "echo effects, dub delays, and ambient washes.",
332
+ category="delay",
333
+ code="""\
334
+ Param time(0.25);
335
+ Param feedback(0.5);
336
+ Param damping(0.7);
337
+
338
+ History prev_out(0);
339
+
340
+ delay_samples = max(time, 0.001) * samplerate;
341
+ delayed = delay(in1 + prev_out * feedback, delay_samples);
342
+
343
+ // One-pole lowpass in feedback path
344
+ filtered = delayed * damping + prev_out * (1 - damping);
345
+ prev_out = filtered;
346
+
347
+ out1 = in1 * 0.5 + filtered * 0.5;""",
348
+ params=[
349
+ GenExprParam(name="time", default=0.25, min_val=0.001, max_val=2.0),
350
+ GenExprParam(name="feedback", default=0.5, min_val=0.0, max_val=0.95),
351
+ GenExprParam(name="damping", default=0.7, min_val=0.0, max_val=1.0),
352
+ ],
353
+ ))
354
+
355
+ _register(GenExprTemplate(
356
+ template_id="feedback_delay_network",
357
+ name="Feedback Delay Network",
358
+ description="4-line FDN with Hadamard-like cross-coupling matrix. Creates dense, "
359
+ "diffuse reverb tails. Tune delay times to musical intervals for harmonic reverb.",
360
+ category="delay",
361
+ code="""\
362
+ Param size(0.05);
363
+ Param feedback(0.7);
364
+ Param damping(0.8);
365
+
366
+ History a(0);
367
+ History b(0);
368
+ History c(0);
369
+ History d(0);
370
+
371
+ base = max(size, 0.001) * samplerate;
372
+ da = delay(a, base * 1.0);
373
+ db = delay(b, base * 1.347);
374
+ dc = delay(c, base * 1.573);
375
+ dd = delay(d, base * 1.811);
376
+
377
+ // Hadamard-like mixing matrix (preserves energy)
378
+ na = ( da + db + dc + dd) * 0.5;
379
+ nb = ( da - db + dc - dd) * 0.5;
380
+ nc = ( da + db - dc - dd) * 0.5;
381
+ nd = ( da - db - dc + dd) * 0.5;
382
+
383
+ // Damping + feedback + input injection
384
+ a = (na * damping) * feedback + in1;
385
+ b = (nb * damping) * feedback;
386
+ c = (nc * damping) * feedback;
387
+ d = (nd * damping) * feedback;
388
+
389
+ out1 = (da + db + dc + dd) * 0.25;""",
390
+ params=[
391
+ GenExprParam(name="size", default=0.05, min_val=0.001, max_val=0.5),
392
+ GenExprParam(name="feedback", default=0.7, min_val=0.0, max_val=0.95),
393
+ GenExprParam(name="damping", default=0.8, min_val=0.0, max_val=1.0),
394
+ ],
395
+ ))
396
+
397
+ _register(GenExprTemplate(
398
+ template_id="granular_delay",
399
+ name="Granular Delay",
400
+ description="Buffer-based granular delay — reads from a circular delay buffer "
401
+ "at variable positions and grain sizes for textural delays.",
402
+ category="delay",
403
+ code="""\
404
+ Param position(0.5);
405
+ Param grain_size(0.05);
406
+ Param density(10);
407
+ Param mix(0.5);
408
+
409
+ grain_phase = phasor(density);
410
+ window = sin(grain_phase * 3.14159);
411
+
412
+ delay_samples = position * samplerate;
413
+ grain_samples = max(grain_size, 0.001) * samplerate;
414
+ read_pos = delay_samples + grain_phase * grain_samples;
415
+
416
+ wet = delay(in1, max(read_pos, 1)) * window;
417
+
418
+ out1 = in1 * (1 - mix) + wet * mix;""",
419
+ params=[
420
+ GenExprParam(name="position", default=0.5, min_val=0.0, max_val=2.0),
421
+ GenExprParam(name="grain_size", default=0.05, min_val=0.001, max_val=0.5),
422
+ GenExprParam(name="density", default=10, min_val=1, max_val=100),
423
+ GenExprParam(name="mix", default=0.5, min_val=0.0, max_val=1.0, unit_style=UNIT_STYLE_PERCENT),
424
+ ],
425
+ ))
426
+
427
+ # ═══════════════════════════════════════════════════════════════════════
428
+ # FILTER
429
+ # ═══════════════════════════════════════════════════════════════════════
430
+
431
+ _register(GenExprTemplate(
432
+ template_id="resonator",
433
+ name="Comb Resonator",
434
+ description="Tuned comb filter that resonates at a specific frequency — "
435
+ "feed it noise for metallic tones, or audio for pitched resonance.",
436
+ category="filter",
437
+ code="""\
438
+ Param freq(440);
439
+ Param resonance(0.9);
440
+ Param mix(0.5);
441
+
442
+ History fb(0);
443
+
444
+ delay_samples = samplerate / max(freq, 20);
445
+ delayed = delay(fb, delay_samples);
446
+ fb = in1 + delayed * min(resonance, 0.999);
447
+
448
+ out1 = in1 * (1 - mix) + fb * mix;""",
449
+ params=[
450
+ GenExprParam(name="freq", default=440, min_val=20, max_val=20000, unit_style=UNIT_STYLE_HERTZ),
451
+ GenExprParam(name="resonance", default=0.9, min_val=0.0, max_val=0.999),
452
+ GenExprParam(name="mix", default=0.5, min_val=0.0, max_val=1.0, unit_style=UNIT_STYLE_PERCENT),
453
+ ],
454
+ ))
455
+
456
+ # ═══════════════════════════════════════════════════════════════════════
457
+ # TEXTURE
458
+ # ═══════════════════════════════════════════════════════════════════════
459
+
460
+ _register(GenExprTemplate(
461
+ template_id="stochastic_resonance",
462
+ name="Stochastic Resonance",
463
+ description="Noise + threshold + feedback system — adding controlled noise to a "
464
+ "nonlinear threshold creates granular textures and organic movement.",
465
+ category="texture",
466
+ code="""\
467
+ Param threshold(0.3);
468
+ Param noise_amount(0.2);
469
+ Param feedback(0.5);
470
+
471
+ History state(0);
472
+
473
+ noisy = in1 + noise() * noise_amount + state * feedback;
474
+ triggered = noisy > threshold;
475
+ state = triggered * 0.8 + state * 0.2;
476
+
477
+ out1 = (triggered * 2 - 1) * 0.5;""",
478
+ params=[
479
+ GenExprParam(name="threshold", default=0.3, min_val=0.0, max_val=1.0),
480
+ GenExprParam(name="noise_amount", default=0.2, min_val=0.0, max_val=1.0),
481
+ GenExprParam(name="feedback", default=0.5, min_val=0.0, max_val=0.95),
482
+ ],
483
+ ))
@@ -0,0 +1,162 @@
1
+ """Device Forge MCP tools — generate M4L devices from gen~ code.
2
+
3
+ 3 tools for the device_forge domain.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import tempfile
9
+ from pathlib import Path
10
+
11
+ from fastmcp import Context
12
+
13
+ from ..server import mcp
14
+ from .models import DeviceSpec, DeviceType, GenExprParam
15
+ from .builder import build_device, ABLETON_USER_LIBRARY, _SUBDIR_MAP
16
+ from .templates import get_template, list_templates, list_categories
17
+
18
+
19
+ @mcp.tool()
20
+ async def generate_m4l_effect(
21
+ ctx: Context,
22
+ name: str,
23
+ gen_code: str,
24
+ description: str = "Generated by LivePilot Device Forge",
25
+ device_type: str = "audio_effect",
26
+ params: list[dict] | None = None,
27
+ install: bool = True,
28
+ ) -> dict:
29
+ """Generate a Max for Live device from gen~ codebox code.
30
+
31
+ The gen_code parameter accepts GenExpr DSP code that will be compiled
32
+ at runtime by Max's gen~ engine. Safety clipping is automatically added.
33
+
34
+ Args:
35
+ name: Device name (used for filename and display)
36
+ gen_code: GenExpr source code for the gen~ codebox
37
+ description: Device description
38
+ device_type: "audio_effect", "midi_effect", "instrument", "midi_generator", or "midi_transformation"
39
+ params: List of parameter dicts with keys: name, default, min_val, max_val, unit_style
40
+ install: If True, copy to Ableton User Library automatically
41
+ """
42
+ type_map = {
43
+ "audio_effect": DeviceType.AUDIO_EFFECT,
44
+ "midi_effect": DeviceType.MIDI_EFFECT,
45
+ "instrument": DeviceType.INSTRUMENT,
46
+ "midi_generator": DeviceType.MIDI_GENERATOR,
47
+ "midi_transformation": DeviceType.MIDI_TRANSFORMATION,
48
+ }
49
+ dt = type_map.get(device_type)
50
+ if dt is None:
51
+ return {"error": f"Invalid device_type: {device_type}. Use: audio_effect, midi_effect, instrument"}
52
+
53
+ parsed_params = []
54
+ if params:
55
+ for p in params:
56
+ parsed_params.append(GenExprParam(
57
+ name=p["name"],
58
+ default=p.get("default", 0.5),
59
+ min_val=p.get("min_val", 0.0),
60
+ max_val=p.get("max_val", 1.0),
61
+ unit_style=p.get("unit_style", 1),
62
+ ))
63
+
64
+ spec = DeviceSpec(
65
+ name=name,
66
+ device_type=dt,
67
+ gen_code=gen_code,
68
+ description=description,
69
+ params=parsed_params,
70
+ )
71
+
72
+ if install:
73
+ subdir = _SUBDIR_MAP[dt]
74
+ output_dir = ABLETON_USER_LIBRARY / subdir
75
+ path = build_device(spec, output_dir=output_dir)
76
+ else:
77
+ path = build_device(spec, output_dir=tempfile.mkdtemp(prefix="livepilot_forge_"))
78
+
79
+ return {
80
+ "status": "created",
81
+ "name": spec.name,
82
+ "filename": spec.safe_filename,
83
+ "path": str(path),
84
+ "device_type": device_type,
85
+ "installed": install,
86
+ "param_count": len(parsed_params),
87
+ "hint": "Device will appear in Ableton's browser. Use find_and_load_device to load it onto a track.",
88
+ }
89
+
90
+
91
+ @mcp.tool()
92
+ async def list_genexpr_templates(
93
+ ctx: Context,
94
+ category: str = "",
95
+ ) -> dict:
96
+ """List available gen~ DSP building block templates.
97
+
98
+ Templates are pre-built GenExpr algorithms that can be used directly
99
+ or as starting points for custom devices. Each template has working
100
+ code, parameters, and descriptions.
101
+
102
+ Args:
103
+ category: Filter by category (chaos, delay, distortion, filter, modulation, synthesis, texture, utility). Empty = all.
104
+ """
105
+ templates = list_templates(category=category)
106
+ categories = list_categories()
107
+
108
+ return {
109
+ "templates": templates,
110
+ "count": len(templates),
111
+ "categories": sorted(categories),
112
+ "hint": "Use generate_m4l_effect with a template's code to create a device, or write custom GenExpr.",
113
+ }
114
+
115
+
116
+ @mcp.tool()
117
+ async def install_m4l_device(
118
+ ctx: Context,
119
+ source_path: str,
120
+ ) -> dict:
121
+ """Copy a .amxd file to Ableton's User Library.
122
+
123
+ Args:
124
+ source_path: Path to the .amxd file to install
125
+ """
126
+ src = Path(source_path)
127
+ if not src.exists():
128
+ return {"error": f"File not found: {source_path}"}
129
+ if src.suffix != ".amxd":
130
+ return {"error": f"Not an .amxd file: {source_path}"}
131
+
132
+ data = src.read_bytes()
133
+ if len(data) < 12 or data[:4] != b"ampf":
134
+ return {"error": "Invalid .amxd file — missing ampf header"}
135
+
136
+ marker = data[8:12]
137
+ type_map = {
138
+ b"aaaa": (DeviceType.AUDIO_EFFECT, "audio_effect"),
139
+ b"mmmm": (DeviceType.MIDI_EFFECT, "midi_effect"),
140
+ b"iiii": (DeviceType.INSTRUMENT, "instrument"),
141
+ b"nagg": (DeviceType.MIDI_GENERATOR, "midi_generator"),
142
+ b"natt": (DeviceType.MIDI_TRANSFORMATION, "midi_transformation"),
143
+ }
144
+ dt_info = type_map.get(marker)
145
+ if dt_info is None:
146
+ return {"error": f"Unknown device type marker: {marker}"}
147
+
148
+ dt, type_name = dt_info
149
+ subdir = _SUBDIR_MAP[dt]
150
+ dest_dir = ABLETON_USER_LIBRARY / subdir
151
+ dest_dir.mkdir(parents=True, exist_ok=True)
152
+
153
+ dest = dest_dir / src.name
154
+ dest.write_bytes(data)
155
+
156
+ return {
157
+ "status": "installed",
158
+ "filename": src.name,
159
+ "device_type": type_name,
160
+ "path": str(dest),
161
+ "hint": "Device now available in Ableton's browser.",
162
+ }
@@ -82,9 +82,32 @@ def find_hook_candidates(
82
82
  development_potential=0.5,
83
83
  ))
84
84
 
85
+ # 4. Section-placement analysis: boost hooks that appear in payoff sections
86
+ payoff_sections = {
87
+ s.get("label", "").lower()
88
+ for s in (composition.get("sections", []) if composition else [])
89
+ if s.get("is_payoff")
90
+ } or {"chorus", "drop", "hook"}
91
+
92
+ for c in candidates:
93
+ # Check if hook is present in payoff sections (via motif locations)
94
+ if c.hook_type == "melodic" and motif_data:
95
+ for motif in motif_data.get("motifs", []):
96
+ if motif.get("name", "") in c.hook_id:
97
+ # Motif with high recurrence across sections = stronger hook
98
+ c.memorability = min(1.0, c.memorability + motif.get("recurrence", 0) * 0.2)
99
+
85
100
  # Score all candidates
86
101
  for c in candidates:
87
102
  c.salience = _compute_salience(c)
103
+ # Add evidence sources
104
+ c.evidence_sources = []
105
+ if "motif_" in c.hook_id:
106
+ c.evidence_sources.append("motif_recurrence")
107
+ if "track_" in c.hook_id:
108
+ c.evidence_sources.append("track_name")
109
+ if "groove" in c.hook_id:
110
+ c.evidence_sources.append("clip_reuse")
88
111
 
89
112
  # Sort by salience
90
113
  candidates.sort(key=lambda c: c.salience, reverse=True)
@@ -19,6 +19,7 @@ class HookCandidate:
19
19
  contrast_potential: float = 0.0 # 0-1 how well it stands out
20
20
  development_potential: float = 0.0 # 0-1 how much room to develop
21
21
  salience: float = 0.0 # composite score
22
+ evidence_sources: list[str] = field(default_factory=list) # what data informed this
22
23
 
23
24
  def to_dict(self) -> dict:
24
25
  return asdict(self)
@@ -53,9 +53,11 @@ def _fetch_tracks_and_scenes(ctx: Context) -> tuple[list[dict], list[dict], dict
53
53
  except Exception:
54
54
  pass
55
55
 
56
- # Fetch motif data from the motif engine for salience-based hook discovery
56
+ # Fetch motif data via shared motif service
57
57
  try:
58
- motif_data = ableton.send_command("get_motif_graph")
58
+ from ..services.motif_service import get_motif_data, fetch_notes_from_ableton
59
+ notes_by_track = fetch_notes_from_ableton(ableton, tracks)
60
+ motif_data = get_motif_data(notes_by_track)
59
61
  except Exception:
60
62
  pass # Motif graph requires notes in clips; empty dict is valid fallback
61
63