Initial implementation: full Ableton Live 11 OSC remote script
Covers all major Live API objects with get/set/listen/method handlers: song, track, return_track, clip, clip_slot, device (incl. rack chains and drum pads), scene, view, application, browser, groove. Zero external runtime dependencies — OSC encoded/decoded in osc_server.py. Wildcard * support for track/scene indices. Listener callbacks fire to matching /get/ addresses for bidirectional state sync. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
"""Handles /live/return_track/* OSC addresses (mirrors TrackHandler for return tracks)."""
|
||||
import logging
|
||||
from typing import Any, List, Optional, Tuple
|
||||
from .handler import AbletonOSCHandler
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PROPS_RW = ["name", "mute", "solo", "color", "color_index"]
|
||||
PROPS_RO = ["output_meter_level", "output_meter_left", "output_meter_right"]
|
||||
|
||||
|
||||
class ReturnTrackHandler(AbletonOSCHandler):
|
||||
def init_api(self) -> None:
|
||||
self.clear_listeners()
|
||||
|
||||
for prop in PROPS_RW:
|
||||
self._add(f"/live/return_track/get/{prop}", self._make_getter(prop))
|
||||
self._add(f"/live/return_track/set/{prop}", self._make_setter(prop))
|
||||
self._add(f"/live/return_track/start_listen/{prop}",
|
||||
self._make_start_listen(prop))
|
||||
self._add(f"/live/return_track/stop_listen/{prop}",
|
||||
self._make_stop_listen(prop))
|
||||
|
||||
for prop in PROPS_RO:
|
||||
self._add(f"/live/return_track/get/{prop}", self._make_getter(prop))
|
||||
self._add(f"/live/return_track/start_listen/{prop}",
|
||||
self._make_start_listen(prop))
|
||||
self._add(f"/live/return_track/stop_listen/{prop}",
|
||||
self._make_stop_listen(prop))
|
||||
|
||||
self._add("/live/return_track/get/volume", self._get_volume)
|
||||
self._add("/live/return_track/set/volume", self._set_volume)
|
||||
self._add("/live/return_track/get/panning", self._get_panning)
|
||||
self._add("/live/return_track/set/panning", self._set_panning)
|
||||
self._add("/live/return_track/get/send", self._get_send)
|
||||
self._add("/live/return_track/set/send", self._set_send)
|
||||
self._add("/live/return_track/get/num_devices", self._get_num_devices)
|
||||
self._add("/live/return_track/get/devices/name", self._get_devices_name)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _get_return_tracks(self, params) -> List[Tuple[int, Any]]:
|
||||
tracks = list(self.song.return_tracks)
|
||||
if not params or params[0] == "*":
|
||||
return list(enumerate(tracks))
|
||||
idx = int(params[0])
|
||||
return [(idx, tracks[idx])] if 0 <= idx < len(tracks) else []
|
||||
|
||||
def _get_return_track(self, params) -> Optional[Any]:
|
||||
tracks = list(self.song.return_tracks)
|
||||
if not params:
|
||||
return None
|
||||
idx = int(params[0])
|
||||
return tracks[idx] if 0 <= idx < len(tracks) else None
|
||||
|
||||
def _make_getter(self, prop: str):
|
||||
def handler(params: tuple) -> Optional[tuple]:
|
||||
result = []
|
||||
for idx, track in self._get_return_tracks(params):
|
||||
val = self._get_prop(track, prop)
|
||||
if val is not None:
|
||||
result += [idx, val]
|
||||
return tuple(result) if result else None
|
||||
return handler
|
||||
|
||||
def _make_setter(self, prop: str):
|
||||
def handler(params: tuple) -> None:
|
||||
if len(params) < 2:
|
||||
return None
|
||||
track = self._get_return_track(params)
|
||||
if track:
|
||||
self._set_prop(track, prop, params[1])
|
||||
return None
|
||||
return handler
|
||||
|
||||
def _make_start_listen(self, prop: str):
|
||||
def handler(params: tuple) -> None:
|
||||
for idx, track in self._get_return_tracks(params):
|
||||
self._register_listener(
|
||||
f"return_track.{idx}.{prop}", track, prop,
|
||||
f"/live/return_track/get/{prop}", (idx,)
|
||||
)
|
||||
return handler
|
||||
|
||||
def _make_stop_listen(self, prop: str):
|
||||
def handler(params: tuple) -> None:
|
||||
for idx, _ in self._get_return_tracks(params):
|
||||
self._remove_listener(f"return_track.{idx}.{prop}")
|
||||
return handler
|
||||
|
||||
def _get_volume(self, params: tuple) -> Optional[tuple]:
|
||||
result = []
|
||||
for idx, track in self._get_return_tracks(params):
|
||||
try:
|
||||
result += [idx, track.mixer_device.volume.value]
|
||||
except Exception:
|
||||
pass
|
||||
return tuple(result) if result else None
|
||||
|
||||
def _set_volume(self, params: tuple) -> None:
|
||||
if len(params) < 2:
|
||||
return None
|
||||
track = self._get_return_track(params)
|
||||
if track:
|
||||
try:
|
||||
track.mixer_device.volume.value = float(params[1])
|
||||
except Exception as e:
|
||||
logger.warning("return_track set volume: %s", e)
|
||||
return None
|
||||
|
||||
def _get_panning(self, params: tuple) -> Optional[tuple]:
|
||||
result = []
|
||||
for idx, track in self._get_return_tracks(params):
|
||||
try:
|
||||
result += [idx, track.mixer_device.panning.value]
|
||||
except Exception:
|
||||
pass
|
||||
return tuple(result) if result else None
|
||||
|
||||
def _set_panning(self, params: tuple) -> None:
|
||||
if len(params) < 2:
|
||||
return None
|
||||
track = self._get_return_track(params)
|
||||
if track:
|
||||
try:
|
||||
track.mixer_device.panning.value = float(params[1])
|
||||
except Exception as e:
|
||||
logger.warning("return_track set panning: %s", e)
|
||||
return None
|
||||
|
||||
def _get_send(self, params: tuple) -> Optional[tuple]:
|
||||
if len(params) < 2:
|
||||
return None
|
||||
track = self._get_return_track(params)
|
||||
send_idx = int(params[1])
|
||||
if track:
|
||||
try:
|
||||
sends = list(track.mixer_device.sends)
|
||||
if 0 <= send_idx < len(sends):
|
||||
return (int(params[0]), send_idx, sends[send_idx].value)
|
||||
except Exception as e:
|
||||
logger.warning("return_track get send: %s", e)
|
||||
return None
|
||||
|
||||
def _set_send(self, params: tuple) -> None:
|
||||
if len(params) < 3:
|
||||
return None
|
||||
track = self._get_return_track(params)
|
||||
send_idx = int(params[1])
|
||||
if track:
|
||||
try:
|
||||
sends = list(track.mixer_device.sends)
|
||||
if 0 <= send_idx < len(sends):
|
||||
sends[send_idx].value = float(params[2])
|
||||
except Exception as e:
|
||||
logger.warning("return_track set send: %s", e)
|
||||
return None
|
||||
|
||||
def _get_num_devices(self, params: tuple) -> Optional[tuple]:
|
||||
track = self._get_return_track(params)
|
||||
return (len(track.devices),) if track else None
|
||||
|
||||
def _get_devices_name(self, params: tuple) -> Optional[tuple]:
|
||||
track = self._get_return_track(params)
|
||||
return tuple(d.name for d in track.devices) if track else None
|
||||
Reference in New Issue
Block a user