// Which port do I send OSC messages to? (SupperCollider, ...) const OSCPORT = 57120; // Which port do I listen to (for visuals, calibration) const PORT = 6676 // Do we have a problem, shall we debug? const DEBUG = { osc: true, udp: false, midi: true } // MIDI out const MIDI = true // Serial baud rate const BAUDRATE = 115200 const express = require('express') const http = require('http') const WebSocket = require('ws') const osc = require('osc') const readline = require('readline') const fs = require('fs') const midi = require('midi') // Seznam povezanih tty naprav const tty = process.argv.splice(2) let eulerRotation = [0, 0, 0] const include_files = [ '/anim.js', '/control.js', '/osctl.js', '/test.js', '/node_modules/three/build/three.min.js', '/node_modules/nouislider/distribute/nouislider.min.js', '/node_modules/nouislider/distribute/nouislider.min.css', '/node_modules/osc/dist/osc-browser.js' ]; function isFloat(value) { if ( typeof value === 'number' && !Number.isNaN(value) && !Number.isInteger(value) ) { return true; } return false; } const app = express(); const server = http.Server(app); // Odprti serijski OSC linki let serijskePovezave = {} let mo = null let mi = null if (MIDI) { // Midi port mo = new midi.Output() mi = new midi.Input() //mo.getPortCount() //mo.getPortName(0) //mo.openPort(0) mo.openVirtualPort("kegel") mi.openVirtualPort("ww-midi-in") } function openSerial(ttyAddr) { console.log('opening ', ttyAddr, BAUDRATE) serijskePovezave[ttyAddr] = new osc.SerialPort({ devicePath: ttyAddr, bitrate: BAUDRATE, autoOpen: true, useSLIP: true }) const scon = serijskePovezave[ttyAddr] scon.on('open', e => { console.log(`serial connection ${ttyAddr} opened`) //console.log(scon) }) scon.on('error', e => { console.error(`tty ${ttyAddr} error`, e) //scon.close() }) scon.on('close', e => { console.warn(`serial connection ${ttyAddr} closed, restarting in 1 second`) setTimeout(() => { openSerial(ttyAddr) }, 1000) }) // Arduino OSC gre v web scon.on('message', msg => { const index = Object.keys(serijskePovezave).indexOf(ttyAddr) const prepend = `/ww/${index}` // Debug incoming serial osc if (DEBUG.osc) { console.log('osc SERIAL msg', msg.address, msg.args.map((a) => isFloat(a) ? a.toFixed(3) : a), ttyAddr, prepend) } sendAll(msg, null, null, osclients, prepend) }) scon.open() if (scon._closeCode) { scon = null console.log('restarting serial connection ', ttyAddr) setTimeout(openSerial, 1000) } } if (tty.length > 0) { tty.forEach((ttyAddr) => { openSerial(ttyAddr); }); } app.get('/', (req, res) => { res.sendFile(__dirname + '/index.html'); }); app.get('/ctl', (req, res) => { res.sendFile(__dirname + '/control.html'); }); app.get('/test', (req, res) => { res.sendFile(__dirname + '/test.html'); }); // Hydra inclusion app.get('/hydra', function(req, res) { res.sendFile(__dirname + '/hydra-osc-main/index.html'); }); app.get('/lib/osc.min.js', function(req, res) { res.sendFile(__dirname + '/hydra-osc-main/lib/osc.min.js'); }); let settings = {}; app.get('/settings', function(req, res) { res.send(settings); }); include_files.map(function(file) { app.get(file, function(req, res){ res.sendFile(__dirname + file); }); }); server.listen(PORT, () => console.log('listening on *:' + PORT)) // Websocket init const wss = new WebSocket.Server({ server }) // Relay multicast to websockets // @TODO still sends to supercollider? Do we need two sockets? /* var dgram = require('dgram'); var sss = dgram.createSocket('udp4'); sss.on('listening', () => { sss.addMembership('224.0.1.9'); }) sss.bind(6696, '224.0.1.9'); */ const scudp = new osc.UDPPort({ remotePort: OSCPORT //socket: sss }) scudp.on('open', () => { console.log("UDP to OSC open") }) scudp.on('message', (msg) => { if (DEBUG.udp) { console.log('got UDP msg', msg); } osclients.forEach( client => { if (client) { //console.log("sending", msg, info) client.send(msg) } }) }) scudp.on('error', (e) => { console.log('UDP OSC error!', e) }) scudp.open() if (MIDI) { mi.on('message', (deltaTime, message) => { // The message is an array of numbers corresponding to the MIDI bytes: // [status, data1, data2] // https://www.cs.cf.ac.uk/Dave/Multimedia/node158.html has some helpful // information interpreting the messages. console.log(`midi in: ${message} d: ${deltaTime}`); sendAll({ address: '/midi-in', args: message }, null, null, osclients) }); } function eulerFromQuaternion(quaternion) { // Quaternion to matrix. const w = quaternion[0], x = quaternion[1], y = quaternion[2], z = quaternion[3]; const x2 = x + x, y2 = y + y, z2 = z + z; const xx = x * x2, xy = x * y2, xz = x * z2; const yy = y * y2, yz = y * z2, zz = z * z2; const wx = w * x2, wy = w * y2, wz = w * z2; const matrix = [ 1 - ( yy + zz ), xy + wz, xz - wy, 0, xy - wz, 1 - ( xx + zz ), yz + wx, 0, xz + wy, yz - wx, 1 - ( xx + yy ), 0, 0, 0, 0, 1 ]; // Matrix to euler function clamp( value, min, max ) { return Math.max( min, Math.min( max, value ) ); } const m11 = matrix[ 0 ], m12 = matrix[ 4 ], m13 = matrix[ 8 ]; const m21 = matrix[ 1 ], m22 = matrix[ 5 ], m23 = matrix[ 9 ]; const m31 = matrix[ 2 ], m32 = matrix[ 6 ], m33 = matrix[ 10 ]; var euler = [ 0, 0, 0 ]; euler[1] = Math.asin( clamp( m13, - 1, 1 ) ); if ( Math.abs( m13 ) < 0.9999999 ) { euler[0] = Math.atan2( - m23, m33 ); euler[2] = Math.atan2( - m12, m11 ); } else { euler[0] = Math.atan2( m32, m22 ); euler[2] = 0; } return euler; } const sendAll = (msg, info, oscWS, osclients, prepend = '') => { // Reset euler rotation to 0 if (msg.address == '/keys') { if (msg.args[0] && msg.args[1] && msg.args[2] && msg.args[3]) { eulerRotation = [0, 0, 0] } } // Convert quaternion diff to euler angle diff if (msg.address == '/quaternionDiff') { const euler = eulerFromQuaternion(msg.args, 'XYZ'); sendAll({ address: '/eulerDiff', args: euler }, info, oscWS, osclients, prepend) eulerRotation[0] += euler[0] eulerRotation[1] += euler[1] eulerRotation[2] += euler[2] sendAll({ address: '/euler', args: eulerRotation }, info, oscWS, osclients, prepend) } // Add prepend let sendMsg = { address: `${prepend}${msg.address}`, args: msg.args } // OSC relay osclients.forEach( client => { if (client && oscWS != client) { // console.log("OSC RELAY", sendMsg, info, prepend) client.send(sendMsg) } }) if (scudp) { if (DEBUG.udp) { console.log("UDP SEND", msg) } scudp.send(sendMsg) } } let osclients = [] wss.on('connection', function (ws) { console.log('new client connection', ws._socket.remoteAddress) let oscWS = new osc.WebSocketPort({ socket: ws }); // Vsi OSC sem grejo naprej na kliente OSC oscWS.on('message', ({ address, args}, info) => { console.log('fasal sem', address, args) if (MIDI) { if (address == '/midi') { if (DEBUG.midi) { console.log('midi SEND', args) } mo.send(args) } } sendAll({ address, args}, info, oscWS, osclients) }) oscWS.on('error', error => { console.warn('Ignoring invalid OSC') console.warn(error) oscWS.close() osclients = osclients.filter(ws => ws !== oscWS) }) osclients.push(oscWS) oscWS.on('close', () => { console.log('closing socket', oscWS.socket.remoteAddress) osclients = osclients.filter(ws => ws !== oscWS) }) }) // Zapri midi /* if (MIDI && mo) { mo.closePort() } */