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,127 @@
|
||||
"""
|
||||
Main ControlSurface entry point for Ableton Live.
|
||||
Manages OSC server lifecycle and all handler registrations.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
from ableton.v2.control_surface import ControlSurface
|
||||
except ImportError:
|
||||
# Allow importing outside of Ableton for tooling
|
||||
class ControlSurface: # type: ignore
|
||||
def __init__(self, c_instance=None):
|
||||
self._c_instance = c_instance
|
||||
|
||||
def song(self):
|
||||
return None
|
||||
|
||||
def application(self):
|
||||
return None
|
||||
|
||||
def disconnect(self):
|
||||
pass
|
||||
|
||||
def update_display(self):
|
||||
pass
|
||||
|
||||
def log_message(self, msg):
|
||||
print(msg)
|
||||
|
||||
from .osc_server import OSCServer
|
||||
from .song import SongHandler
|
||||
from .track import TrackHandler
|
||||
from .return_track import ReturnTrackHandler
|
||||
from .clip import ClipHandler
|
||||
from .clip_slot import ClipSlotHandler
|
||||
from .device import DeviceHandler
|
||||
from .scene import SceneHandler
|
||||
from .view import ViewHandler
|
||||
from .application import ApplicationHandler
|
||||
from .browser import BrowserHandler
|
||||
from .groove import GrooveHandler
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
LISTEN_PORT = int(os.environ.get("ABLETON_OSC_LISTEN_PORT", 11000))
|
||||
SEND_PORT = int(os.environ.get("ABLETON_OSC_SEND_PORT", 11001))
|
||||
|
||||
|
||||
class Manager(ControlSurface):
|
||||
def __init__(self, c_instance=None):
|
||||
super().__init__(c_instance)
|
||||
self._setup_logging()
|
||||
self.osc_server = OSCServer(listen_port=LISTEN_PORT, send_port=SEND_PORT)
|
||||
self._handlers = []
|
||||
self._setup_handlers()
|
||||
self.osc_server.start()
|
||||
logger.info("AbletonOSC started (listen=%d, send=%d)", LISTEN_PORT, SEND_PORT)
|
||||
self.osc_server.send("/live/startup", ())
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _setup_logging(self) -> None:
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
||||
)
|
||||
|
||||
def _setup_handlers(self) -> None:
|
||||
handler_classes = [
|
||||
ApplicationHandler,
|
||||
SongHandler,
|
||||
TrackHandler,
|
||||
ReturnTrackHandler,
|
||||
ClipHandler,
|
||||
ClipSlotHandler,
|
||||
DeviceHandler,
|
||||
SceneHandler,
|
||||
ViewHandler,
|
||||
BrowserHandler,
|
||||
GrooveHandler,
|
||||
]
|
||||
for cls in handler_classes:
|
||||
h = cls(self)
|
||||
h.init_api()
|
||||
self._handlers.append(h)
|
||||
|
||||
# Control endpoints
|
||||
self.osc_server.add_handler("/live/test", self._handle_test)
|
||||
self.osc_server.add_handler("/live/api/reload", self._handle_reload)
|
||||
self.osc_server.add_handler("/live/api/show_message", self._handle_show_message)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# ControlSurface interface
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def update_display(self) -> None:
|
||||
"""Called by Live ~100ms; drives the OSC poll loop."""
|
||||
self.osc_server.process()
|
||||
|
||||
def disconnect(self) -> None:
|
||||
for h in self._handlers:
|
||||
h.clear_listeners()
|
||||
self.osc_server.send("/live/shutdown", ())
|
||||
self.osc_server.stop()
|
||||
super().disconnect()
|
||||
logger.info("AbletonOSC disconnected")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Built-in handlers
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _handle_test(self, params: tuple):
|
||||
return ("AbletonOSC active",)
|
||||
|
||||
def _handle_reload(self, params: tuple):
|
||||
# Re-init all handlers (clears listeners and re-registers)
|
||||
for h in self._handlers:
|
||||
h.clear_listeners()
|
||||
h.init_api()
|
||||
return ("reloaded",)
|
||||
|
||||
def _handle_show_message(self, params: tuple):
|
||||
if params:
|
||||
self.log_message(str(params[0]))
|
||||
return None
|
||||
Reference in New Issue
Block a user