159 lines
5.7 KiB
Python
159 lines
5.7 KiB
Python
|
|
"""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
|