Add duplicate_clip command + fix get_notes signature for audio clips
This commit is contained in:
@@ -1559,6 +1559,96 @@ class _AbletonMCP(ControlSurface):
|
|||||||
raise Exception("Failed to load sample: %s" % str(e))
|
raise Exception("Failed to load sample: %s" % str(e))
|
||||||
return {"loaded": False}
|
return {"loaded": False}
|
||||||
|
|
||||||
|
def _cmd_duplicate_clip(self, source_track, source_clip, target_track, target_clip, **kw):
|
||||||
|
"""Duplicate/clone a clip from one slot to another in Session View.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_track: Source track index
|
||||||
|
source_clip: Source clip slot index
|
||||||
|
target_track: Target track index (can be same as source)
|
||||||
|
target_clip: Target clip slot index
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
src_track_idx = int(source_track)
|
||||||
|
src_clip_idx = int(source_clip)
|
||||||
|
tgt_track_idx = int(target_track)
|
||||||
|
tgt_clip_idx = int(target_clip)
|
||||||
|
|
||||||
|
src_track = self._song.tracks[src_track_idx]
|
||||||
|
src_slot = src_track.clip_slots[src_clip_idx]
|
||||||
|
|
||||||
|
if not src_slot.has_clip:
|
||||||
|
return {"duplicated": False, "error": "Source slot has no clip"}
|
||||||
|
|
||||||
|
src_clip = src_slot.clip
|
||||||
|
tgt_track = self._song.tracks[tgt_track_idx]
|
||||||
|
tgt_slot = tgt_track.clip_slots[tgt_clip_idx]
|
||||||
|
|
||||||
|
# Clear target if occupied
|
||||||
|
if tgt_slot.has_clip:
|
||||||
|
tgt_slot.delete_clip()
|
||||||
|
|
||||||
|
# Detect clip type: try MIDI first, fallback to audio
|
||||||
|
is_midi = False
|
||||||
|
notes = []
|
||||||
|
try:
|
||||||
|
# MIDI clips have get_notes with 4 required params
|
||||||
|
notes_data = src_clip.get_notes(0, 0, src_clip.length if hasattr(src_clip, "length") else 4.0, 128)
|
||||||
|
notes = list(notes_data)
|
||||||
|
is_midi = True
|
||||||
|
except Exception:
|
||||||
|
is_midi = False # It's an audio clip
|
||||||
|
|
||||||
|
# Duplicate based on clip type
|
||||||
|
if is_midi and notes:
|
||||||
|
# MIDI clip - copy notes
|
||||||
|
result = self._cmd_generate_midi_clip(
|
||||||
|
tgt_track_idx, tgt_clip_idx,
|
||||||
|
notes=[{
|
||||||
|
"pitch": n[0], "start_time": n[1],
|
||||||
|
"duration": n[2], "velocity": n[3], "mute": n[4]
|
||||||
|
} for n in notes]
|
||||||
|
)
|
||||||
|
# Copy name
|
||||||
|
if result.get("created") and tgt_slot.has_clip:
|
||||||
|
tgt_slot.clip.name = src_clip.name + " (copy)"
|
||||||
|
return {"duplicated": True, "type": "midi", "result": result}
|
||||||
|
else:
|
||||||
|
# Audio clip - copy file reference and properties
|
||||||
|
# Get the file path from the source
|
||||||
|
file_path = None
|
||||||
|
|
||||||
|
# Try multiple methods to get the file path
|
||||||
|
if hasattr(src_clip, "file_path"):
|
||||||
|
file_path = src_clip.file_path
|
||||||
|
elif hasattr(src_clip, "sample"):
|
||||||
|
sample = src_clip.sample
|
||||||
|
if hasattr(sample, "file_path"):
|
||||||
|
file_path = sample.file_path
|
||||||
|
|
||||||
|
# Alternative: try to get from audio clip properties
|
||||||
|
if not file_path and hasattr(src_clip, "external_device"):
|
||||||
|
ext = src_clip.external_device
|
||||||
|
if hasattr(ext, "file_path"):
|
||||||
|
file_path = ext.file_path
|
||||||
|
|
||||||
|
if file_path:
|
||||||
|
result = self._cmd_load_sample_to_clip(
|
||||||
|
tgt_track_idx, tgt_clip_idx, file_path
|
||||||
|
)
|
||||||
|
# Copy warp settings
|
||||||
|
if result.get("loaded") and tgt_slot.has_clip:
|
||||||
|
tgt_slot.clip.name = src_clip.name + " (copy)"
|
||||||
|
if hasattr(src_clip, "warping") and hasattr(tgt_slot.clip, "warping"):
|
||||||
|
tgt_slot.clip.warping = src_clip.warping
|
||||||
|
return {"duplicated": True, "type": "audio", "result": result}
|
||||||
|
else:
|
||||||
|
return {"duplicated": False, "error": "Could not get audio file path from source clip"}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log_message("Error duplicating clip: %s" % str(e))
|
||||||
|
return {"duplicated": False, "error": str(e)}
|
||||||
|
|
||||||
def _cmd_load_sample_to_drum_rack_pad(self, track_index, pad_note, sample_path, **kw):
|
def _cmd_load_sample_to_drum_rack_pad(self, track_index, pad_note, sample_path, **kw):
|
||||||
"""T012: Load a sample into a specific Drum Rack pad (MIDI note)."""
|
"""T012: Load a sample into a specific Drum Rack pad (MIDI note)."""
|
||||||
import os
|
import os
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ TIMEOUTS = {
|
|||||||
"generate_complete_reggaeton": 60.0,
|
"generate_complete_reggaeton": 60.0,
|
||||||
"generate_from_reference": 60.0,
|
"generate_from_reference": 60.0,
|
||||||
"load_sample_to_clip": 15.0,
|
"load_sample_to_clip": 15.0,
|
||||||
|
"duplicate_clip": 15.0,
|
||||||
"create_arrangement_audio_clip": 20.0,
|
"create_arrangement_audio_clip": 20.0,
|
||||||
"set_warp_markers": 15.0,
|
"set_warp_markers": 15.0,
|
||||||
"reverse_clip": 10.0,
|
"reverse_clip": 10.0,
|
||||||
@@ -1123,6 +1124,29 @@ def load_sample_to_clip(ctx: Context, track_index: int, clip_index: int, sample_
|
|||||||
return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))
|
return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def duplicate_clip(ctx: Context, source_track: int, source_clip: int,
|
||||||
|
target_track: int, target_clip: int) -> str:
|
||||||
|
"""Duplicate/clone a clip from one Session View slot to another.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_track: Source track index
|
||||||
|
source_clip: Source clip slot index
|
||||||
|
target_track: Target track index (can be same as source)
|
||||||
|
target_clip: Target clip slot index
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON with duplication status and clip info.
|
||||||
|
"""
|
||||||
|
resp = _send_to_ableton(
|
||||||
|
"duplicate_clip",
|
||||||
|
{"source_track": source_track, "source_clip": source_clip,
|
||||||
|
"target_track": target_track, "target_clip": target_clip},
|
||||||
|
timeout=TIMEOUTS["duplicate_clip"]
|
||||||
|
)
|
||||||
|
return _ok(resp) if resp.get("status") == "success" else _err(resp.get("message"))
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def load_sample_to_drum_rack(ctx: Context, track_index: int, sample_path: str,
|
def load_sample_to_drum_rack(ctx: Context, track_index: int, sample_path: str,
|
||||||
pad_note: int = 36) -> str:
|
pad_note: int = 36) -> str:
|
||||||
@@ -4063,6 +4087,7 @@ def help(ctx: Context, tool_name: str = "") -> str:
|
|||||||
# Arrangement
|
# Arrangement
|
||||||
"create_arrangement_audio_pattern": {"description": "Crea clips de audio en Arrangement View", "category": "Arrangement", "params": [{"name": "track_index", "type": "int"}, {"name": "file_path", "type": "str"}, {"name": "positions", "type": "list", "default": [0]}, {"name": "name", "type": "str", "optional": True}], "example": "create_arrangement_audio_pattern(track_index=0, file_path='...', positions=[0, 4, 8])"},
|
"create_arrangement_audio_pattern": {"description": "Crea clips de audio en Arrangement View", "category": "Arrangement", "params": [{"name": "track_index", "type": "int"}, {"name": "file_path", "type": "str"}, {"name": "positions", "type": "list", "default": [0]}, {"name": "name", "type": "str", "optional": True}], "example": "create_arrangement_audio_pattern(track_index=0, file_path='...', positions=[0, 4, 8])"},
|
||||||
"load_sample_to_clip": {"description": "Carga sample en clip de Session View", "category": "Arrangement", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int"}, {"name": "sample_path", "type": "str"}], "example": "load_sample_to_clip(track_index=0, clip_index=0, sample_path='...')"},
|
"load_sample_to_clip": {"description": "Carga sample en clip de Session View", "category": "Arrangement", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int"}, {"name": "sample_path", "type": "str"}], "example": "load_sample_to_clip(track_index=0, clip_index=0, sample_path='...')"},
|
||||||
|
"duplicate_clip": {"description": "Duplica un clip a otro slot de Session View", "category": "Arrangement", "params": [{"name": "source_track", "type": "int"}, {"name": "source_clip", "type": "int"}, {"name": "target_track", "type": "int"}, {"name": "target_clip", "type": "int"}], "example": "duplicate_clip(source_track=0, source_clip=0, target_track=0, target_clip=1)"},
|
||||||
"load_sample_to_drum_rack": {"description": "Carga sample en pad de Drum Rack", "category": "Arrangement", "params": [{"name": "track_index", "type": "int"}, {"name": "sample_path", "type": "str"}, {"name": "pad_note", "type": "int", "default": 36}], "example": "load_sample_to_drum_rack(track_index=0, sample_path='...', pad_note=36)"},
|
"load_sample_to_drum_rack": {"description": "Carga sample en pad de Drum Rack", "category": "Arrangement", "params": [{"name": "track_index", "type": "int"}, {"name": "sample_path", "type": "str"}, {"name": "pad_note", "type": "int", "default": 36}], "example": "load_sample_to_drum_rack(track_index=0, sample_path='...', pad_note=36)"},
|
||||||
"set_warp_markers": {"description": "Configura marcadores de warp", "category": "Arrangement", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int"}, {"name": "markers", "type": "list"}], "example": "set_warp_markers(track_index=0, clip_index=0, markers=[...])"},
|
"set_warp_markers": {"description": "Configura marcadores de warp", "category": "Arrangement", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int"}, {"name": "markers", "type": "list"}], "example": "set_warp_markers(track_index=0, clip_index=0, markers=[...])"},
|
||||||
"reverse_clip": {"description": "Invierte un clip", "category": "Arrangement", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int"}], "example": "reverse_clip(track_index=0, clip_index=0)"},
|
"reverse_clip": {"description": "Invierte un clip", "category": "Arrangement", "params": [{"name": "track_index", "type": "int"}, {"name": "clip_index", "type": "int"}], "example": "reverse_clip(track_index=0, clip_index=0)"},
|
||||||
|
|||||||
Reference in New Issue
Block a user