START: bright red guide, no transposing unpitched stems, consolidate + warp off in Live
Per the Location Setup worksheet (2026-10-26): - a song saved twice holds two guides; START takes the bright red one (Live clip color 14), not the newer light pink one - drums, percussion, aux drums and other unpitched stems are never transposed (by stem name; sub bass and FX are) - every transposed clip is consolidated in Live (Edit > Consolidate) and its warp turned off, then the set is saved; a hold of START does it for this week's existing sets too. New AbletonOSC commands: clip, select_clip, set_clip - the START key's CONSOLIDATE comes from the saved set (clips still transposed), not the last build's log Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+78
-11
@@ -1848,7 +1848,11 @@ def look(cfg, now=None, data_dir=None, sources=None):
|
||||
lines["track_bundle"] = eval_track_bundle(rc, al)
|
||||
except Exception as e: # noqa: BLE001
|
||||
failed(["tracks", "mix_foh", "lights", "keys", "tune", "light_timing"] + (["track_bundle"] if rc.get("locations") else []), e)
|
||||
if rc.get("tracks_host") and rc.get("ableton_osc"):
|
||||
if rc.get("tracks_host") and rc.get("ableton_osc") and start_in_live():
|
||||
for k in ("routing", "ears"):
|
||||
lines[k] = Line()
|
||||
lines[k].info = "START IN LIVE"
|
||||
elif rc.get("tracks_host") and rc.get("ableton_osc"):
|
||||
try:
|
||||
lines["routing"], lines["ears"], _ = mix_plan(rc, src["live"]())
|
||||
except Exception as e: # noqa: BLE001
|
||||
@@ -1938,13 +1942,53 @@ SERVICES = {} # the services START builds, kept a quarter of an
|
||||
IS_LIVE = [lambda: False] # the Poller's "a service is on": the hold copies gigabytes, never during one
|
||||
|
||||
START_SURVEY = r'''
|
||||
import json, os
|
||||
import gzip, json, os, re
|
||||
import xml.etree.ElementTree as ET
|
||||
P = json.loads(%r)
|
||||
out = {}
|
||||
CACHE = os.path.expanduser("~/Library/Caches/status-panel-start-pending.json")
|
||||
try:
|
||||
cache = json.load(open(CACHE))
|
||||
except Exception:
|
||||
cache = {}
|
||||
def transposed(path):
|
||||
# {song group: clips still transposed} (consolidated ones are back at 0), by the set's mtime
|
||||
key = "%%s|%%s" %% (path, os.path.getmtime(path))
|
||||
if key not in cache:
|
||||
ls = ET.parse(gzip.open(path)).getroot().find("LiveSet")
|
||||
up = {}
|
||||
for t in ls.find("Tracks"):
|
||||
g = t.find("TrackGroupId")
|
||||
up[t.get("Id")] = (t.find("Name/EffectiveName").get("Value"), g.get("Value") if g is not None else "-1")
|
||||
def top(i):
|
||||
while up.get(i, ("", "-1"))[1] in up:
|
||||
i = up[i][1]
|
||||
return up.get(i, ("", ""))[0]
|
||||
got = {}
|
||||
for t in ls.find("Tracks"):
|
||||
for c in t.iter("AudioClip"):
|
||||
if c.find("PitchCoarse") is not None and float(c.find("PitchCoarse").get("Value")) != 0:
|
||||
song = top(t.get("Id"))
|
||||
got[song] = got.get(song, 0) + 1
|
||||
cache[key] = got
|
||||
return cache[key]
|
||||
out, keep = {}, {}
|
||||
for s in P["services"]:
|
||||
d = os.path.join(os.path.expanduser(P["projects"]), s["folder"], s["name"])
|
||||
out[s["name"]] = {"exists": os.path.isdir(d) and any(n.endswith(".als") for n in os.listdir(d)),
|
||||
"building": os.path.isdir(d + ".building")}
|
||||
als = [os.path.join(d, n) for n in os.listdir(d) if n.endswith(".als") and not n.startswith(".")] if os.path.isdir(d) else []
|
||||
out[s["name"]] = {"exists": bool(als), "building": os.path.isdir(d + ".building")}
|
||||
if als:
|
||||
path = max(als, key=os.path.getmtime)
|
||||
try:
|
||||
out[s["name"]]["transposed"] = transposed(path)
|
||||
k = "%%s|%%s" %% (path, os.path.getmtime(path))
|
||||
keep[k] = cache[k]
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
with open(CACHE, "w") as f:
|
||||
json.dump(keep, f)
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
st = json.load(open(os.path.expanduser("~/Library/Caches/status-panel-start.json")))
|
||||
except Exception:
|
||||
@@ -2092,7 +2136,9 @@ def start_params(rc, services, mode):
|
||||
"lighting_track": rc.get("lighting_track", "LIGHTING CUES"), "sections_track": rc.get("sections_track", "SECTIONS"),
|
||||
"guide_track": rc.get("guide_track", "+GUIDE"), "loopguide_track": rc.get("loopguide_track", "+LOOPGUIDE"),
|
||||
"loop_cue_track": rc.get("loop_cue_track", "Loop Cue"), "tempo_track": rc.get("tempo_track", "TEMPO"),
|
||||
"send_a": 1.0, "save_songs": rc.get("start_save_songs", True), "services": services}
|
||||
"send_a": 1.0, "save_songs": rc.get("start_save_songs", True), "services": services,
|
||||
"guide_color": rc.get("start_guide_color", 14), "unpitched": rc.get("start_unpitched") or None,
|
||||
"consolidate": rc.get("start_consolidate", True)}
|
||||
|
||||
|
||||
def start_script(params):
|
||||
@@ -2103,8 +2149,21 @@ def start_script(params):
|
||||
|
||||
|
||||
def start_look(cfg, rc, services):
|
||||
return run_remote(cfg, rc["tracks_host"], START_SURVEY % json.dumps({"projects": rc.get("projects", ""), "services": [
|
||||
look = run_remote(cfg, rc["tracks_host"], START_SURVEY % json.dumps({"projects": rc.get("projects", ""), "services": [
|
||||
{"folder": x["folder"], "name": x["name"]} for x in services]}), timeout=30)
|
||||
st = look.get("status") or {}
|
||||
LAST["start_status"] = st
|
||||
# START used AbletonOSC from the Tracks Mac (consolidating): hand it back to Companion once it is done.
|
||||
if st.get("live") and st.get("state") != "running" and LAST.get("start_handed") != st["live"]:
|
||||
hand_back(cfg, rc["tracks_host"])
|
||||
LAST["start_handed"] = st["live"]
|
||||
return look
|
||||
|
||||
|
||||
def start_in_live():
|
||||
"""START is consolidating in Live just now (it holds AbletonOSC's answers on the Tracks Mac)."""
|
||||
st = LAST.get("start_status") or {}
|
||||
return st.get("state") == "running" and time.time() - (st.get("live") or 0) < 600
|
||||
|
||||
|
||||
def eval_start(rc, services, look, now):
|
||||
@@ -2128,14 +2187,22 @@ def eval_start(rc, services, look, now):
|
||||
if res.get("name") not in names:
|
||||
continue
|
||||
for note in res.get("notes") or []:
|
||||
m = re.match(r"TRANSPOSE (.+?) \S+>\S+", note)
|
||||
n = re.match(r"NEW SONG (.+?):", note)
|
||||
if m:
|
||||
line.fault(WARN, "CONSOLIDATE " + short(m.group(1), 12), "%s: %s" % (res["name"], note))
|
||||
elif n:
|
||||
if n:
|
||||
line.fault(WARN, "NEW SONG " + short(n.group(1), 12), "%s: %s" % (res["name"], note))
|
||||
else:
|
||||
line.notes.append("%s: %s" % (res["name"], note))
|
||||
if not running:
|
||||
# The workflow consolidates every transposed clip (and turns its warp off): a hold does it in Live.
|
||||
for svc in services:
|
||||
for song, n in sorted(((survey.get(svc["name"]) or {}).get("transposed") or {}).items()):
|
||||
line.fault(WARN, "CONSOLIDATE " + short(song.upper(), 12),
|
||||
"%s: %s has %d transposed clip%s not consolidated: hold START to consolidate them in Live"
|
||||
% (svc["name"], song, n, "" if n == 1 else "s"))
|
||||
if st.get("state") == "done" and now - (st.get("time") or 0) < 86400:
|
||||
for note in st.get("log") or []:
|
||||
if "CONSOLIDATE IN LIVE:" in note or note.startswith("OPEN ") or "NOT CONSOLIDATED" in note or "DID NOT SAVE" in note:
|
||||
line.notes.append(note)
|
||||
line.info = " ".join(x["label"] for x in services) or "NO SERVICES"
|
||||
return line
|
||||
|
||||
|
||||
Reference in New Issue
Block a user