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,111 @@
|
||||
"""Handles /live/scene/* OSC addresses."""
|
||||
import logging
|
||||
from typing import Any, Optional
|
||||
from .handler import AbletonOSCHandler
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SCENE_PROPS_RW = [
|
||||
"name", "color", "color_index",
|
||||
"tempo", "tempo_enabled",
|
||||
"time_signature_numerator", "time_signature_denominator",
|
||||
"time_signature_enabled",
|
||||
]
|
||||
|
||||
SCENE_PROPS_RO = ["is_empty", "is_triggered"]
|
||||
|
||||
|
||||
class SceneHandler(AbletonOSCHandler):
|
||||
def init_api(self) -> None:
|
||||
self.clear_listeners()
|
||||
|
||||
for prop in SCENE_PROPS_RW:
|
||||
self._add(f"/live/scene/get/{prop}", self._make_getter(prop))
|
||||
self._add(f"/live/scene/set/{prop}", self._make_setter(prop))
|
||||
self._add(f"/live/scene/start_listen/{prop}", self._make_start_listen(prop))
|
||||
self._add(f"/live/scene/stop_listen/{prop}", self._make_stop_listen(prop))
|
||||
|
||||
for prop in SCENE_PROPS_RO:
|
||||
self._add(f"/live/scene/get/{prop}", self._make_getter(prop))
|
||||
self._add(f"/live/scene/start_listen/{prop}", self._make_start_listen(prop))
|
||||
self._add(f"/live/scene/stop_listen/{prop}", self._make_stop_listen(prop))
|
||||
|
||||
self._add("/live/scene/fire", self._fire)
|
||||
self._add("/live/scene/fire_as_selected", self._fire_as_selected)
|
||||
self._add("/live/scene/get/num_clips", self._get_num_clips)
|
||||
self._add("/live/scene/get/clip_slots/has_clip", self._get_has_clips)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _get_scene(self, params: tuple) -> Optional[Any]:
|
||||
if not params:
|
||||
return None
|
||||
idx = int(params[0])
|
||||
scenes = list(self.song.scenes)
|
||||
return scenes[idx] if 0 <= idx < len(scenes) else None
|
||||
|
||||
def _make_getter(self, prop: str):
|
||||
def handler(params: tuple) -> Optional[tuple]:
|
||||
scene = self._get_scene(params)
|
||||
if scene is None:
|
||||
return None
|
||||
val = self._get_prop(scene, prop)
|
||||
return (int(params[0]), val) if val is not None else None
|
||||
return handler
|
||||
|
||||
def _make_setter(self, prop: str):
|
||||
def handler(params: tuple) -> None:
|
||||
if len(params) < 2:
|
||||
return None
|
||||
scene = self._get_scene(params)
|
||||
if scene:
|
||||
self._set_prop(scene, prop, params[1])
|
||||
return None
|
||||
return handler
|
||||
|
||||
def _make_start_listen(self, prop: str):
|
||||
def handler(params: tuple) -> None:
|
||||
scene = self._get_scene(params)
|
||||
if scene:
|
||||
self._register_listener(
|
||||
f"scene.{params[0]}.{prop}", scene, prop,
|
||||
f"/live/scene/get/{prop}", (int(params[0]),)
|
||||
)
|
||||
return handler
|
||||
|
||||
def _make_stop_listen(self, prop: str):
|
||||
def handler(params: tuple) -> None:
|
||||
if params:
|
||||
self._remove_listener(f"scene.{params[0]}.{prop}")
|
||||
return handler
|
||||
|
||||
def _fire(self, params: tuple) -> None:
|
||||
scene = self._get_scene(params)
|
||||
if scene:
|
||||
try:
|
||||
scene.fire()
|
||||
except Exception as e:
|
||||
logger.warning("scene.fire: %s", e)
|
||||
return None
|
||||
|
||||
def _fire_as_selected(self, params: tuple) -> None:
|
||||
scene = self._get_scene(params)
|
||||
if scene:
|
||||
try:
|
||||
scene.fire_as_selected()
|
||||
except Exception as e:
|
||||
logger.warning("scene.fire_as_selected: %s", e)
|
||||
return None
|
||||
|
||||
def _get_num_clips(self, params: tuple) -> Optional[tuple]:
|
||||
scene = self._get_scene(params)
|
||||
if scene is None:
|
||||
return None
|
||||
count = sum(1 for slot in scene.clip_slots if slot.has_clip)
|
||||
return (int(params[0]), count)
|
||||
|
||||
def _get_has_clips(self, params: tuple) -> Optional[tuple]:
|
||||
scene = self._get_scene(params)
|
||||
if scene is None:
|
||||
return None
|
||||
return (int(params[0]),) + tuple(bool(slot.has_clip) for slot in scene.clip_slots)
|
||||
Reference in New Issue
Block a user