Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added plugins/scs/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions plugins/scs/info.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<h3>Connection Requirements</h3>
<ul>
<li>Remote App must be set to OSC App under Remote App Interface settings</li>
<li>Network Protocol must be set to TCP under Remote App Interface settings</li>
<li>OSC Version must be set to 1.1 under Remote App Interface settings</li>
</ul>
242 changes: 242 additions & 0 deletions plugins/scs/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
exports.config = {
defaultName: 'Show Cue Systems',
connectionType: 'osc',
defaultPort: 58100,
mayChangePorts: true,
heartbeatInterval: 5000,
searchOptions: {
type: 'TCPport',
searchBuffer: Buffer.from('/status', 'ascii'),
testPort: 58100,
validateResponse(msg, info) {
return msg.toString().includes('/status');
},
},
fields: [],
};

const typeMap = {
A: 'Video/Image',
E: 'Memo',
F: 'Audio File',
G: 'GoTo Cue',
H: 'Multi-File Cue',
I: 'Live Input',
J: 'Enable/Disable',
K: 'Lighting',
L: 'Level Change',
M: 'Control Send',
N: 'Note',
P: 'Playlist',
Q: 'Call Cue',
R: 'Run Program',
S: 'SFR',
T: 'Set Position',
U: 'MIDI Time Code',
};

const versionMap = {
10: 'LITE',
20: 'STD',
30: 'PRO',
40: 'PLUS',
45: 'DEMO',
50: 'PLAT',
};

const activationMap = {
man: 'Manual',
'm+c': 'Manual w/ Confirmation',
auto: 'Auto-start',
'a+c': 'Auto-start w/ Confirmation',
callq: 'Call Cue',
hot: 'Hotkey (Trigger)',
hktg: 'Hotkey (Toggle)',
hknt: 'Hotkey (Note)',
time: 'Time-Based',
ext: 'External (Trigger)',
extg: 'External (Toggle)',
exnt: 'External (Note)',
mtc: 'MIDI Time Code',
ocm: 'On Cue Marker',
};

const stateMap = {
0: 'Ready',
1: 'Playing',
2: 'Paused',
3: 'Completed',
};

exports.ready = function ready(_device) {
const device = _device;
device.data.cues = [];
device.data.initialized = false;
device.send('/prod/gettitle');
};

