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'); };