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