LIGHT TIMING: the real offset up to a full 16th, no flag on a cue already on a solid hit, hold moves the cues
- Detection in 2.5 ms steps; where a hit starts is halfway up its rise; each song's cues on solid hits set its "on time"; a cue already on a solid hit is never named; "about a 16th" when it is - Hold: moves the off cues onto their hit in the open set through a status panel command added to AbletonOSC (tools/abletonosc), keeping each cue's end; the key asks for a save until the set is saved Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
#!/bin/zsh
|
||||
# Adds the status panel's commands to AbletonOSC on this Mac (the Tracks Mac) and reloads AbletonOSC.
|
||||
# Run from NA Ingest: ssh tracks 'zsh -s' < install.sh (statuspanel.py copied next to AbletonOSC first)
|
||||
# Reloading clears AbletonOSC's listeners: reconnect Companion's AbletonOSC connection afterwards.
|
||||
set -e
|
||||
D="$HOME/Music/Ableton/User Library/Remote Scripts/AbletonOSC/abletonosc"
|
||||
cp /tmp/statuspanel.py "$D/statuspanel.py"
|
||||
if ! grep -q "statuspanel" "$D/__init__.py"; then
|
||||
cat >> "$D/__init__.py" <<'PY'
|
||||
|
||||
# The status panel's commands (status-indicators tools/abletonosc): registered with the track handler.
|
||||
try:
|
||||
import importlib as _importlib
|
||||
from . import statuspanel as _statuspanel
|
||||
_importlib.reload(_statuspanel)
|
||||
|
||||
class TrackHandler(TrackHandler):
|
||||
def init_api(self):
|
||||
super().init_api()
|
||||
try:
|
||||
_statuspanel.register(self)
|
||||
except Exception:
|
||||
logger.exception("statuspanel: not registered")
|
||||
except Exception:
|
||||
logger.exception("statuspanel: not loaded")
|
||||
PY
|
||||
fi
|
||||
/usr/bin/python3 -c 'import socket; s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.sendto(b"/live/api/reload\0\0\0\0,\0\0\0", ("127.0.0.1", 11000))'
|
||||
echo "installed and reloaded"
|
||||
@@ -0,0 +1,36 @@
|
||||
"""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.
|
||||
"""
|
||||
import logging
|
||||
|
||||
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)
|
||||
Reference in New Issue
Block a user