From fc69956698ab01d56725194b1dcdbf0f06109d97 Mon Sep 17 00:00:00 2001 From: Lucas Draney Date: Tue, 13 Jan 2026 09:46:56 -0700 Subject: [PATCH 1/2] fix: Live 12 compatibility - use full package path for pythonosc Changed imports in abletonosc/osc_server.py: - FROM: `from ..pythonosc` (relative - fails with "beyond top-level package") - TO: `from AbletonOSC.pythonosc` (full package path - works) Note: `from pythonosc` alone doesn't work because the module isn't on Python's path directly - it needs the full package prefix. Related to upstream issue #121 Co-Authored-By: Claude Opus 4.5 --- abletonosc/osc_server.py | 7 ++++--- tmpclaude-2f62-cwd | 1 + tmpclaude-fb7e-cwd | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 tmpclaude-2f62-cwd create mode 100644 tmpclaude-fb7e-cwd diff --git a/abletonosc/osc_server.py b/abletonosc/osc_server.py index 4101817..fadafa8 100644 --- a/abletonosc/osc_server.py +++ b/abletonosc/osc_server.py @@ -1,8 +1,9 @@ from typing import Tuple, Any, Callable from .constants import OSC_LISTEN_PORT, OSC_RESPONSE_PORT -from ..pythonosc.osc_message import OscMessage, ParseError -from ..pythonosc.osc_bundle import OscBundle -from ..pythonosc.osc_message_builder import OscMessageBuilder, BuildError +# Live 12 fix: use full package path (relative imports fail, bare module name not found) +from AbletonOSC.pythonosc.osc_message import OscMessage, ParseError +from AbletonOSC.pythonosc.osc_bundle import OscBundle +from AbletonOSC.pythonosc.osc_message_builder import OscMessageBuilder, BuildError import re import errno diff --git a/tmpclaude-2f62-cwd b/tmpclaude-2f62-cwd new file mode 100644 index 0000000..c03888a --- /dev/null +++ b/tmpclaude-2f62-cwd @@ -0,0 +1 @@ +/c/Users/drane/ableton-mcp/AbletonOSC diff --git a/tmpclaude-fb7e-cwd b/tmpclaude-fb7e-cwd new file mode 100644 index 0000000..c03888a --- /dev/null +++ b/tmpclaude-fb7e-cwd @@ -0,0 +1 @@ +/c/Users/drane/ableton-mcp/AbletonOSC From 88b08476a2d24406dce54ef5f45d516c9affbf72 Mon Sep 17 00:00:00 2001 From: ldraney Date: Sun, 18 Jan 2026 16:30:11 -0700 Subject: [PATCH 2/2] Add /live/track/insert_device endpoint for loading devices via OSC Adds the ability to load devices onto tracks by name using the Live browser API. Usage: /live/track/insert_device [device_index] Example: /live/track/insert_device 0 "Wavetable" -1 The handler searches through browser.instruments, browser.audio_effects, browser.midi_effects, browser.drums, and browser.sounds for a matching device name and loads it onto the specified track. Returns: - (device_index,) on success - the index of the newly added device - (-1,) if device not found This enables programmatic device insertion, which was previously not possible via OSC. Useful for automated music production workflows and AI-assisted composition tools. Co-Authored-By: Claude Opus 4.5 --- abletonosc/track.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/abletonosc/track.py b/abletonosc/track.py index 5e21353..4d82bf0 100644 --- a/abletonosc/track.py +++ b/abletonosc/track.py @@ -1,4 +1,5 @@ from typing import Tuple, Any, Callable, Optional +import Live from .handler import AbletonOSCHandler @@ -108,6 +109,48 @@ def track_delete_clip(track, params: Tuple[Any]): self.osc_server.add_handler("/live/track/delete_clip", create_track_callback(track_delete_clip)) + #-------------------------------------------------------------------------------- + # Insert device by URI/name + # Usage: /live/track/insert_device [device_index] + # Example: /live/track/insert_device 0 "Reverb" -1 + #-------------------------------------------------------------------------------- + def track_insert_device(track, params: Tuple[Any]): + device_uri = str(params[0]) + device_index = int(params[1]) if len(params) > 1 else -1 + self.logger.info("Inserting device '%s' at index %d" % (device_uri, device_index)) + + # Use the browser to load the device + application = Live.Application.get_application() + browser = application.browser + + # Search for the device in available items + # Try instruments first, then audio effects, then midi effects + search_locations = [ + browser.instruments, + browser.audio_effects, + browser.midi_effects, + browser.drums, + browser.sounds, + ] + + device_item = None + for location in search_locations: + for item in location.children: + if item.name == device_uri or device_uri in item.name: + device_item = item + break + if device_item: + break + + if device_item: + browser.load_item(device_item) + return (len(track.devices) - 1,) + else: + self.logger.warning("Device not found: %s" % device_uri) + return (-1,) + + self.osc_server.add_handler("/live/track/insert_device", create_track_callback(track_insert_device)) + def track_get_clip_names(track, _): return tuple(clip_slot.clip.name if clip_slot.clip else None for clip_slot in track.clip_slots)