"""Handles /live/clip_slot/* OSC addresses.""" import logging from typing import Any, Optional from .handler import AbletonOSCHandler logger = logging.getLogger(__name__) SLOT_PROPS_RO = [ "has_clip", "is_playing", "is_triggered", "is_recording", "is_group_slot", "controls_other_clips", "playing_status", "will_record_on_start", ] SLOT_PROPS_RW = ["has_stop_button"] class ClipSlotHandler(AbletonOSCHandler): def init_api(self) -> None: self.clear_listeners() for prop in SLOT_PROPS_RO: self._add(f"/live/clip_slot/get/{prop}", self._make_getter(prop)) self._add(f"/live/clip_slot/start_listen/{prop}", self._make_start_listen(prop)) self._add(f"/live/clip_slot/stop_listen/{prop}", self._make_stop_listen(prop)) for prop in SLOT_PROPS_RW: self._add(f"/live/clip_slot/get/{prop}", self._make_getter(prop)) self._add(f"/live/clip_slot/set/{prop}", self._make_setter(prop)) self._add(f"/live/clip_slot/start_listen/{prop}", self._make_start_listen(prop)) self._add(f"/live/clip_slot/stop_listen/{prop}", self._make_stop_listen(prop)) self._add("/live/clip_slot/fire", self._fire) self._add("/live/clip_slot/stop", self._stop) self._add("/live/clip_slot/create_clip", self._create_clip) self._add("/live/clip_slot/delete_clip", self._delete_clip) self._add("/live/clip_slot/duplicate_clip_to", self._duplicate_clip_to) # ------------------------------------------------------------------ def _get_slot(self, params: tuple) -> Optional[Any]: if len(params) < 2: return None try: track_idx = int(params[0]) slot_idx = int(params[1]) tracks = list(self.song.tracks) if 0 <= track_idx < len(tracks): slots = list(tracks[track_idx].clip_slots) if 0 <= slot_idx < len(slots): return slots[slot_idx] except Exception as e: logger.warning("get_slot(%s): %s", params, e) return None def _slot_key(self, params: tuple) -> str: return f"clip_slot.{params[0]}.{params[1]}" # ------------------------------------------------------------------ def _make_getter(self, prop: str): def handler(params: tuple) -> Optional[tuple]: slot = self._get_slot(params) if slot is None: return None val = self._get_prop(slot, prop) if val is None: return None return (int(params[0]), int(params[1]), val) return handler def _make_setter(self, prop: str): def handler(params: tuple) -> None: if len(params) < 3: return None slot = self._get_slot(params) if slot: self._set_prop(slot, prop, params[2]) return None return handler def _make_start_listen(self, prop: str): def handler(params: tuple) -> None: slot = self._get_slot(params) if slot: key = f"{self._slot_key(params)}.{prop}" prefix = (int(params[0]), int(params[1])) self._register_listener( key, slot, prop, f"/live/clip_slot/get/{prop}", prefix ) return handler def _make_stop_listen(self, prop: str): def handler(params: tuple) -> None: if len(params) >= 2: self._remove_listener(f"{self._slot_key(params)}.{prop}") return handler # ------------------------------------------------------------------ def _fire(self, params: tuple) -> None: slot = self._get_slot(params) if slot: try: slot.fire() except Exception as e: logger.warning("clip_slot.fire: %s", e) return None def _stop(self, params: tuple) -> None: slot = self._get_slot(params) if slot: try: slot.stop() except Exception as e: logger.warning("clip_slot.stop: %s", e) return None def _create_clip(self, params: tuple) -> None: """params: track_idx, slot_idx [, length_in_beats]""" slot = self._get_slot(params) if slot: try: length = float(params[2]) if len(params) > 2 else 4.0 slot.create_clip(length) except Exception as e: logger.warning("clip_slot.create_clip: %s", e) return None def _delete_clip(self, params: tuple) -> None: slot = self._get_slot(params) if slot and slot.has_clip: try: slot.delete_clip() except Exception as e: logger.warning("clip_slot.delete_clip: %s", e) return None def _duplicate_clip_to(self, params: tuple) -> None: """params: src_track_idx, src_slot_idx, dst_track_idx, dst_slot_idx""" if len(params) < 4: return None src_slot = self._get_slot(params) if src_slot and src_slot.has_clip: try: dst_track_idx = int(params[2]) dst_slot_idx = int(params[3]) tracks = list(self.song.tracks) if 0 <= dst_track_idx < len(tracks): dst_slots = list(tracks[dst_track_idx].clip_slots) if 0 <= dst_slot_idx < len(dst_slots): src_slot.duplicate_clip_to(dst_slots[dst_slot_idx]) except Exception as e: logger.warning("clip_slot.duplicate_clip_to: %s", e) return None