Files
ableton-osc/AbletonOSC/manager.py
T
valknar aad042650e 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>
2026-06-01 12:27:47 +02:00

128 lines
3.9 KiB
Python

"""
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