Add chain triggers, fix overlay→light type, fix signal source segments

This commit is contained in:
Ian Gulliver
2026-02-18 22:28:15 -07:00
parent f0db8aa5cd
commit 4935eb0150
2 changed files with 91 additions and 77 deletions

View File

@@ -19,8 +19,6 @@
--light-bg: rgba(42, 10, 42, 0.55); --light-bg: rgba(42, 10, 42, 0.55);
--video-color: #4d4; --video-color: #4d4;
--video-bg: rgba(10, 42, 10, 0.55); --video-bg: rgba(10, 42, 10, 0.55);
--overlay-color: #2cb;
--overlay-bg: rgba(10, 34, 34, 0.55);
--audio-color: #58f; --audio-color: #58f;
--audio-bg: rgba(10, 10, 42, 0.55); --audio-bg: rgba(10, 10, 42, 0.55);
--delay-color: #999; --delay-color: #999;
@@ -109,7 +107,6 @@ header h1 { font-size: 16px; font-weight: 600; letter-spacing: 0.05em; }
} }
.block.light { color: var(--light-color); border-color: var(--light-color); background: var(--light-bg); } .block.light { color: var(--light-color); border-color: var(--light-color); background: var(--light-bg); }
.block.video { color: var(--video-color); border-color: var(--video-color); background: var(--video-bg); } .block.video { color: var(--video-color); border-color: var(--video-color); background: var(--video-bg); }
.block.overlay { color: var(--overlay-color); border-color: var(--overlay-color); background: var(--overlay-bg); }
.block.audio { color: var(--audio-color); border-color: var(--audio-color); background: var(--audio-bg); } .block.audio { color: var(--audio-color); border-color: var(--audio-color); background: var(--audio-bg); }
.block.delay { color: var(--delay-color); border-color: var(--delay-color); background: var(--delay-bg); } .block.delay { color: var(--delay-color); border-color: var(--delay-color); background: var(--delay-bg); }
@@ -169,27 +166,23 @@ function render(data) {
const CUE_TRACK = '_cue'; const CUE_TRACK = '_cue';
const trackIds = [CUE_TRACK, ...data.tracks.map(t => t.id)]; const trackIds = [CUE_TRACK, ...data.tracks.map(t => t.id)];
function ref(block, signal) { return block + ':' + signal; } function ref(b, s) { return b + ':' + s; }
const triggerTargetSet = new Set();
const triggerSourceSet = new Set(); const triggerSourceSet = new Set();
data.triggers.forEach(t => { data.triggers.forEach(t => triggerSourceSet.add(ref(t.source.block, t.source.signal)));
triggerSourceSet.add(ref(t.source.block, t.source.signal));
t.targets.forEach(tgt => triggerTargetSet.add(ref(tgt.block, tgt.hook)));
});
const trackBlocks = new Map();
trackIds.forEach(id => trackBlocks.set(id, []));
data.blocks.forEach(b => {
const tid = b.type === 'cue' ? CUE_TRACK : b.track;
trackBlocks.get(tid).push(b.id);
});
function blockTrack(bid) { function blockTrack(bid) {
const b = blockMap.get(bid); const b = blockMap.get(bid);
return b.type === 'cue' ? CUE_TRACK : b.track; return b.type === 'cue' ? CUE_TRACK : b.track;
} }
function isChain(trigger) {
const src = trigger.source;
if (src.signal !== 'END' || trigger.targets.length !== 1) return false;
return trigger.targets[0].hook === 'START' &&
blockTrack(src.block) === blockTrack(trigger.targets[0].block);
}
document.getElementById('header-status').innerHTML = document.getElementById('header-status').innerHTML =
'<span><span class="status-dot"></span>QLab Connected</span>' + '<span><span class="status-dot"></span>QLab Connected</span>' +
'<span>Show: ' + data.show + '</span>' + '<span>Show: ' + data.show + '</span>' +
@@ -198,15 +191,9 @@ function render(data) {
const active = new Map(); const active = new Map();
const pendingEnds = new Set(); const pendingEnds = new Set();
const pendingTitles = new Set(); const pendingTitles = new Set();
const titled = new Set();
const rows = []; const rows = [];
function nextInTrack(bid) {
const tid = blockTrack(bid);
const seq = trackBlocks.get(tid);
const idx = seq.indexOf(bid);
return idx < seq.length - 1 ? seq[idx + 1] : null;
}
function mid(tid) { function mid(tid) {
const a = active.get(tid); const a = active.get(tid);
return a ? { blockId: a, segment: 'mid' } : { empty: true }; return a ? { blockId: a, segment: 'mid' } : { empty: true };
@@ -216,8 +203,8 @@ function render(data) {
rows.push({ cells, rowClass: rowClass || '' }); rows.push({ cells, rowClass: rowClass || '' });
} }
function canAutoStart(bid) { function wantTitle(bid) {
return !triggerTargetSet.has(ref(bid, 'START')) && !triggerSourceSet.has(ref(bid, 'START')); if (!titled.has(bid)) pendingTitles.add(bid);
} }
function emitTitles() { function emitTitles() {
@@ -227,40 +214,15 @@ function render(data) {
return t ? { blockId: t, segment: 'mid', title: blockMap.get(t).name } : mid(tid); return t ? { blockId: t, segment: 'mid', title: blockMap.get(t).name } : mid(tid);
}); });
addRow(cells); addRow(cells);
pendingTitles.forEach(bid => titled.add(bid));
pendingTitles.clear(); pendingTitles.clear();
} }
function doAutoStarts(ended) { function flush() {
const starts = [];
ended.forEach(bid => {
const next = nextInTrack(bid);
if (next && canAutoStart(next)) starts.push(next);
});
if (starts.length === 0) return;
const cells = trackIds.map(tid => {
const s = starts.find(bid => blockTrack(bid) === tid);
if (s) {
active.set(tid, s);
pendingTitles.add(s);
return { blockId: s, segment: 'start', event: 'START' };
}
return mid(tid);
});
addRow(cells);
}
function flush(upcomingSrc) {
emitTitles(); emitTitles();
if (pendingEnds.size === 0) return; if (pendingEnds.size === 0) return;
const holdBack = new Set(); const toEnd = [...pendingEnds].filter(bid => !triggerSourceSet.has(ref(bid, 'END')));
if (upcomingSrc) {
for (const bid of pendingEnds) {
if (upcomingSrc.block === bid && upcomingSrc.signal === 'END') holdBack.add(bid);
}
}
const toEnd = [...pendingEnds].filter(bid => !holdBack.has(bid));
if (toEnd.length === 0) return; if (toEnd.length === 0) return;
const cells = trackIds.map(tid => { const cells = trackIds.map(tid => {
@@ -269,31 +231,36 @@ function render(data) {
}); });
addRow(cells); addRow(cells);
toEnd.forEach(bid => { active.delete(blockTrack(bid)); pendingEnds.delete(bid); }); toEnd.forEach(bid => { active.delete(blockTrack(bid)); pendingEnds.delete(bid); });
doAutoStarts(toEnd);
emitTitles();
} }
function processTrigger(trigger) { function processTrigger(trigger) {
const src = trigger.source; const src = trigger.source;
const isCue = src.signal === 'GO'; const isCue = src.signal === 'GO';
flush(src); flush();
if (!isCue) { if (isChain(trigger)) {
const tid = blockTrack(src.block); const tid = blockTrack(src.block);
if (src.signal === 'START') { const tgt = trigger.targets[0].block;
const cur = active.get(tid); if (active.get(tid) === src.block || pendingEnds.has(src.block)) {
if (cur && cur !== src.block) { pendingEnds.delete(src.block);
pendingEnds.delete(cur); const endCells = trackIds.map(t =>
const cells = trackIds.map(t => t === tid ? { blockId: src.block, segment: 'end', event: 'END' } : mid(t)
t === tid ? { blockId: cur, segment: 'end', event: 'END' } : mid(t) );
); addRow(endCells);
addRow(cells); active.delete(tid);
active.delete(tid);
}
active.set(tid, src.block);
} }
active.set(tid, tgt);
wantTitle(tgt);
const startCells = trackIds.map(t =>
t === tid ? { blockId: tgt, segment: 'start', event: 'START' } : mid(t)
);
addRow(startCells);
return;
}
if (!isCue && src.signal === 'START' && active.get(blockTrack(src.block)) !== src.block) {
active.set(blockTrack(src.block), src.block);
} }
emitTitles(); emitTitles();
@@ -310,7 +277,8 @@ function render(data) {
return { cueLabel: blockMap.get(src.block).name }; return { cueLabel: blockMap.get(src.block).name };
if (!isCue && blockTrack(src.block) === tid) { if (!isCue && blockTrack(src.block) === tid) {
const seg = src.signal === 'START' ? 'start' : src.signal === 'END' ? 'end' : 'mid'; const alreadyActive = active.get(tid) === src.block;
const seg = alreadyActive ? 'mid' : src.signal === 'START' ? 'start' : src.signal === 'END' ? 'end' : 'mid';
return { blockId: src.block, segment: seg, event: src.signal, isSignal: true }; return { blockId: src.block, segment: seg, event: src.signal, isSignal: true };
} }
@@ -320,7 +288,7 @@ function render(data) {
const isHook = !isCue; const isHook = !isCue;
if (hook === 'START') { if (hook === 'START') {
active.set(tid, bid); active.set(tid, bid);
pendingTitles.add(bid); wantTitle(bid);
return { blockId: bid, segment: 'start', event: 'START', isHook }; return { blockId: bid, segment: 'start', event: 'START', isHook };
} }
if (hook === 'END') { if (hook === 'END') {
@@ -339,21 +307,19 @@ function render(data) {
addRow(cells, rowClass); addRow(cells, rowClass);
directEnds.forEach(bid => active.delete(blockTrack(bid))); directEnds.forEach(bid => active.delete(blockTrack(bid)));
doAutoStarts(directEnds);
if (!isCue) { if (!isCue) {
if (src.signal === 'START') pendingTitles.add(src.block); if (src.signal === 'START' && !pendingTitles.has(src.block)) wantTitle(src.block);
if (src.signal === 'FADE_OUT') pendingEnds.add(src.block); if (src.signal === 'FADE_OUT') pendingEnds.add(src.block);
if (src.signal === 'END') { if (src.signal === 'END') {
active.delete(blockTrack(src.block)); active.delete(blockTrack(src.block));
pendingEnds.delete(src.block); pendingEnds.delete(src.block);
doAutoStarts([src.block]);
} }
} }
} }
data.triggers.forEach(t => processTrigger(t)); data.triggers.forEach(t => processTrigger(t));
flush(null); flush();
emitTitles(); emitTitles();
const stillActive = []; const stillActive = [];

View File

@@ -20,10 +20,10 @@
{"id": "block_01kht41b8deg8ae996tzqay0rg", "type": "light", "track": "track_01kht419m1e65bfgqzj28se459", "name": "SC1 Focus"}, {"id": "block_01kht41b8deg8ae996tzqay0rg", "type": "light", "track": "track_01kht419m1e65bfgqzj28se459", "name": "SC1 Focus"},
{"id": "block_01kht41b9neqf84ap7dm3mey9r", "type": "light", "track": "track_01kht419n7esr929mxvsacsy0k", "name": "SC1 Blue 80%"}, {"id": "block_01kht41b9neqf84ap7dm3mey9r", "type": "light", "track": "track_01kht419n7esr929mxvsacsy0k", "name": "SC1 Blue 80%"},
{"id": "block_01kht41bawfbhr38a49tjvyahy", "type": "video", "track": "track_01kht419pbfx2992sjpagc0syy", "name": "Sc1 Projection"}, {"id": "block_01kht41bawfbhr38a49tjvyahy", "type": "video", "track": "track_01kht419pbfx2992sjpagc0syy", "name": "Sc1 Projection"},
{"id": "block_01kht41bc3evybtn5hqnk6p2f7", "type": "overlay", "track": "track_01kht419qdeb1bjm9qe5acas2f", "name": "Lightning Flash"}, {"id": "block_01kht41bc3evybtn5hqnk6p2f7", "type": "video", "track": "track_01kht419qdeb1bjm9qe5acas2f", "name": "Lightning Flash"},
{"id": "block_01kht41bdafggbbm8959svkm4n", "type": "audio", "track": "track_01kht419rffhnayzmgqnnkddtz", "name": "Storm Ambience", "loop": true}, {"id": "block_01kht41bdafggbbm8959svkm4n", "type": "audio", "track": "track_01kht419rffhnayzmgqnnkddtz", "name": "Storm Ambience", "loop": true},
{"id": "block_01kht41bejev2997rfps2kpbat", "type": "cue", "name": "Q13 Sc1 Dialog"}, {"id": "block_01kht41bejev2997rfps2kpbat", "type": "cue", "name": "Q13 Sc1 Dialog"},
{"id": "block_01kht41bfsfhkvtvze4rh7k7z6", "type": "overlay", "track": "track_01kht419qdeb1bjm9qe5acas2f", "name": "Wave Overlay"}, {"id": "block_01kht41bfsfhkvtvze4rh7k7z6", "type": "video", "track": "track_01kht419qdeb1bjm9qe5acas2f", "name": "Wave Overlay"},
{"id": "block_01kht41bgzfjcrkwrvrrjkqs0f", "type": "light", "track": "track_01kht419m1e65bfgqzj28se459", "name": "Dialog Spots"}, {"id": "block_01kht41bgzfjcrkwrvrrjkqs0f", "type": "light", "track": "track_01kht419m1e65bfgqzj28se459", "name": "Dialog Spots"},
{"id": "block_01kht41bj7ea89xn71nn5h400a", "type": "light", "track": "track_01kht419n7esr929mxvsacsy0k", "name": "Warm 90%"}, {"id": "block_01kht41bj7ea89xn71nn5h400a", "type": "light", "track": "track_01kht419n7esr929mxvsacsy0k", "name": "Warm 90%"},
{"id": "block_01kht41bkee1n81gnfq6bydmm2", "type": "audio", "track": "track_01kht419rffhnayzmgqnnkddtz", "name": "Dialog Underscore"}, {"id": "block_01kht41bkee1n81gnfq6bydmm2", "type": "audio", "track": "track_01kht419rffhnayzmgqnnkddtz", "name": "Dialog Underscore"},
@@ -49,6 +49,12 @@
{"block": "block_01kht41azrenbaxyc2vebv7s4q", "hook": "FADE_OUT"} {"block": "block_01kht41azrenbaxyc2vebv7s4q", "hook": "FADE_OUT"}
] ]
}, },
{
"source": {"block": "block_01kht41azrenbaxyc2vebv7s4q", "signal": "END"},
"targets": [
{"block": "block_01kht41b5zf4ya2044tczm8tz6", "hook": "START"}
]
},
{ {
"source": {"block": "block_01kht41b4rfnhvk83p9afhp08y", "signal": "GO"}, "source": {"block": "block_01kht41b4rfnhvk83p9afhp08y", "signal": "GO"},
"targets": [ "targets": [
@@ -57,6 +63,18 @@
{"block": "block_01kht41b29eyes3b6neh4h56mn", "hook": "FADE_OUT"} {"block": "block_01kht41b29eyes3b6neh4h56mn", "hook": "FADE_OUT"}
] ]
}, },
{
"source": {"block": "block_01kht41b10emgt1gfvg7dy60ws", "signal": "END"},
"targets": [
{"block": "block_01kht41b76ebtraravpknavdm5", "hook": "START"}
]
},
{
"source": {"block": "block_01kht41b76ebtraravpknavdm5", "signal": "END"},
"targets": [
{"block": "block_01kht41bawfbhr38a49tjvyahy", "hook": "START"}
]
},
{ {
"source": {"block": "block_01kht41bawfbhr38a49tjvyahy", "signal": "START"}, "source": {"block": "block_01kht41bawfbhr38a49tjvyahy", "signal": "START"},
"targets": [ "targets": [
@@ -65,6 +83,12 @@
{"block": "block_01kht41bdafggbbm8959svkm4n", "hook": "START"} {"block": "block_01kht41bdafggbbm8959svkm4n", "hook": "START"}
] ]
}, },
{
"source": {"block": "block_01kht41b5zf4ya2044tczm8tz6", "signal": "END"},
"targets": [
{"block": "block_01kht41b9neqf84ap7dm3mey9r", "hook": "START"}
]
},
{ {
"source": {"block": "block_01kht41b9neqf84ap7dm3mey9r", "signal": "START"}, "source": {"block": "block_01kht41b9neqf84ap7dm3mey9r", "signal": "START"},
"targets": [ "targets": [
@@ -86,6 +110,18 @@
{"block": "block_01kht41bfsfhkvtvze4rh7k7z6", "hook": "START"} {"block": "block_01kht41bfsfhkvtvze4rh7k7z6", "hook": "START"}
] ]
}, },
{
"source": {"block": "block_01kht41b9neqf84ap7dm3mey9r", "signal": "END"},
"targets": [
{"block": "block_01kht41bj7ea89xn71nn5h400a", "hook": "START"}
]
},
{
"source": {"block": "block_01kht41bdafggbbm8959svkm4n", "signal": "END"},
"targets": [
{"block": "block_01kht41bkee1n81gnfq6bydmm2", "hook": "START"}
]
},
{ {
"source": {"block": "block_01kht41bkee1n81gnfq6bydmm2", "signal": "START"}, "source": {"block": "block_01kht41bkee1n81gnfq6bydmm2", "signal": "START"},
"targets": [ "targets": [
@@ -102,6 +138,18 @@
{"block": "block_01kht41bkee1n81gnfq6bydmm2", "hook": "END"} {"block": "block_01kht41bkee1n81gnfq6bydmm2", "hook": "END"}
] ]
}, },
{
"source": {"block": "block_01kht41bgzfjcrkwrvrrjkqs0f", "signal": "END"},
"targets": [
{"block": "block_01kht41bnxfme95cspptf87j9b", "hook": "START"}
]
},
{
"source": {"block": "block_01kht41bj7ea89xn71nn5h400a", "signal": "END"},
"targets": [
{"block": "block_01kht41bq2ekwswsf51b0h1bd2", "hook": "START"}
]
},
{ {
"source": {"block": "block_01kht41bfsfhkvtvze4rh7k7z6", "signal": "END"}, "source": {"block": "block_01kht41bfsfhkvtvze4rh7k7z6", "signal": "END"},
"targets": [ "targets": [