livepilot 1.9.15 → 1.9.16

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 (36) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +1 -1
  3. package/CHANGELOG.md +40 -0
  4. package/README.md +1 -1
  5. package/livepilot/.Codex-plugin/plugin.json +1 -1
  6. package/livepilot/.claude-plugin/plugin.json +1 -1
  7. package/livepilot/skills/livepilot-core/references/overview.md +1 -1
  8. package/m4l_device/livepilot_bridge.js +27 -13
  9. package/mcp_server/__init__.py +1 -1
  10. package/mcp_server/connection.py +24 -2
  11. package/mcp_server/curves.py +3 -3
  12. package/mcp_server/evaluation/fabric.py +1 -1
  13. package/mcp_server/m4l_bridge.py +9 -1
  14. package/mcp_server/memory/technique_store.py +25 -17
  15. package/mcp_server/mix_engine/critics.py +1 -1
  16. package/mcp_server/mix_engine/tools.py +14 -8
  17. package/mcp_server/performance_engine/safety.py +6 -3
  18. package/mcp_server/project_brain/refresh.py +8 -2
  19. package/mcp_server/project_brain/tools.py +12 -12
  20. package/mcp_server/reference_engine/tools.py +16 -15
  21. package/mcp_server/runtime/action_ledger_models.py +10 -3
  22. package/mcp_server/runtime/capability_state.py +3 -2
  23. package/mcp_server/runtime/tools.py +6 -3
  24. package/mcp_server/tools/agent_os.py +47 -39
  25. package/mcp_server/tools/composition.py +114 -32
  26. package/mcp_server/tools/devices.py +15 -1
  27. package/mcp_server/tools/midi_io.py +3 -1
  28. package/mcp_server/tools/research.py +31 -31
  29. package/mcp_server/tools/tracks.py +3 -3
  30. package/mcp_server/translation_engine/tools.py +50 -16
  31. package/package.json +1 -1
  32. package/remote_script/LivePilot/__init__.py +1 -1
  33. package/remote_script/LivePilot/arrangement.py +9 -1
  34. package/remote_script/LivePilot/clips.py +22 -6
  35. package/remote_script/LivePilot/notes.py +9 -1
  36. package/remote_script/LivePilot/server.py +6 -6
@@ -50,6 +50,11 @@ def create_clip(song, params):
50
50
  raise ValueError("Clip length must be > 0")
51
51
 
52
52
  clip_slot = get_clip_slot(song, track_index, clip_index)
53
+ if clip_slot.has_clip:
54
+ raise ValueError(
55
+ "Clip slot %d on track %d already has a clip. "
56
+ "Delete it first with delete_clip." % (clip_index, track_index)
57
+ )
53
58
  clip_slot.create_clip(length)
54
59
  clip = clip_slot.clip
55
60
 
@@ -147,12 +152,23 @@ def set_clip_loop(song, params):
147
152
  clip_index = int(params["clip_index"])
148
153
  clip = get_clip(song, track_index, clip_index)
149
154
 
150
- # Set end before start to avoid Live's loop_start < loop_end clamping.
151
- # Expanding the window first ensures the left edge can move freely.
152
- if "end" in params:
153
- clip.loop_end = float(params["end"])
154
- if "start" in params:
155
- clip.loop_start = float(params["start"])
155
+ # Conditional ordering to avoid Live's loop_start < loop_end clamping.
156
+ # When expanding the window, set the expanding edge first.
157
+ # When shrinking, set the contracting edge first.
158
+ new_end = float(params["end"]) if "end" in params else None
159
+ new_start = float(params["start"]) if "start" in params else None
160
+
161
+ if new_end is not None and new_end > clip.loop_end:
162
+ # Expanding right — set end first so start can move freely
163
+ clip.loop_end = new_end
164
+ if new_start is not None:
165
+ clip.loop_start = new_start
166
+ else:
167
+ # Shrinking or only changing start — set start first
168
+ if new_start is not None:
169
+ clip.loop_start = new_start
170
+ if new_end is not None:
171
+ clip.loop_end = new_end
156
172
  if "enabled" in params:
157
173
  clip.looping = bool(params["enabled"])
158
174
 
@@ -172,6 +172,12 @@ def modify_notes(song, params):
172
172
  note.velocity = float(mod["velocity"])
173
173
  if "probability" in mod:
174
174
  note.probability = float(mod["probability"])
175
+ if "mute" in mod:
176
+ note.mute = bool(mod["mute"])
177
+ if "velocity_deviation" in mod:
178
+ note.velocity_deviation = float(mod["velocity_deviation"])
179
+ if "release_velocity" in mod:
180
+ note.release_velocity = float(mod["release_velocity"])
175
181
  modified_count += 1
176
182
 
177
183
  # Pass the original NoteVector back — Boost.Python requires the C++ type
@@ -273,7 +279,9 @@ def transpose_notes(song, params):
273
279
  clip = get_clip(song, track_index, clip_index)
274
280
 
275
281
  from_time = float(params.get("from_time", 0.0))
276
- time_span = float(params.get("time_span", clip.length))
282
+ # Default span covers from from_time to end of clip, not the full clip length
283
+ default_span = max(0.0, clip.length - from_time) if clip.length > 0 else 32768.0
284
+ time_span = float(params.get("time_span", default_span))
277
285
 
278
286
  # Get notes — returns C++ NoteVector that must be passed back intact
279
287
  all_notes = clip.get_notes_extended(0, 128, from_time, time_span)
@@ -162,12 +162,12 @@ class LivePilotServer(object):
162
162
  pass
163
163
  continue
164
164
  self._client_connected = True
165
- self._client_thread = threading.Thread(
166
- target=self._run_client_session,
167
- args=(client, addr),
168
- )
169
- self._client_thread.daemon = True
170
- self._client_thread.start()
165
+ self._client_thread = threading.Thread(
166
+ target=self._run_client_session,
167
+ args=(client, addr),
168
+ )
169
+ self._client_thread.daemon = True
170
+ self._client_thread.start()
171
171
  except socket.timeout:
172
172
  continue
173
173
  except OSError: