ROUTING learns stems the template does not name from the set (routing-learned.json); amber ones are fixed by hold/auto-fix from what the name says and earlier sets; START routes learned stems too
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+66
-6
@@ -819,6 +819,47 @@ def template_for(routing, name):
|
||||
return list(fam.pop()) if len(fam) == 1 else None
|
||||
|
||||
|
||||
# Stems the routing template does not name are learned from the set, as routed there (user, 2026-10-27: "where it's
|
||||
# routed right now is correct ... build your knowledge"). Kept beside the code, not in config.json: the settings'
|
||||
# template still wins, and a learned route is only ever asked about, never changed by a hold.
|
||||
LEARNED_ROUTING = os.path.join(os.path.dirname(os.path.abspath(__file__)), "routing-learned.json")
|
||||
|
||||
|
||||
def learned_routing():
|
||||
try:
|
||||
with open(LEARNED_ROUTING) as f:
|
||||
return json.load(f)
|
||||
except (OSError, ValueError):
|
||||
return {}
|
||||
|
||||
|
||||
def save_learned_routing(learned):
|
||||
tmp = LEARNED_ROUTING + ".tmp"
|
||||
with open(tmp, "w") as f:
|
||||
json.dump(learned, f, indent=1, sort_keys=True)
|
||||
os.replace(tmp, LEARNED_ROUTING)
|
||||
|
||||
|
||||
# What a stem is, from its name (the workflow's routing table: keys sounds 5/6, guitars and regular bass 7/8, drums,
|
||||
# percussion, aux drums, sub bass and FX 9/10, vocal effects, BGVs and choir 11/12), in this order: "VOX FX" is
|
||||
# vocals, "SYNTH BASS" the low end, "SYNTH LOOP" keys, a bare "LOOP" drums. The output comes from the template's own
|
||||
# entry for that kind of stem.
|
||||
KINDS = (("BGVS", r"\b(VOX|VOCALS?|BGVS?|CHOIR|VOICES?|HARMON(Y|IES)|SINGERS?)\b"),
|
||||
("SYNTH BASS", r"\b(SUB|SYNTH BASS|808)\b"),
|
||||
("PIANO", r"\b(KEYS?|PIANO|SYNTHS?|ORGAN|PADS?|STRINGS?|ARPS?|BELLS?|RHODES|WURLI|PLUCKS?|CELLO|VIOLINS?|BRASS|HORNS?|ORCH\w*|CELESTE|GLOCK\w*)\b"),
|
||||
("EG 1", r"\b(GTRS?|GUITARS?|AG|EG|ACOUSTIC|ELECTRIC|MANDOLIN|BANJO|BASS)\b"),
|
||||
("PERC", r"\b(DRUMS?|PERC\w*|SHAKERS?|TAMB\w*|CYMBALS?|KICK|SNARE|HATS?|CLAPS?|LOOPS?|HITS?|IMPACTS?|RISERS?|FX|TOMS|TIMPANI)\b"))
|
||||
|
||||
|
||||
def stem_kind(table, name):
|
||||
"""Where a stem's kind goes ([type, channel]), by its name; None when the name says nothing."""
|
||||
n = stem_name(name)
|
||||
for rep_, pattern in KINDS:
|
||||
if re.search(pattern, n) and rep_ in table:
|
||||
return list(table[rep_])
|
||||
return None
|
||||
|
||||
|
||||
def mix_plan(rc, live):
|
||||
"""(routing Line, ears Line, changes): what is routed away from the template, which song stems have
|
||||
their in-ear send at -inf, and the changes that put both right."""
|
||||
@@ -828,8 +869,10 @@ def mix_plan(rc, live):
|
||||
line.fault(WARN, live["error"], "%s: AbletonOSC on the Tracks Mac did not answer" % live["error"])
|
||||
return routing, ears, []
|
||||
table = {stem_name(k): v for k, v in (rc.get("routing") or {}).items()}
|
||||
learned = learned_routing()
|
||||
learned_before = dict(learned)
|
||||
skip = {norm(g) for g in rc.get("ears_skip_groups", [])}
|
||||
changes, top, n_route, n_ears, new = {}, None, 0, 0, []
|
||||
changes, top, n_route, n_ears = {}, None, 0, 0
|
||||
for t in live.get("tracks") or []:
|
||||
if t["group"] and not t["grouped"]:
|
||||
top = t["name"]
|
||||
@@ -847,16 +890,33 @@ def mix_plan(rc, live):
|
||||
elif want:
|
||||
n_route += 1
|
||||
elif table and top and norm(top) not in skip:
|
||||
new.append(t["name"])
|
||||
# Not in the template: what earlier sets taught (learned) and what its name says (kind) decide;
|
||||
# when they disagree the name wins, and a set that agrees with the name teaches it.
|
||||
cur = [t["type"], t["channel"] or ""]
|
||||
had, kind = template_for(learned, t["name"]), stem_kind(table, t["name"])
|
||||
fix = kind if kind and cur != kind and had != cur else (had if had and not kind and cur != list(had) else None)
|
||||
if fix:
|
||||
why = ("its name says %s" % stem_name(t["name"]).split()[0]) if fix == kind else "earlier sets had it there"
|
||||
changes.setdefault(t["index"], {"index": t["index"], "name": t["name"]}).update(type=fix[0], channel=fix[1])
|
||||
routing.fault(WARN, "%s TO %s" % (short(stem_name(t["name"]), 12), (t["channel"] or t["type"]).upper()[:10]),
|
||||
"%s (%s) goes to %s %s; it belongs at %s %s (%s): a hold moves it" % (
|
||||
t["name"], top, t["type"], t["channel"], fix[0], fix[1], why))
|
||||
else:
|
||||
if had is None or list(had) != cur:
|
||||
learned[stem_name(t["name"])] = cur
|
||||
routing.notes.append("learned: %s goes to %s %s (%s)" % (stem_name(t["name"]), t["type"], t["channel"], top))
|
||||
n_route += 1
|
||||
if top and norm(top) not in skip and t.get("send") is not None:
|
||||
if t["send"] <= 0.0:
|
||||
changes.setdefault(t["index"], {"index": t["index"], "name": t["name"]})["send"] = 1.0
|
||||
ears.fault(ERR, "EARS OFF " + short(top, 12), "%s in %s: send A is at -inf" % (t["name"], top))
|
||||
else:
|
||||
n_ears += 1
|
||||
if new:
|
||||
routing.fault(WARN, "NEW STEM " + short(stem_name(new[0]), 12), "not in the routing template (add them in the "
|
||||
"settings): " + ", ".join(new[:12]))
|
||||
if learned != learned_before:
|
||||
try:
|
||||
save_learned_routing(learned)
|
||||
except OSError:
|
||||
pass
|
||||
routing.info = "%d ROUTED" % n_route if table else "NO TEMPLATE"
|
||||
ears.info = "%d SENDS ON" % n_ears
|
||||
if live.get("playing"):
|
||||
@@ -2172,7 +2232,7 @@ def start_params(rc, services, mode):
|
||||
return {"mode": mode, "projects": projects, "site_folder": rc.get("site_folder", ""),
|
||||
"folder_format": rc.get("folder_format", "{date} {loc} Project"), "finished_folders": rc.get("finished_folders", []),
|
||||
"pads_folder": rc.get("pads_folder") or os.path.join(os.path.dirname(projects.rstrip("/")), "PADS"),
|
||||
"tune_library": rc.get("tune_library", ""), "routing": rc.get("routing") or {},
|
||||
"tune_library": rc.get("tune_library", ""), "routing": dict(learned_routing(), **(rc.get("routing") or {})),
|
||||
"keep_groups": rc.get("start_keep_groups") or rc.get("ears_skip_groups") or ["MIDI GROUP", "RESI TRACKS"],
|
||||
"pad_track": rc.get("pad_track", "AMBIENT PADS"), "tune_track": rc.get("tune_track", "WAVES TUNE"),
|
||||
"lighting_track": rc.get("lighting_track", "LIGHTING CUES"), "sections_track": rc.get("sections_track", "SECTIONS"),
|
||||
|
||||
Reference in New Issue
Block a user