diff --git a/app/lib/sequence/Readme.md b/app/lib/sequence/Readme.md
new file mode 100644
index 0000000..248755d
--- /dev/null
+++ b/app/lib/sequence/Readme.md
@@ -0,0 +1,567 @@
+
+
+## Sequence
+Run the sequence
+
+seq.run = function () {
+ 'use strict';
+ var c = mcopy.state.sequence.arr[seq.i],
+ timeEnd = 0,
+ rgb,
+ action = function () {
+ setTimeout(function () {
+ seq.i++;
+ seq.run();
+ }, mcopy.cfg.arduino.sequenceDelay);
+ }
+ if (seq.i == 0) {
+ $('#loop_current').text(gui.fmtZero(mcopy.loopCount + 1, 6));
+ ipcRenderer.send('seq', { action : 'loop' });
+ }
+ if (seq.stop()) {
+ $('.row input').removeClass('h');
+ $('#numbers div').removeClass('h');
+ log.info('Sequence stopped', 'SERIAL', true);
+ return false;
+ }
+ if (seq.i <= mcopy.state.sequence.arr.length && c !== undefined) {
+ log.info('Step ' + seq.i + ' command ' + c, 'SEQUENCE', true);
+ //gui action
+ $('.row input').removeClass('h');
+ $('#numbers div').removeClass('h');
+ $('.row input[x=' + seq.i + ']').addClass('h');
+ $('#numbers div[x=' + seq.i + ']').addClass('h');
+ if (c === 'CF'){
+ rgb = mcopy.state.sequence.light[seq.i].split(',');
+ cmd.cam_forward(rgb, action);
+ } else if (c === 'CB') {
+ rgb = mcopy.state.sequence.light[seq.i].split(',');
+ cmd.cam_backward(rgb, action);
+ } else if (c === 'PF') {
+ cmd.proj_forward(action);
+ } else if (c === 'PB') {
+ cmd.proj_backward(action);
+ } else if (c === 'BF') {
+ cmd.black_forward(action);
+ } else if (c === 'BB') {
+ cmd.black_backward(action);
+ }
+ } else {
+ mcopy.loopCount++;
+ $('#loop_current').text(gui.fmtZero(mcopy.loopCount + 1, 6));
+ if (mcopy.loopCount < mcopy.loop) {
+ log.info('Loop ' + mcopy.loopCount + ' completed', 'SEQUENCE', true);
+ $('.row input').removeClass('h');
+ $('#numbers div').removeClass('h');
+ seq.i = 0;
+ seq.run();
+ } else {
+ timeEnd = +new Date();
+ timeEnd = timeEnd - seq.time;
+ if (timeEnd < 2000) {
+ log.info('Sequence completed in ' + timeEnd + 'ms', 'SEQUENCE', true);
+ } else {
+ log.info('Sequence completed in ' + humanizeDuration(timeEnd), 'SEQUENCE', true);
+ }
+ ipcRenderer.send('seq', { action : 'stop' });
+ gui.notify('Sequence done!', (mcopy.state.sequence.arr.length * mcopy.loop) + ' actions completed in ' + humanizeDuration(timeEnd));
+ //clear gui
+ $('.row input').removeClass('h');
+ $('#numbers div').removeClass('h');
+ $('#loop_current').text('');
+ seq.stats();
+ }
+ }
+};
+seq.stop = function (state) {
+ 'use strict';
+ if (typeof state === 'undefined') {
+ if (seq.stopState === true) {
+ ipcRenderer.send('seq', { action : 'stop' });
+ }
+ return seq.stopState;
+ } else {
+ seq.stopState = state;
+ }
+ if (state === false) {
+ mcopy.loopCount = 0
+ $('#loop_current').text('');
+ } else {
+ ipcRenderer.send('seq', { action : 'stop' });
+ }
+ return state
+};
+seq.init = function (start) {
+ 'use strict';
+ if (typeof start === 'undefined') {
+ start = 0;
+ mcopy.loopCount = 0;
+ seq.time = +new Date();
+ }
+ seq.stop(false);
+ seq.i = start;
+
+ ipcRenderer.send('seq', { action : 'start' });
+ seq.run();
+};
+//!!! redo
+seq.stats = function () {
+ 'use strict';
+ var ms = 0,
+ c = '',
+ cam_total = 0,
+ proj_total = 0,
+ real_total = mcopy.state.sequence.arr.filter(function (elem) {
+ if (elem === undefined) {
+ return false;
+ }
+ return true;
+ });
+
+ //timing
+ for (var i = 0; i < mcopy.state.sequence.arr.length; i++) {
+ c = mcopy.state.sequence.arr[i];
+ if (c === 'CF' || c === 'CB'){
+ ms += mcopy.cfg.arduino.cam.time;
+ ms += mcopy.cfg.arduino.cam.delay;
+ ms += mcopy.cfg.arduino.serialDelay;
+ }
+ if (c === 'PF' || c === 'PB'){
+ ms += mcopy.cfg.arduino.proj.time;
+ ms += mcopy.cfg.arduino.proj.delay;
+ ms += mcopy.cfg.arduino.serialDelay;
+ }
+ if (c === 'BF' || c === 'BB'){
+ ms += mcopy.cfg.arduino.black.before;
+ ms += mcopy.cfg.arduino.black.after;
+ ms += mcopy.cfg.arduino.cam.time;
+ ms += mcopy.cfg.arduino.cam.delay;
+ ms += mcopy.cfg.arduino.serialDelay;
+ }
+ ms += mcopy.cfg.arduino.sequenceDelay;
+
+ if (c === 'CF' || c === 'BF') {
+ cam_total++;
+ }
+ if (c === 'CB' || c === 'BB') {
+ cam_total--;
+ }
+ if (c === 'PF') {
+ proj_total++;
+ }
+ if (c === 'PB') {
+ proj_total--;
+ }
+ }
+
+ //timing
+ ms = ms * mcopy.loop;
+ if (ms < 2000) {
+ $('#seq_stats .timing span').text(ms + 'ms');
+ } else {
+ $('#seq_stats .timing span').text(humanizeDuration(ms));
+ }
+
+ //ending frames
+ cam_total = cam_total * mcopy.loop;
+ proj_total = proj_total * mcopy.loop;
+
+ $('#seq_stats .cam_end span').text(gui.fmtZero(mcopy.state.camera.pos + cam_total, 6));
+ $('#seq_stats .proj_end span').text(gui.fmtZero(mcopy.state.projector.pos + proj_total, 6));
+
+ //count
+ $('#seq_stats .seq_count span').text(real_total.length * mcopy.loop);
+ return ms;
+};
+seq.clear = function () {
+ 'use strict';
+ mcopy.state.sequence.size = 24;
+ mcopy.state.sequence.arr = [];
+};
+seq.exec = function (arr) {
+ 'use strict';
+ seq.running = true;
+ seq.state.len = arr.length;
+ //setup queue
+ seq.queue = arr;
+ //console.dir(arr);
+ gui.overlay(true);
+ gui.spinner(true, `Running sequence of ${arr.length} frame${(arr.length === 1 ? '' : 's')}`, 0);
+ log.info(`Sequence started`, 'SEQUENCE', true);
+ seq.step();
+};
+
+seq.execStop = function (msg) {
+ 'use strict';
+ gui.overlay(false);
+ gui.spinner(false);
+ log.info(`Sequence ${msg}`, 'SEQUENCE', true);
+ return false;
+};
+
+seq.step = function () {
+ 'use strict';
+ let elem;
+ let c;
+ let rgb;
+ let current;
+ let max;
+
+ if (!seq.running) {
+ return seq.execStop('stopped');
+ }
+
+ return setTimeout(() => {
+ elem = seq.queue.shift();
+ if (typeof elem !== 'undefined') {
+ c = elem.cmd;
+ if (typeof elem.light !== 'undefined') {
+ rgb = elem.light.split(',');
+ } else {
+ rgb = light.color;
+ }
+ } else {
+ return seq.execStop('completed');
+ }
+ if (typeof elem !== 'undefined') {
+ current = seq.state.len - seq.queue.length;
+ max = seq.state.len;
+ gui.spinner(true, `Sequence: step ${c} ${current}/${max}`, (current / max) * 100);
+ log.info(`Sequence: step ${c} ${current}/${max}`, 'SEQUENCE', true);
+ if (c === 'CF'){
+ cmd.cam_forward(rgb, seq.step);
+ } else if (c === 'CB') {
+ cmd.cam_backward(rgb, seq.step);
+ } else if (c === 'PF') {
+ cmd.proj_forward(seq.step);
+ } else if (c === 'PB') {
+ cmd.proj_backward(seq.step);
+ } else if (c === 'BF') {
+ cmd.black_forward(seq.step);
+ } else if (c === 'BB') {
+ cmd.black_backward(seq.step);
+ }
+ }
+ }, mcopy.cfg.arduino.sequenceDelay);
+};
+
+// FROM SERVER SIDE
+proj.state = {
+ dir : true, //default dir
+ digital : false
+}
+proj.init = function () {
+ proj.listen()
+}
+proj.set = async function (dir, id) {
+ let cmd
+ let ms
+ if (dir) {
+ cmd = mcopy.cfg.arduino.cmd.proj_forward
+ } else {
+ cmd = mcopy.cfg.arduino.cmd.proj_backward
+ }
+ proj.state.dir = dir
+ if (proj.state.digital) {
+ dig.set(dir)
+ } else {
+ try {
+ ms = await arduino.send('projector', cmd)
+ } catch (err) {
+ console.error(err)
+ }
+ }
+ return await proj.end(cmd, id, ms)
+}
+proj.move = async function (frame, id) {
+ const cmd = mcopy.cfg.arduino.cmd.projector
+ let ms
+ if (proj.digital) {
+ try {
+ ms = await dig.move()
+ } catch (err) {
+ console.error(err)
+ }
+ } else {
+ try {
+ ms = await arduino.send('projector', cmd)
+ } catch (err) {
+ console.error(err)
+ }
+ }
+ return await proj.end(mcopy.cfg.arduino.cmd.projector, id, ms)
+}
+proj.listen = function () {
+ ipcMain.on('proj', async (event, arg) => {
+ if (typeof arg.dir !== 'undefined') {
+ try {
+ await proj.set(arg.dir, arg.id)
+ } catch (err) {
+ console.error(err)
+ }
+ } else if (typeof arg.frame !== 'undefined') {
+ try {
+ await proj.move(arg.frame, arg.id)
+ } catch (err) {
+ console.error(err)
+ }
+ } else if (typeof arg.val !== 'undefined') {
+ dig.state.frame = arg.val
+ }
+ event.returnValue = true
+ })
+ ipcMain.on('digital', proj.connectDigital)
+}
+proj.end = async function (cmd, id, ms) {
+ let message = ''
+ if (cmd === mcopy.cfg.arduino.cmd.proj_forward) {
+ message = 'Projector set to FORWARD'
+ } else if (cmd === mcopy.cfg.arduino.cmd.proj_backward) {
+ message = 'Projector set to BACKWARD'
+ } else if (cmd === mcopy.cfg.arduino.cmd.projector) {
+ message = 'Projector '
+ if (proj.state.dir) {
+ message += 'ADVANCED'
+ } else {
+ message += 'REWOUND'
+ }
+ message += ' 1 frame'
+ }
+ log.info(message, 'PROJECTOR', true, true)
+ return await mainWindow.webContents.send('proj', {cmd: cmd, id : id, ms: ms})
+}
+proj.connectDigital = async function (evt, arg) {
+ let info;
+ let frames = 0;
+
+ try {
+ info = await ffprobe.info(arg.path);
+ } catch (err) {
+ log.error(err, 'DIGITAL', true, true);
+ proj.digital = false;
+ await mainWindow.webContents.send('digital', { valid : false });
+ return false;
+ }
+ try {
+ frames = await ffprobe.frames(arg.path);
+ } catch (err) {
+ log.error(err, 'DIGITAL', true, true);
+ proj.digital = false;
+ await mainWindow.webContents.send('digital', { valid : false });
+ return false;
+ }
+
+ dig.state.frame = 0;
+ dig.state.path = arg.path;
+ dig.state.fileName = arg.fileName;
+ dig.state.frames = frames;
+ dig.state.info = info;
+
+ //console.dir(dig.state);
+
+ log.info(`Opened ${dig.state.fileName}`, 'DIGITAL', true, true);
+ log.info(`Frames : ${frames}`, 'DIGITAL', true, true);
+ proj.digital = true;
+ return await mainWindow.webContents.send('digital', { valid : true, state : JSON.stringify(dig.state) });
+}
+
+const dig = {};
+dig.state = {
+ frame : 0,
+ frames : 0,
+ path : null,
+ fileName : null,
+ info : {},
+ dir : true
+};
+
+dig.set = function (dir) {
+ dig.state.dir = dir;
+}
+
+dig.move = async function () {
+ let start = +new Date()
+ let last = dig.state.dir + 0;
+ if (dig.state.dir) {
+ dig.state.frame++
+ } else {
+ dig.state.frame--
+ }
+ if (dig.state.frame < 1) {
+ dig.state.frame = 1
+ }
+ return (+new Date()) - start
+}
+
+dig.start = async function () {
+ try {
+ await ffmpeg.clearAll()
+ } catch (err) {
+ console.error(err)
+ }
+
+ try {
+ await ffmpeg.frame(dig.state, light.state)
+ } catch (err) {
+ console.error(err)
+ }
+
+ display.start(dig.state.frame)
+ await delay(20)
+}
+
+dig.end = async function () {
+ await delay(20)
+ display.end()
+}
+
+cam.intval = null
+cam.state = {
+ dir : true //default dir
+}
+cam.init = function () {
+ cam.listen()
+}
+cam.set = async function (dir, id) {
+ let cmd
+ let ms
+ if (dir) {
+ cmd = mcopy.cfg.arduino.cmd.cam_forward
+ } else {
+ cmd = mcopy.cfg.arduino.cmd.cam_backward
+ }
+ cam.state.dir = dir
+
+ if (cam.intval) {
+ try {
+ ms = await cam.intval.setDir(dir)
+ } catch (err) {
+ console.error(err)
+ }
+ } else {
+ try {
+ ms = await arduino.send('camera', cmd)
+ } catch (err) {
+ console.error(err)
+ }
+ }
+ return await cam.end(cmd, id, ms)
+}
+
+cam.move = async function (frame, id) {
+ const cmd = mcopy.cfg.arduino.cmd.camera
+ let ms
+ if (proj.digital) {
+ await dig.start()
+ }
+ if (cam.intval) {
+ try {
+ ms = await cam.intval.move()
+ } catch (err) {
+ console.error(err)
+ }
+ } else {
+ try {
+ ms = await arduino.send('camera', cmd)
+ } catch (err) {
+ console.error(err)
+ }
+ }
+ if (proj.digital) {
+ await dig.end()
+ }
+ log.info('Camera move time', { ms })
+ return cam.end(cmd, id, ms)
+}
+
+cam.exposure = function (exposure, id) {
+ let cmd = 'E'
+ cam.intval.setExposure('camera', exposure, ms => {
+ cam.end(cmd, id, ms)
+ })
+}
+
+cam.connectIntval = async function (event, arg) {
+ return new Promise((resolve, reject) => {
+ if (arg.connect) {
+ cam.intval = new Intval(arg.url)
+ cam.intval.connect((err, ms, state) => {
+ if (err) {
+ mainWindow.webContents.send('intval', { connected : false })
+ log.info(`Cannot connect to ${arg.url}`, 'INTVAL', true, true)
+ cam.intval = null
+ delete cam.intval
+ } else {
+ mainWindow.webContents.send('intval', { connected : true, url : arg.url, state : state })
+ log.info(`Connected to INTVAL3 @ ${arg.url}`, 'INTVAL', true, true)
+ settings.update('camera', { intval : arg.url })
+ settings.save()
+ dev.remember('intval', arg.url, 'camera')
+ }
+ return resolve(true)
+ })
+ } else if (arg.disconnect) {
+ cam.intval = null
+ return resolve(false)
+ }
+ })
+}
+
+cam.listen = function () {
+ ipcMain.on('cam', async (event, arg) => {
+ if (typeof arg.dir !== 'undefined') {
+ try {
+ await cam.set(arg.dir, arg.id)
+ } catch (err) {
+ console.error(err)
+ }
+ } else if (typeof arg.frame !== 'undefined') {
+ try {
+ await cam.move(arg.frame, arg.id)
+ } catch (err) {
+ console.error(err)
+ }
+ }
+ event.returnValue = true
+ })
+ ipcMain.on('intval', cam.connectIntval)
+}
+cam.end = async function (cmd, id, ms) {
+ var message = ''
+ if (cmd === mcopy.cfg.arduino.cmd.cam_forward) {
+ message = 'Camera set to FORWARD'
+ } else if (cmd === mcopy.cfg.arduino.cmd.cam_backward) {
+ message = 'Camera set to BACKWARD'
+ } else if (cmd === mcopy.cfg.arduino.cmd.camera) {
+ message = 'Camera '
+ if (cam.state.dir) {
+ message += 'ADVANCED'
+ } else {
+ message += 'REWOUND'
+ }
+ message += ' 1 frame'
+ }
+ log.info(message, 'CAMERA', true, true)
+ mainWindow.webContents.send('cam', {cmd: cmd, id : id, ms: ms})
+};
+
+const seq = {};
+seq.init = function () {
+ seq.listen();
+}
+
+seq.listen = function () {
+ ipcMain.on('seq', async (evt, arg) => {
+ if (arg.action === 'stop' && proj.digital) {
+ display.end()
+ }
+ })
+}
+
+**Kind**: global class
+
+
+### sequence.stop()
+Stop the sequence
+
+**Kind**: instance method of [Sequence
](#Sequence)
diff --git a/app/lib/sequence/package.json b/app/lib/sequence/package.json
new file mode 100644
index 0000000..6687419
--- /dev/null
+++ b/app/lib/sequence/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "sequence",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC"
+}
diff --git a/cli/lib/sequence/package.json b/cli/lib/sequence/package.json
new file mode 100644
index 0000000..6687419
--- /dev/null
+++ b/cli/lib/sequence/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "sequence",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC"
+}
diff --git a/lib/sequence/package.json b/lib/sequence/package.json
new file mode 100644
index 0000000..6687419
--- /dev/null
+++ b/lib/sequence/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "sequence",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC"
+}