// 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 = true 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') let midiOn = false let midiNotes = {} // Vzemi iz argumenta const tty = process.argv[2] let eulerRotation = [0, 0, 0] let baudrate = parseInt(process.argv[3]) if (!baudrate) { baudrate = 115200 } 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' ]; const app = express(); const server = http.Server(app); // Odprti serijski OSC link let scon = null // Midi port const mo = new midi.Output() //mo.getPortCount() //mo.getPortName(0) //mo.openPort(0) mo.openVirtualPort("kegel") function openSerial() { console.log('opening ', tty, baudrate) scon = new osc.SerialPort({ devicePath: tty, bitrate: baudrate, autoOpen: true, useSLIP: true }) scon.on('open', e => { console.log('serial connection opened') //console.log(scon) }) scon.on('error', e => { console.error('tty error', e) //scon.close() }) scon.on('close', e => { console.warn('serial connection closed, restarting in 1 second') setTimeout(openSerial, 1000) }) // Arduino OSC gre v web scon.on('message', msg => { // Debug incoming osc if (DEBUG) { console.log('osc msg', msg) } sendAll(msg, null, null, osclients) }) scon.open() if (scon._closeCode) { scon = null console.log('restarting serial connection') setTimeout(openSerial, 1000) } } if (tty) { openSerial(baudrate) } 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) { 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() 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) => { // 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) eulerRotation[0] += euler[0] eulerRotation[1] += euler[1] eulerRotation[2] += euler[2] sendAll({ address: '/euler', args: eulerRotation }, info, oscWS, osclients) } if (msg.address == '/accel') { if (msg.args[1] > 5000) { console.log("SEND MIDI!") const visina = Math.round((msg.args[1] - 5000) / 100) midiNotes[visina] = true mo.send([144, visina, 127]) midiOn = true } if (msg.args[1] < 5000 && midiOn) { Object.keys(midiNotes).forEach(visina => { mo.send([128, visina, 127]) }) midiOn = false } } osclients.forEach( client => { if (client && oscWS != client) { // console.log("sending", msg, info) client.send(msg) } }) if (scudp) { if (DEBUG) { console.log("UDP SEND", msg) } scudp.send(msg) } } const osclients = [] wss.on('connection', function (ws) { console.log('new client connection') let oscWS = new osc.WebSocketPort({ socket: ws, metadata: false }); // Vsi OSC sem grejo naprej na kliente OSC oscWS.on('packet', (packet, info) => { // Broadcast adjust msg const [address, args] = packet sendAll({ address, args}, info, oscWS, osclients) }) oscWS.on('error', error => { console.warn('Ignoring invalid OSC') console.warn(error) }) osclients.push(oscWS) /* oscWS.on('close', () => { oscWS = null }) */ }) // Zapri midi mo.closePort()