Settings > MIDI links: the wired network and every link's address, editable, with a not-wired flag
An address change also updates the same link on the READY page. Without a wired rule, a button adds one. The add-a-link screen shares the same wired-network helpers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+27
-5
@@ -1225,10 +1225,23 @@
|
|||||||
return `<div class="card">${!cc ? "<p class='notice grey'>Companion is not answering; the list is from the keys.</p>" : ""}<div class="tablewrap"><table><tr><th></th><th>in use${hint("Tick a deck and it gets an HD key in the REC group; untick and the key goes (its cell stays empty)")}</th><th>key</th><th></th></tr>${rows || "<tr><td colspan=3 class='muted'>No HyperDeck connections in Companion.</td></tr>"}</table></div>
|
return `<div class="card">${!cc ? "<p class='notice grey'>Companion is not answering; the list is from the keys.</p>" : ""}<div class="tablewrap"><table><tr><th></th><th>in use${hint("Tick a deck and it gets an HD key in the REC group; untick and the key goes (its cell stays empty)")}</th><th>key</th><th></th></tr>${rows || "<tr><td colspan=3 class='muted'>No HyperDeck connections in Companion.</td></tr>"}</table></div>
|
||||||
${editing() && rows ? `<div class="row" style="margin-top:8px"><button class="primary small" id="decksave">Use these decks</button></div>` : ""}</div>`;
|
${editing() && rows ? `<div class="row" style="margin-top:8px"><button class="primary small" id="decksave">Use these decks</button></div>` : ""}</div>`;
|
||||||
}
|
}
|
||||||
|
// The wired network(s) MIDI must ride: the MIDI key's wired check (empty = no rule).
|
||||||
|
function midiNets(c) { return c.tiles.concat((c.ready_page || {}).keys || []).flatMap((t) => (t.checks || []).filter((ch) => ch.type === "midi_wired" && !ch.disabled).flatMap((ch) => ch.nets || [])); }
|
||||||
|
function ip4(a) { const m = /^(\d+)\.(\d+)\.(\d+)\.(\d+)(?::\d+)?$/.exec(String(a || "").trim()); return m ? m.slice(1, 5).reduce((n, x) => n * 256 + Number(x), 0) : null; }
|
||||||
|
function onWired(nets, a) { const v = ip4(a); return v !== null && nets.some((n) => { const [b, bits] = String(n).split("/"); const k = Number(bits || 32), base = ip4(b); return base !== null && Math.floor(v / 2 ** (32 - k)) === Math.floor(base / 2 ** (32 - k)); }); }
|
||||||
function midiHtml(c) {
|
function midiHtml(c) {
|
||||||
|
const nets = midiNets(c);
|
||||||
|
let rule = null;
|
||||||
|
c.tiles.forEach((t, ti) => (t.checks || []).forEach((ch, j) => { if (!rule && ch.type === "midi_wired") rule = { ti, j, ch }; }));
|
||||||
|
const netRow = rule
|
||||||
|
? `<div class="field"><span class="fl">wired network${hint("Every MIDI link must come over this network (several: comma between). A link on Wi-Fi or Dante, or joined by name, turns the MIDI key red; holding MIDI moves it here.")}</span><input data-path="tiles.${rule.ti}.checks.${rule.j}.nets" value="${esc((rule.ch.nets || []).join(", "))}" placeholder="10.1.0.0/16" ${editing() ? "" : "disabled"}></div>`
|
||||||
|
: `<p class="small muted">No wired-network rule: MIDI links may come over any network.${editing() ? ` <button class="small" id="addwired">Require wired</button>` : ""}</p>`;
|
||||||
const rows = [];
|
const rows = [];
|
||||||
c.tiles.forEach((t, ti) => (t.checks || []).forEach((ch, j) => { if (ch.type === "midi_peer") rows.push(`<tr class="${ch.disabled || t.disabled ? "off" : ""}"><td>${onOff(`tiles.${ti}.checks.${j}`, ch, "this MIDI link")}</td><td>${esc(t.label)}</td><td>${esc(ch.host ? (c.hosts[ch.host] || {}).name || ch.host : (c.site || {}).ingest_name || "this Mac")}</td><td class="mono small">${esc(ch.session)}</td><td class="mono small">${esc(ch.peer)}</td><td class="small">${esc(ch.tag || "")}</td><td>${editing() ? `<button class="small danger" data-rmcheck="${esc(t.id)}|${j}">Remove</button>` : ""}</td></tr>`); }));
|
c.tiles.forEach((t, ti) => (t.checks || []).forEach((ch, j) => { if (ch.type === "midi_peer") {
|
||||||
return `<div class="card"><div class="tablewrap"><table><tr><th></th><th>key</th><th>Mac</th><th>session</th><th>peer</th><th>key text</th><th></th></tr>${rows.join("") || "<tr><td colspan=7 class='muted'>none</td></tr>"}</table></div>
|
const bad = nets.length && !onWired(nets, ch.address);
|
||||||
|
const addr = editing() ? `<input class="mono small" data-midiaddr="${ti}|${j}" value="${esc(ch.address || "")}" placeholder="10.1.x.x:5004" size="16">` : `<span class="mono small">${esc(ch.address || "–")}</span>`;
|
||||||
|
rows.push(`<tr class="${ch.disabled || t.disabled ? "off" : ""}"><td>${onOff(`tiles.${ti}.checks.${j}`, ch, "this MIDI link")}</td><td>${esc(t.label)}</td><td>${esc(ch.host ? (c.hosts[ch.host] || {}).name || ch.host : (c.site || {}).ingest_name || "this Mac")}</td><td class="mono small">${esc(ch.session)}</td><td class="mono small">${esc(ch.peer)}</td><td>${addr}${bad ? ` <span class="pill err" data-hint="Not on the wired network: give its wired address">not wired</span>` : ""}</td><td class="small">${esc(ch.tag || "")}</td><td>${editing() ? `<button class="small danger" data-rmcheck="${esc(t.id)}|${j}">Remove</button>` : ""}</td></tr>`); } }));
|
||||||
|
return `<div class="card">${netRow}<div class="tablewrap"><table><tr><th></th><th>key</th><th>Mac</th><th>session</th><th>peer</th><th>address${hint("Where a reconnect joins it. Must be on the wired network.")}</th><th>key text</th><th></th></tr>${rows.join("") || "<tr><td colspan=8 class='muted'>none</td></tr>"}</table></div>
|
||||||
${editing() ? `<div class="row" style="margin-top:8px"><button class="small" data-job="midi">+ MIDI link</button></div>` : ""}</div>`;
|
${editing() ? `<div class="row" style="margin-top:8px"><button class="small" data-job="midi">+ MIDI link</button></div>` : ""}</div>`;
|
||||||
}
|
}
|
||||||
function computersHtml(c) {
|
function computersHtml(c) {
|
||||||
@@ -1444,6 +1457,17 @@
|
|||||||
bx.querySelector("#dok").onclick = () => { const g = d.tiles.find((x) => x.id === bx.querySelector("#ngroup").value); d.tiles.push(t); if (g) g.members.push(id); saveDraft(); closeDialog(); location.hash = "#keys/" + id; };
|
bx.querySelector("#dok").onclick = () => { const g = d.tiles.find((x) => x.id === bx.querySelector("#ngroup").value); d.tiles.push(t); if (g) g.members.push(id); saveDraft(); closeDialog(); location.hash = "#keys/" + id; };
|
||||||
}); };
|
}); };
|
||||||
for (const cb of main.querySelectorAll("[data-autofix]")) cb.onchange = () => { if (!ensureDraft()) return; const d = state.draft.config, id = cb.dataset.autofix, f = d.fixes[id]; const set = new Set(d.auto_fix.fixes || []); if (cb.checked) { set.add(id); if (closes(f)) f.restart_ok = true; if (f.never_auto) delete f.never_auto; } else set.delete(id); setPath("auto_fix.fixes", [...set]); };
|
for (const cb of main.querySelectorAll("[data-autofix]")) cb.onchange = () => { if (!ensureDraft()) return; const d = state.draft.config, id = cb.dataset.autofix, f = d.fixes[id]; const set = new Set(d.auto_fix.fixes || []); if (cb.checked) { set.add(id); if (closes(f)) f.restart_ok = true; if (f.never_auto) delete f.never_auto; } else set.delete(id); setPath("auto_fix.fixes", [...set]); };
|
||||||
|
for (const el of main.querySelectorAll("[data-midiaddr]")) el.onchange = () => {
|
||||||
|
if (!ensureDraft()) return;
|
||||||
|
const d = state.draft.config, [ti, j] = el.dataset.midiaddr.split("|").map(Number), ch = d.tiles[ti].checks[j], v = el.value.trim();
|
||||||
|
for (const t of d.tiles.concat((d.ready_page || {}).keys || [])) for (const x of t.checks || [])
|
||||||
|
if (x.type === "midi_peer" && (x.host || "") === (ch.host || "") && x.session === ch.session && x.peer === ch.peer) { if (v) x.address = v; else delete x.address; }
|
||||||
|
saveDraft(); route();
|
||||||
|
};
|
||||||
|
const aw = main.querySelector("#addwired");
|
||||||
|
if (aw) aw.onclick = () => { if (!ensureDraft()) return; const d = state.draft.config; const t = d.tiles.find((x) => (x.checks || []).some((ch) => ch.type === "midi_peer")); if (!t) return alertBox("Add a MIDI link first.");
|
||||||
|
const own = d.hosts && Object.values(d.hosts).find((h) => h.self); const ip = own && (own.ip || ""); const guess = /^(\d+\.\d+)\./.exec(ip || ((d.site || {}).ingest_ips || [])[0] || "");
|
||||||
|
t.checks.push({ type: "midi_wired", nets: [guess ? guess[1] + ".0.0/16" : "10.1.0.0/16"], fix: Object.keys(d.fixes).find((id) => d.fixes[id].kind === "midi_connect") }); saveDraft(); route(); };
|
||||||
for (const b of main.querySelectorAll("[data-rmcheck]")) b.onclick = () => confirmBox("Remove this MIDI link from its key?", "Remove", () => { if (!ensureDraft()) return; const [tid, j] = b.dataset.rmcheck.split("|"); const t = state.draft.config.tiles.find((x) => x.id === tid); t.checks.splice(+j, 1); saveDraft(); route(); });
|
for (const b of main.querySelectorAll("[data-rmcheck]")) b.onclick = () => confirmBox("Remove this MIDI link from its key?", "Remove", () => { if (!ensureDraft()) return; const [tid, j] = b.dataset.rmcheck.split("|"); const t = state.draft.config.tiles.find((x) => x.id === tid); t.checks.splice(+j, 1); saveDraft(); route(); });
|
||||||
const ds = q("#decksave"); if (ds) ds.onclick = () => applyDecks(main);
|
const ds = q("#decksave"); if (ds) ds.onclick = () => applyDecks(main);
|
||||||
const dc = () => danteCheck(state.draft.config);
|
const dc = () => danteCheck(state.draft.config);
|
||||||
@@ -1502,9 +1526,7 @@
|
|||||||
const midiKey = c.tiles.find((t) => (t.checks || []).some((ch) => ch.type === "midi_peer"));
|
const midiKey = c.tiles.find((t) => (t.checks || []).some((ch) => ch.type === "midi_peer"));
|
||||||
const fixId = Object.keys(c.fixes).find((id) => c.fixes[id].kind === "midi_connect");
|
const fixId = Object.keys(c.fixes).find((id) => c.fixes[id].kind === "midi_connect");
|
||||||
// The wired network(s) MIDI must ride (the MIDI key's wired check): suggest and insist on an address on it.
|
// The wired network(s) MIDI must ride (the MIDI key's wired check): suggest and insist on an address on it.
|
||||||
const nets = c.tiles.concat((c.ready_page || {}).keys || []).flatMap((t) => (t.checks || []).filter((ch) => ch.type === "midi_wired").flatMap((ch) => ch.nets || []));
|
const nets = midiNets(c), wired = (a) => onWired(nets, a);
|
||||||
const ip4 = (a) => { const m = /^(\d+)\.(\d+)\.(\d+)\.(\d+)(?::\d+)?$/.exec(String(a || "").trim()); return m ? m.slice(1, 5).reduce((n, x) => n * 256 + Number(x), 0) : null; };
|
|
||||||
const wired = (a) => { const v = ip4(a); return v !== null && nets.some((n) => { const [b, bits] = String(n).split("/"); const k = Number(bits || 32), base = ip4(b); if (base === null) return false; const mask = k ? (2 ** 32 - 2 ** (32 - k)) : 0; return Math.floor(v / 2 ** (32 - k)) === Math.floor(base / 2 ** (32 - k)) && (mask >= 0); }); };
|
|
||||||
const wiredFor = (peer) => { for (const src of [live.local || {}].concat(Object.values(live.remote || {}))) for (const s of Object.values(src)) for (const p of s.peers || []) if (p.name === peer && wired(p.address)) return p.address;
|
const wiredFor = (peer) => { for (const src of [live.local || {}].concat(Object.values(live.remote || {}))) for (const s of Object.values(src)) for (const p of s.peers || []) if (p.name === peer && wired(p.address)) return p.address;
|
||||||
const h = Object.values(c.hosts).find((x) => x.name === peer || (x.midi_name || "") === peer); const ip = h && String(h.ssh || "").split("@").pop(); return ip && wired(ip) ? ip + ":5004" : ""; };
|
const h = Object.values(c.hosts).find((x) => x.name === peer || (x.midi_name || "") === peer); const ip = h && String(h.ssh || "").split("@").pop(); return ip && wired(ip) ? ip + ":5004" : ""; };
|
||||||
const sessionsOf = (alias) => { const h = c.hosts[alias]; const src = h.self ? (live.local || {}) : ((live.remote || {})[alias] || {}); return Object.entries(src).map(([name, v]) => [name, (v.peers || []).map((p) => p.name)]); };
|
const sessionsOf = (alias) => { const h = c.hosts[alias]; const src = h.self ? (live.local || {}) : ((live.remote || {})[alias] || {}); return Object.entries(src).map(([name, v]) => [name, (v.peers || []).map((p) => p.name)]); };
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ th { color: var(--muted); font-weight: 600; font-size: .78rem; text-transform: u
|
|||||||
.problems { list-style: none; padding: 0; margin: 0; } .problems li { display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: center; padding: 6px 0; border-bottom: 1px dashed var(--line); }
|
.problems { list-style: none; padding: 0; margin: 0; } .problems li { display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: center; padding: 6px 0; border-bottom: 1px dashed var(--line); }
|
||||||
.problems .lvl { display: inline-block; width: 10px; height: 10px; border-radius: 3px; }
|
.problems .lvl { display: inline-block; width: 10px; height: 10px; border-radius: 3px; }
|
||||||
.pill { display: inline-block; padding: 1px 8px; border-radius: 999px; background: var(--accent-soft); font-size: .82rem; font-family: var(--mono); }
|
.pill { display: inline-block; padding: 1px 8px; border-radius: 999px; background: var(--accent-soft); font-size: .82rem; font-family: var(--mono); }
|
||||||
|
.pill.err { background: transparent; color: var(--problem); border: 1px solid var(--problem); }
|
||||||
.chips { display: flex; flex-wrap: wrap; gap: 6px; } .chip { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 999px; background: var(--surface); border: 1px solid var(--line); font-size: .85rem; }
|
.chips { display: flex; flex-wrap: wrap; gap: 6px; } .chip { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 999px; background: var(--surface); border: 1px solid var(--line); font-size: .85rem; }
|
||||||
.chip.ok { border-color: var(--ok); } .chip.bad { border-color: var(--problem); color: var(--problem); }
|
.chip.ok { border-color: var(--ok); } .chip.bad { border-color: var(--problem); color: var(--problem); }
|
||||||
.deck { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); gap: 5px; background: var(--deck); padding: 8px; border-radius: 12px; max-width: 640px; width: 100%; }
|
.deck { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); gap: 5px; background: var(--deck); padding: 8px; border-radius: 12px; max-width: 640px; width: 100%; }
|
||||||
|
|||||||
Reference in New Issue
Block a user