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 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 12:46:42 +02:00
parent caaa055c8a
commit ac6a049516
3 changed files with 40 additions and 19 deletions
+4 -9
View File
@@ -1,9 +1,4 @@
""" try:
AbletonOSC — full Ableton Live 11 OSC remote script. from .manager import Manager
Ableton calls create_instance() to instantiate the control surface. except ImportError:
""" pass
from .manager import Manager
def create_instance(c_instance):
return Manager(c_instance)
+20 -3
View File
@@ -9,7 +9,8 @@ import sys
try: try:
from ableton.v2.control_surface import ControlSurface from ableton.v2.control_surface import ControlSurface
except ImportError: except ImportError:
# Allow importing outside of Ableton for tooling from contextlib import contextmanager
class ControlSurface: # type: ignore class ControlSurface: # type: ignore
def __init__(self, c_instance=None): def __init__(self, c_instance=None):
self._c_instance = c_instance self._c_instance = c_instance
@@ -29,6 +30,16 @@ except ImportError:
def log_message(self, msg): def log_message(self, msg):
print(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 .osc_server import OSCServer
from .song import SongHandler from .song import SongHandler
from .track import TrackHandler from .track import TrackHandler
@@ -52,12 +63,17 @@ class Manager(ControlSurface):
def __init__(self, c_instance=None): def __init__(self, c_instance=None):
super().__init__(c_instance) super().__init__(c_instance)
self._setup_logging() self._setup_logging()
self.osc_server = OSCServer(listen_port=LISTEN_PORT, send_port=SEND_PORT)
self._handlers = [] self._handlers = []
self._setup_handlers() try:
self.osc_server = OSCServer(listen_port=LISTEN_PORT, send_port=SEND_PORT)
self.osc_server.start() 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) logger.info("AbletonOSC started (listen=%d, send=%d)", LISTEN_PORT, SEND_PORT)
self.osc_server.send("/live/startup", ()) 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,6 +97,7 @@ class Manager(ControlSurface):
BrowserHandler, BrowserHandler,
GrooveHandler, GrooveHandler,
] ]
with self.component_guard():
for cls in handler_classes: for cls in handler_classes:
h = cls(self) h = cls(self)
h.init_api() h.init_api()
+9
View File
@@ -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)