From ba6f1b2794e293333e6c0473210f35870c0c3bce Mon Sep 17 00:00:00 2001 From: michaelpalumbo Date: Wed, 13 Jan 2021 13:22:42 -0500 Subject: [PATCH] added a fact-specific patch for use by others --- allhands_cli.js | 18 +- allhands_fact_max.js | 453 +++++++++++++++++++++++++++++ fact.maxpat | 678 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1146 insertions(+), 3 deletions(-) create mode 100644 allhands_fact_max.js create mode 100644 fact.maxpat diff --git a/allhands_cli.js b/allhands_cli.js index 5448d2a..4510c4f 100644 --- a/allhands_cli.js +++ b/allhands_cli.js @@ -223,9 +223,9 @@ if (mode === "server"){ let APRoot = msg.addressPattern.split('/')[1] // Max.outlet(msg.addressPattern, msg.typeTagString) - if(APRoot == 'ping'){ - console.log('pingTime:', msg.typeTagString + 'ms') - } + // if(APRoot == 'ping'){ + // console.log('pingTime:', msg.typeTagString + 'ms') + // } // prevent data loopback from server broadcast (i.e. we don't ewant to receive our own) if(APRoot != name){ localSend.send(msg.addressPattern, msg.typeTagString, (err) => { @@ -247,7 +247,19 @@ if (mode === "server"){ ws.send(pong) // console.log(pong) break + case 'pingReport': + // console.log(msg.data) + let remotes = Object.keys(msg.data) + for(i=0;i { + if (err) console.error(err); + }); + } + break default: // inform user that unknown message commang used console.log('client sent message with unknown cmd: ' + msg.cmd) diff --git a/allhands_fact_max.js b/allhands_fact_max.js new file mode 100644 index 0000000..fce9a27 --- /dev/null +++ b/allhands_fact_max.js @@ -0,0 +1,453 @@ +// this is the max/msp-node version, still in dev + +// note: this version is for running wspd's server as a cloud instance on heroku, so to push updates, use: +// git push heroku placeholder:master + +// const yargs = require('yargs/yargs') +// const { hideBin } = require('yargs/helpers') +// const argv = yargs(hideBin(process.argv)).argv +const Max = require('max-api') + +// UDP send/receive +let localsend = null; +let localreceive = null; + +let host = 'allhandsjs.herokuapp.com' + +// we will now add a name to the address pattern of all local OSC messages that are to be sent over IP +let name; +if(process.argv[2]){ + name = process.argv[2] +}else{ + console.log('error: need to specify your name (one string, no spaces. i.e. steve') + process.exit() +} +if(process.argv[3]){ + console.log('error: client name cannot include spaces.') + process.exit() +} + +/* note: this removed for cloud version +if (mode === 'client' && !argv.host){ + console.log('error: client mode requires --host flag to specify server IP address\nexample:\n\nnode app.js --mode client --host localhost') + process.exit() +} +*/ + +// ***** Local UDP Send & Receive Config ******* // +let localReceivePort; +let localSendPort; + +const mode = 'client' +if (mode === 'client'){ + localReceivePort = 7404 + localSendPort = 7403 +} else if (mode === 'listener'){ + // localReceivePort = 7404 + listenerModeSendPort = 7405 +} + + +// ***** Local UDP-Sender ******* // +// this is used by either mode! +function init(){ + +} + + + +// both modes require these libs +const { Client, Server } = require('node-osc'); +const WebSocket = require('ws'); +let ws; // keep this here + +if (mode === "server"){ + + // run the serverconst WebSocket = require('ws'); + const app = require('express')() + const http = require('http').createServer(app);; + + let listenPort = (process.env.PORT || 8081) + const wss = new WebSocket.Server({ 'server': http, clientTracking: true }); + http.listen(listenPort, function(){ + }) + wss.on('connection', function connection(ws, req, client) { + + console.log('new connection established ') + + + + /* in the clooud version we don't need to have the server communicate with udp ports for OSC + const localSend = new Client('127.0.0.1', localSendPort); + console.log('Configure your local pd patch(es) to listen on UDP Port ' + localSendPort) + + const localReceive = new Server(localReceivePort, '0.0.0.0'); + // once running, inform user + localReceive.on('listening', () => { + console.log('Configure your local pd patch(es) to send on UDP Port ' + localReceivePort) + }) + // handle message: + localReceive.on('message', (msg) => { + // validate correct OSC address pattern syntax + // console.log(msg) + if(msg[0].charAt(0) === '/'){ + // get the address pattern + ap = '/' + name + msg[0] + // trim the address pattern + msg.shift() + // construct object to send over websocket + message = { + // cmd allows us to send other types of messages, ask Michael for more info if curious! + sender: name, + cmd: 'OSC', + date: new Date().toUTCString(), + addressPattern: ap, + // this is the data! + typeTagString: msg + } + // inform user + // console.log('sending to remote:\n', message) + // package data for the web, send it! + // if(ws){ + // now using a broadcast server + broadcast(JSON.stringify(message)) + // } + + } else { + // if incoming OSC message does not have an address pattern, refuse to handle it + console.log('error, OSC Message must lead with an addressPattern\n\ni.e. /bioData') + } + }); + */ + + ws.on('message', function incoming(message) { + msg = JSON.parse(message) + // console.log(msg) + + switch (msg.cmd){ + // in case you want to receive other data and route it elsewhere + case 'OSC': + /* in the cloud version, we don't want to send OSC data locally because there's nothing on heroku for it + localSend.send(msg.addressPattern, msg.typeTagString, (err) => { + if (err) console.error(err); + }); + */ + + + // inform user + // console.log('sending to remote:\n', message) + // package data for the web, send it! + // if(ws){ + // now using a broadcast server + broadcast(JSON.stringify(msg)) + + break; + + + default: + console.log('client sent message with unknown cmd: ' + msg) + // ws.send('server received message but did not understand: ' + msg) + break; + + + + } + }); + + ws.on('close', function(code, reason) { + /* commented out foor cloud-based version + localSend.close(); + localReceive.close(); + */ + }) + }); + // we can use this if we want to send to multiple clients! + function broadcast(msg){ + wss.clients.forEach(function each(client) { + if (client.readyState === WebSocket.OPEN) { + client.send(msg); + } + }); + } + + + + + +} else if (mode === 'client'){ + localSend = new Client('127.0.0.1', localSendPort); + console.log('Configure your local pd patch(es) to listen on UDP Port ' + localSendPort) + // run the app in client mode + // ***** Websocket ******* // + // WebSocket that will automatically attempt to reconnect if the connection is closed, or if the remote server goes down + const ReconnectingWebSocket = require('reconnecting-websocket'); + const serverIP = host + const serverPort = '8081'; + const serverWSAddress = `ws://${serverIP}/${serverPort}`; + // options for the reconnecting websocket + const rwsOptions = { + // make rws use the webSocket module implementation + WebSocket: WebSocket, + // ms to try reconnecting: + connectionTimeout: 1000, + //debug:true, + } + + // create a websocket + // console.log(`attempting to connect to server at ${serverWSAddress}`) + ws = new ReconnectingWebSocket(serverWSAddress, [], rwsOptions); + + // if the server responds with an error + ws.addEventListener('error', () => { + console.log(`connection error: ${serverIP}`); + }); + // on successful connection to server: + ws.addEventListener('open', () => { + console.log (`connected to server at ${serverWSAddress}`) + }); + // on close: + ws.addEventListener('close', () => { + console.log("server connection closed"); + // localSend.close(); + // localReceive.close(); + }); + // handle messages + ws.addEventListener('message', (data) => { + let msg = JSON.parse(data.data); + // console.log(msg) + switch (msg.cmd){ + // in case you want to receive other data and route it elsewhere + case 'OSC': + // send formatted OSC message locally (i.e. a pd patch) + // console.log(msg.addressPattern.split('/')[1]) + let APRoot = msg.addressPattern.split('/')[1] + // Max.outlet(msg.addressPattern, msg.typeTagString) + + // if(APRoot == 'ping'){ + // console.log('pingTime:', msg.typeTagString + 'ms') + // } + // prevent data loopback from server broadcast (i.e. we don't ewant to receive our own) + if(APRoot != name){ + localSend.send(msg.addressPattern, msg.typeTagString, (err) => { + if (err) console.error(err); + }); + // Max.outlet(msg.addressPattern, msg.typeTagString) + } + + + + break; + // respond to ping from server with a pong + case 'ping': + let pong = JSON.stringify({ + cmd: 'pong', + data: msg.data, + name: name + }) + ws.send(pong) + // console.log(pong) + break + case 'pingReport': + // console.log(msg.data) + let remotes = Object.keys(msg.data) + for(i=0;i { + if (err) console.error(err); + }); + if(name == remotes[i]){ + Max.outlet('thisPing', tts) + } + + + + } + + break + default: + // inform user that unknown message commang used + console.log('client sent message with unknown cmd: ' + msg.cmd) + break; + } + }); + + // ***** Local UDP-Receiver ******* // + // this is used by either mode! + let localReceivePort; + if(mode === 'server'){ + localReceivePort = 7402 + } else if (mode === 'client'){ + localReceivePort = 7404 + } + const localReceive = new Server(localReceivePort, '0.0.0.0'); + // once running, inform user + localReceive.on('listening', () => { + console.log('Configure your local pd patch(es) to send on UDP Port ' + localReceivePort) + }) + // handle message: + localReceive.on('message', (msg) => { + // validate correct OSC address pattern syntax + // console.log(msg) + if(msg[0].charAt(0) === '/'){ + // get the address pattern + ap = '/' + name + msg[0] + // trim the address pattern + msg.shift() + // construct object to send over websocket + message = { + // cmd allows us to send other types of messages, ask Michael for more info if curious! + cmd: 'OSC', + date: new Date().toUTCString(), + addressPattern: ap, + // this is the data! + typeTagString: msg, + } + // inform user + // console.log('sending to remote:\n', message) + // package data for the web, send it! + // if(ws){ + ws.send(JSON.stringify(message)) + // } + + } else { + // if incoming OSC message does not have an address pattern, refuse to handle it + console.log('error, OSC Message must lead with an addressPattern\n\ni.e. /bioData') + } + }); + +} else if (mode === 'listener'){ + const localSend = new Client('127.0.0.1', listenerModeSendPort); + console.log('Configure your local pd patch(es) to listen on UDP Port ' + listenerModeSendPort) + // run the app in client mode + // ***** Websocket ******* // + // WebSocket that will automatically attempt to reconnect if the connection is closed, or if the remote server goes down + const ReconnectingWebSocket = require('reconnecting-websocket'); + const serverIP = host + const serverPort = '8081'; + const serverWSAddress = `ws://${serverIP}:${serverPort}`; + // options for the reconnecting websocket + const rwsOptions = { + // make rws use the webSocket module implementation + WebSocket: WebSocket, + // ms to try reconnecting: + connectionTimeout: 1000, + //debug:true, + } + + // create a websocket + // console.log(`attempting to connect to server at ${serverWSAddress}`) + ws = new ReconnectingWebSocket(serverWSAddress, [], rwsOptions); + + // if the server responds with an error + ws.addEventListener('error', () => { + console.log(`connection error: ${serverIP}`); + }); + // on successful connection to server: + ws.addEventListener('open', () => { + console.log (`connected to server at ${serverWSAddress}`) + }); + // on close: + ws.addEventListener('close', () => { + console.log("server connection closed"); + // localSend.close(); + // localReceive.close(); + }); + // handle messages + ws.addEventListener('message', (data) => { + let msg = JSON.parse(data.data); + // console.log(msg) + switch (msg.cmd){ + // in case you want to receive other data and route it elsewhere + case 'OSC': + // send formatted OSC message locally (i.e. a pd patch) + + localSend.send(msg.addressPattern, msg.typeTagString, (err) => { + if (err) console.error(err); + }); + + + break; + + // respond to ping from server with a pong + case 'ping': + let pong = JSON.stringify({ + cmd: 'pong', + data: msg.data, + name: name + }) + ws.send(pong) + // console.log(pong) + break + + default: + // inform user that unknown message commang used + console.log('client sent message with unknown cmd: ' + msg.cmd) + break; + } + }); + + + // Commented this out so that the 'listener' doesn't send data back into the network. we can revisit this another time, oc. + // // ***** Local UDP-Receiver ******* // + // // this is used by either mode! + // let localReceivePort; + // if(mode === 'server'){ + // localReceivePort = 7402 + // } else if (mode === 'client'){ + // localReceivePort = 7404 + // } + // const localReceive = new Server(localReceivePort, '0.0.0.0'); + // // once running, inform user + // localReceive.on('listening', () => { + // console.log('Configure your local pd patch(es) to send on UDP Port ' + localReceivePort) + // }) + // // handle message: + // localReceive.on('message', (msg) => { + // // validate correct OSC address pattern syntax + // // console.log(msg) + // if(msg[0].charAt(0) === '/'){ + // // get the address pattern + // ap = msg[0] + // // trim the address pattern + // msg.shift() + // // construct object to send over websocket + // message = { + // // cmd allows us to send other types of messages, ask Michael for more info if curious! + // cmd: 'OSC', + // date: new Date().toUTCString(), + // addressPattern: ap, + // // this is the data! + // typeTagString: msg + // } + // // inform user + // // console.log('sending to remote:\n', message) + // // package data for the web, send it! + // // if(ws){ + // ws.send(JSON.stringify(message)) + // // } + + // } else { + // // if incoming OSC message does not have an address pattern, refuse to handle it + // console.log('error, OSC Message must lead with an addressPattern\n\ni.e. /bioData') + // } + // }); + +} + + + + else { + // app needs to know what mode to run in + console.log('error! first argument must be either \'client\' or \'server\'') + // stop node process + process.exit() +} + +// TODO: +// Max.addHandler('sendMSG', (OSC_AP, OSC_Data) => { + +// }) + diff --git a/fact.maxpat b/fact.maxpat new file mode 100644 index 0000000..d36b7a6 --- /dev/null +++ b/fact.maxpat @@ -0,0 +1,678 @@ +{ + "patcher" : { + "fileversion" : 1, + "appversion" : { + "major" : 8, + "minor" : 1, + "revision" : 8, + "architecture" : "x64", + "modernui" : 1 + } +, + "classnamespace" : "box", + "rect" : [ 34.0, 79.0, 1123.0, 937.0 ], + "bglocked" : 0, + "openinpresentation" : 1, + "default_fontsize" : 12.0, + "default_fontface" : 0, + "default_fontname" : "Arial", + "gridonopen" : 1, + "gridsize" : [ 15.0, 15.0 ], + "gridsnaponopen" : 1, + "objectsnaponopen" : 1, + "statusbarvisible" : 2, + "toolbarvisible" : 1, + "lefttoolbarpinned" : 0, + "toptoolbarpinned" : 0, + "righttoolbarpinned" : 0, + "bottomtoolbarpinned" : 0, + "toolbars_unpinned_last_save" : 0, + "tallnewobj" : 0, + "boxanimatetime" : 200, + "enablehscroll" : 1, + "enablevscroll" : 1, + "devicewidth" : 0.0, + "description" : "", + "digest" : "", + "tags" : "", + "style" : "", + "subpatcher_template" : "", + "assistshowspatchername" : 0, + "boxes" : [ { + "box" : { + "id" : "obj-17", + "linecount" : 2, + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 420.0, 395.0, 150.0, 33.0 ], + "presentation" : 1, + "presentation_rect" : [ 5.5, 300.0, 239.0, 20.0 ], + "text" : "Latency for each connected \"satellite\"" + } + + } +, { + "box" : { + "id" : "obj-15", + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 515.0, 532.0, 341.0, 20.0 ], + "presentation" : 1, + "presentation_rect" : [ 238.5, 531.0, 341.0, 20.0 ], + "text" : "<<< Joel: this is the number you need for the reverb time!" + } + + } +, { + "box" : { + "id" : "obj-13", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 282.0, 532.0, 231.0, 22.0 ], + "presentation" : 1, + "presentation_rect" : [ 5.5, 531.0, 231.0, 22.0 ], + "text" : "convolution reverb delay time: 34" + } + + } +, { + "box" : { + "id" : "obj-10", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 282.0, 502.0, 235.0, 22.0 ], + "text" : "prepend set convolution reverb delay time:" + } + + } +, { + "box" : { + "id" : "obj-8", + "maxclass" : "newobj", + "numinlets" : 3, + "numoutlets" : 3, + "outlettype" : [ "", "", "" ], + "patching_rect" : [ 168.0, 283.0, 140.0, 22.0 ], + "text" : "route pingTimes thisPing" + } + + } +, { + "box" : { + "id" : "obj-5", + "maxclass" : "dict.view", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 168.0, 324.0, 100.0, 100.0 ], + "presentation" : 1, + "presentation_rect" : [ 5.5, 322.0, 400.0, 197.0 ] + } + + } +, { + "box" : { + "id" : "obj-9", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 275.0, 186.0, 153.0, 22.0 ], + "text" : "loadmess script npm install" + } + + } +, { + "box" : { + "id" : "obj-3", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 21.0, 59.0, 79.0, 22.0 ], + "text" : "loadmess set" + } + + } +, { + "box" : { + "id" : "obj-34", + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 306.0, 60.5, 150.0, 20.0 ], + "presentation" : 1, + "presentation_rect" : [ 181.5, 102.0, 150.0, 20.0 ], + "text" : "<< Type here + hit Enter" + } + + } +, { + "box" : { + "id" : "obj-32", + "linecount" : 5, + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 151.0, -19.0, 150.0, 74.0 ], + "presentation" : 1, + "presentation_linecount" : 2, + "presentation_rect" : [ 5.5, 9.0, 320.0, 33.0 ], + "text" : "1. Assign a name for your computer (i.e. your first name). Hint: Helps others if you keep this consistent with last time" + } + + } +, { + "box" : { + "id" : "obj-26", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 10.0, 18.0, 54.0, 22.0 ], + "text" : "onecopy" + } + + } +, { + "box" : { + "id" : "obj-24", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 2, + "outlettype" : [ "select", "" ], + "patcher" : { + "fileversion" : 1, + "appversion" : { + "major" : 8, + "minor" : 1, + "revision" : 8, + "architecture" : "x64", + "modernui" : 1 + } +, + "classnamespace" : "box", + "rect" : [ 84.0, 129.0, 640.0, 480.0 ], + "bglocked" : 0, + "openinpresentation" : 0, + "default_fontsize" : 12.0, + "default_fontface" : 0, + "default_fontname" : "Arial", + "gridonopen" : 1, + "gridsize" : [ 15.0, 15.0 ], + "gridsnaponopen" : 1, + "objectsnaponopen" : 1, + "statusbarvisible" : 2, + "toolbarvisible" : 1, + "lefttoolbarpinned" : 0, + "toptoolbarpinned" : 0, + "righttoolbarpinned" : 0, + "bottomtoolbarpinned" : 0, + "toolbars_unpinned_last_save" : 0, + "tallnewobj" : 0, + "boxanimatetime" : 200, + "enablehscroll" : 1, + "enablevscroll" : 1, + "devicewidth" : 0.0, + "description" : "", + "digest" : "", + "tags" : "", + "style" : "", + "subpatcher_template" : "", + "assistshowspatchername" : 0, + "boxes" : [ { + "box" : { + "id" : "obj-1", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 146.0, 399.0, 97.0, 22.0 ], + "text" : "value localName" + } + + } +, { + "box" : { + "id" : "obj-17", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 101.0, 155.5, 72.0, 22.0 ], + "text" : "prepend set" + } + + } +, { + "box" : { + "id" : "obj-12", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 105.0, 302.5, 133.0, 22.0 ], + "text" : "tosymbol @separator _" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-20", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 2, + "outlettype" : [ "", "" ], + "patching_rect" : [ 50.0, 266.5, 74.0, 23.0 ], + "text" : "route bang" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-19", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 3, + "outlettype" : [ "select", "", "clear" ], + "patching_rect" : [ 50.0, 110.5, 121.0, 23.0 ], + "text" : "trigger select l clear" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-10", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 2, + "outlettype" : [ "", "" ], + "patching_rect" : [ 50.0, 238.5, 66.0, 23.0 ], + "text" : "route text" + } + + } +, { + "box" : { + "comment" : "", + "id" : "obj-21", + "index" : 1, + "maxclass" : "inlet", + "numinlets" : 0, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 50.0, 40.0, 30.0, 30.0 ] + } + + } +, { + "box" : { + "comment" : "", + "id" : "obj-22", + "index" : 1, + "maxclass" : "outlet", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 61.0, 399.0, 30.0, 30.0 ] + } + + } +, { + "box" : { + "comment" : "", + "id" : "obj-23", + "index" : 2, + "maxclass" : "outlet", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 105.0, 399.0, 30.0, 30.0 ] + } + + } + ], + "lines" : [ { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "source" : [ "obj-10", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-1", 0 ], + "order" : 0, + "source" : [ "obj-12", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-19", 0 ], + "midpoints" : [ 114.5, 336.5, 248.0, 336.5, 248.0, 101.5, 59.5, 101.5 ], + "order" : 2, + "source" : [ "obj-12", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-23", 0 ], + "order" : 1, + "source" : [ "obj-12", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-22", 0 ], + "source" : [ "obj-17", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-17", 0 ], + "source" : [ "obj-19", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-22", 0 ], + "source" : [ "obj-19", 2 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-22", 0 ], + "source" : [ "obj-19", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-12", 0 ], + "source" : [ "obj-20", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-10", 0 ], + "source" : [ "obj-21", 0 ] + } + + } + ] + } +, + "patching_rect" : [ 127.5, 88.5, 59.5, 22.0 ], + "saved_object_attributes" : { + "description" : "", + "digest" : "", + "globalpatchername" : "", + "tags" : "" + } +, + "text" : "p" + } + + } +, { + "box" : { + "id" : "obj-18", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 2, + "outlettype" : [ "", "bang" ], + "patching_rect" : [ 168.0, 153.0, 45.5, 22.0 ], + "text" : "t l b" + } + + } +, { + "box" : { + "autoscroll" : 0, + "bangmode" : 1, + "fontface" : 0, + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-11", + "keymode" : 1, + "lines" : 1, + "maxclass" : "textedit", + "numinlets" : 1, + "numoutlets" : 4, + "outlettype" : [ "", "int", "", "" ], + "parameter_enable" : 0, + "patching_rect" : [ 127.5, 59.0, 174.0, 23.0 ], + "presentation" : 1, + "presentation_rect" : [ 5.5, 44.0, 174.0, 23.0 ], + "tabmode" : 0, + "text" : "michael" + } + + } +, { + "box" : { + "id" : "obj-7", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 168.0, 122.0, 111.0, 22.0 ], + "text" : "prepend script start" + } + + } +, { + "box" : { + "id" : "obj-6", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 194.5, 186.0, 63.0, 22.0 ], + "text" : "script stop" + } + + } +, { + "box" : { + "bgmode" : 0, + "border" : 0, + "clickthrough" : 0, + "enablehscroll" : 0, + "enablevscroll" : 0, + "id" : "obj-2", + "lockeddragscroll" : 0, + "maxclass" : "bpatcher", + "name" : "n4m.monitor.maxpat", + "numinlets" : 1, + "numoutlets" : 1, + "offset" : [ 0.0, 0.0 ], + "outlettype" : [ "bang" ], + "patching_rect" : [ 351.0, 251.0, 400.0, 220.0 ], + "presentation" : 1, + "presentation_rect" : [ 5.5, 72.0, 400.0, 220.0 ], + "viewvisibility" : 1 + } + + } +, { + "box" : { + "id" : "obj-1", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 2, + "outlettype" : [ "", "" ], + "patching_rect" : [ 168.0, 222.0, 239.0, 22.0 ], + "saved_object_attributes" : { + "autostart" : 0, + "defer" : 0, + "node_bin_path" : "", + "npm_bin_path" : "", + "watch" : 1 + } +, + "text" : "node.script allhands_fact_max.js @watch 1" + } + + } + ], + "lines" : [ { + "patchline" : { + "destination" : [ "obj-2", 0 ], + "midpoints" : [ 397.5, 251.0, 360.5, 251.0 ], + "source" : [ "obj-1", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-8", 0 ], + "source" : [ "obj-1", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-13", 0 ], + "source" : [ "obj-10", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-24", 0 ], + "source" : [ "obj-11", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-1", 0 ], + "source" : [ "obj-18", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-6", 0 ], + "source" : [ "obj-18", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-11", 0 ], + "midpoints" : [ 137.0, 120.5, 121.0, 120.5, 121.0, 48.0, 137.0, 48.0 ], + "source" : [ "obj-24", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-7", 0 ], + "source" : [ "obj-24", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-11", 0 ], + "source" : [ "obj-3", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-1", 0 ], + "midpoints" : [ 204.0, 216.0, 177.5, 216.0 ], + "source" : [ "obj-6", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-18", 0 ], + "source" : [ "obj-7", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-10", 0 ], + "midpoints" : [ 238.0, 316.0, 291.5, 316.0 ], + "source" : [ "obj-8", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-5", 0 ], + "source" : [ "obj-8", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-1", 0 ], + "source" : [ "obj-9", 0 ] + } + + } + ], + "dependency_cache" : [ { + "name" : "allhands_fact_max.js", + "bootpath" : "~/allhands", + "patcherrelativepath" : ".", + "type" : "TEXT", + "implicit" : 1 + } +, { + "name" : "n4m.monitor.maxpat", + "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", + "type" : "JSON", + "implicit" : 1 + } +, { + "name" : "resize_n4m_monitor_patcher.js", + "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", + "type" : "TEXT", + "implicit" : 1 + } +, { + "name" : "fit_jweb_to_bounds.js", + "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", + "type" : "TEXT", + "implicit" : 1 + } + ], + "autosave" : 0, + "styles" : [ { + "name" : "helpfile_label-1", + "default" : { + "fontname" : [ "Arial" ], + "textcolor" : [ 0.501961, 0.501961, 0.501961, 1 ], + "fontsize" : [ 13 ] + } +, + "parentstyle" : "", + "multi" : 0 + } + ] + } + +}