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.
@@ -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
- freshness = float(kernel.get("freshness", 0.5) or 0.5)
109
- track = profile.track_index
110
- device = profile.device_index
111
-
112
- # Skip proposal if already plucky — avoids doubling-down on the
113
- # same treatment.
114
- if any("Already plucky" in n for n in profile.notes):
115
- return []
116
-
117
- current_env = float(profile.parameter_state.get("F1 Env Amount", 0.0) or 0.0)
118
- # Target filter-env amount scales with freshness.
119
- new_env = min(1.0, max(current_env, 0.45 if freshness < 0.5 else 0.65))
120
- current_decay = float(profile.parameter_state.get("F1 Env D", 0.5) or 0.5)
121
- new_decay = min(current_decay, 0.25 if freshness < 0.5 else 0.15)
122
-
123
- seed = freeform_seed(
124
- seed_id=_short_id("an_plk", f"{track}:{device}:{new_env:.2f}:{new_decay:.2f}"),
125
- hypothesis=(
126
- f"Analog filter-pluck: Env Amount → {new_env:.2f}, "
127
- f"Decay → {new_decay:.2f} for attack character"
128
- ),
129
- source="synthesis",
130
- novelty_label="strong" if freshness < 0.7 else "unexpected",
131
- risk_label="low",
132
- affected_scope={
133
- "track_indices": [track],
134
- "device_paths": [f"track/{track}/device/{device}"],
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
- distinctness_reason="only Analog seed that couples Filter Env + Decay",
137
- )
138
- plan = {
139
- "steps": [
140
- {
141
- "tool": "set_device_parameter",
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
- "step_count": 2,
160
- "summary": f"F1 Env Amount → {new_env:.2f}, F1 Env D → {new_decay:.2f}",
161
- }
162
- return [(seed, plan)]
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
- freshness = float(kernel.get("freshness", 0.5) or 0.5)
109
- track = profile.track_index
110
- device = profile.device_index
111
-
112
- current_char = float(profile.parameter_state.get("Character", 0.0) or 0.0)
113
- current_sub = float(profile.parameter_state.get("Sub Level", 0.0) or 0.0)
114
-
115
- # Toggle character in the opposite direction to create contrast.
116
- new_char = min(1.0, max(current_char + 0.3, 0.3)) if current_char <= 0.5 else max(0.0, current_char - 0.3)
117
- # Move sub slightly toward 0.3 if we're starting outside 0.2-0.4
118
- target_sub = 0.3
119
- new_sub = current_sub + (target_sub - current_sub) * (0.5 if freshness < 0.5 else 0.8)
120
- new_sub = round(max(0.0, min(1.0, new_sub)), 3)
121
-
122
- seed = freeform_seed(
123
- seed_id=_short_id("dr_chr", f"{track}:{device}:{new_char:.2f}:{new_sub:.2f}"),
124
- hypothesis=(
125
- f"Drift character blend: Character → {new_char:.2f}, "
126
- f"Sub Level → {new_sub:.2f} for a different core tone"
127
- ),
128
- source="synthesis",
129
- novelty_label="strong",
130
- risk_label="low",
131
- affected_scope={
132
- "track_indices": [track],
133
- "device_paths": [f"track/{track}/device/{device}"],
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
- distinctness_reason="only Drift seed that shifts Character + Sub balance",
136
- )
137
- plan = {
138
- "steps": [
139
- {
140
- "tool": "set_device_parameter",
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
- "step_count": 2,
159
- "summary": f"Character → {new_char:.2f}, Sub Level → {new_sub:.2f}",
160
- }
161
- return [(seed, plan)]
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
- freshness = float(kernel.get("freshness", 0.5) or 0.5)
106
- track = profile.track_index
107
- device = profile.device_index
108
-
109
- # Algorithm indices are device-internal; we propose a relative shift
110
- # by +1 (modulo a safe ceiling of 10 — Meld has 12 algos but we're
111
- # conservative). Freshness amplifies the shift to +3.
112
- current_algo = int(profile.parameter_state.get("Engine 1 Algorithm", 0) or 0)
113
- shift = 1 if freshness < 0.5 else 3
114
- new_algo = (current_algo + shift) % 10
115
-
116
- seed = freeform_seed(
117
- seed_id=_short_id("ml_algo", f"{track}:{device}:{new_algo}"),
118
- hypothesis=(
119
- f"Meld Engine 1 algorithm swap: {current_algo} → {new_algo} "
120
- f"for a materially different core timbre"
121
- ),
122
- source="synthesis",
123
- novelty_label="unexpected" if shift == 3 else "strong",
124
- risk_label="medium",
125
- affected_scope={
126
- "track_indices": [track],
127
- "device_paths": [f"track/{track}/device/{device}"],
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
- distinctness_reason="only Meld seed that changes Engine 1 algorithm",
130
- )
131
- plan = {
132
- "steps": [
133
- {
134
- "tool": "set_device_parameter",
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
- "step_count": 1,
144
- "summary": f"Engine 1 Algorithm {current_algo} → {new_algo}",
145
- }
146
- return [(seed, plan)]
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: