128 lines
3.9 KiB
Python
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
|