exports.data = function data(_device, oscData) {
const device = _device;
this.deviceInfoUpdate(device, 'status', 'ok');
const messagePath = oscData.address;
if (messagePath === '/prod/gettitle') {
const title = oscData.args[0].replaceAll('"', '').trim();
this.deviceInfoUpdate(device, 'defaultName', title);
device.send('/info/scsversion');
} else if (messagePath === '/info/scsversion') {
const versionInfo = oscData.args;
const versionNumber = versionInfo[0].toString();
const versionMajor = versionNumber.substring(0, 2).replace('0', '');
const versionMinor = versionNumber.substring(2, 4).replace('0', '');
const versionPatch = versionNumber.substring(4, 6).replace('0', '');
const versionString = `${versionMajor}.${versionMinor}.${versionPatch}`;

const versionType = versionMap[versionInfo[1]];
device.data.version = {
number: versionString,
type: versionType,
};
device.send('/info/finalcue');
} else if (messagePath === '/info/finalcue') {
const cueCount = oscData.args[0];
if (cueCount !== device.data.cues.length) {
device.data.initialized = false;
device.data.cues = new Array(cueCount).fill(0).map(() => ({
label: '',
page: '',
description: '',
type: { code: '', display: '' },
state: '',
activation: { code: '', display: '' },
file_info: '',
length: '',
colors: ['', ''],
}));
device.send('/cue/getitemsn', [
{ type: 'i', value: 1 },
{ type: 's', value: 'QNTCLSPARZ' },
]);
}
} else if (messagePath === '/info/getcue') {
const cueLabelInfo = oscData.args;
const cueNumber = cueLabelInfo[0];
const cueLabel = cueLabelInfo[1].replaceAll('"', '');

device.data.cues[cueNumber - 1].label = cueLabel;
device.draw();
} else if (messagePath === '/cue/getpage') {
const pageInfo = oscData.args;
const cueLabel = pageInfo[0].replaceAll('"', '');
const cueIndex = device.data.cues.map((cue) => cue.label).indexOf(cueLabel);
device.data.cues[cueIndex].page = pageInfo[1].replaceAll('"', '');
if (cueIndex < device.data.cues.length - 1) {
device.send('/cue/getpage', [{ type: 's', value: device.data.cues[cueIndex + 1].label }]);
} else {
device.draw();
device.data.initialized = true;
device.send('/status');
}
} else if (messagePath === '/cue/getname') {
const nameInfo = oscData.args;
const cueLabel = nameInfo[0].replaceAll('"', '');
const cueIndex = device.data.cues.map((cue) => cue.label).indexOf(cueLabel);

device.data.cues[cueIndex].description = nameInfo[1].replaceAll('"', '');
device.draw();
} else if (messagePath === '/cue/gettype') {
const typeInfo = oscData.args;
const cueLabel = typeInfo[0].replaceAll('"', '');
const cueIndex = device.data.cues.map((cue) => cue.label).indexOf(cueLabel);

device.data.cues[cueIndex].type = {
code: typeInfo[1].replaceAll('"', ''),
display: typeMap[typeInfo[1].replaceAll('"', '')],
};
device.draw();
} else if (messagePath === '/cue/getitemsn') {
const itemParts = oscData.args;
if (itemParts.length === 12) {
const cueIndex = itemParts[0] - 1;
device.data.cues[cueIndex].label = itemParts[2];
device.data.cues[cueIndex].description = itemParts[3];
device.data.cues[cueIndex].type = {
code: itemParts[4],
display: typeMap[itemParts[4]],
};
device.data.cues[cueIndex].colors = itemParts[5].split(', ');
device.data.cues[cueIndex].length = millisToString(itemParts[6]);
device.data.cues[cueIndex].state = stateMap[itemParts[7]];
device.data.cues[cueIndex].position = itemParts[8];
device.data.cues[cueIndex].activation = {
code: itemParts[9],
display: activationMap[itemParts[9]],
};
device.data.cues[cueIndex].repeat = itemParts[10];
device.data.cues[cueIndex].loop = itemParts[11];
device.draw();

if (!device.data.initialized) {
if (cueIndex < device.data.cues.length - 1) {
device.send('/cue/getitemsn', [
{ type: 'i', value: cueIndex + 2 },
{ type: 's', value: 'QNTCLSPARZ' },
]);
} else {
device.send('/cue/getpage', [{ type: 's', value: device.data.cues[0].label }]);
}
}
} else {
console.error('bad response from getitems');
}
} else if (messagePath === '/cue/statechange') {
const cueLabel = oscData.args[0];
const cueIndex = device.data.cues.map((cue) => cue.label).indexOf(cueLabel);
if (cueIndex !== undefined) {
device.data.cues[cueIndex].state = stateMap[oscData.args[1]];
device.draw();
device.send('/cue/getitemsn', [
{ type: 'i', value: cueIndex + 1 },
{ type: 's', value: 'QNTCLSPARZ' },
]);
}
} else if (messagePath === '/status') {
// const status = oscData.args[0];
device.send('/info/currcue');
} else if (messagePath === '/connected') {
device.send('/prod/gettitle');
} else if (messagePath === '/info/currcue') {
device.data.currentCue = oscData.args[0];
device.draw();
} else if (messagePath === '/info/nextcue') {
device.data.nextCue = oscData.args[0];
device.draw();
device.data.initialized = false;
device.send('/cue/getitemsn', [
{ type: 'i', value: 1 },
{ type: 's', value: 'QNTCLSPARZ' },
]);
} else {
console.error(`unhandled message path: ${messagePath}`);
}
};

function millisToString(duration) {
const milliseconds = parseInt(duration % 1000, 10);
let seconds = parseInt((duration / 1000) % 60, 10);
const minutes = parseInt((duration / (1000 * 60)) % 60, 10);

if (minutes === 0) {
return `${seconds}.${milliseconds}`;
}
if (seconds === 0) {
return `0.${milliseconds}`;
}
if (seconds < 10) {
seconds = `0${seconds}`;
}
return `${minutes}:${seconds}.${milliseconds}`;
}

exports.heartbeat = function heartbeat(device) {
device.send('/info/finalcue');
};
17 changes: 17 additions & 0 deletions plugins/scs/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
table {
background: white;
}

th {
text-align: left;
font-weight: normal;
}

.header-row {
background: white;
}

td {
padding-left: 5px;
padding-right: 5px;
}
34 changes: 34 additions & 0 deletions plugins/scs/template.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<header>
<h1><%= listName %></h1>
</header>
<table>
<tr class="header-row">
<th>Label</th>
<th>Page</th>
<th>Description</th>
<th>Cue Type</th>
<th>State</th>
<th>Activation</th>
<th>Length</th>
</tr>
<% data.cues.forEach((cue, index )=>{ %>

<% if (data.currentCue === index + 1){ %>
<tr style="background-color: black; color: white;">
<%} else if (index + 1 < data.currentCue){ %>
<tr style="background-color: #808080; color: #E5E5E5;">
<%} else{ %>
<tr style="background-color: <%=cue.colors[0]%>; color: <%=cue.colors[1]%>;">
<% } %>


<td><%=cue.label%></td>
<td><%=cue.page%></td>
<td><%=cue.description%></td>
<td><%=cue.type.display%></td>
<td><%=cue.state%></td>
<td><%=cue.activation.display%></td>
<td><%=cue.length%></td>
</tr>
<% }) %>
</table>
1 change: 1 addition & 0 deletions src/device.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ function initDeviceConnection(id) {
device.connection = new osc.TCPSocketPort({
address: device.addresses[0],
port: device.remotePort,
useSLIP: plugins[type].config.useSLIP !== undefined ? plugins[type].config.useSLIP : true
});
}
device.connection.open();
Expand Down