From ac6a049516222d9eff6c7cc943e753571d59e900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Mon, 1 Jun 2026 12:46:42 +0200 Subject: [PATCH] Fix Ableton Live detection: add root __init__.py and harden Manager init Live scans the Remote Scripts folder for __init__.py with create_instance() at the *root* of the script folder. Our create_instance was buried inside the AbletonOSC/ subpackage, so Live never found it. Also: - Inner AbletonOSC/__init__.py: guard import with try/except for pytest compat - Manager: use show_message() (Live status bar) instead of log_message() - Manager: wrap OSCServer.start() in try/except OSError so a port conflict surfaces as a readable status message instead of a silent crash - Manager: run handler init inside component_guard() as required by the Ableton ControlSurface framework - Manager stub: add show_message, schedule_message, component_guard no-ops so out-of-Ableton tooling/tests don't break Co-Authored-By: Claude Sonnet 4.6 --- AbletonOSC/__init__.py | 13 ++++--------- AbletonOSC/manager.py | 37 +++++++++++++++++++++++++++---------- __init__.py | 9 +++++++++ 3 files changed, 40 insertions(+), 19 deletions(-) create mode 100644 __init__.py diff --git a/AbletonOSC/__init__.py b/AbletonOSC/__init__.py index 4d64c26..b61f0ad 100644 --- a/AbletonOSC/__init__.py +++ b/AbletonOSC/__init__.py @@ -1,9 +1,4 @@ -""" -AbletonOSC — full Ableton Live 11 OSC remote script. -Ableton calls create_instance() to instantiate the control surface. -""" -from .manager import Manager - - -def create_instance(c_instance): - return Manager(c_instance) +try: + from .manager import Manager +except ImportError: + pass diff --git a/AbletonOSC/manager.py b/AbletonOSC/manager.py index d6d78c2..52ac68b 100644 --- a/AbletonOSC/manager.py +++ b/AbletonOSC/manager.py @@ -9,7 +9,8 @@ import sys try: from ableton.v2.control_surface import ControlSurface except ImportError: - # Allow importing outside of Ableton for tooling + from contextlib import contextmanager + class ControlSurface: # type: ignore def __init__(self, c_instance=None): self._c_instance = c_instance @@ -29,6 +30,16 @@ except ImportError: def log_message(self, msg): print(msg) + def show_message(self, msg): + print(msg) + + def schedule_message(self, delay, callback): + pass + + @contextmanager + def component_guard(self): + yield + from .osc_server import OSCServer from .song import SongHandler from .track import TrackHandler @@ -52,12 +63,17 @@ 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", ()) + try: + self.osc_server = OSCServer(listen_port=LISTEN_PORT, send_port=SEND_PORT) + self.osc_server.start() + self._setup_handlers() + self.show_message("AbletonOSC: Listening on port %d" % LISTEN_PORT) + logger.info("AbletonOSC started (listen=%d, send=%d)", LISTEN_PORT, SEND_PORT) + self.osc_server.send("/live/startup", ()) + except OSError as e: + self.show_message("AbletonOSC: Couldn't bind to port %d (%s)" % (LISTEN_PORT, e)) + logger.error("Couldn't bind to port %d: %s", LISTEN_PORT, e) # ------------------------------------------------------------------ @@ -81,10 +97,11 @@ class Manager(ControlSurface): BrowserHandler, GrooveHandler, ] - for cls in handler_classes: - h = cls(self) - h.init_api() - self._handlers.append(h) + with self.component_guard(): + 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) diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..189a27a --- /dev/null +++ b/__init__.py @@ -0,0 +1,9 @@ +try: + from .AbletonOSC.manager import Manager +except ImportError: + # Allows pytest to import without Ableton's runtime present + pass + + +def create_instance(c_instance): + return Manager(c_instance)