livepilot 1.13.0 → 1.14.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/CHANGELOG.md +137 -0
- package/README.md +6 -6
- package/m4l_device/LivePilot_Analyzer.amxd +0 -0
- package/m4l_device/livepilot_bridge.js +1 -1
- package/mcp_server/__init__.py +1 -1
- package/mcp_server/branches/__init__.py +2 -0
- package/mcp_server/branches/types.py +57 -1
- package/mcp_server/composer/__init__.py +2 -2
- package/mcp_server/composer/branch_producer.py +120 -0
- package/mcp_server/composer/tools.py +58 -1
- package/mcp_server/evaluation/policy.py +98 -0
- package/mcp_server/experiment/models.py +40 -1
- package/mcp_server/experiment/tools.py +283 -15
- package/mcp_server/server.py +1 -0
- package/mcp_server/synthesis_brain/adapters/analog.py +158 -52
- package/mcp_server/synthesis_brain/adapters/drift.py +156 -51
- package/mcp_server/synthesis_brain/adapters/meld.py +150 -40
- package/mcp_server/synthesis_brain/adapters/operator.py +137 -14
- package/mcp_server/synthesis_brain/adapters/wavetable.py +156 -20
- package/mcp_server/synthesis_brain/tools.py +231 -0
- package/package.json +2 -2
- package/remote_script/LivePilot/__init__.py +1 -1
- package/server.json +3 -3
|
@@ -104,62 +104,168 @@ class AnalogAdapter:
|
|
|
104
104
|
target: TimbralFingerprint,
|
|
105
105
|
kernel: Optional[dict] = None,
|
|
106
106
|
) -> list[tuple[BranchSeed, dict]]:
|
|
107
|
+
# Strategy registry — each candidate strategy's ``applicable()``
|
|
108
|
+
# gates on profile+role+target, and ``build()`` emits (seed, plan).
|
|
109
|
+
# Adapter returns ALL applicable strategies' proposals so Wonder /
|
|
110
|
+
# create_experiment can offer them as branches. Callers cap total
|
|
111
|
+
# via max_seeds.
|
|
107
112
|
kernel = kernel or {}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
113
|
+
results: list[tuple[BranchSeed, dict]] = []
|
|
114
|
+
for strategy_fn in (_strategy_filter_pluck, _strategy_detune_warmth):
|
|
115
|
+
try:
|
|
116
|
+
maybe = strategy_fn(profile, target, kernel, adapter=self)
|
|
117
|
+
except Exception:
|
|
118
|
+
# Never let one strategy's crash kill the rest.
|
|
119
|
+
continue
|
|
120
|
+
if maybe is not None:
|
|
121
|
+
results.append(maybe)
|
|
122
|
+
return results
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ── Strategy registry ────────────────────────────────────────────────
|
|
126
|
+
#
|
|
127
|
+
# Each strategy is a pure function (profile, target, kernel, adapter) →
|
|
128
|
+
# Optional[(BranchSeed, plan_dict)]. A strategy returns None when not
|
|
129
|
+
# applicable to the current profile+target+role combination. This lets
|
|
130
|
+
# the adapter stay thin while the intelligence lives in the strategies.
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _strategy_filter_pluck(
|
|
134
|
+
profile: SynthProfile,
|
|
135
|
+
target: TimbralFingerprint,
|
|
136
|
+
kernel: dict,
|
|
137
|
+
adapter,
|
|
138
|
+
) -> Optional[tuple[BranchSeed, dict]]:
|
|
139
|
+
"""Couple Filter Env Amount up + Filter Decay down → attack pluck.
|
|
140
|
+
|
|
141
|
+
Gates: skip when profile already flags 'Already plucky'. Most useful
|
|
142
|
+
when role_hint is "bass", "pluck", "lead", or when target.bite > 0.
|
|
143
|
+
"""
|
|
144
|
+
if any("Already plucky" in n for n in profile.notes):
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
role = (profile.role_hint or "").lower()
|
|
148
|
+
want_bite = target.bite > 0.1 or role in {"bass", "pluck", "lead", "stab"}
|
|
149
|
+
if not want_bite and role in {"pad", "drone"}:
|
|
150
|
+
# Sustained roles actively fight this strategy.
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
freshness = float(kernel.get("freshness", 0.5) or 0.5)
|
|
154
|
+
track = profile.track_index
|
|
155
|
+
device = profile.device_index
|
|
156
|
+
current_env = float(profile.parameter_state.get("F1 Env Amount", 0.0) or 0.0)
|
|
157
|
+
new_env = min(1.0, max(current_env, 0.45 if freshness < 0.5 else 0.65))
|
|
158
|
+
current_decay = float(profile.parameter_state.get("F1 Env D", 0.5) or 0.5)
|
|
159
|
+
new_decay = min(current_decay, 0.25 if freshness < 0.5 else 0.15)
|
|
160
|
+
|
|
161
|
+
seed = freeform_seed(
|
|
162
|
+
seed_id=_short_id("an_plk", f"{track}:{device}:{new_env:.2f}:{new_decay:.2f}"),
|
|
163
|
+
hypothesis=(
|
|
164
|
+
f"Analog filter-pluck: Env Amount → {new_env:.2f}, "
|
|
165
|
+
f"Decay → {new_decay:.2f} for attack character"
|
|
166
|
+
),
|
|
167
|
+
source="synthesis",
|
|
168
|
+
novelty_label="strong" if freshness < 0.7 else "unexpected",
|
|
169
|
+
risk_label="low",
|
|
170
|
+
affected_scope={
|
|
171
|
+
"track_indices": [track],
|
|
172
|
+
"device_paths": [f"track/{track}/device/{device}"],
|
|
173
|
+
},
|
|
174
|
+
distinctness_reason="couples Filter Env Amount + Decay for attack character",
|
|
175
|
+
producer_payload={
|
|
176
|
+
"device_name": adapter.device_name,
|
|
177
|
+
"track_index": track,
|
|
178
|
+
"device_index": device,
|
|
179
|
+
"strategy": "filter_pluck",
|
|
180
|
+
"topology_hint": {
|
|
181
|
+
"role_hint": profile.role_hint,
|
|
182
|
+
"current_env": current_env,
|
|
183
|
+
"new_env": new_env,
|
|
184
|
+
"current_decay": current_decay,
|
|
185
|
+
"new_decay": new_decay,
|
|
135
186
|
},
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
"params": {
|
|
143
|
-
"track_index": track,
|
|
144
|
-
"device_index": device,
|
|
187
|
+
},
|
|
188
|
+
)
|
|
189
|
+
plan = {
|
|
190
|
+
"steps": [
|
|
191
|
+
{"tool": "set_device_parameter",
|
|
192
|
+
"params": {"track_index": track, "device_index": device,
|
|
145
193
|
"parameter_name": "F1 Env Amount",
|
|
146
|
-
"value": round(new_env, 3),
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
{
|
|
150
|
-
"tool": "set_device_parameter",
|
|
151
|
-
"params": {
|
|
152
|
-
"track_index": track,
|
|
153
|
-
"device_index": device,
|
|
194
|
+
"value": round(new_env, 3)}},
|
|
195
|
+
{"tool": "set_device_parameter",
|
|
196
|
+
"params": {"track_index": track, "device_index": device,
|
|
154
197
|
"parameter_name": "F1 Env D",
|
|
155
|
-
"value": round(new_decay, 3),
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
198
|
+
"value": round(new_decay, 3)}},
|
|
199
|
+
],
|
|
200
|
+
"step_count": 2,
|
|
201
|
+
"summary": f"F1 Env Amount → {new_env:.2f}, F1 Env D → {new_decay:.2f}",
|
|
202
|
+
}
|
|
203
|
+
return (seed, plan)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _strategy_detune_warmth(
|
|
207
|
+
profile: SynthProfile,
|
|
208
|
+
target: TimbralFingerprint,
|
|
209
|
+
kernel: dict,
|
|
210
|
+
adapter,
|
|
211
|
+
) -> Optional[tuple[BranchSeed, dict]]:
|
|
212
|
+
"""Detune Osc2 slightly + lean warmer tone.
|
|
213
|
+
|
|
214
|
+
Gates: applicable when role_hint is "pad" / "lead" / "stab" / "drone"
|
|
215
|
+
or target.warmth > 0. Skip on "pluck"/"bass" to avoid woofiness.
|
|
216
|
+
"""
|
|
217
|
+
role = (profile.role_hint or "").lower()
|
|
218
|
+
if role in {"bass", "pluck", "kick"}:
|
|
219
|
+
return None
|
|
220
|
+
want_warm = target.warmth > 0.1 or role in {"pad", "lead", "stab", "drone"}
|
|
221
|
+
if not want_warm:
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
freshness = float(kernel.get("freshness", 0.5) or 0.5)
|
|
225
|
+
track = profile.track_index
|
|
226
|
+
device = profile.device_index
|
|
227
|
+
current_detune = float(profile.parameter_state.get("Osc2 Tune", 0.0) or 0.0)
|
|
228
|
+
# Detune is in semitones by convention on Analog; keep shifts musical.
|
|
229
|
+
step = 0.04 if freshness < 0.5 else 0.09
|
|
230
|
+
new_detune = round(current_detune + step, 3)
|
|
231
|
+
|
|
232
|
+
seed = freeform_seed(
|
|
233
|
+
seed_id=_short_id("an_det", f"{track}:{device}:{new_detune:.3f}"),
|
|
234
|
+
hypothesis=(
|
|
235
|
+
f"Analog detune warmth: Osc2 Tune {current_detune:.3f} → "
|
|
236
|
+
f"{new_detune:.3f} semitones for a wider, lusher body"
|
|
237
|
+
),
|
|
238
|
+
source="synthesis",
|
|
239
|
+
novelty_label="safe",
|
|
240
|
+
risk_label="low",
|
|
241
|
+
affected_scope={
|
|
242
|
+
"track_indices": [track],
|
|
243
|
+
"device_paths": [f"track/{track}/device/{device}"],
|
|
244
|
+
},
|
|
245
|
+
distinctness_reason="slight Osc2 detune for body, no filter changes",
|
|
246
|
+
producer_payload={
|
|
247
|
+
"device_name": adapter.device_name,
|
|
248
|
+
"track_index": track,
|
|
249
|
+
"device_index": device,
|
|
250
|
+
"strategy": "detune_warmth",
|
|
251
|
+
"topology_hint": {
|
|
252
|
+
"role_hint": profile.role_hint,
|
|
253
|
+
"current_detune": current_detune,
|
|
254
|
+
"new_detune": new_detune,
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
)
|
|
258
|
+
plan = {
|
|
259
|
+
"steps": [
|
|
260
|
+
{"tool": "set_device_parameter",
|
|
261
|
+
"params": {"track_index": track, "device_index": device,
|
|
262
|
+
"parameter_name": "Osc2 Tune",
|
|
263
|
+
"value": new_detune}},
|
|
264
|
+
],
|
|
265
|
+
"step_count": 1,
|
|
266
|
+
"summary": f"Osc2 Tune → {new_detune:.3f}",
|
|
267
|
+
}
|
|
268
|
+
return (seed, plan)
|
|
163
269
|
|
|
164
270
|
|
|
165
271
|
def _short_id(prefix: str, key: str) -> str:
|
|
@@ -105,60 +105,165 @@ class DriftAdapter:
|
|
|
105
105
|
kernel: Optional[dict] = None,
|
|
106
106
|
) -> list[tuple[BranchSeed, dict]]:
|
|
107
107
|
kernel = kernel or {}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
108
|
+
results: list[tuple[BranchSeed, dict]] = []
|
|
109
|
+
for strategy_fn in (_strategy_character_blend, _strategy_filter_sweep):
|
|
110
|
+
try:
|
|
111
|
+
maybe = strategy_fn(profile, target, kernel, adapter=self)
|
|
112
|
+
except Exception:
|
|
113
|
+
continue
|
|
114
|
+
if maybe is not None:
|
|
115
|
+
results.append(maybe)
|
|
116
|
+
return results
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ── Strategy registry ────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _strategy_character_blend(
|
|
123
|
+
profile: SynthProfile,
|
|
124
|
+
target: TimbralFingerprint,
|
|
125
|
+
kernel: dict,
|
|
126
|
+
adapter,
|
|
127
|
+
) -> Optional[tuple[BranchSeed, dict]]:
|
|
128
|
+
"""Shift Character + Sub balance. Always applicable."""
|
|
129
|
+
freshness = float(kernel.get("freshness", 0.5) or 0.5)
|
|
130
|
+
track = profile.track_index
|
|
131
|
+
device = profile.device_index
|
|
132
|
+
current_char = float(profile.parameter_state.get("Character", 0.0) or 0.0)
|
|
133
|
+
current_sub = float(profile.parameter_state.get("Sub Level", 0.0) or 0.0)
|
|
134
|
+
|
|
135
|
+
new_char = (
|
|
136
|
+
min(1.0, max(current_char + 0.3, 0.3))
|
|
137
|
+
if current_char <= 0.5
|
|
138
|
+
else max(0.0, current_char - 0.3)
|
|
139
|
+
)
|
|
140
|
+
target_sub = 0.3
|
|
141
|
+
new_sub = current_sub + (target_sub - current_sub) * (
|
|
142
|
+
0.5 if freshness < 0.5 else 0.8
|
|
143
|
+
)
|
|
144
|
+
new_sub = round(max(0.0, min(1.0, new_sub)), 3)
|
|
145
|
+
|
|
146
|
+
seed = freeform_seed(
|
|
147
|
+
seed_id=_short_id("dr_chr", f"{track}:{device}:{new_char:.2f}:{new_sub:.2f}"),
|
|
148
|
+
hypothesis=(
|
|
149
|
+
f"Drift character blend: Character → {new_char:.2f}, "
|
|
150
|
+
f"Sub Level → {new_sub:.2f} for a different core tone"
|
|
151
|
+
),
|
|
152
|
+
source="synthesis",
|
|
153
|
+
novelty_label="strong",
|
|
154
|
+
risk_label="low",
|
|
155
|
+
affected_scope={
|
|
156
|
+
"track_indices": [track],
|
|
157
|
+
"device_paths": [f"track/{track}/device/{device}"],
|
|
158
|
+
},
|
|
159
|
+
distinctness_reason="shifts Character + Sub balance",
|
|
160
|
+
producer_payload={
|
|
161
|
+
"device_name": adapter.device_name,
|
|
162
|
+
"track_index": track,
|
|
163
|
+
"device_index": device,
|
|
164
|
+
"strategy": "character_blend",
|
|
165
|
+
"topology_hint": {
|
|
166
|
+
"role_hint": profile.role_hint,
|
|
167
|
+
"current_char": current_char,
|
|
168
|
+
"new_char": round(new_char, 3),
|
|
169
|
+
"current_sub": current_sub,
|
|
170
|
+
"new_sub": new_sub,
|
|
134
171
|
},
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
"params": {
|
|
142
|
-
"track_index": track,
|
|
143
|
-
"device_index": device,
|
|
172
|
+
},
|
|
173
|
+
)
|
|
174
|
+
plan = {
|
|
175
|
+
"steps": [
|
|
176
|
+
{"tool": "set_device_parameter",
|
|
177
|
+
"params": {"track_index": track, "device_index": device,
|
|
144
178
|
"parameter_name": "Character",
|
|
145
|
-
"value": round(new_char, 3),
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
{
|
|
149
|
-
"tool": "set_device_parameter",
|
|
150
|
-
"params": {
|
|
151
|
-
"track_index": track,
|
|
152
|
-
"device_index": device,
|
|
179
|
+
"value": round(new_char, 3)}},
|
|
180
|
+
{"tool": "set_device_parameter",
|
|
181
|
+
"params": {"track_index": track, "device_index": device,
|
|
153
182
|
"parameter_name": "Sub Level",
|
|
154
|
-
"value": new_sub,
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
183
|
+
"value": new_sub}},
|
|
184
|
+
],
|
|
185
|
+
"step_count": 2,
|
|
186
|
+
"summary": f"Character → {new_char:.2f}, Sub Level → {new_sub:.2f}",
|
|
187
|
+
}
|
|
188
|
+
return (seed, plan)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _strategy_filter_sweep(
|
|
192
|
+
profile: SynthProfile,
|
|
193
|
+
target: TimbralFingerprint,
|
|
194
|
+
kernel: dict,
|
|
195
|
+
adapter,
|
|
196
|
+
) -> Optional[tuple[BranchSeed, dict]]:
|
|
197
|
+
"""Sweep Filter Freq toward target brightness.
|
|
198
|
+
|
|
199
|
+
Gates: applicable when target.brightness != 0 OR role_hint suggests
|
|
200
|
+
motion ("lead", "pad", "drone"). Skip when role is "bass" (sub roles
|
|
201
|
+
want a stable low-pass, not a sweep).
|
|
202
|
+
"""
|
|
203
|
+
role = (profile.role_hint or "").lower()
|
|
204
|
+
if role in {"bass", "sub", "kick"}:
|
|
205
|
+
return None
|
|
206
|
+
want_motion = abs(target.brightness) > 0.1 or role in {"lead", "pad", "drone"}
|
|
207
|
+
if not want_motion:
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
freshness = float(kernel.get("freshness", 0.5) or 0.5)
|
|
211
|
+
track = profile.track_index
|
|
212
|
+
device = profile.device_index
|
|
213
|
+
# Drift's filter freq is normalized 0-1 in the API; display is Hz.
|
|
214
|
+
current_freq = float(profile.parameter_state.get("Filter Freq", 0.5) or 0.5)
|
|
215
|
+
if target.brightness > 0:
|
|
216
|
+
# Open filter toward bright.
|
|
217
|
+
new_freq = min(1.0, current_freq + (0.15 if freshness < 0.5 else 0.3))
|
|
218
|
+
direction = "open"
|
|
219
|
+
else:
|
|
220
|
+
# Close toward warm.
|
|
221
|
+
new_freq = max(0.0, current_freq - (0.12 if freshness < 0.5 else 0.25))
|
|
222
|
+
direction = "close"
|
|
223
|
+
|
|
224
|
+
if abs(new_freq - current_freq) < 0.03:
|
|
225
|
+
return None # barely any change — skip
|
|
226
|
+
|
|
227
|
+
seed = freeform_seed(
|
|
228
|
+
seed_id=_short_id(
|
|
229
|
+
"dr_flt", f"{track}:{device}:{direction}:{new_freq:.2f}"
|
|
230
|
+
),
|
|
231
|
+
hypothesis=(
|
|
232
|
+
f"Drift filter sweep: Filter Freq {current_freq:.2f} → "
|
|
233
|
+
f"{new_freq:.2f} ({direction}) for a {direction}d voice"
|
|
234
|
+
),
|
|
235
|
+
source="synthesis",
|
|
236
|
+
novelty_label="strong" if freshness < 0.7 else "unexpected",
|
|
237
|
+
risk_label="low",
|
|
238
|
+
affected_scope={
|
|
239
|
+
"track_indices": [track],
|
|
240
|
+
"device_paths": [f"track/{track}/device/{device}"],
|
|
241
|
+
},
|
|
242
|
+
distinctness_reason=f"filter {direction} without touching core oscillator",
|
|
243
|
+
producer_payload={
|
|
244
|
+
"device_name": adapter.device_name,
|
|
245
|
+
"track_index": track,
|
|
246
|
+
"device_index": device,
|
|
247
|
+
"strategy": f"filter_sweep_{direction}",
|
|
248
|
+
"topology_hint": {
|
|
249
|
+
"role_hint": profile.role_hint,
|
|
250
|
+
"target_brightness": target.brightness,
|
|
251
|
+
"current_freq": current_freq,
|
|
252
|
+
"new_freq": round(new_freq, 3),
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
)
|
|
256
|
+
plan = {
|
|
257
|
+
"steps": [
|
|
258
|
+
{"tool": "set_device_parameter",
|
|
259
|
+
"params": {"track_index": track, "device_index": device,
|
|
260
|
+
"parameter_name": "Filter Freq",
|
|
261
|
+
"value": round(new_freq, 3)}},
|
|
262
|
+
],
|
|
263
|
+
"step_count": 1,
|
|
264
|
+
"summary": f"Filter Freq {current_freq:.2f} → {new_freq:.2f}",
|
|
265
|
+
}
|
|
266
|
+
return (seed, plan)
|
|
162
267
|
|
|
163
268
|
|
|
164
269
|
def _short_id(prefix: str, key: str) -> str:
|
|
@@ -102,48 +102,158 @@ class MeldAdapter:
|
|
|
102
102
|
kernel: Optional[dict] = None,
|
|
103
103
|
) -> list[tuple[BranchSeed, dict]]:
|
|
104
104
|
kernel = kernel or {}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
105
|
+
results: list[tuple[BranchSeed, dict]] = []
|
|
106
|
+
for strategy_fn in (_strategy_engine_algo_swap, _strategy_engine_mix_shift):
|
|
107
|
+
try:
|
|
108
|
+
maybe = strategy_fn(profile, target, kernel, adapter=self)
|
|
109
|
+
except Exception:
|
|
110
|
+
continue
|
|
111
|
+
if maybe is not None:
|
|
112
|
+
results.append(maybe)
|
|
113
|
+
return results
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# ── Strategy registry ────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _strategy_engine_algo_swap(
|
|
120
|
+
profile: SynthProfile,
|
|
121
|
+
target: TimbralFingerprint,
|
|
122
|
+
kernel: dict,
|
|
123
|
+
adapter,
|
|
124
|
+
) -> Optional[tuple[BranchSeed, dict]]:
|
|
125
|
+
"""Shift Engine 1 Algorithm by +1 (low freshness) or +3 (high).
|
|
126
|
+
|
|
127
|
+
Always applicable — algorithm swaps are guaranteed to change tone
|
|
128
|
+
regardless of current state.
|
|
129
|
+
"""
|
|
130
|
+
freshness = float(kernel.get("freshness", 0.5) or 0.5)
|
|
131
|
+
track = profile.track_index
|
|
132
|
+
device = profile.device_index
|
|
133
|
+
current_algo = int(profile.parameter_state.get("Engine 1 Algorithm", 0) or 0)
|
|
134
|
+
shift = 1 if freshness < 0.5 else 3
|
|
135
|
+
new_algo = (current_algo + shift) % 10
|
|
136
|
+
|
|
137
|
+
seed = freeform_seed(
|
|
138
|
+
seed_id=_short_id("ml_algo", f"{track}:{device}:{new_algo}"),
|
|
139
|
+
hypothesis=(
|
|
140
|
+
f"Meld Engine 1 algorithm swap: {current_algo} → {new_algo} "
|
|
141
|
+
f"for a materially different core timbre"
|
|
142
|
+
),
|
|
143
|
+
source="synthesis",
|
|
144
|
+
novelty_label="unexpected" if shift == 3 else "strong",
|
|
145
|
+
risk_label="medium",
|
|
146
|
+
affected_scope={
|
|
147
|
+
"track_indices": [track],
|
|
148
|
+
"device_paths": [f"track/{track}/device/{device}"],
|
|
149
|
+
},
|
|
150
|
+
distinctness_reason="changes Engine 1 algorithm",
|
|
151
|
+
producer_payload={
|
|
152
|
+
"device_name": adapter.device_name,
|
|
153
|
+
"track_index": track,
|
|
154
|
+
"device_index": device,
|
|
155
|
+
"strategy": "engine_algo_swap",
|
|
156
|
+
"topology_hint": {
|
|
157
|
+
"role_hint": profile.role_hint,
|
|
158
|
+
"current_algo": current_algo,
|
|
159
|
+
"new_algo": new_algo,
|
|
128
160
|
},
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
"params": {
|
|
136
|
-
"track_index": track,
|
|
137
|
-
"device_index": device,
|
|
161
|
+
},
|
|
162
|
+
)
|
|
163
|
+
plan = {
|
|
164
|
+
"steps": [
|
|
165
|
+
{"tool": "set_device_parameter",
|
|
166
|
+
"params": {"track_index": track, "device_index": device,
|
|
138
167
|
"parameter_name": "Engine 1 Algorithm",
|
|
139
|
-
"value": new_algo,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
168
|
+
"value": new_algo}},
|
|
169
|
+
],
|
|
170
|
+
"step_count": 1,
|
|
171
|
+
"summary": f"Engine 1 Algorithm {current_algo} → {new_algo}",
|
|
172
|
+
}
|
|
173
|
+
return (seed, plan)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _strategy_engine_mix_shift(
|
|
177
|
+
profile: SynthProfile,
|
|
178
|
+
target: TimbralFingerprint,
|
|
179
|
+
kernel: dict,
|
|
180
|
+
adapter,
|
|
181
|
+
) -> Optional[tuple[BranchSeed, dict]]:
|
|
182
|
+
"""Rebalance Engine 1 / Engine 2 Level for layered character.
|
|
183
|
+
|
|
184
|
+
Gates: applicable when BOTH engines have non-zero Level (mix makes
|
|
185
|
+
no sense if one engine is silent). Shifts the balance by 0.15-0.3
|
|
186
|
+
depending on freshness.
|
|
187
|
+
"""
|
|
188
|
+
e1 = float(profile.parameter_state.get("Engine 1 Level", 0.0) or 0.0)
|
|
189
|
+
e2 = float(profile.parameter_state.get("Engine 2 Level", 0.0) or 0.0)
|
|
190
|
+
if e1 < 0.05 or e2 < 0.05:
|
|
191
|
+
return None # one engine silent — mix shift is meaningless
|
|
192
|
+
|
|
193
|
+
freshness = float(kernel.get("freshness", 0.5) or 0.5)
|
|
194
|
+
track = profile.track_index
|
|
195
|
+
device = profile.device_index
|
|
196
|
+
|
|
197
|
+
# Push toward the engine with LESS level currently — highlights the
|
|
198
|
+
# underused engine's character. When roughly equal, pick Engine 2.
|
|
199
|
+
if e1 < e2:
|
|
200
|
+
direction = "to_e1"
|
|
201
|
+
delta = 0.15 if freshness < 0.5 else 0.3
|
|
202
|
+
new_e1 = min(1.0, e1 + delta)
|
|
203
|
+
new_e2 = max(0.0, e2 - delta / 2)
|
|
204
|
+
else:
|
|
205
|
+
direction = "to_e2"
|
|
206
|
+
delta = 0.15 if freshness < 0.5 else 0.3
|
|
207
|
+
new_e2 = min(1.0, e2 + delta)
|
|
208
|
+
new_e1 = max(0.0, e1 - delta / 2)
|
|
209
|
+
|
|
210
|
+
seed = freeform_seed(
|
|
211
|
+
seed_id=_short_id(
|
|
212
|
+
"ml_mix", f"{track}:{device}:{direction}:{new_e1:.2f}:{new_e2:.2f}"
|
|
213
|
+
),
|
|
214
|
+
hypothesis=(
|
|
215
|
+
f"Meld engine mix shift {direction}: E1 {e1:.2f} → {new_e1:.2f}, "
|
|
216
|
+
f"E2 {e2:.2f} → {new_e2:.2f}"
|
|
217
|
+
),
|
|
218
|
+
source="synthesis",
|
|
219
|
+
novelty_label="strong",
|
|
220
|
+
risk_label="low",
|
|
221
|
+
affected_scope={
|
|
222
|
+
"track_indices": [track],
|
|
223
|
+
"device_paths": [f"track/{track}/device/{device}"],
|
|
224
|
+
},
|
|
225
|
+
distinctness_reason=(
|
|
226
|
+
f"Meld mix rebalance {direction}; algorithm unchanged"
|
|
227
|
+
),
|
|
228
|
+
producer_payload={
|
|
229
|
+
"device_name": adapter.device_name,
|
|
230
|
+
"track_index": track,
|
|
231
|
+
"device_index": device,
|
|
232
|
+
"strategy": f"engine_mix_shift_{direction}",
|
|
233
|
+
"topology_hint": {
|
|
234
|
+
"role_hint": profile.role_hint,
|
|
235
|
+
"current_e1": e1,
|
|
236
|
+
"current_e2": e2,
|
|
237
|
+
"new_e1": round(new_e1, 3),
|
|
238
|
+
"new_e2": round(new_e2, 3),
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
)
|
|
242
|
+
plan = {
|
|
243
|
+
"steps": [
|
|
244
|
+
{"tool": "set_device_parameter",
|
|
245
|
+
"params": {"track_index": track, "device_index": device,
|
|
246
|
+
"parameter_name": "Engine 1 Level",
|
|
247
|
+
"value": round(new_e1, 3)}},
|
|
248
|
+
{"tool": "set_device_parameter",
|
|
249
|
+
"params": {"track_index": track, "device_index": device,
|
|
250
|
+
"parameter_name": "Engine 2 Level",
|
|
251
|
+
"value": round(new_e2, 3)}},
|
|
252
|
+
],
|
|
253
|
+
"step_count": 2,
|
|
254
|
+
"summary": f"E1 {e1:.2f}→{new_e1:.2f}, E2 {e2:.2f}→{new_e2:.2f}",
|
|
255
|
+
}
|
|
256
|
+
return (seed, plan)
|
|
147
257
|
|
|
148
258
|
|
|
149
259
|
def _short_id(prefix: str, key: str) -> str:
|