Files
ableton-osc/AbletonOSC/browser.py
T
valknar 6fc0a84dfe Fix song/application access: property not method call
ControlSurface exposes song and application as properties, not callable
methods. Replace manager.song() and manager.application() with the correct
attribute access throughout handler, application, browser, and view modules.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 13:27:55 +02:00

150 lines
5.0 KiB
Python

"""Handles /live/browser/* OSC addresses."""
import logging
from typing import Optional
from .handler import AbletonOSCHandler
logger = logging.getLogger(__name__)
def _iter_items(node, path_parts, depth=0):
"""Recursively find a browser item by path."""
if not path_parts:
return node
name = path_parts[0]
try:
children = list(node.children)
except Exception:
return None
for child in children:
if child.name == name:
return _iter_items(child, path_parts[1:], depth + 1)
return None
class BrowserHandler(AbletonOSCHandler):
def init_api(self) -> None:
self.clear_listeners()
# Category listings (returns flat list of names)
for category in [
"audio_effects", "instruments", "midi_effects",
"samples", "sounds", "clips", "packs", "plugins",
]:
self._add(f"/live/browser/get/{category}",
self._make_category_lister(category))
# Load by path (e.g., "Instruments/Wavetable")
self._add("/live/browser/load_item", self._load_item)
self._add("/live/browser/preview_item", self._preview_item)
self._add("/live/browser/stop_preview", self._stop_preview)
# Hotswap
self._add("/live/browser/get/hotswap_target", self._get_hotswap_target)
self._add("/live/browser/begin_hotswap", self._begin_hotswap)
# ------------------------------------------------------------------
def _browser(self):
try:
return self.manager.application.browser
except Exception:
return None
def _make_category_lister(self, category: str):
def handler(params: tuple) -> Optional[tuple]:
browser = self._browser()
if browser is None:
return None
try:
root = getattr(browser, category)
names = [item.name for item in root.children]
return tuple(names)
except Exception as e:
logger.warning("browser.%s: %s", category, e)
return None
return handler
def _load_item(self, params: tuple) -> None:
"""params: path_part1 [, path_part2, ...] -- e.g. 'Instruments' 'Wavetable'"""
if not params:
return None
browser = self._browser()
if browser is None:
return None
path = [str(p) for p in params]
# Try to find within all top-level categories
for category in [
"audio_effects", "instruments", "midi_effects",
"samples", "sounds", "clips", "packs", "plugins",
]:
try:
root = getattr(browser, category)
item = _iter_items(root, path)
if item is not None:
browser.load_item(item)
return None
except Exception:
continue
logger.warning("browser.load_item: item not found: %s", path)
return None
def _preview_item(self, params: tuple) -> None:
if not params:
return None
browser = self._browser()
if browser is None:
return None
path = [str(p) for p in params]
for category in [
"audio_effects", "instruments", "midi_effects",
"samples", "sounds", "clips",
]:
try:
root = getattr(browser, category)
item = _iter_items(root, path)
if item is not None:
browser.preview_item(item)
return None
except Exception:
continue
return None
def _stop_preview(self, params: tuple) -> None:
browser = self._browser()
if browser:
try:
browser.stop_preview()
except Exception as e:
logger.warning("stop_preview: %s", e)
return None
def _get_hotswap_target(self, params: tuple) -> Optional[tuple]:
browser = self._browser()
if browser:
try:
target = browser.hotswap_target
if target is not None:
return (target.name,)
return ("",)
except Exception as e:
logger.warning("hotswap_target: %s", e)
return None
def _begin_hotswap(self, params: tuple) -> None:
"""params: track_idx, device_idx"""
if len(params) < 2:
return None
try:
track_idx = int(params[0])
device_idx = int(params[1])
tracks = list(self.song.tracks)
if 0 <= track_idx < len(tracks):
devices = list(tracks[track_idx].devices)
if 0 <= device_idx < len(devices):
browser = self._browser()
if browser:
browser.hotswap_target = devices[device_idx]
except Exception as e:
logger.warning("begin_hotswap: %s", e)
return None