Files
status-indicators/tools/abletonosc/statuspanel.py
T

98 lines
4.3 KiB
Python
Raw Normal View History

"""Status panel additions to AbletonOSC (installed by tools/abletonosc/install.sh on the Tracks Mac).
/live/statuspanel/move_note <track name> <beat> <pitch> <new beat>
Moves the note of `pitch` that starts at arrangement `beat` on the arrangement MIDI clip of the track
called `track name` so it starts at `new beat`, keeping where it ends (the next cue is not touched).
Answers (<track name>, <beat>, <pitch>, <moved: 0 or 1>). Undo in Live (Cmd+Z) puts it back.
START's consolidating (ableton_start.py) finds an arrangement clip by <track index> (Live's, no returns),
<track name> (any track of that name when the index has moved) and the <beat> it starts at:
/live/statuspanel/clip <index> <name> <beat>
Answers (<index>, <name>, <beat>, <found>, <pitch_coarse>, <warping>, <file path>).
/live/statuspanel/select_clip <index> <name> <beat>
Selects the clip in the arrangement (Edit > Consolidate then works on it). Answers (..., <found>).
/live/statuspanel/set_clip <index> <name> <beat> <pitch_coarse> <warping>
Sets the clip's transposition, then its warp. Answers (..., <found>, <pitch_coarse>, <warping>).
"""
import logging
import Live
logger = logging.getLogger("abletonosc")
def register(handler):
def move_note(params):
song = handler.song
name, beat, pitch, new = str(params[0]), float(params[1]), int(params[2]), float(params[3])
track = next((t for t in song.tracks if t.name.strip().upper() == name.strip().upper() and t.has_midi_input), None)
moved = 0
for clip in (track.arrangement_clips if track else []):
if not (clip.start_time - 1e-6 <= beat < clip.end_time):
continue
zero = clip.start_time - (clip.loop_start if clip.looping else clip.start_marker)
local, new_local = beat - zero, new - zero
notes = clip.get_notes_extended(pitch, 1, local - 0.002, 0.004)
for n in notes:
end = n.start_time + n.duration
if new_local < end:
n.start_time, n.duration = new_local, end - new_local
moved += 1
if moved:
clip.apply_note_modifications(notes)
break
logger.info("statuspanel: %s note %d at %s -> %s: %d moved", name, pitch, beat, new, moved)
return name, beat, pitch, moved
handler.osc_server.add_handler("/live/statuspanel/move_note", move_note)
def find(params):
song = handler.song
i, name, beat = int(params[0]), str(params[1]).strip().upper(), float(params[2])
ts = list(song.tracks)
order = ([ts[i]] if 0 <= i < len(ts) and ts[i].name.strip().upper() == name else []) + \
[t for t in ts if t.name.strip().upper() == name]
for t in order:
try:
arr = t.arrangement_clips
except Exception:
continue
for c in arr:
if abs(c.start_time - beat) < 0.01:
return t, c
return None, None
def info(c):
if c is None:
return 0, 0, 0, ""
return 1, int(c.pitch_coarse), int(c.warping), str(c.file_path)
def clip(params):
_, c = find(params)
return (int(params[0]), str(params[1]), float(params[2])) + info(c)
def select_clip(params):
t, c = find(params)
if c is not None:
song = handler.song
view = Live.Application.get_application().view
view.show_view("Arranger")
view.focus_view("Arranger")
song.view.selected_track = t
song.view.detail_clip = c
view.focus_view("Arranger")
logger.info("statuspanel: select %s at %s: %s", params[1], params[2], c is not None)
return int(params[0]), str(params[1]), float(params[2]), int(c is not None)
def set_clip(params):
_, c = find(params)
if c is not None:
c.pitch_coarse = int(params[3])
c.warping = bool(int(params[4]))
logger.info("statuspanel: %s at %s: pitch %s warp %s", params[1], params[2], params[3], params[4])
return (int(params[0]), str(params[1]), float(params[2])) + info(c)[:3]
handler.osc_server.add_handler("/live/statuspanel/clip", clip)
handler.osc_server.add_handler("/live/statuspanel/select_clip", select_clip)
handler.osc_server.add_handler("/live/statuspanel/set_clip", set_clip)