├── .gitignore ├── 1-watch-youtube-videos ├── .gitignore ├── package-lock.json ├── package.json ├── watch-youtube.js └── watch-youtube.maxpat ├── 2-static-d3js ├── data │ ├── data.csv │ ├── groupchart.js │ └── style.css ├── package-lock.json ├── package.json ├── static-d3.js └── static-d3.maxpat ├── 3-socketio-client ├── package-lock.json ├── package.json ├── server │ ├── index.html │ ├── index.js │ ├── package-lock.json │ └── package.json ├── socketio-client.js └── socketio-client.maxpat ├── 4-mqtt ├── mqtt-publisher.js ├── mqtt-subscriber.js ├── mqtt.maxpat ├── package-lock.json └── package.json ├── 5-repl ├── package-lock.json ├── package.json ├── repl-server.js └── repl.maxpat ├── 6-repl-with-history ├── repl-server.js └── repl-with-history.maxpat ├── 7-live-coded-patcher-scripting ├── filter-frequencies.maxpat ├── filter-resonances.maxpat ├── live-coded-patcher-scripting.maxpat ├── pitches.maxpat └── repl-server.js ├── 8-express-js-sound-player ├── data │ ├── sound1.wav │ ├── sound2.wav │ ├── sound3.wav │ └── sounds.csv ├── express-js-sound-player.maxpat ├── package-lock.json ├── package.json └── server.js ├── README.md └── transcripts ├── .keep ├── 01-watch-youtube-videos.md ├── 02-static-d3js.md ├── 03-socketio-client.md ├── 04-mqtt-subscriber.md ├── 05-live-coding-repl.md ├── 06-repl-with-history.md ├── 07-live-coded-patcher-scripting.md └── 08-express-js-sound-player.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /1-watch-youtube-videos/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/** -------------------------------------------------------------------------------- /1-watch-youtube-videos/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "1-watch-youtube-videos", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "html-entities": { 8 | "version": "1.2.1", 9 | "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", 10 | "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=" 11 | }, 12 | "m3u8stream": { 13 | "version": "0.4.2", 14 | "resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.4.2.tgz", 15 | "integrity": "sha512-VZeobA83iQdd2TY12K2DNLuUmE1XNjN660SYU9s6A4kP7ZhFEp/bQPUpOWU2EbkFYwsonyaFT+RxLDr1OhPTRA==", 16 | "requires": { 17 | "miniget": "^1.4.0", 18 | "sax": "^1.2.4" 19 | } 20 | }, 21 | "miniget": { 22 | "version": "1.5.0", 23 | "resolved": "https://registry.npmjs.org/miniget/-/miniget-1.5.0.tgz", 24 | "integrity": "sha512-RbRCj2m8Q/arS1nExuMB8qO4QavCpvfetQR+HVCeHGiFJGOryPn8u0z4WrIx9AAIDYFDVcFhQsRYvSY+UA5HJQ==" 25 | }, 26 | "sax": { 27 | "version": "1.2.4", 28 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 29 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 30 | }, 31 | "ytdl-core": { 32 | "version": "0.25.2", 33 | "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-0.25.2.tgz", 34 | "integrity": "sha512-/iBcw6SQ2jH3jyCdMy8lhRGo9ZY7ufhgVv9DEO1Zyvw6wadWolrcEc0qJ54k0Yin1IehU8NjL2Psnx9e6/F/zA==", 35 | "requires": { 36 | "html-entities": "^1.1.3", 37 | "m3u8stream": "^0.4.0", 38 | "miniget": "^1.4.0", 39 | "sax": "^1.1.3" 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /1-watch-youtube-videos/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "1-watch-youtube-videos", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "watch-youtube.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Julian Rubisch", 10 | "license": "MIT", 11 | "dependencies": { 12 | "ytdl-core": "^0.25.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /1-watch-youtube-videos/watch-youtube.js: -------------------------------------------------------------------------------- 1 | const maxApi = require("max-api"); 2 | const ytdl = require('ytdl-core'); 3 | 4 | maxApi.addHandler("open", (url) => { 5 | ytdl.getInfo(url) 6 | .then(info => { 7 | let format = ytdl.chooseFormat(info.formats, {}); 8 | maxApi.outlet("download_url", format.url); 9 | }) 10 | .catch(() => { 11 | maxApi.post(`Error fetching video: ${url}`) 12 | }) 13 | }); -------------------------------------------------------------------------------- /1-watch-youtube-videos/watch-youtube.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 8, 6 | "minor" : 0, 7 | "revision" : 0, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "classnamespace" : "box", 13 | "rect" : [ 59.0, 104.0, 984.0, 657.0 ], 14 | "bglocked" : 0, 15 | "openinpresentation" : 0, 16 | "default_fontsize" : 12.0, 17 | "default_fontface" : 0, 18 | "default_fontname" : "Arial", 19 | "gridonopen" : 1, 20 | "gridsize" : [ 15.0, 15.0 ], 21 | "gridsnaponopen" : 1, 22 | "objectsnaponopen" : 1, 23 | "statusbarvisible" : 2, 24 | "toolbarvisible" : 1, 25 | "lefttoolbarpinned" : 0, 26 | "toptoolbarpinned" : 0, 27 | "righttoolbarpinned" : 0, 28 | "bottomtoolbarpinned" : 0, 29 | "toolbars_unpinned_last_save" : 0, 30 | "tallnewobj" : 0, 31 | "boxanimatetime" : 200, 32 | "enablehscroll" : 1, 33 | "enablevscroll" : 1, 34 | "devicewidth" : 0.0, 35 | "description" : "", 36 | "digest" : "", 37 | "tags" : "", 38 | "style" : "", 39 | "subpatcher_template" : "", 40 | "boxes" : [ { 41 | "box" : { 42 | "id" : "obj-32", 43 | "maxclass" : "message", 44 | "numinlets" : 2, 45 | "numoutlets" : 1, 46 | "outlettype" : [ "" ], 47 | "patching_rect" : [ 134.0, 5.0, 177.0, 22.0 ], 48 | "text" : "open https://youtu.be/foobar" 49 | } 50 | 51 | } 52 | , { 53 | "box" : { 54 | "id" : "obj-30", 55 | "maxclass" : "playbar", 56 | "numinlets" : 1, 57 | "numoutlets" : 2, 58 | "outlettype" : [ "", "int" ], 59 | "patching_rect" : [ 141.0, 200.0, 320.0, 16.0 ] 60 | } 61 | 62 | } 63 | , { 64 | "box" : { 65 | "id" : "obj-27", 66 | "maxclass" : "newobj", 67 | "numinlets" : 1, 68 | "numoutlets" : 1, 69 | "outlettype" : [ "" ], 70 | "patching_rect" : [ 174.0, 152.0, 80.0, 22.0 ], 71 | "text" : "prepend read" 72 | } 73 | 74 | } 75 | , { 76 | "box" : { 77 | "id" : "obj-26", 78 | "maxclass" : "newobj", 79 | "numinlets" : 2, 80 | "numoutlets" : 2, 81 | "outlettype" : [ "", "" ], 82 | "patching_rect" : [ 179.0, 113.0, 111.0, 22.0 ], 83 | "text" : "route download_url" 84 | } 85 | 86 | } 87 | , { 88 | "box" : { 89 | "bgmode" : 0, 90 | "border" : 0, 91 | "clickthrough" : 0, 92 | "enablehscroll" : 0, 93 | "enablevscroll" : 0, 94 | "id" : "obj-21", 95 | "lockeddragscroll" : 0, 96 | "maxclass" : "bpatcher", 97 | "name" : "n4m.monitor.maxpat", 98 | "numinlets" : 1, 99 | "numoutlets" : 0, 100 | "offset" : [ 0.0, 0.0 ], 101 | "patching_rect" : [ 495.0, 113.0, 400.0, 220.0 ], 102 | "viewvisibility" : 1 103 | } 104 | 105 | } 106 | , { 107 | "box" : { 108 | "id" : "obj-16", 109 | "linecount" : 2, 110 | "maxclass" : "newobj", 111 | "numinlets" : 1, 112 | "numoutlets" : 2, 113 | "outlettype" : [ "", "" ], 114 | "patching_rect" : [ 350.0, 45.0, 164.0, 35.0 ], 115 | "saved_object_attributes" : { 116 | "autostart" : 1, 117 | "defer" : 0, 118 | "node" : "", 119 | "npm" : "", 120 | "watch" : 1 121 | } 122 | , 123 | "text" : "node.script watch-youtube.js @autostart 1 @watch 1" 124 | } 125 | 126 | } 127 | , { 128 | "box" : { 129 | "fontname" : "Arial", 130 | "fontsize" : 12.0, 131 | "id" : "obj-9", 132 | "maxclass" : "newobj", 133 | "numinlets" : 2, 134 | "numoutlets" : 1, 135 | "outlettype" : [ "bang" ], 136 | "patching_rect" : [ 54.0, 171.0, 65.0, 22.0 ], 137 | "text" : "qmetro 40" 138 | } 139 | 140 | } 141 | , { 142 | "box" : { 143 | "id" : "obj-15", 144 | "maxclass" : "toggle", 145 | "numinlets" : 1, 146 | "numoutlets" : 1, 147 | "outlettype" : [ "int" ], 148 | "parameter_enable" : 0, 149 | "patching_rect" : [ 54.0, 143.0, 20.0, 20.0 ] 150 | } 151 | 152 | } 153 | , { 154 | "box" : { 155 | "id" : "obj-8", 156 | "maxclass" : "jit.pwindow", 157 | "numinlets" : 1, 158 | "numoutlets" : 2, 159 | "outlettype" : [ "", "" ], 160 | "patching_rect" : [ 53.0, 277.0, 339.0, 190.0 ] 161 | } 162 | 163 | } 164 | , { 165 | "box" : { 166 | "id" : "obj-5", 167 | "maxclass" : "message", 168 | "numinlets" : 2, 169 | "numoutlets" : 1, 170 | "outlettype" : [ "" ], 171 | "patching_rect" : [ 350.0, 5.0, 201.0, 22.0 ], 172 | "text" : "open https://youtu.be/sF4FHNgKl4w" 173 | } 174 | 175 | } 176 | , { 177 | "box" : { 178 | "id" : "obj-3", 179 | "maxclass" : "newobj", 180 | "numinlets" : 1, 181 | "numoutlets" : 2, 182 | "outlettype" : [ "jit_matrix", "" ], 183 | "patching_rect" : [ 53.0, 235.0, 53.0, 22.0 ], 184 | "text" : "jit.movie" 185 | } 186 | 187 | } 188 | ], 189 | "lines" : [ { 190 | "patchline" : { 191 | "destination" : [ "obj-9", 0 ], 192 | "source" : [ "obj-15", 0 ] 193 | } 194 | 195 | } 196 | , { 197 | "patchline" : { 198 | "destination" : [ "obj-21", 0 ], 199 | "source" : [ "obj-16", 1 ] 200 | } 201 | 202 | } 203 | , { 204 | "patchline" : { 205 | "destination" : [ "obj-26", 0 ], 206 | "source" : [ "obj-16", 0 ] 207 | } 208 | 209 | } 210 | , { 211 | "patchline" : { 212 | "destination" : [ "obj-27", 0 ], 213 | "source" : [ "obj-26", 0 ] 214 | } 215 | 216 | } 217 | , { 218 | "patchline" : { 219 | "destination" : [ "obj-3", 0 ], 220 | "source" : [ "obj-27", 0 ] 221 | } 222 | 223 | } 224 | , { 225 | "patchline" : { 226 | "destination" : [ "obj-8", 0 ], 227 | "source" : [ "obj-3", 0 ] 228 | } 229 | 230 | } 231 | , { 232 | "patchline" : { 233 | "destination" : [ "obj-3", 0 ], 234 | "source" : [ "obj-30", 0 ] 235 | } 236 | 237 | } 238 | , { 239 | "patchline" : { 240 | "destination" : [ "obj-16", 0 ], 241 | "source" : [ "obj-32", 0 ] 242 | } 243 | 244 | } 245 | , { 246 | "patchline" : { 247 | "destination" : [ "obj-16", 0 ], 248 | "source" : [ "obj-5", 0 ] 249 | } 250 | 251 | } 252 | , { 253 | "patchline" : { 254 | "destination" : [ "obj-3", 0 ], 255 | "source" : [ "obj-9", 0 ] 256 | } 257 | 258 | } 259 | ], 260 | "dependency_cache" : [ { 261 | "name" : "n4m.monitor.maxpat", 262 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 263 | "type" : "JSON", 264 | "implicit" : 1 265 | } 266 | , { 267 | "name" : "resize_n4m_monitor_patcher.js", 268 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 269 | "type" : "TEXT", 270 | "implicit" : 1 271 | } 272 | ], 273 | "autosave" : 0 274 | } 275 | 276 | } 277 | -------------------------------------------------------------------------------- /2-static-d3js/data/data.csv: -------------------------------------------------------------------------------- 1 | State,Under 5 Years,5 to 13 Years,14 to 17 Years,18 to 24 Years,25 to 44 Years,45 to 64 Years,65 Years and Over 2 | CA,2704659,4499890,2159981,3853788,10604510,8819342,4114496 3 | TX,2027307,3277946,1420518,2454721,7017731,5656528,2472223 4 | NY,1208495,2141490,1058031,1999120,5355235,5120254,2607672 5 | FL,1140516,1938695,925060,1607297,4782119,4746856,3187797 6 | IL,894368,1558919,725973,1311479,3596343,3239173,1575308 7 | PA,737462,1345341,679201,1203944,3157759,3414001,1910571 -------------------------------------------------------------------------------- /2-static-d3js/data/groupchart.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const csvString = fs.readFileSync('./data/data.csv', 'UTF-8').toString(); 3 | 4 | module.exports = function(d3, svg, options) { 5 | 6 | var g = svg.append("g").attr("transform", "translate(" + options.margin.left + "," + options.margin.top + ")"); 7 | 8 | var x0 = d3.scaleBand() 9 | .rangeRound([0, options.width]) 10 | .paddingInner(0.1); 11 | 12 | var x1 = d3.scaleBand() 13 | .padding(0.05); 14 | 15 | var y = d3.scaleLinear() 16 | .rangeRound([options.height, 0]); 17 | 18 | var z = d3.scaleOrdinal() 19 | .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]); 20 | 21 | var data = d3.csvParse(csvString); 22 | 23 | var keys = data.columns.slice(1); 24 | 25 | x0.domain(data.map(function(d) { return d.State; })); 26 | x1.domain(keys).rangeRound([0, x0.bandwidth()]); 27 | y.domain([0, d3.max(data, function(d) { return d3.max(keys, function(key) { return +d[key]; }); })]).nice(); 28 | 29 | g.append("g") 30 | .selectAll("g") 31 | .data(data) 32 | .enter().append("g") 33 | .attr("transform", function(d) { return "translate(" + x0(d.State) + ",0)"; }) 34 | .selectAll("rect") 35 | .data(function(d) { return keys.map(function(key) { return {key: key, value: d[key]}; }); }) 36 | .enter().append("rect") 37 | .attr("x", function(d) { return x1(d.key); }) 38 | .attr("y", function(d) { return y(d.value); }) 39 | .attr("width", x1.bandwidth()) 40 | .attr("height", function(d) { return options.height - y(d.value); }) 41 | .attr("fill", function(d) { return z(d.key); }); 42 | 43 | g.append("g") 44 | .attr("class", "axis") 45 | .attr("transform", "translate(0," + options.height + ")") 46 | .call(d3.axisBottom(x0)); 47 | 48 | g.append("g") 49 | .attr("class", "axis") 50 | .call(d3.axisLeft(y).ticks(null, "s")) 51 | .append("text") 52 | .attr("x", 2) 53 | .attr("y", y(y.ticks().pop()) + 0.5) 54 | .attr("dy", "0.32em") 55 | .attr("fill", "#000") 56 | .attr("font-weight", "bold") 57 | .attr("text-anchor", "start") 58 | .text("Population"); 59 | 60 | var legend = g.append("g") 61 | .attr("font-family", "sans-serif") 62 | .attr("font-size", 10) 63 | .attr("text-anchor", "end") 64 | .selectAll("g") 65 | .data(keys.slice().reverse()) 66 | .enter().append("g") 67 | .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); 68 | 69 | legend.append("rect") 70 | .attr("x", options.width - 19) 71 | .attr("width", 19) 72 | .attr("height", 19) 73 | .attr("fill", z); 74 | 75 | legend.append("text") 76 | .attr("x", options.width - 24) 77 | .attr("y", 9.5) 78 | .attr("dy", "0.32em") 79 | .text(function(d) { return d; }); 80 | 81 | } -------------------------------------------------------------------------------- /2-static-d3js/data/style.css: -------------------------------------------------------------------------------- 1 | .bar rect { 2 | fill: steelblue; 3 | } 4 | .bar text { 5 | fill: #fff; 6 | font: 10px sans-serif; 7 | } -------------------------------------------------------------------------------- /2-static-d3js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "static-d3js", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "static-d3.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Julian Rubisch", 10 | "license": "MIT", 11 | "dependencies": { 12 | "d3-node": "^2.1.0", 13 | "svg2png": "^4.1.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /2-static-d3js/static-d3.js: -------------------------------------------------------------------------------- 1 | const maxApi = require('max-api'); 2 | const fs = require('fs'); 3 | const D3Node = require('d3-node'); 4 | const svg2png = require('svg2png'); 5 | 6 | let styleFile = 'style.css'; 7 | 8 | maxApi.addHandler("style", (file) => { 9 | styleFile = file; 10 | }); 11 | 12 | 13 | maxApi.addHandler("d3", (inputFile) => { 14 | const styles = fs.readFileSync(`./data/${styleFile}`); 15 | 16 | var options = { 17 | styles, 18 | }; 19 | 20 | var d3n = new D3Node(options); 21 | 22 | const d3 = d3n.d3; 23 | 24 | var margin = {top: 10, right: 30, bottom: 30, left: 30}, 25 | width = 1920 - margin.left - margin.right, 26 | height = 1080 - margin.top - margin.bottom; 27 | 28 | const svgWidth = width + margin.left + margin.right; 29 | const svgHeight = height + margin.top + margin.bottom; 30 | 31 | var svg = d3n.createSVG(svgWidth, svgHeight); 32 | 33 | require(`./data/${inputFile || 'chart.js'}`)(d3, svg, { width, height, margin }); 34 | 35 | var svgBuffer = new Buffer(d3n.svgString(), 'utf-8'); 36 | svg2png(svgBuffer) 37 | .then(buffer => fs.writeFile(`./data/${inputFile.slice(0, -3)}.png`, buffer, (err) => { 38 | if (err) throw err; 39 | maxApi.outlet("filepath", `${__dirname}/data/${inputFile.slice(0, -3)}.png`) 40 | })) 41 | .catch(e => console.error('ERR:', e)) 42 | 43 | }); -------------------------------------------------------------------------------- /2-static-d3js/static-d3.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 8, 6 | "minor" : 0, 7 | "revision" : 0, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "classnamespace" : "box", 13 | "rect" : [ 232.0, 274.0, 893.0, 450.0 ], 14 | "bglocked" : 0, 15 | "openinpresentation" : 0, 16 | "default_fontsize" : 12.0, 17 | "default_fontface" : 0, 18 | "default_fontname" : "Arial", 19 | "gridonopen" : 1, 20 | "gridsize" : [ 15.0, 15.0 ], 21 | "gridsnaponopen" : 1, 22 | "objectsnaponopen" : 1, 23 | "statusbarvisible" : 2, 24 | "toolbarvisible" : 1, 25 | "lefttoolbarpinned" : 0, 26 | "toptoolbarpinned" : 0, 27 | "righttoolbarpinned" : 0, 28 | "bottomtoolbarpinned" : 0, 29 | "toolbars_unpinned_last_save" : 0, 30 | "tallnewobj" : 0, 31 | "boxanimatetime" : 200, 32 | "enablehscroll" : 1, 33 | "enablevscroll" : 1, 34 | "devicewidth" : 0.0, 35 | "description" : "", 36 | "digest" : "", 37 | "tags" : "", 38 | "style" : "", 39 | "subpatcher_template" : "", 40 | "boxes" : [ { 41 | "box" : { 42 | "id" : "obj-31", 43 | "linecount" : 9, 44 | "maxclass" : "comment", 45 | "numinlets" : 1, 46 | "numoutlets" : 0, 47 | "patching_rect" : [ 402.5, 311.0, 502.0, 127.0 ], 48 | "text" : "A little script that allows you to render PNG images from a D3JS script.\n\nClick [script npm install], then [script start] below\n\nSend a [d3] message followed by a valid d3js script file. An example based on Mike Bostock's grouped bar chart is provided. https://bl.ocks.org/mbostock/3887051\n\nFor details on the script, refer here: https://www.znibbl.es/videos/10-node-in-max-02-static-d3js" 49 | } 50 | 51 | } 52 | , { 53 | "box" : { 54 | "id" : "obj-6", 55 | "maxclass" : "toggle", 56 | "numinlets" : 1, 57 | "numoutlets" : 1, 58 | "outlettype" : [ "int" ], 59 | "parameter_enable" : 0, 60 | "patching_rect" : [ 158.0, 103.0, 24.0, 24.0 ] 61 | } 62 | 63 | } 64 | , { 65 | "box" : { 66 | "id" : "obj-3", 67 | "maxclass" : "newobj", 68 | "numinlets" : 1, 69 | "numoutlets" : 2, 70 | "outlettype" : [ "bang", "erase" ], 71 | "patching_rect" : [ 158.0, 181.0, 57.0, 22.0 ], 72 | "text" : "t b erase" 73 | } 74 | 75 | } 76 | , { 77 | "box" : { 78 | "id" : "obj-42", 79 | "maxclass" : "newobj", 80 | "numinlets" : 1, 81 | "numoutlets" : 2, 82 | "outlettype" : [ "bang", "" ], 83 | "patching_rect" : [ 105.0, 214.0, 229.0, 22.0 ], 84 | "text" : "jit.gl.render chart @erase_color 1. 1. 1. 0." 85 | } 86 | 87 | } 88 | , { 89 | "box" : { 90 | "id" : "obj-41", 91 | "maxclass" : "jit.pwindow", 92 | "name" : "chart", 93 | "numinlets" : 1, 94 | "numoutlets" : 2, 95 | "outlettype" : [ "", "" ], 96 | "patching_rect" : [ 41.0, 321.0, 338.0, 203.0 ] 97 | } 98 | 99 | } 100 | , { 101 | "box" : { 102 | "id" : "obj-34", 103 | "maxclass" : "newobj", 104 | "numinlets" : 1, 105 | "numoutlets" : 2, 106 | "outlettype" : [ "jit_matrix", "" ], 107 | "patching_rect" : [ 41.0, 280.0, 329.0, 22.0 ], 108 | "text" : "jit.gl.videoplane chart @blend_enable 1 @scale 1. 0.5625 1." 109 | } 110 | 111 | } 112 | , { 113 | "box" : { 114 | "fontface" : 0, 115 | "fontname" : "Arial", 116 | "fontsize" : 13.0, 117 | "id" : "obj-28", 118 | "maxclass" : "newobj", 119 | "numinlets" : 1, 120 | "numoutlets" : 2, 121 | "outlettype" : [ "jit_matrix", "" ], 122 | "patching_rect" : [ 41.0, 240.5, 167.0, 23.0 ], 123 | "text" : "jit.movie" 124 | } 125 | 126 | } 127 | , { 128 | "box" : { 129 | "id" : "obj-18", 130 | "maxclass" : "newobj", 131 | "numinlets" : 1, 132 | "numoutlets" : 1, 133 | "outlettype" : [ "" ], 134 | "patching_rect" : [ 41.0, 117.0, 83.0, 22.0 ], 135 | "text" : "prepend read" 136 | } 137 | 138 | } 139 | , { 140 | "box" : { 141 | "id" : "obj-7", 142 | "maxclass" : "newobj", 143 | "numinlets" : 2, 144 | "numoutlets" : 2, 145 | "outlettype" : [ "", "" ], 146 | "patching_rect" : [ 41.0, 85.0, 78.0, 22.0 ], 147 | "text" : "route filepath" 148 | } 149 | 150 | } 151 | , { 152 | "box" : { 153 | "id" : "obj-5", 154 | "maxclass" : "message", 155 | "numinlets" : 2, 156 | "numoutlets" : 1, 157 | "outlettype" : [ "" ], 158 | "patching_rect" : [ 41.0, 15.0, 95.0, 22.0 ], 159 | "text" : "d3 groupchart.js" 160 | } 161 | 162 | } 163 | , { 164 | "box" : { 165 | "id" : "obj-24", 166 | "maxclass" : "newobj", 167 | "numinlets" : 0, 168 | "numoutlets" : 1, 169 | "outlettype" : [ "" ], 170 | "patching_rect" : [ 283.0, 15.0, 59.0, 22.0 ], 171 | "text" : "r to_node" 172 | } 173 | 174 | } 175 | , { 176 | "box" : { 177 | "id" : "obj-4", 178 | "maxclass" : "message", 179 | "numinlets" : 2, 180 | "numoutlets" : 1, 181 | "outlettype" : [ "" ], 182 | "patching_rect" : [ 841.5, 437.0, 63.0, 22.0 ], 183 | "text" : "script stop" 184 | } 185 | 186 | } 187 | , { 188 | "box" : { 189 | "id" : "obj-1", 190 | "maxclass" : "message", 191 | "numinlets" : 2, 192 | "numoutlets" : 1, 193 | "outlettype" : [ "" ], 194 | "patching_rect" : [ 754.0, 437.0, 64.0, 22.0 ], 195 | "text" : "script start" 196 | } 197 | 198 | } 199 | , { 200 | "box" : { 201 | "id" : "obj-25", 202 | "maxclass" : "newobj", 203 | "numinlets" : 1, 204 | "numoutlets" : 0, 205 | "patching_rect" : [ 635.5, 468.0, 61.0, 22.0 ], 206 | "text" : "s to_node" 207 | } 208 | 209 | } 210 | , { 211 | "box" : { 212 | "id" : "obj-23", 213 | "maxclass" : "message", 214 | "numinlets" : 2, 215 | "numoutlets" : 1, 216 | "outlettype" : [ "" ], 217 | "patching_rect" : [ 635.5, 437.0, 98.0, 22.0 ], 218 | "text" : "script npm install" 219 | } 220 | 221 | } 222 | , { 223 | "box" : { 224 | "fontface" : 1, 225 | "id" : "obj-13", 226 | "maxclass" : "comment", 227 | "numinlets" : 1, 228 | "numoutlets" : 0, 229 | "patching_rect" : [ 402.5, 438.0, 231.0, 20.0 ], 230 | "text" : "Click this to install necessary libraries:" 231 | } 232 | 233 | } 234 | , { 235 | "box" : { 236 | "fontsize" : 24.0, 237 | "id" : "obj-29", 238 | "maxclass" : "comment", 239 | "numinlets" : 1, 240 | "numoutlets" : 0, 241 | "patching_rect" : [ 402.5, 274.0, 199.0, 33.0 ], 242 | "text" : "static-d3", 243 | "underline" : 1 244 | } 245 | 246 | } 247 | , { 248 | "box" : { 249 | "bgmode" : 0, 250 | "border" : 0, 251 | "clickthrough" : 0, 252 | "enablehscroll" : 0, 253 | "enablevscroll" : 0, 254 | "id" : "obj-21", 255 | "lockeddragscroll" : 0, 256 | "maxclass" : "bpatcher", 257 | "name" : "n4m.monitor.maxpat", 258 | "numinlets" : 1, 259 | "numoutlets" : 0, 260 | "offset" : [ 0.0, 0.0 ], 261 | "patching_rect" : [ 402.5, 10.0, 400.0, 220.0 ], 262 | "viewvisibility" : 1 263 | } 264 | 265 | } 266 | , { 267 | "box" : { 268 | "id" : "obj-16", 269 | "maxclass" : "newobj", 270 | "numinlets" : 1, 271 | "numoutlets" : 2, 272 | "outlettype" : [ "", "" ], 273 | "patching_rect" : [ 41.0, 52.0, 185.0, 22.0 ], 274 | "saved_object_attributes" : { 275 | "autostart" : 0, 276 | "defer" : 0, 277 | "node" : "", 278 | "npm" : "", 279 | "watch" : 1 280 | } 281 | , 282 | "text" : "node.script static-d3.js @watch 1" 283 | } 284 | 285 | } 286 | , { 287 | "box" : { 288 | "fontname" : "Arial", 289 | "fontsize" : 12.0, 290 | "id" : "obj-9", 291 | "maxclass" : "newobj", 292 | "numinlets" : 2, 293 | "numoutlets" : 1, 294 | "outlettype" : [ "bang" ], 295 | "patching_rect" : [ 158.0, 147.0, 63.0, 22.0 ], 296 | "text" : "qmetro 40" 297 | } 298 | 299 | } 300 | ], 301 | "lines" : [ { 302 | "patchline" : { 303 | "destination" : [ "obj-25", 0 ], 304 | "source" : [ "obj-1", 0 ] 305 | } 306 | 307 | } 308 | , { 309 | "patchline" : { 310 | "destination" : [ "obj-21", 0 ], 311 | "source" : [ "obj-16", 1 ] 312 | } 313 | 314 | } 315 | , { 316 | "patchline" : { 317 | "destination" : [ "obj-7", 0 ], 318 | "source" : [ "obj-16", 0 ] 319 | } 320 | 321 | } 322 | , { 323 | "patchline" : { 324 | "destination" : [ "obj-28", 0 ], 325 | "source" : [ "obj-18", 0 ] 326 | } 327 | 328 | } 329 | , { 330 | "patchline" : { 331 | "destination" : [ "obj-25", 0 ], 332 | "source" : [ "obj-23", 0 ] 333 | } 334 | 335 | } 336 | , { 337 | "patchline" : { 338 | "destination" : [ "obj-16", 0 ], 339 | "source" : [ "obj-24", 0 ] 340 | } 341 | 342 | } 343 | , { 344 | "patchline" : { 345 | "destination" : [ "obj-34", 0 ], 346 | "source" : [ "obj-28", 0 ] 347 | } 348 | 349 | } 350 | , { 351 | "patchline" : { 352 | "destination" : [ "obj-42", 0 ], 353 | "source" : [ "obj-3", 1 ] 354 | } 355 | 356 | } 357 | , { 358 | "patchline" : { 359 | "destination" : [ "obj-42", 0 ], 360 | "source" : [ "obj-3", 0 ] 361 | } 362 | 363 | } 364 | , { 365 | "patchline" : { 366 | "destination" : [ "obj-25", 0 ], 367 | "source" : [ "obj-4", 0 ] 368 | } 369 | 370 | } 371 | , { 372 | "patchline" : { 373 | "destination" : [ "obj-16", 0 ], 374 | "source" : [ "obj-5", 0 ] 375 | } 376 | 377 | } 378 | , { 379 | "patchline" : { 380 | "destination" : [ "obj-9", 0 ], 381 | "source" : [ "obj-6", 0 ] 382 | } 383 | 384 | } 385 | , { 386 | "patchline" : { 387 | "destination" : [ "obj-18", 0 ], 388 | "source" : [ "obj-7", 0 ] 389 | } 390 | 391 | } 392 | , { 393 | "patchline" : { 394 | "destination" : [ "obj-28", 0 ], 395 | "order" : 1, 396 | "source" : [ "obj-9", 0 ] 397 | } 398 | 399 | } 400 | , { 401 | "patchline" : { 402 | "destination" : [ "obj-3", 0 ], 403 | "order" : 0, 404 | "source" : [ "obj-9", 0 ] 405 | } 406 | 407 | } 408 | ], 409 | "dependency_cache" : [ { 410 | "name" : "n4m.monitor.maxpat", 411 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 412 | "type" : "JSON", 413 | "implicit" : 1 414 | } 415 | , { 416 | "name" : "resize_n4m_monitor_patcher.js", 417 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 418 | "type" : "TEXT", 419 | "implicit" : 1 420 | } 421 | ], 422 | "autosave" : 0 423 | } 424 | 425 | } 426 | -------------------------------------------------------------------------------- /3-socketio-client/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "3-socketio-client", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "after": { 8 | "version": "0.8.2", 9 | "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", 10 | "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" 11 | }, 12 | "arraybuffer.slice": { 13 | "version": "0.0.7", 14 | "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", 15 | "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" 16 | }, 17 | "async-limiter": { 18 | "version": "1.0.0", 19 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", 20 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" 21 | }, 22 | "backo2": { 23 | "version": "1.0.2", 24 | "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", 25 | "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" 26 | }, 27 | "base64-arraybuffer": { 28 | "version": "0.1.5", 29 | "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", 30 | "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" 31 | }, 32 | "better-assert": { 33 | "version": "1.0.2", 34 | "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", 35 | "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", 36 | "requires": { 37 | "callsite": "1.0.0" 38 | } 39 | }, 40 | "blob": { 41 | "version": "0.0.5", 42 | "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", 43 | "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" 44 | }, 45 | "callsite": { 46 | "version": "1.0.0", 47 | "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", 48 | "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" 49 | }, 50 | "component-bind": { 51 | "version": "1.0.0", 52 | "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", 53 | "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" 54 | }, 55 | "component-emitter": { 56 | "version": "1.2.1", 57 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", 58 | "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" 59 | }, 60 | "component-inherit": { 61 | "version": "0.0.3", 62 | "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", 63 | "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" 64 | }, 65 | "debug": { 66 | "version": "3.1.0", 67 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 68 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 69 | "requires": { 70 | "ms": "2.0.0" 71 | } 72 | }, 73 | "engine.io-client": { 74 | "version": "3.2.1", 75 | "resolved": "http://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", 76 | "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", 77 | "requires": { 78 | "component-emitter": "1.2.1", 79 | "component-inherit": "0.0.3", 80 | "debug": "3.1.0", 81 | "engine.io-parser": "2.1.3", 82 | "has-cors": "1.1.0", 83 | "indexof": "0.0.1", 84 | "parseqs": "0.0.5", 85 | "parseuri": "0.0.5", 86 | "ws": "3.3.3", 87 | "xmlhttprequest-ssl": "1.5.5", 88 | "yeast": "0.1.2" 89 | } 90 | }, 91 | "engine.io-parser": { 92 | "version": "2.1.3", 93 | "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", 94 | "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", 95 | "requires": { 96 | "after": "0.8.2", 97 | "arraybuffer.slice": "0.0.7", 98 | "base64-arraybuffer": "0.1.5", 99 | "blob": "0.0.5", 100 | "has-binary2": "1.0.3" 101 | } 102 | }, 103 | "has-binary2": { 104 | "version": "1.0.3", 105 | "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", 106 | "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", 107 | "requires": { 108 | "isarray": "2.0.1" 109 | } 110 | }, 111 | "has-cors": { 112 | "version": "1.1.0", 113 | "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", 114 | "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" 115 | }, 116 | "indexof": { 117 | "version": "0.0.1", 118 | "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", 119 | "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" 120 | }, 121 | "isarray": { 122 | "version": "2.0.1", 123 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", 124 | "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" 125 | }, 126 | "ms": { 127 | "version": "2.0.0", 128 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 129 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 130 | }, 131 | "object-component": { 132 | "version": "0.0.3", 133 | "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", 134 | "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" 135 | }, 136 | "parseqs": { 137 | "version": "0.0.5", 138 | "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", 139 | "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", 140 | "requires": { 141 | "better-assert": "1.0.2" 142 | } 143 | }, 144 | "parseuri": { 145 | "version": "0.0.5", 146 | "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", 147 | "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", 148 | "requires": { 149 | "better-assert": "1.0.2" 150 | } 151 | }, 152 | "safe-buffer": { 153 | "version": "5.1.2", 154 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 155 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 156 | }, 157 | "socket.io-client": { 158 | "version": "2.1.1", 159 | "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", 160 | "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", 161 | "requires": { 162 | "backo2": "1.0.2", 163 | "base64-arraybuffer": "0.1.5", 164 | "component-bind": "1.0.0", 165 | "component-emitter": "1.2.1", 166 | "debug": "3.1.0", 167 | "engine.io-client": "3.2.1", 168 | "has-binary2": "1.0.3", 169 | "has-cors": "1.1.0", 170 | "indexof": "0.0.1", 171 | "object-component": "0.0.3", 172 | "parseqs": "0.0.5", 173 | "parseuri": "0.0.5", 174 | "socket.io-parser": "3.2.0", 175 | "to-array": "0.1.4" 176 | } 177 | }, 178 | "socket.io-parser": { 179 | "version": "3.2.0", 180 | "resolved": "http://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", 181 | "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", 182 | "requires": { 183 | "component-emitter": "1.2.1", 184 | "debug": "3.1.0", 185 | "isarray": "2.0.1" 186 | } 187 | }, 188 | "to-array": { 189 | "version": "0.1.4", 190 | "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", 191 | "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" 192 | }, 193 | "ultron": { 194 | "version": "1.1.1", 195 | "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", 196 | "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" 197 | }, 198 | "ws": { 199 | "version": "3.3.3", 200 | "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", 201 | "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", 202 | "requires": { 203 | "async-limiter": "1.0.0", 204 | "safe-buffer": "5.1.2", 205 | "ultron": "1.1.1" 206 | } 207 | }, 208 | "xmlhttprequest-ssl": { 209 | "version": "1.5.5", 210 | "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", 211 | "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" 212 | }, 213 | "yeast": { 214 | "version": "0.1.2", 215 | "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", 216 | "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /3-socketio-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "3-socketio-client", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "socketio-client.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Julian Rubisch", 10 | "license": "ISC", 11 | "dependencies": { 12 | "socket.io-client": "^2.1.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /3-socketio-client/server/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Socket.IO chat 5 | 6 | 26 | 27 | 28 |
29 |
30 |
31 |
32 | 33 |
34 | 35 | 36 | 49 | 50 | -------------------------------------------------------------------------------- /3-socketio-client/server/index.js: -------------------------------------------------------------------------------- 1 | const app = require('express')(); 2 | const http = require('http').Server(app); 3 | const io = require('socket.io')(http); 4 | 5 | app.get('/', (req, res) => { 6 | res.sendFile(__dirname + '/index.html'); 7 | }); 8 | 9 | io.on('connection', (socket) => { 10 | console.log('a user connected'); 11 | socket.on('disconnect', function(){ 12 | console.log('user disconnected'); 13 | }); 14 | 15 | socket.on('message', (msg) => { 16 | io.emit('message', msg); 17 | }); 18 | 19 | socket.on('talkback', (msg) => { 20 | socket.broadcast.emit('talkback', msg); 21 | }) 22 | }); 23 | 24 | http.listen(3000, () => { 25 | console.log('listening on *:3000'); 26 | }); -------------------------------------------------------------------------------- /3-socketio-client/server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socketio-server", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.5", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", 10 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", 11 | "requires": { 12 | "mime-types": "~2.1.18", 13 | "negotiator": "0.6.1" 14 | } 15 | }, 16 | "after": { 17 | "version": "0.8.2", 18 | "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", 19 | "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" 20 | }, 21 | "array-flatten": { 22 | "version": "1.1.1", 23 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 24 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 25 | }, 26 | "arraybuffer.slice": { 27 | "version": "0.0.7", 28 | "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", 29 | "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" 30 | }, 31 | "async-limiter": { 32 | "version": "1.0.0", 33 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", 34 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" 35 | }, 36 | "backo2": { 37 | "version": "1.0.2", 38 | "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", 39 | "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" 40 | }, 41 | "base64-arraybuffer": { 42 | "version": "0.1.5", 43 | "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", 44 | "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" 45 | }, 46 | "base64id": { 47 | "version": "1.0.0", 48 | "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", 49 | "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" 50 | }, 51 | "better-assert": { 52 | "version": "1.0.2", 53 | "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", 54 | "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", 55 | "requires": { 56 | "callsite": "1.0.0" 57 | } 58 | }, 59 | "blob": { 60 | "version": "0.0.5", 61 | "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", 62 | "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" 63 | }, 64 | "body-parser": { 65 | "version": "1.18.3", 66 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", 67 | "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", 68 | "requires": { 69 | "bytes": "3.0.0", 70 | "content-type": "~1.0.4", 71 | "debug": "2.6.9", 72 | "depd": "~1.1.2", 73 | "http-errors": "~1.6.3", 74 | "iconv-lite": "0.4.23", 75 | "on-finished": "~2.3.0", 76 | "qs": "6.5.2", 77 | "raw-body": "2.3.3", 78 | "type-is": "~1.6.16" 79 | } 80 | }, 81 | "bytes": { 82 | "version": "3.0.0", 83 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 84 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 85 | }, 86 | "callsite": { 87 | "version": "1.0.0", 88 | "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", 89 | "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" 90 | }, 91 | "component-bind": { 92 | "version": "1.0.0", 93 | "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", 94 | "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" 95 | }, 96 | "component-emitter": { 97 | "version": "1.2.1", 98 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", 99 | "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" 100 | }, 101 | "component-inherit": { 102 | "version": "0.0.3", 103 | "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", 104 | "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" 105 | }, 106 | "content-disposition": { 107 | "version": "0.5.2", 108 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 109 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 110 | }, 111 | "content-type": { 112 | "version": "1.0.4", 113 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 114 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 115 | }, 116 | "cookie": { 117 | "version": "0.3.1", 118 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 119 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 120 | }, 121 | "cookie-signature": { 122 | "version": "1.0.6", 123 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 124 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 125 | }, 126 | "debug": { 127 | "version": "2.6.9", 128 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 129 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 130 | "requires": { 131 | "ms": "2.0.0" 132 | } 133 | }, 134 | "depd": { 135 | "version": "1.1.2", 136 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 137 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 138 | }, 139 | "destroy": { 140 | "version": "1.0.4", 141 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 142 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 143 | }, 144 | "ee-first": { 145 | "version": "1.1.1", 146 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 147 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 148 | }, 149 | "encodeurl": { 150 | "version": "1.0.2", 151 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 152 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 153 | }, 154 | "engine.io": { 155 | "version": "3.2.1", 156 | "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", 157 | "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", 158 | "requires": { 159 | "accepts": "~1.3.4", 160 | "base64id": "1.0.0", 161 | "cookie": "0.3.1", 162 | "debug": "~3.1.0", 163 | "engine.io-parser": "~2.1.0", 164 | "ws": "~3.3.1" 165 | }, 166 | "dependencies": { 167 | "debug": { 168 | "version": "3.1.0", 169 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 170 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 171 | "requires": { 172 | "ms": "2.0.0" 173 | } 174 | } 175 | } 176 | }, 177 | "engine.io-client": { 178 | "version": "3.2.1", 179 | "resolved": "http://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", 180 | "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", 181 | "requires": { 182 | "component-emitter": "1.2.1", 183 | "component-inherit": "0.0.3", 184 | "debug": "~3.1.0", 185 | "engine.io-parser": "~2.1.1", 186 | "has-cors": "1.1.0", 187 | "indexof": "0.0.1", 188 | "parseqs": "0.0.5", 189 | "parseuri": "0.0.5", 190 | "ws": "~3.3.1", 191 | "xmlhttprequest-ssl": "~1.5.4", 192 | "yeast": "0.1.2" 193 | }, 194 | "dependencies": { 195 | "debug": { 196 | "version": "3.1.0", 197 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 198 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 199 | "requires": { 200 | "ms": "2.0.0" 201 | } 202 | } 203 | } 204 | }, 205 | "engine.io-parser": { 206 | "version": "2.1.3", 207 | "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", 208 | "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", 209 | "requires": { 210 | "after": "0.8.2", 211 | "arraybuffer.slice": "~0.0.7", 212 | "base64-arraybuffer": "0.1.5", 213 | "blob": "0.0.5", 214 | "has-binary2": "~1.0.2" 215 | } 216 | }, 217 | "escape-html": { 218 | "version": "1.0.3", 219 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 220 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 221 | }, 222 | "etag": { 223 | "version": "1.8.1", 224 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 225 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 226 | }, 227 | "express": { 228 | "version": "4.16.4", 229 | "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", 230 | "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", 231 | "requires": { 232 | "accepts": "~1.3.5", 233 | "array-flatten": "1.1.1", 234 | "body-parser": "1.18.3", 235 | "content-disposition": "0.5.2", 236 | "content-type": "~1.0.4", 237 | "cookie": "0.3.1", 238 | "cookie-signature": "1.0.6", 239 | "debug": "2.6.9", 240 | "depd": "~1.1.2", 241 | "encodeurl": "~1.0.2", 242 | "escape-html": "~1.0.3", 243 | "etag": "~1.8.1", 244 | "finalhandler": "1.1.1", 245 | "fresh": "0.5.2", 246 | "merge-descriptors": "1.0.1", 247 | "methods": "~1.1.2", 248 | "on-finished": "~2.3.0", 249 | "parseurl": "~1.3.2", 250 | "path-to-regexp": "0.1.7", 251 | "proxy-addr": "~2.0.4", 252 | "qs": "6.5.2", 253 | "range-parser": "~1.2.0", 254 | "safe-buffer": "5.1.2", 255 | "send": "0.16.2", 256 | "serve-static": "1.13.2", 257 | "setprototypeof": "1.1.0", 258 | "statuses": "~1.4.0", 259 | "type-is": "~1.6.16", 260 | "utils-merge": "1.0.1", 261 | "vary": "~1.1.2" 262 | } 263 | }, 264 | "finalhandler": { 265 | "version": "1.1.1", 266 | "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", 267 | "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", 268 | "requires": { 269 | "debug": "2.6.9", 270 | "encodeurl": "~1.0.2", 271 | "escape-html": "~1.0.3", 272 | "on-finished": "~2.3.0", 273 | "parseurl": "~1.3.2", 274 | "statuses": "~1.4.0", 275 | "unpipe": "~1.0.0" 276 | } 277 | }, 278 | "forwarded": { 279 | "version": "0.1.2", 280 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 281 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 282 | }, 283 | "fresh": { 284 | "version": "0.5.2", 285 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 286 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 287 | }, 288 | "has-binary2": { 289 | "version": "1.0.3", 290 | "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", 291 | "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", 292 | "requires": { 293 | "isarray": "2.0.1" 294 | } 295 | }, 296 | "has-cors": { 297 | "version": "1.1.0", 298 | "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", 299 | "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" 300 | }, 301 | "http-errors": { 302 | "version": "1.6.3", 303 | "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 304 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 305 | "requires": { 306 | "depd": "~1.1.2", 307 | "inherits": "2.0.3", 308 | "setprototypeof": "1.1.0", 309 | "statuses": ">= 1.4.0 < 2" 310 | } 311 | }, 312 | "iconv-lite": { 313 | "version": "0.4.23", 314 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", 315 | "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", 316 | "requires": { 317 | "safer-buffer": ">= 2.1.2 < 3" 318 | } 319 | }, 320 | "indexof": { 321 | "version": "0.0.1", 322 | "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", 323 | "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" 324 | }, 325 | "inherits": { 326 | "version": "2.0.3", 327 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 328 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 329 | }, 330 | "ipaddr.js": { 331 | "version": "1.8.0", 332 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", 333 | "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" 334 | }, 335 | "isarray": { 336 | "version": "2.0.1", 337 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", 338 | "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" 339 | }, 340 | "media-typer": { 341 | "version": "0.3.0", 342 | "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 343 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 344 | }, 345 | "merge-descriptors": { 346 | "version": "1.0.1", 347 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 348 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 349 | }, 350 | "methods": { 351 | "version": "1.1.2", 352 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 353 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 354 | }, 355 | "mime": { 356 | "version": "1.4.1", 357 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 358 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" 359 | }, 360 | "mime-db": { 361 | "version": "1.37.0", 362 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", 363 | "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" 364 | }, 365 | "mime-types": { 366 | "version": "2.1.21", 367 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", 368 | "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", 369 | "requires": { 370 | "mime-db": "~1.37.0" 371 | } 372 | }, 373 | "ms": { 374 | "version": "2.0.0", 375 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 376 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 377 | }, 378 | "negotiator": { 379 | "version": "0.6.1", 380 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 381 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 382 | }, 383 | "object-component": { 384 | "version": "0.0.3", 385 | "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", 386 | "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" 387 | }, 388 | "on-finished": { 389 | "version": "2.3.0", 390 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 391 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 392 | "requires": { 393 | "ee-first": "1.1.1" 394 | } 395 | }, 396 | "parseqs": { 397 | "version": "0.0.5", 398 | "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", 399 | "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", 400 | "requires": { 401 | "better-assert": "~1.0.0" 402 | } 403 | }, 404 | "parseuri": { 405 | "version": "0.0.5", 406 | "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", 407 | "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", 408 | "requires": { 409 | "better-assert": "~1.0.0" 410 | } 411 | }, 412 | "parseurl": { 413 | "version": "1.3.2", 414 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 415 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 416 | }, 417 | "path-to-regexp": { 418 | "version": "0.1.7", 419 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 420 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 421 | }, 422 | "proxy-addr": { 423 | "version": "2.0.4", 424 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", 425 | "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", 426 | "requires": { 427 | "forwarded": "~0.1.2", 428 | "ipaddr.js": "1.8.0" 429 | } 430 | }, 431 | "qs": { 432 | "version": "6.5.2", 433 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 434 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 435 | }, 436 | "range-parser": { 437 | "version": "1.2.0", 438 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 439 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 440 | }, 441 | "raw-body": { 442 | "version": "2.3.3", 443 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", 444 | "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", 445 | "requires": { 446 | "bytes": "3.0.0", 447 | "http-errors": "1.6.3", 448 | "iconv-lite": "0.4.23", 449 | "unpipe": "1.0.0" 450 | } 451 | }, 452 | "safe-buffer": { 453 | "version": "5.1.2", 454 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 455 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 456 | }, 457 | "safer-buffer": { 458 | "version": "2.1.2", 459 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 460 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 461 | }, 462 | "send": { 463 | "version": "0.16.2", 464 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", 465 | "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", 466 | "requires": { 467 | "debug": "2.6.9", 468 | "depd": "~1.1.2", 469 | "destroy": "~1.0.4", 470 | "encodeurl": "~1.0.2", 471 | "escape-html": "~1.0.3", 472 | "etag": "~1.8.1", 473 | "fresh": "0.5.2", 474 | "http-errors": "~1.6.2", 475 | "mime": "1.4.1", 476 | "ms": "2.0.0", 477 | "on-finished": "~2.3.0", 478 | "range-parser": "~1.2.0", 479 | "statuses": "~1.4.0" 480 | } 481 | }, 482 | "serve-static": { 483 | "version": "1.13.2", 484 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", 485 | "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", 486 | "requires": { 487 | "encodeurl": "~1.0.2", 488 | "escape-html": "~1.0.3", 489 | "parseurl": "~1.3.2", 490 | "send": "0.16.2" 491 | } 492 | }, 493 | "setprototypeof": { 494 | "version": "1.1.0", 495 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 496 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" 497 | }, 498 | "socket.io": { 499 | "version": "2.1.1", 500 | "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", 501 | "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", 502 | "requires": { 503 | "debug": "~3.1.0", 504 | "engine.io": "~3.2.0", 505 | "has-binary2": "~1.0.2", 506 | "socket.io-adapter": "~1.1.0", 507 | "socket.io-client": "2.1.1", 508 | "socket.io-parser": "~3.2.0" 509 | }, 510 | "dependencies": { 511 | "debug": { 512 | "version": "3.1.0", 513 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 514 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 515 | "requires": { 516 | "ms": "2.0.0" 517 | } 518 | } 519 | } 520 | }, 521 | "socket.io-adapter": { 522 | "version": "1.1.1", 523 | "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", 524 | "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=" 525 | }, 526 | "socket.io-client": { 527 | "version": "2.1.1", 528 | "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", 529 | "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", 530 | "requires": { 531 | "backo2": "1.0.2", 532 | "base64-arraybuffer": "0.1.5", 533 | "component-bind": "1.0.0", 534 | "component-emitter": "1.2.1", 535 | "debug": "~3.1.0", 536 | "engine.io-client": "~3.2.0", 537 | "has-binary2": "~1.0.2", 538 | "has-cors": "1.1.0", 539 | "indexof": "0.0.1", 540 | "object-component": "0.0.3", 541 | "parseqs": "0.0.5", 542 | "parseuri": "0.0.5", 543 | "socket.io-parser": "~3.2.0", 544 | "to-array": "0.1.4" 545 | }, 546 | "dependencies": { 547 | "debug": { 548 | "version": "3.1.0", 549 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 550 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 551 | "requires": { 552 | "ms": "2.0.0" 553 | } 554 | } 555 | } 556 | }, 557 | "socket.io-parser": { 558 | "version": "3.2.0", 559 | "resolved": "http://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", 560 | "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", 561 | "requires": { 562 | "component-emitter": "1.2.1", 563 | "debug": "~3.1.0", 564 | "isarray": "2.0.1" 565 | }, 566 | "dependencies": { 567 | "debug": { 568 | "version": "3.1.0", 569 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 570 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 571 | "requires": { 572 | "ms": "2.0.0" 573 | } 574 | } 575 | } 576 | }, 577 | "statuses": { 578 | "version": "1.4.0", 579 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 580 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 581 | }, 582 | "to-array": { 583 | "version": "0.1.4", 584 | "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", 585 | "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" 586 | }, 587 | "type-is": { 588 | "version": "1.6.16", 589 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", 590 | "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", 591 | "requires": { 592 | "media-typer": "0.3.0", 593 | "mime-types": "~2.1.18" 594 | } 595 | }, 596 | "ultron": { 597 | "version": "1.1.1", 598 | "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", 599 | "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" 600 | }, 601 | "unpipe": { 602 | "version": "1.0.0", 603 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 604 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 605 | }, 606 | "utils-merge": { 607 | "version": "1.0.1", 608 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 609 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 610 | }, 611 | "vary": { 612 | "version": "1.1.2", 613 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 614 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 615 | }, 616 | "ws": { 617 | "version": "3.3.3", 618 | "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", 619 | "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", 620 | "requires": { 621 | "async-limiter": "~1.0.0", 622 | "safe-buffer": "~5.1.0", 623 | "ultron": "~1.1.0" 624 | } 625 | }, 626 | "xmlhttprequest-ssl": { 627 | "version": "1.5.5", 628 | "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", 629 | "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" 630 | }, 631 | "yeast": { 632 | "version": "0.1.2", 633 | "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", 634 | "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" 635 | } 636 | } 637 | } 638 | -------------------------------------------------------------------------------- /3-socketio-client/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socketio-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "Julian Rubisch", 11 | "license": "ISC", 12 | "dependencies": { 13 | "express": "^4.16.4", 14 | "socket.io": "^2.1.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /3-socketio-client/socketio-client.js: -------------------------------------------------------------------------------- 1 | const maxApi = require('max-api'); 2 | const io = require('socket.io-client'); 3 | 4 | let socket; 5 | 6 | maxApi.addHandler('connect', (url) => { 7 | socket = io(url); 8 | 9 | socket.on('talkback', (msg) => { 10 | maxApi.outlet("talkback", msg); 11 | }); 12 | }); 13 | 14 | maxApi.addHandler('disconnect', () => { 15 | socket.close(); 16 | }); 17 | 18 | maxApi.addHandler('message', (msg) => { 19 | console.log(msg) 20 | socket.emit('message', msg); 21 | }); -------------------------------------------------------------------------------- /3-socketio-client/socketio-client.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 8, 6 | "minor" : 0, 7 | "revision" : 1, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "classnamespace" : "box", 13 | "rect" : [ 297.0, 134.0, 1017.0, 536.0 ], 14 | "bglocked" : 0, 15 | "openinpresentation" : 0, 16 | "default_fontsize" : 12.0, 17 | "default_fontface" : 0, 18 | "default_fontname" : "Arial", 19 | "gridonopen" : 1, 20 | "gridsize" : [ 15.0, 15.0 ], 21 | "gridsnaponopen" : 1, 22 | "objectsnaponopen" : 1, 23 | "statusbarvisible" : 2, 24 | "toolbarvisible" : 1, 25 | "lefttoolbarpinned" : 0, 26 | "toptoolbarpinned" : 0, 27 | "righttoolbarpinned" : 0, 28 | "bottomtoolbarpinned" : 0, 29 | "toolbars_unpinned_last_save" : 0, 30 | "tallnewobj" : 0, 31 | "boxanimatetime" : 200, 32 | "enablehscroll" : 1, 33 | "enablevscroll" : 1, 34 | "devicewidth" : 0.0, 35 | "description" : "", 36 | "digest" : "", 37 | "tags" : "", 38 | "style" : "", 39 | "subpatcher_template" : "", 40 | "boxes" : [ { 41 | "box" : { 42 | "id" : "obj-22", 43 | "maxclass" : "message", 44 | "numinlets" : 2, 45 | "numoutlets" : 1, 46 | "outlettype" : [ "" ], 47 | "patching_rect" : [ 39.0, 212.0, 50.0, 22.0 ], 48 | "text" : "bla" 49 | } 50 | 51 | } 52 | , { 53 | "box" : { 54 | "id" : "obj-19", 55 | "maxclass" : "newobj", 56 | "numinlets" : 2, 57 | "numoutlets" : 2, 58 | "outlettype" : [ "", "" ], 59 | "patching_rect" : [ 39.0, 164.0, 83.0, 22.0 ], 60 | "text" : "route talkback" 61 | } 62 | 63 | } 64 | , { 65 | "box" : { 66 | "id" : "obj-14", 67 | "maxclass" : "message", 68 | "numinlets" : 2, 69 | "numoutlets" : 1, 70 | "outlettype" : [ "" ], 71 | "patching_rect" : [ 134.0, 57.0, 66.0, 22.0 ], 72 | "text" : "disconnect" 73 | } 74 | 75 | } 76 | , { 77 | "box" : { 78 | "format" : 6, 79 | "id" : "obj-11", 80 | "maxclass" : "flonum", 81 | "numinlets" : 1, 82 | "numoutlets" : 2, 83 | "outlettype" : [ "", "bang" ], 84 | "parameter_enable" : 0, 85 | "patching_rect" : [ 223.0, 15.0, 50.0, 22.0 ] 86 | } 87 | 88 | } 89 | , { 90 | "box" : { 91 | "id" : "obj-9", 92 | "maxclass" : "message", 93 | "numinlets" : 2, 94 | "numoutlets" : 1, 95 | "outlettype" : [ "" ], 96 | "patching_rect" : [ 223.0, 49.0, 74.0, 22.0 ], 97 | "text" : "message $1" 98 | } 99 | 100 | } 101 | , { 102 | "box" : { 103 | "id" : "obj-2", 104 | "maxclass" : "message", 105 | "numinlets" : 2, 106 | "numoutlets" : 1, 107 | "outlettype" : [ "" ], 108 | "patching_rect" : [ 39.0, 15.0, 161.0, 22.0 ], 109 | "text" : "connect http://localhost:3000" 110 | } 111 | 112 | } 113 | , { 114 | "box" : { 115 | "id" : "obj-31", 116 | "linecount" : 10, 117 | "maxclass" : "comment", 118 | "numinlets" : 1, 119 | "numoutlets" : 0, 120 | "patching_rect" : [ 402.5, 284.0, 581.0, 141.0 ], 121 | "text" : "A little script that allows node.script to act as a socketio client\n\nClick [script npm install], then [script start] below\n\nSend a [connect] message followed by the URL of a listening socket.io server (e.g. localhost:3000).\nOpen that server URL in a browser\nSend a [message] message followed by an arbitrary message and watch them appear in the browser\nListen for \"talkback\" messages from the inbound socket\n\nFor details on the script, refer here: https://www.znibbl.es/video/socketio-client" 122 | } 123 | 124 | } 125 | , { 126 | "box" : { 127 | "id" : "obj-24", 128 | "maxclass" : "newobj", 129 | "numinlets" : 0, 130 | "numoutlets" : 1, 131 | "outlettype" : [ "" ], 132 | "patching_rect" : [ 60.0, 57.0, 59.0, 22.0 ], 133 | "text" : "r to_node" 134 | } 135 | 136 | } 137 | , { 138 | "box" : { 139 | "id" : "obj-4", 140 | "maxclass" : "message", 141 | "numinlets" : 2, 142 | "numoutlets" : 1, 143 | "outlettype" : [ "" ], 144 | "patching_rect" : [ 841.5, 437.0, 63.0, 22.0 ], 145 | "text" : "script stop" 146 | } 147 | 148 | } 149 | , { 150 | "box" : { 151 | "id" : "obj-1", 152 | "maxclass" : "message", 153 | "numinlets" : 2, 154 | "numoutlets" : 1, 155 | "outlettype" : [ "" ], 156 | "patching_rect" : [ 754.0, 437.0, 64.0, 22.0 ], 157 | "text" : "script start" 158 | } 159 | 160 | } 161 | , { 162 | "box" : { 163 | "id" : "obj-25", 164 | "maxclass" : "newobj", 165 | "numinlets" : 1, 166 | "numoutlets" : 0, 167 | "patching_rect" : [ 635.5, 468.0, 61.0, 22.0 ], 168 | "text" : "s to_node" 169 | } 170 | 171 | } 172 | , { 173 | "box" : { 174 | "id" : "obj-23", 175 | "maxclass" : "message", 176 | "numinlets" : 2, 177 | "numoutlets" : 1, 178 | "outlettype" : [ "" ], 179 | "patching_rect" : [ 635.5, 437.0, 98.0, 22.0 ], 180 | "text" : "script npm install" 181 | } 182 | 183 | } 184 | , { 185 | "box" : { 186 | "fontface" : 1, 187 | "id" : "obj-13", 188 | "maxclass" : "comment", 189 | "numinlets" : 1, 190 | "numoutlets" : 0, 191 | "patching_rect" : [ 402.5, 438.0, 231.0, 20.0 ], 192 | "text" : "Click this to install necessary libraries:" 193 | } 194 | 195 | } 196 | , { 197 | "box" : { 198 | "fontsize" : 24.0, 199 | "id" : "obj-29", 200 | "maxclass" : "comment", 201 | "numinlets" : 1, 202 | "numoutlets" : 0, 203 | "patching_rect" : [ 402.5, 247.0, 199.0, 33.0 ], 204 | "text" : "socketio-client", 205 | "underline" : 1 206 | } 207 | 208 | } 209 | , { 210 | "box" : { 211 | "bgmode" : 0, 212 | "border" : 0, 213 | "clickthrough" : 0, 214 | "enablehscroll" : 0, 215 | "enablevscroll" : 0, 216 | "id" : "obj-21", 217 | "lockeddragscroll" : 0, 218 | "maxclass" : "bpatcher", 219 | "name" : "n4m.monitor.maxpat", 220 | "numinlets" : 1, 221 | "numoutlets" : 0, 222 | "offset" : [ 0.0, 0.0 ], 223 | "patching_rect" : [ 402.5, 10.0, 400.0, 220.0 ], 224 | "viewvisibility" : 1 225 | } 226 | 227 | } 228 | , { 229 | "box" : { 230 | "id" : "obj-16", 231 | "maxclass" : "newobj", 232 | "numinlets" : 1, 233 | "numoutlets" : 2, 234 | "outlettype" : [ "", "" ], 235 | "patching_rect" : [ 39.0, 117.0, 215.0, 22.0 ], 236 | "saved_object_attributes" : { 237 | "autostart" : 0, 238 | "defer" : 0, 239 | "node" : "", 240 | "npm" : "", 241 | "watch" : 1 242 | } 243 | , 244 | "text" : "node.script socketio-client.js @watch 1" 245 | } 246 | 247 | } 248 | ], 249 | "lines" : [ { 250 | "patchline" : { 251 | "destination" : [ "obj-25", 0 ], 252 | "source" : [ "obj-1", 0 ] 253 | } 254 | 255 | } 256 | , { 257 | "patchline" : { 258 | "destination" : [ "obj-9", 0 ], 259 | "source" : [ "obj-11", 0 ] 260 | } 261 | 262 | } 263 | , { 264 | "patchline" : { 265 | "destination" : [ "obj-16", 0 ], 266 | "source" : [ "obj-14", 0 ] 267 | } 268 | 269 | } 270 | , { 271 | "patchline" : { 272 | "destination" : [ "obj-19", 0 ], 273 | "source" : [ "obj-16", 0 ] 274 | } 275 | 276 | } 277 | , { 278 | "patchline" : { 279 | "destination" : [ "obj-21", 0 ], 280 | "source" : [ "obj-16", 1 ] 281 | } 282 | 283 | } 284 | , { 285 | "patchline" : { 286 | "destination" : [ "obj-22", 1 ], 287 | "source" : [ "obj-19", 0 ] 288 | } 289 | 290 | } 291 | , { 292 | "patchline" : { 293 | "destination" : [ "obj-16", 0 ], 294 | "source" : [ "obj-2", 0 ] 295 | } 296 | 297 | } 298 | , { 299 | "patchline" : { 300 | "destination" : [ "obj-25", 0 ], 301 | "source" : [ "obj-23", 0 ] 302 | } 303 | 304 | } 305 | , { 306 | "patchline" : { 307 | "destination" : [ "obj-16", 0 ], 308 | "source" : [ "obj-24", 0 ] 309 | } 310 | 311 | } 312 | , { 313 | "patchline" : { 314 | "destination" : [ "obj-25", 0 ], 315 | "source" : [ "obj-4", 0 ] 316 | } 317 | 318 | } 319 | , { 320 | "patchline" : { 321 | "destination" : [ "obj-16", 0 ], 322 | "source" : [ "obj-9", 0 ] 323 | } 324 | 325 | } 326 | ], 327 | "dependency_cache" : [ { 328 | "name" : "n4m.monitor.maxpat", 329 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 330 | "type" : "JSON", 331 | "implicit" : 1 332 | } 333 | , { 334 | "name" : "resize_n4m_monitor_patcher.js", 335 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 336 | "type" : "TEXT", 337 | "implicit" : 1 338 | } 339 | ], 340 | "autosave" : 0 341 | } 342 | 343 | } 344 | -------------------------------------------------------------------------------- /4-mqtt/mqtt-publisher.js: -------------------------------------------------------------------------------- 1 | const maxApi = require('max-api'); 2 | const mqtt = require('mqtt'); 3 | 4 | let client; 5 | 6 | maxApi.addHandler('connect', (url) => { 7 | client = mqtt.connect(url); 8 | 9 | client.on('connect', () => { 10 | maxApi.outlet('connected'); 11 | }); 12 | }); 13 | 14 | maxApi.addHandler('publish', (topic, value) => { 15 | client.publish(topic, value.toString()); 16 | }); 17 | -------------------------------------------------------------------------------- /4-mqtt/mqtt-subscriber.js: -------------------------------------------------------------------------------- 1 | const maxApi = require('max-api'); 2 | const mqtt = require('mqtt'); 3 | 4 | let client; 5 | 6 | maxApi.addHandler('connect', (url) => { 7 | client = mqtt.connect(url); 8 | 9 | client.on('connect', () => { 10 | maxApi.outlet('connected'); 11 | }); 12 | }); 13 | 14 | maxApi.addHandler('subscribe', (topic) => { 15 | client.subscribe(topic); 16 | 17 | client.on('message', (topic, message) => { 18 | maxApi.outlet(message.toString()); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /4-mqtt/mqtt.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 8, 6 | "minor" : 0, 7 | "revision" : 2, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "classnamespace" : "box", 13 | "rect" : [ 34.0, 268.0, 894.0, 474.0 ], 14 | "bglocked" : 0, 15 | "openinpresentation" : 0, 16 | "default_fontsize" : 12.0, 17 | "default_fontface" : 0, 18 | "default_fontname" : "Arial", 19 | "gridonopen" : 1, 20 | "gridsize" : [ 15.0, 15.0 ], 21 | "gridsnaponopen" : 1, 22 | "objectsnaponopen" : 1, 23 | "statusbarvisible" : 2, 24 | "toolbarvisible" : 1, 25 | "lefttoolbarpinned" : 0, 26 | "toptoolbarpinned" : 0, 27 | "righttoolbarpinned" : 0, 28 | "bottomtoolbarpinned" : 0, 29 | "toolbars_unpinned_last_save" : 0, 30 | "tallnewobj" : 0, 31 | "boxanimatetime" : 200, 32 | "enablehscroll" : 1, 33 | "enablevscroll" : 1, 34 | "devicewidth" : 0.0, 35 | "description" : "", 36 | "digest" : "", 37 | "tags" : "", 38 | "style" : "", 39 | "subpatcher_template" : "", 40 | "boxes" : [ { 41 | "box" : { 42 | "id" : "obj-49", 43 | "maxclass" : "toggle", 44 | "numinlets" : 1, 45 | "numoutlets" : 1, 46 | "outlettype" : [ "int" ], 47 | "parameter_enable" : 0, 48 | "patching_rect" : [ 48.0, 39.0, 24.0, 24.0 ] 49 | } 50 | 51 | } 52 | , { 53 | "box" : { 54 | "id" : "obj-47", 55 | "maxclass" : "message", 56 | "numinlets" : 2, 57 | "numoutlets" : 1, 58 | "outlettype" : [ "" ], 59 | "patching_rect" : [ 215.0, 280.0, 50.0, 22.0 ], 60 | "text" : "\"15\"" 61 | } 62 | 63 | } 64 | , { 65 | "box" : { 66 | "id" : "obj-45", 67 | "maxclass" : "message", 68 | "numinlets" : 2, 69 | "numoutlets" : 1, 70 | "outlettype" : [ "" ], 71 | "patching_rect" : [ 216.0, 10.0, 135.0, 22.0 ], 72 | "text" : "connect mqtt://localhost" 73 | } 74 | 75 | } 76 | , { 77 | "box" : { 78 | "id" : "obj-41", 79 | "maxclass" : "newobj", 80 | "numinlets" : 1, 81 | "numoutlets" : 1, 82 | "outlettype" : [ "" ], 83 | "patching_rect" : [ 48.0, 194.0, 94.0, 22.0 ], 84 | "text" : "prepend publish" 85 | } 86 | 87 | } 88 | , { 89 | "box" : { 90 | "id" : "obj-40", 91 | "maxclass" : "newobj", 92 | "numinlets" : 1, 93 | "numoutlets" : 1, 94 | "outlettype" : [ "" ], 95 | "patching_rect" : [ 48.0, 164.0, 174.0, 22.0 ], 96 | "text" : "sprintf \"temperature/pool/1 %d\"" 97 | } 98 | 99 | } 100 | , { 101 | "box" : { 102 | "id" : "obj-39", 103 | "maxclass" : "number", 104 | "numinlets" : 1, 105 | "numoutlets" : 2, 106 | "outlettype" : [ "", "bang" ], 107 | "parameter_enable" : 0, 108 | "patching_rect" : [ 48.0, 131.0, 50.0, 22.0 ] 109 | } 110 | 111 | } 112 | , { 113 | "box" : { 114 | "id" : "obj-37", 115 | "maxclass" : "newobj", 116 | "numinlets" : 3, 117 | "numoutlets" : 1, 118 | "outlettype" : [ "int" ], 119 | "patching_rect" : [ 48.0, 103.0, 65.0, 22.0 ], 120 | "text" : "drunk 40 5" 121 | } 122 | 123 | } 124 | , { 125 | "box" : { 126 | "id" : "obj-36", 127 | "maxclass" : "newobj", 128 | "numinlets" : 2, 129 | "numoutlets" : 1, 130 | "outlettype" : [ "bang" ], 131 | "patching_rect" : [ 48.0, 72.0, 63.0, 22.0 ], 132 | "text" : "metro 500" 133 | } 134 | 135 | } 136 | , { 137 | "box" : { 138 | "id" : "obj-32", 139 | "maxclass" : "message", 140 | "numinlets" : 2, 141 | "numoutlets" : 1, 142 | "outlettype" : [ "" ], 143 | "patching_rect" : [ 29.0, 280.0, 92.0, 22.0 ], 144 | "text" : "connected" 145 | } 146 | 147 | } 148 | , { 149 | "box" : { 150 | "id" : "obj-27", 151 | "maxclass" : "message", 152 | "numinlets" : 2, 153 | "numoutlets" : 1, 154 | "outlettype" : [ "" ], 155 | "patching_rect" : [ 227.0, 164.0, 164.0, 22.0 ], 156 | "text" : "subscribe temperature/+/1" 157 | } 158 | 159 | } 160 | , { 161 | "box" : { 162 | "id" : "obj-22", 163 | "maxclass" : "newobj", 164 | "numinlets" : 1, 165 | "numoutlets" : 2, 166 | "outlettype" : [ "", "" ], 167 | "patching_rect" : [ 212.0, 233.0, 165.0, 22.0 ], 168 | "saved_object_attributes" : { 169 | "autostart" : 0, 170 | "defer" : 0, 171 | "node" : "", 172 | "npm" : "", 173 | "watch" : 0 174 | } 175 | , 176 | "text" : "node.script mqtt-subscriber.js" 177 | } 178 | 179 | } 180 | , { 181 | "box" : { 182 | "id" : "obj-5", 183 | "maxclass" : "message", 184 | "numinlets" : 2, 185 | "numoutlets" : 1, 186 | "outlettype" : [ "" ], 187 | "patching_rect" : [ 29.0, 10.0, 135.0, 22.0 ], 188 | "text" : "connect mqtt://localhost" 189 | } 190 | 191 | } 192 | , { 193 | "box" : { 194 | "id" : "obj-2", 195 | "maxclass" : "newobj", 196 | "numinlets" : 1, 197 | "numoutlets" : 2, 198 | "outlettype" : [ "", "" ], 199 | "patching_rect" : [ 25.0, 233.0, 158.0, 22.0 ], 200 | "saved_object_attributes" : { 201 | "autostart" : 0, 202 | "defer" : 0, 203 | "node" : "", 204 | "npm" : "", 205 | "watch" : 0 206 | } 207 | , 208 | "text" : "node.script mqtt-publisher.js" 209 | } 210 | 211 | } 212 | , { 213 | "box" : { 214 | "id" : "obj-24", 215 | "maxclass" : "newobj", 216 | "numinlets" : 0, 217 | "numoutlets" : 1, 218 | "outlettype" : [ "" ], 219 | "patching_rect" : [ 145.0, 194.0, 59.0, 22.0 ], 220 | "text" : "r to_node" 221 | } 222 | 223 | } 224 | , { 225 | "box" : { 226 | "id" : "obj-4", 227 | "maxclass" : "message", 228 | "numinlets" : 2, 229 | "numoutlets" : 1, 230 | "outlettype" : [ "" ], 231 | "patching_rect" : [ 610.5, 250.0, 63.0, 22.0 ], 232 | "text" : "script stop" 233 | } 234 | 235 | } 236 | , { 237 | "box" : { 238 | "id" : "obj-1", 239 | "maxclass" : "message", 240 | "numinlets" : 2, 241 | "numoutlets" : 1, 242 | "outlettype" : [ "" ], 243 | "patching_rect" : [ 523.0, 250.0, 64.0, 22.0 ], 244 | "text" : "script start" 245 | } 246 | 247 | } 248 | , { 249 | "box" : { 250 | "id" : "obj-25", 251 | "maxclass" : "newobj", 252 | "numinlets" : 1, 253 | "numoutlets" : 0, 254 | "patching_rect" : [ 404.5, 281.0, 61.0, 22.0 ], 255 | "text" : "s to_node" 256 | } 257 | 258 | } 259 | , { 260 | "box" : { 261 | "id" : "obj-23", 262 | "maxclass" : "message", 263 | "numinlets" : 2, 264 | "numoutlets" : 1, 265 | "outlettype" : [ "" ], 266 | "patching_rect" : [ 404.5, 250.0, 98.0, 22.0 ], 267 | "text" : "script npm install" 268 | } 269 | 270 | } 271 | , { 272 | "box" : { 273 | "bgmode" : 0, 274 | "border" : 0, 275 | "clickthrough" : 0, 276 | "enablehscroll" : 0, 277 | "enablevscroll" : 0, 278 | "id" : "obj-21", 279 | "lockeddragscroll" : 0, 280 | "maxclass" : "bpatcher", 281 | "name" : "n4m.monitor.maxpat", 282 | "numinlets" : 1, 283 | "numoutlets" : 0, 284 | "offset" : [ 0.0, 0.0 ], 285 | "patching_rect" : [ 404.5, 16.0, 400.0, 220.0 ], 286 | "viewvisibility" : 1 287 | } 288 | 289 | } 290 | ], 291 | "lines" : [ { 292 | "patchline" : { 293 | "destination" : [ "obj-25", 0 ], 294 | "source" : [ "obj-1", 0 ] 295 | } 296 | 297 | } 298 | , { 299 | "patchline" : { 300 | "destination" : [ "obj-32", 1 ], 301 | "source" : [ "obj-2", 0 ] 302 | } 303 | 304 | } 305 | , { 306 | "patchline" : { 307 | "destination" : [ "obj-21", 0 ], 308 | "source" : [ "obj-22", 1 ] 309 | } 310 | 311 | } 312 | , { 313 | "patchline" : { 314 | "destination" : [ "obj-47", 1 ], 315 | "source" : [ "obj-22", 0 ] 316 | } 317 | 318 | } 319 | , { 320 | "patchline" : { 321 | "destination" : [ "obj-25", 0 ], 322 | "source" : [ "obj-23", 0 ] 323 | } 324 | 325 | } 326 | , { 327 | "patchline" : { 328 | "destination" : [ "obj-2", 0 ], 329 | "order" : 1, 330 | "source" : [ "obj-24", 0 ] 331 | } 332 | 333 | } 334 | , { 335 | "patchline" : { 336 | "destination" : [ "obj-22", 0 ], 337 | "order" : 0, 338 | "source" : [ "obj-24", 0 ] 339 | } 340 | 341 | } 342 | , { 343 | "patchline" : { 344 | "destination" : [ "obj-22", 0 ], 345 | "source" : [ "obj-27", 0 ] 346 | } 347 | 348 | } 349 | , { 350 | "patchline" : { 351 | "destination" : [ "obj-37", 0 ], 352 | "source" : [ "obj-36", 0 ] 353 | } 354 | 355 | } 356 | , { 357 | "patchline" : { 358 | "destination" : [ "obj-39", 0 ], 359 | "source" : [ "obj-37", 0 ] 360 | } 361 | 362 | } 363 | , { 364 | "patchline" : { 365 | "destination" : [ "obj-40", 0 ], 366 | "source" : [ "obj-39", 0 ] 367 | } 368 | 369 | } 370 | , { 371 | "patchline" : { 372 | "destination" : [ "obj-25", 0 ], 373 | "source" : [ "obj-4", 0 ] 374 | } 375 | 376 | } 377 | , { 378 | "patchline" : { 379 | "destination" : [ "obj-41", 0 ], 380 | "source" : [ "obj-40", 0 ] 381 | } 382 | 383 | } 384 | , { 385 | "patchline" : { 386 | "destination" : [ "obj-2", 0 ], 387 | "source" : [ "obj-41", 0 ] 388 | } 389 | 390 | } 391 | , { 392 | "patchline" : { 393 | "destination" : [ "obj-22", 0 ], 394 | "source" : [ "obj-45", 0 ] 395 | } 396 | 397 | } 398 | , { 399 | "patchline" : { 400 | "destination" : [ "obj-36", 0 ], 401 | "source" : [ "obj-49", 0 ] 402 | } 403 | 404 | } 405 | , { 406 | "patchline" : { 407 | "destination" : [ "obj-2", 0 ], 408 | "source" : [ "obj-5", 0 ] 409 | } 410 | 411 | } 412 | ], 413 | "dependency_cache" : [ { 414 | "name" : "n4m.monitor.maxpat", 415 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 416 | "type" : "JSON", 417 | "implicit" : 1 418 | } 419 | , { 420 | "name" : "resize_n4m_monitor_patcher.js", 421 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 422 | "type" : "TEXT", 423 | "implicit" : 1 424 | } 425 | , { 426 | "name" : "fit_jweb_to_bounds.js", 427 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 428 | "type" : "TEXT", 429 | "implicit" : 1 430 | } 431 | , { 432 | "name" : "mqtt-publisher.js", 433 | "bootpath" : "~/Documents/_CODE/art_music/znibbles/znibbles-github/10-node-in-max/4-mqtt", 434 | "patcherrelativepath" : ".", 435 | "type" : "TEXT", 436 | "implicit" : 1 437 | } 438 | , { 439 | "name" : "mqtt-subscriber.js", 440 | "bootpath" : "~/Documents/_CODE/art_music/znibbles/znibbles-github/10-node-in-max/4-mqtt", 441 | "patcherrelativepath" : ".", 442 | "type" : "TEXT", 443 | "implicit" : 1 444 | } 445 | ], 446 | "autosave" : 0 447 | } 448 | 449 | } 450 | -------------------------------------------------------------------------------- /4-mqtt/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "4-mqtt", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "async-limiter": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", 10 | "integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=" 11 | }, 12 | "balanced-match": { 13 | "version": "1.0.0", 14 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 15 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 16 | }, 17 | "bl": { 18 | "version": "1.2.2", 19 | "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", 20 | "integrity": "sha1-oWCRFxcQPAdBDO9j71Gzl8Alr5w=", 21 | "requires": { 22 | "readable-stream": "^2.3.5", 23 | "safe-buffer": "^5.1.1" 24 | } 25 | }, 26 | "brace-expansion": { 27 | "version": "1.1.11", 28 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 29 | "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", 30 | "requires": { 31 | "balanced-match": "^1.0.0", 32 | "concat-map": "0.0.1" 33 | } 34 | }, 35 | "buffer-from": { 36 | "version": "1.1.1", 37 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 38 | "integrity": "sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8=" 39 | }, 40 | "callback-stream": { 41 | "version": "1.1.0", 42 | "resolved": "https://registry.npmjs.org/callback-stream/-/callback-stream-1.1.0.tgz", 43 | "integrity": "sha1-RwGlEmbwbgbqpx/BcjOCLYdfSQg=", 44 | "requires": { 45 | "inherits": "^2.0.1", 46 | "readable-stream": "> 1.0.0 < 3.0.0" 47 | } 48 | }, 49 | "commist": { 50 | "version": "1.0.0", 51 | "resolved": "https://registry.npmjs.org/commist/-/commist-1.0.0.tgz", 52 | "integrity": "sha1-wMNSUBz29S6RJOPvicmAbiAi6+8=", 53 | "requires": { 54 | "leven": "^1.0.0", 55 | "minimist": "^1.1.0" 56 | } 57 | }, 58 | "concat-map": { 59 | "version": "0.0.1", 60 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 61 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 62 | }, 63 | "concat-stream": { 64 | "version": "1.6.2", 65 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 66 | "integrity": "sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ=", 67 | "requires": { 68 | "buffer-from": "^1.0.0", 69 | "inherits": "^2.0.3", 70 | "readable-stream": "^2.2.2", 71 | "typedarray": "^0.0.6" 72 | } 73 | }, 74 | "core-util-is": { 75 | "version": "1.0.2", 76 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 77 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 78 | }, 79 | "d": { 80 | "version": "1.0.0", 81 | "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", 82 | "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", 83 | "requires": { 84 | "es5-ext": "^0.10.9" 85 | } 86 | }, 87 | "duplexify": { 88 | "version": "3.6.1", 89 | "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", 90 | "integrity": "sha1-saeinEq/1jlYXvrszoDWZrHjQSU=", 91 | "requires": { 92 | "end-of-stream": "^1.0.0", 93 | "inherits": "^2.0.1", 94 | "readable-stream": "^2.0.0", 95 | "stream-shift": "^1.0.0" 96 | } 97 | }, 98 | "end-of-stream": { 99 | "version": "1.4.1", 100 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", 101 | "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", 102 | "requires": { 103 | "once": "^1.4.0" 104 | } 105 | }, 106 | "es5-ext": { 107 | "version": "0.10.46", 108 | "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", 109 | "integrity": "sha1-79mfZ8Wn7Hibqj2qf3mHA4j39XI=", 110 | "requires": { 111 | "es6-iterator": "~2.0.3", 112 | "es6-symbol": "~3.1.1", 113 | "next-tick": "1" 114 | } 115 | }, 116 | "es6-iterator": { 117 | "version": "2.0.3", 118 | "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", 119 | "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", 120 | "requires": { 121 | "d": "1", 122 | "es5-ext": "^0.10.35", 123 | "es6-symbol": "^3.1.1" 124 | } 125 | }, 126 | "es6-map": { 127 | "version": "0.1.5", 128 | "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", 129 | "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", 130 | "requires": { 131 | "d": "1", 132 | "es5-ext": "~0.10.14", 133 | "es6-iterator": "~2.0.1", 134 | "es6-set": "~0.1.5", 135 | "es6-symbol": "~3.1.1", 136 | "event-emitter": "~0.3.5" 137 | } 138 | }, 139 | "es6-set": { 140 | "version": "0.1.5", 141 | "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", 142 | "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", 143 | "requires": { 144 | "d": "1", 145 | "es5-ext": "~0.10.14", 146 | "es6-iterator": "~2.0.1", 147 | "es6-symbol": "3.1.1", 148 | "event-emitter": "~0.3.5" 149 | } 150 | }, 151 | "es6-symbol": { 152 | "version": "3.1.1", 153 | "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", 154 | "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", 155 | "requires": { 156 | "d": "1", 157 | "es5-ext": "~0.10.14" 158 | } 159 | }, 160 | "event-emitter": { 161 | "version": "0.3.5", 162 | "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", 163 | "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", 164 | "requires": { 165 | "d": "1", 166 | "es5-ext": "~0.10.14" 167 | } 168 | }, 169 | "extend": { 170 | "version": "3.0.2", 171 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 172 | "integrity": "sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo=" 173 | }, 174 | "fs.realpath": { 175 | "version": "1.0.0", 176 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 177 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 178 | }, 179 | "glob": { 180 | "version": "7.1.3", 181 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 182 | "integrity": "sha1-OWCDLT8VdBCDQtr9OmezMsCWnfE=", 183 | "requires": { 184 | "fs.realpath": "^1.0.0", 185 | "inflight": "^1.0.4", 186 | "inherits": "2", 187 | "minimatch": "^3.0.4", 188 | "once": "^1.3.0", 189 | "path-is-absolute": "^1.0.0" 190 | } 191 | }, 192 | "glob-parent": { 193 | "version": "3.1.0", 194 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", 195 | "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", 196 | "requires": { 197 | "is-glob": "^3.1.0", 198 | "path-dirname": "^1.0.0" 199 | } 200 | }, 201 | "glob-stream": { 202 | "version": "6.1.0", 203 | "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", 204 | "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", 205 | "requires": { 206 | "extend": "^3.0.0", 207 | "glob": "^7.1.1", 208 | "glob-parent": "^3.1.0", 209 | "is-negated-glob": "^1.0.0", 210 | "ordered-read-streams": "^1.0.0", 211 | "pumpify": "^1.3.5", 212 | "readable-stream": "^2.1.5", 213 | "remove-trailing-separator": "^1.0.1", 214 | "to-absolute-glob": "^2.0.0", 215 | "unique-stream": "^2.0.2" 216 | } 217 | }, 218 | "help-me": { 219 | "version": "1.1.0", 220 | "resolved": "https://registry.npmjs.org/help-me/-/help-me-1.1.0.tgz", 221 | "integrity": "sha1-jy1QjQYAtKRW2i8IZVbn5cBWo8Y=", 222 | "requires": { 223 | "callback-stream": "^1.0.2", 224 | "glob-stream": "^6.1.0", 225 | "through2": "^2.0.1", 226 | "xtend": "^4.0.0" 227 | } 228 | }, 229 | "inflight": { 230 | "version": "1.0.6", 231 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 232 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 233 | "requires": { 234 | "once": "^1.3.0", 235 | "wrappy": "1" 236 | } 237 | }, 238 | "inherits": { 239 | "version": "2.0.3", 240 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 241 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 242 | }, 243 | "is-absolute": { 244 | "version": "1.0.0", 245 | "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", 246 | "integrity": "sha1-OV4a6EsR8mrReV5zwXN45IowFXY=", 247 | "requires": { 248 | "is-relative": "^1.0.0", 249 | "is-windows": "^1.0.1" 250 | } 251 | }, 252 | "is-extglob": { 253 | "version": "2.1.1", 254 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 255 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" 256 | }, 257 | "is-glob": { 258 | "version": "3.1.0", 259 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", 260 | "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", 261 | "requires": { 262 | "is-extglob": "^2.1.0" 263 | } 264 | }, 265 | "is-negated-glob": { 266 | "version": "1.0.0", 267 | "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", 268 | "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=" 269 | }, 270 | "is-relative": { 271 | "version": "1.0.0", 272 | "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", 273 | "integrity": "sha1-obtpNc6MXboei5dUubLcwCDiJg0=", 274 | "requires": { 275 | "is-unc-path": "^1.0.0" 276 | } 277 | }, 278 | "is-unc-path": { 279 | "version": "1.0.0", 280 | "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", 281 | "integrity": "sha1-1zHoiY7QkKEsNSrS6u1Qla0yLJ0=", 282 | "requires": { 283 | "unc-path-regex": "^0.1.2" 284 | } 285 | }, 286 | "is-windows": { 287 | "version": "1.0.2", 288 | "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", 289 | "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=" 290 | }, 291 | "isarray": { 292 | "version": "1.0.0", 293 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 294 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 295 | }, 296 | "json-stable-stringify-without-jsonify": { 297 | "version": "1.0.1", 298 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 299 | "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" 300 | }, 301 | "leven": { 302 | "version": "1.0.2", 303 | "resolved": "https://registry.npmjs.org/leven/-/leven-1.0.2.tgz", 304 | "integrity": "sha1-kUS27ryl8dBoAWnxpncNzqYLdcM=" 305 | }, 306 | "minimatch": { 307 | "version": "3.0.4", 308 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 309 | "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", 310 | "requires": { 311 | "brace-expansion": "^1.1.7" 312 | } 313 | }, 314 | "minimist": { 315 | "version": "1.2.5", 316 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 317 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 318 | }, 319 | "mqtt": { 320 | "version": "2.18.8", 321 | "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-2.18.8.tgz", 322 | "integrity": "sha1-nSE8yrkhUazPsh7owIYNxoZqslk=", 323 | "requires": { 324 | "commist": "^1.0.0", 325 | "concat-stream": "^1.6.2", 326 | "end-of-stream": "^1.4.1", 327 | "es6-map": "^0.1.5", 328 | "help-me": "^1.0.1", 329 | "inherits": "^2.0.3", 330 | "minimist": "^1.2.0", 331 | "mqtt-packet": "^5.6.0", 332 | "pump": "^3.0.0", 333 | "readable-stream": "^2.3.6", 334 | "reinterval": "^1.1.0", 335 | "split2": "^2.1.1", 336 | "websocket-stream": "^5.1.2", 337 | "xtend": "^4.0.1" 338 | } 339 | }, 340 | "mqtt-packet": { 341 | "version": "5.6.1", 342 | "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-5.6.1.tgz", 343 | "integrity": "sha512-eaF9rO2uFrIYEHomJxziuKTDkbWW5psLBaIGCazQSKqYsTaB3n4SpvJ1PexKaDBiPnMLPIFWBIiTYT3IfEJfww==", 344 | "requires": { 345 | "bl": "^1.2.1", 346 | "inherits": "^2.0.3", 347 | "process-nextick-args": "^2.0.0", 348 | "safe-buffer": "^5.1.0" 349 | } 350 | }, 351 | "next-tick": { 352 | "version": "1.0.0", 353 | "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", 354 | "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" 355 | }, 356 | "once": { 357 | "version": "1.4.0", 358 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 359 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 360 | "requires": { 361 | "wrappy": "1" 362 | } 363 | }, 364 | "ordered-read-streams": { 365 | "version": "1.0.1", 366 | "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", 367 | "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", 368 | "requires": { 369 | "readable-stream": "^2.0.1" 370 | } 371 | }, 372 | "path-dirname": { 373 | "version": "1.0.2", 374 | "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", 375 | "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" 376 | }, 377 | "path-is-absolute": { 378 | "version": "1.0.1", 379 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 380 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 381 | }, 382 | "process-nextick-args": { 383 | "version": "2.0.0", 384 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", 385 | "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o=" 386 | }, 387 | "pump": { 388 | "version": "3.0.0", 389 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 390 | "integrity": "sha1-tKIRaBW94vTh6mAjVOjHVWUQemQ=", 391 | "requires": { 392 | "end-of-stream": "^1.1.0", 393 | "once": "^1.3.1" 394 | } 395 | }, 396 | "pumpify": { 397 | "version": "1.5.1", 398 | "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", 399 | "integrity": "sha1-NlE74karJ1cLGjdKXOJ4v9dDcM4=", 400 | "requires": { 401 | "duplexify": "^3.6.0", 402 | "inherits": "^2.0.3", 403 | "pump": "^2.0.0" 404 | }, 405 | "dependencies": { 406 | "pump": { 407 | "version": "2.0.1", 408 | "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", 409 | "integrity": "sha1-Ejma3W5M91Jtlzy8i1zi4pCLOQk=", 410 | "requires": { 411 | "end-of-stream": "^1.1.0", 412 | "once": "^1.3.1" 413 | } 414 | } 415 | } 416 | }, 417 | "readable-stream": { 418 | "version": "2.3.6", 419 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 420 | "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", 421 | "requires": { 422 | "core-util-is": "~1.0.0", 423 | "inherits": "~2.0.3", 424 | "isarray": "~1.0.0", 425 | "process-nextick-args": "~2.0.0", 426 | "safe-buffer": "~5.1.1", 427 | "string_decoder": "~1.1.1", 428 | "util-deprecate": "~1.0.1" 429 | } 430 | }, 431 | "reinterval": { 432 | "version": "1.1.0", 433 | "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz", 434 | "integrity": "sha1-M2Hs+jymwYKDOA3Qu5VG85D17Oc=" 435 | }, 436 | "remove-trailing-separator": { 437 | "version": "1.1.0", 438 | "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", 439 | "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" 440 | }, 441 | "safe-buffer": { 442 | "version": "5.1.2", 443 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 444 | "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=" 445 | }, 446 | "split2": { 447 | "version": "2.2.0", 448 | "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", 449 | "integrity": "sha1-GGsldbz4PoW30YRldWI47k7kJJM=", 450 | "requires": { 451 | "through2": "^2.0.2" 452 | } 453 | }, 454 | "stream-shift": { 455 | "version": "1.0.0", 456 | "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", 457 | "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" 458 | }, 459 | "string_decoder": { 460 | "version": "1.1.1", 461 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 462 | "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", 463 | "requires": { 464 | "safe-buffer": "~5.1.0" 465 | } 466 | }, 467 | "through2": { 468 | "version": "2.0.5", 469 | "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", 470 | "integrity": "sha1-AcHjnrMdB8t9A6lqcIIyYLIxMs0=", 471 | "requires": { 472 | "readable-stream": "~2.3.6", 473 | "xtend": "~4.0.1" 474 | } 475 | }, 476 | "through2-filter": { 477 | "version": "3.0.0", 478 | "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", 479 | "integrity": "sha1-cA54bfI2fCyIzYqlvkz5weeDElQ=", 480 | "requires": { 481 | "through2": "~2.0.0", 482 | "xtend": "~4.0.0" 483 | } 484 | }, 485 | "to-absolute-glob": { 486 | "version": "2.0.2", 487 | "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", 488 | "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", 489 | "requires": { 490 | "is-absolute": "^1.0.0", 491 | "is-negated-glob": "^1.0.0" 492 | } 493 | }, 494 | "typedarray": { 495 | "version": "0.0.6", 496 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 497 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" 498 | }, 499 | "ultron": { 500 | "version": "1.1.1", 501 | "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", 502 | "integrity": "sha1-n+FTahCmZKZSZqHjzPhf02MCvJw=" 503 | }, 504 | "unc-path-regex": { 505 | "version": "0.1.2", 506 | "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", 507 | "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=" 508 | }, 509 | "unique-stream": { 510 | "version": "2.3.1", 511 | "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", 512 | "integrity": "sha1-xl0RDppK35psWUiygFPZqNBMvqw=", 513 | "requires": { 514 | "json-stable-stringify-without-jsonify": "^1.0.1", 515 | "through2-filter": "^3.0.0" 516 | } 517 | }, 518 | "util-deprecate": { 519 | "version": "1.0.2", 520 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 521 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 522 | }, 523 | "websocket-stream": { 524 | "version": "5.1.2", 525 | "resolved": "https://registry.npmjs.org/websocket-stream/-/websocket-stream-5.1.2.tgz", 526 | "integrity": "sha1-HDHGJ7zfNPGpvazJ2qFb+kgW2a0=", 527 | "requires": { 528 | "duplexify": "^3.5.1", 529 | "inherits": "^2.0.1", 530 | "readable-stream": "^2.3.3", 531 | "safe-buffer": "^5.1.1", 532 | "ws": "^3.2.0", 533 | "xtend": "^4.0.0" 534 | } 535 | }, 536 | "wrappy": { 537 | "version": "1.0.2", 538 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 539 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 540 | }, 541 | "ws": { 542 | "version": "3.3.3", 543 | "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", 544 | "integrity": "sha1-8c+E/i1ekB686U767OeF8YeiKPI=", 545 | "requires": { 546 | "async-limiter": "~1.0.0", 547 | "safe-buffer": "~5.1.0", 548 | "ultron": "~1.1.0" 549 | } 550 | }, 551 | "xtend": { 552 | "version": "4.0.1", 553 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 554 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" 555 | } 556 | } 557 | } 558 | -------------------------------------------------------------------------------- /4-mqtt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "4-mqtt", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "mqtt": "^2.18.8" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /5-repl/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "5-repl", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "repl.history": { 8 | "version": "0.1.4", 9 | "resolved": "https://registry.npmjs.org/repl.history/-/repl.history-0.1.4.tgz", 10 | "integrity": "sha1-gDZxcfN4HW5CmccXWMJTCX9dWDI=" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /5-repl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "5-repl", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /5-repl/repl-server.js: -------------------------------------------------------------------------------- 1 | const readline = require('readline'); 2 | 3 | const rl = readline.createInterface({ 4 | input: process.stdin, 5 | output: process.stdout, 6 | }); 7 | 8 | rl.prompt(); 9 | 10 | rl.on('line', (line) => { 11 | try { 12 | console.log(eval(line.trim())); 13 | } catch(e) { 14 | console.log(e); 15 | } 16 | 17 | rl.prompt(); 18 | }).on('close', () => { 19 | console.log('Have a great day!'); 20 | process.exit(0); 21 | }); 22 | -------------------------------------------------------------------------------- /5-repl/repl.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 8, 6 | "minor" : 0, 7 | "revision" : 3, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "classnamespace" : "box", 13 | "rect" : [ 1269.0, 273.0, 572.0, 687.0 ], 14 | "bglocked" : 0, 15 | "openinpresentation" : 0, 16 | "default_fontsize" : 12.0, 17 | "default_fontface" : 0, 18 | "default_fontname" : "Arial", 19 | "gridonopen" : 1, 20 | "gridsize" : [ 15.0, 15.0 ], 21 | "gridsnaponopen" : 1, 22 | "objectsnaponopen" : 1, 23 | "statusbarvisible" : 2, 24 | "toolbarvisible" : 1, 25 | "lefttoolbarpinned" : 0, 26 | "toptoolbarpinned" : 0, 27 | "righttoolbarpinned" : 0, 28 | "bottomtoolbarpinned" : 0, 29 | "toolbars_unpinned_last_save" : 0, 30 | "tallnewobj" : 0, 31 | "boxanimatetime" : 200, 32 | "enablehscroll" : 1, 33 | "enablevscroll" : 1, 34 | "devicewidth" : 0.0, 35 | "description" : "", 36 | "digest" : "", 37 | "tags" : "", 38 | "style" : "", 39 | "subpatcher_template" : "", 40 | "boxes" : [ { 41 | "box" : { 42 | "id" : "obj-23", 43 | "maxclass" : "newobj", 44 | "numinlets" : 1, 45 | "numoutlets" : 2, 46 | "outlettype" : [ "select", "clear" ], 47 | "patching_rect" : [ 97.0, 368.0, 76.0, 22.0 ], 48 | "text" : "t select clear" 49 | } 50 | 51 | } 52 | , { 53 | "box" : { 54 | "id" : "obj-22", 55 | "maxclass" : "newobj", 56 | "numinlets" : 2, 57 | "numoutlets" : 2, 58 | "outlettype" : [ "", "" ], 59 | "patching_rect" : [ 26.5, 176.0, 59.0, 22.0 ], 60 | "text" : "route text" 61 | } 62 | 63 | } 64 | , { 65 | "box" : { 66 | "fontname" : "Fira Code", 67 | "fontsize" : 14.0, 68 | "id" : "obj-20", 69 | "keymode" : 1, 70 | "maxclass" : "textedit", 71 | "numinlets" : 1, 72 | "numoutlets" : 4, 73 | "outlettype" : [ "", "int", "", "" ], 74 | "parameter_enable" : 0, 75 | "patching_rect" : [ 26.5, 46.0, 415.0, 25.0 ], 76 | "tabmode" : 0 77 | } 78 | 79 | } 80 | , { 81 | "box" : { 82 | "id" : "obj-19", 83 | "maxclass" : "newobj", 84 | "numinlets" : 1, 85 | "numoutlets" : 1, 86 | "outlettype" : [ "" ], 87 | "patching_rect" : [ 120.0, 443.0, 96.0, 22.0 ], 88 | "text" : "prepend append" 89 | } 90 | 91 | } 92 | , { 93 | "box" : { 94 | "id" : "obj-18", 95 | "maxclass" : "newobj", 96 | "numinlets" : 3, 97 | "numoutlets" : 1, 98 | "outlettype" : [ "int" ], 99 | "patching_rect" : [ 120.0, 414.0, 40.0, 22.0 ], 100 | "text" : "itoa" 101 | } 102 | 103 | } 104 | , { 105 | "box" : { 106 | "id" : "obj-17", 107 | "maxclass" : "newobj", 108 | "numinlets" : 1, 109 | "numoutlets" : 2, 110 | "outlettype" : [ "int", "" ], 111 | "patching_rect" : [ 211.0, 399.0, 38.0, 22.0 ], 112 | "text" : "t 13 s" 113 | } 114 | 115 | } 116 | , { 117 | "box" : { 118 | "id" : "obj-16", 119 | "maxclass" : "message", 120 | "numinlets" : 2, 121 | "numoutlets" : 1, 122 | "outlettype" : [ "" ], 123 | "patching_rect" : [ 468.0, 8.0, 35.0, 22.0 ], 124 | "text" : "clear" 125 | } 126 | 127 | } 128 | , { 129 | "box" : { 130 | "id" : "obj-14", 131 | "maxclass" : "newobj", 132 | "numinlets" : 1, 133 | "numoutlets" : 1, 134 | "outlettype" : [ "" ], 135 | "patching_rect" : [ 225.0, 431.0, 113.0, 22.0 ], 136 | "text" : "sprintf \"append %s\"" 137 | } 138 | 139 | } 140 | , { 141 | "box" : { 142 | "fontname" : "Fira Code", 143 | "fontsize" : 14.0, 144 | "id" : "obj-13", 145 | "keymode" : 1, 146 | "maxclass" : "textedit", 147 | "numinlets" : 1, 148 | "numoutlets" : 4, 149 | "outlettype" : [ "", "int", "", "" ], 150 | "parameter_enable" : 0, 151 | "patching_rect" : [ 468.0, 46.0, 407.0, 172.0 ], 152 | "tabmode" : 0 153 | } 154 | 155 | } 156 | , { 157 | "box" : { 158 | "id" : "obj-10", 159 | "maxclass" : "newobj", 160 | "numinlets" : 3, 161 | "numoutlets" : 3, 162 | "outlettype" : [ "", "", "" ], 163 | "patching_rect" : [ 225.0, 368.0, 106.0, 22.0 ], 164 | "text" : "route stdout stderr" 165 | } 166 | 167 | } 168 | , { 169 | "box" : { 170 | "id" : "obj-7", 171 | "maxclass" : "newobj", 172 | "numinlets" : 1, 173 | "numoutlets" : 1, 174 | "outlettype" : [ "" ], 175 | "patching_rect" : [ 46.0, 284.0, 81.0, 22.0 ], 176 | "text" : "prepend stdin" 177 | } 178 | 179 | } 180 | , { 181 | "box" : { 182 | "bgmode" : 0, 183 | "border" : 0, 184 | "clickthrough" : 0, 185 | "enablehscroll" : 0, 186 | "enablevscroll" : 0, 187 | "id" : "obj-6", 188 | "lockeddragscroll" : 0, 189 | "maxclass" : "bpatcher", 190 | "name" : "n4m.monitor.maxpat", 191 | "numinlets" : 1, 192 | "numoutlets" : 0, 193 | "offset" : [ 0.0, 0.0 ], 194 | "patching_rect" : [ 475.0, 251.0, 400.0, 220.0 ], 195 | "viewvisibility" : 1 196 | } 197 | 198 | } 199 | , { 200 | "box" : { 201 | "id" : "obj-5", 202 | "maxclass" : "message", 203 | "numinlets" : 2, 204 | "numoutlets" : 1, 205 | "outlettype" : [ "" ], 206 | "patching_rect" : [ 237.0, 291.0, 63.0, 22.0 ], 207 | "text" : "script stop" 208 | } 209 | 210 | } 211 | , { 212 | "box" : { 213 | "id" : "obj-3", 214 | "maxclass" : "message", 215 | "numinlets" : 2, 216 | "numoutlets" : 1, 217 | "outlettype" : [ "" ], 218 | "patching_rect" : [ 157.0, 291.0, 64.0, 22.0 ], 219 | "text" : "script start" 220 | } 221 | 222 | } 223 | , { 224 | "box" : { 225 | "id" : "obj-1", 226 | "maxclass" : "newobj", 227 | "numinlets" : 1, 228 | "numoutlets" : 2, 229 | "outlettype" : [ "", "" ], 230 | "patching_rect" : [ 46.0, 329.0, 196.0, 22.0 ], 231 | "saved_object_attributes" : { 232 | "autostart" : 0, 233 | "defer" : 0, 234 | "node" : "", 235 | "npm" : "", 236 | "watch" : 1 237 | } 238 | , 239 | "text" : "node.script repl-server.js @watch 1" 240 | } 241 | 242 | } 243 | ], 244 | "lines" : [ { 245 | "patchline" : { 246 | "destination" : [ "obj-10", 0 ], 247 | "order" : 0, 248 | "source" : [ "obj-1", 1 ] 249 | } 250 | 251 | } 252 | , { 253 | "patchline" : { 254 | "destination" : [ "obj-6", 0 ], 255 | "midpoints" : [ 232.5, 354.0, 462.0, 354.0, 462.0, 246.0, 484.5, 246.0 ], 256 | "order" : 1, 257 | "source" : [ "obj-1", 1 ] 258 | } 259 | 260 | } 261 | , { 262 | "patchline" : { 263 | "destination" : [ "obj-17", 0 ], 264 | "order" : 0, 265 | "source" : [ "obj-10", 0 ] 266 | } 267 | 268 | } 269 | , { 270 | "patchline" : { 271 | "destination" : [ "obj-23", 0 ], 272 | "midpoints" : [ 234.5, 393.0, 183.0, 393.0, 183.0, 363.0, 106.5, 363.0 ], 273 | "order" : 1, 274 | "source" : [ "obj-10", 0 ] 275 | } 276 | 277 | } 278 | , { 279 | "patchline" : { 280 | "destination" : [ "obj-13", 0 ], 281 | "midpoints" : [ 234.5, 465.0, 453.0, 465.0, 453.0, 42.0, 477.5, 42.0 ], 282 | "source" : [ "obj-14", 0 ] 283 | } 284 | 285 | } 286 | , { 287 | "patchline" : { 288 | "destination" : [ "obj-13", 0 ], 289 | "source" : [ "obj-16", 0 ] 290 | } 291 | 292 | } 293 | , { 294 | "patchline" : { 295 | "destination" : [ "obj-14", 0 ], 296 | "source" : [ "obj-17", 1 ] 297 | } 298 | 299 | } 300 | , { 301 | "patchline" : { 302 | "destination" : [ "obj-18", 0 ], 303 | "source" : [ "obj-17", 0 ] 304 | } 305 | 306 | } 307 | , { 308 | "patchline" : { 309 | "destination" : [ "obj-19", 0 ], 310 | "source" : [ "obj-18", 0 ] 311 | } 312 | 313 | } 314 | , { 315 | "patchline" : { 316 | "destination" : [ "obj-13", 0 ], 317 | "midpoints" : [ 129.5, 477.0, 453.0, 477.0, 453.0, 42.0, 477.5, 42.0 ], 318 | "source" : [ "obj-19", 0 ] 319 | } 320 | 321 | } 322 | , { 323 | "patchline" : { 324 | "destination" : [ "obj-22", 0 ], 325 | "source" : [ "obj-20", 0 ] 326 | } 327 | 328 | } 329 | , { 330 | "patchline" : { 331 | "destination" : [ "obj-7", 0 ], 332 | "midpoints" : [ 36.0, 270.0, 55.5, 270.0 ], 333 | "source" : [ "obj-22", 0 ] 334 | } 335 | 336 | } 337 | , { 338 | "patchline" : { 339 | "destination" : [ "obj-20", 0 ], 340 | "midpoints" : [ 163.5, 393.0, 12.0, 393.0, 12.0, 42.0, 36.0, 42.0 ], 341 | "source" : [ "obj-23", 1 ] 342 | } 343 | 344 | } 345 | , { 346 | "patchline" : { 347 | "destination" : [ "obj-20", 0 ], 348 | "midpoints" : [ 106.5, 393.0, 12.0, 393.0, 12.0, 42.0, 36.0, 42.0 ], 349 | "source" : [ "obj-23", 0 ] 350 | } 351 | 352 | } 353 | , { 354 | "patchline" : { 355 | "destination" : [ "obj-1", 0 ], 356 | "source" : [ "obj-3", 0 ] 357 | } 358 | 359 | } 360 | , { 361 | "patchline" : { 362 | "destination" : [ "obj-1", 0 ], 363 | "source" : [ "obj-5", 0 ] 364 | } 365 | 366 | } 367 | , { 368 | "patchline" : { 369 | "destination" : [ "obj-1", 0 ], 370 | "source" : [ "obj-7", 0 ] 371 | } 372 | 373 | } 374 | ], 375 | "dependency_cache" : [ { 376 | "name" : "repl-server.js", 377 | "bootpath" : "~/Documents/_CODE/art_music/znibbles/znibbles-github/10-node-in-max/5-repl", 378 | "patcherrelativepath" : ".", 379 | "type" : "TEXT", 380 | "implicit" : 1 381 | } 382 | , { 383 | "name" : "n4m.monitor.maxpat", 384 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 385 | "type" : "JSON", 386 | "implicit" : 1 387 | } 388 | , { 389 | "name" : "resize_n4m_monitor_patcher.js", 390 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 391 | "type" : "TEXT", 392 | "implicit" : 1 393 | } 394 | , { 395 | "name" : "fit_jweb_to_bounds.js", 396 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 397 | "type" : "TEXT", 398 | "implicit" : 1 399 | } 400 | ], 401 | "autosave" : 0 402 | } 403 | 404 | } 405 | -------------------------------------------------------------------------------- /6-repl-with-history/repl-server.js: -------------------------------------------------------------------------------- 1 | const readline = require('readline'); 2 | const Max = require('max-api'); 3 | 4 | const myHistory = []; 5 | let historyPos = -1; 6 | 7 | const rl = readline.createInterface({ 8 | input: process.stdin, 9 | output: process.stdout, 10 | //terminal: false 11 | }); 12 | 13 | rl.prompt(); 14 | 15 | rl.on('line', (line) => { 16 | myHistory.unshift(line); 17 | Max.post(line); 18 | 19 | try { 20 | console.log(eval(line.trim())); 21 | } catch(e) { 22 | console.log(e); 23 | } 24 | 25 | historyPos = -1; 26 | rl.prompt(); 27 | }).on('close', () => { 28 | console.log('Have a great day!'); 29 | process.exit(0); 30 | }); 31 | 32 | Max.addHandler('history_up', () => { 33 | historyPos = historyPos < myHistory.length - 1 ? historyPos + 1 : myHistory.length - 1; 34 | console.error(`history ${myHistory[historyPos]}`); 35 | }); 36 | 37 | Max.addHandler('history_down', () => { 38 | historyPos = historyPos > 0 ? historyPos - 1 : 0; 39 | console.error(`history ${myHistory[historyPos]}`); 40 | }); 41 | -------------------------------------------------------------------------------- /6-repl-with-history/repl-with-history.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 8, 6 | "minor" : 0, 7 | "revision" : 3, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "classnamespace" : "box", 13 | "rect" : [ 368.0, 79.0, 1134.0, 687.0 ], 14 | "bglocked" : 0, 15 | "openinpresentation" : 0, 16 | "default_fontsize" : 12.0, 17 | "default_fontface" : 0, 18 | "default_fontname" : "Arial", 19 | "gridonopen" : 1, 20 | "gridsize" : [ 15.0, 15.0 ], 21 | "gridsnaponopen" : 1, 22 | "objectsnaponopen" : 1, 23 | "statusbarvisible" : 2, 24 | "toolbarvisible" : 1, 25 | "lefttoolbarpinned" : 0, 26 | "toptoolbarpinned" : 0, 27 | "righttoolbarpinned" : 0, 28 | "bottomtoolbarpinned" : 0, 29 | "toolbars_unpinned_last_save" : 0, 30 | "tallnewobj" : 0, 31 | "boxanimatetime" : 200, 32 | "enablehscroll" : 1, 33 | "enablevscroll" : 1, 34 | "devicewidth" : 0.0, 35 | "description" : "", 36 | "digest" : "", 37 | "tags" : "", 38 | "style" : "", 39 | "subpatcher_template" : "", 40 | "boxes" : [ { 41 | "box" : { 42 | "id" : "obj-35", 43 | "maxclass" : "newobj", 44 | "numinlets" : 1, 45 | "numoutlets" : 1, 46 | "outlettype" : [ "" ], 47 | "patching_rect" : [ 358.0, 421.0, 72.0, 22.0 ], 48 | "text" : "prepend set" 49 | } 50 | 51 | } 52 | , { 53 | "box" : { 54 | "id" : "obj-34", 55 | "maxclass" : "newobj", 56 | "numinlets" : 2, 57 | "numoutlets" : 2, 58 | "outlettype" : [ "", "" ], 59 | "patching_rect" : [ 358.0, 395.0, 75.0, 22.0 ], 60 | "text" : "route history" 61 | } 62 | 63 | } 64 | , { 65 | "box" : { 66 | "id" : "obj-33", 67 | "maxclass" : "newobj", 68 | "numinlets" : 1, 69 | "numoutlets" : 1, 70 | "outlettype" : [ "" ], 71 | "patching_rect" : [ 358.0, 368.0, 71.0, 22.0 ], 72 | "text" : "fromsymbol" 73 | } 74 | 75 | } 76 | , { 77 | "box" : { 78 | "id" : "obj-32", 79 | "maxclass" : "button", 80 | "numinlets" : 1, 81 | "numoutlets" : 1, 82 | "outlettype" : [ "bang" ], 83 | "parameter_enable" : 0, 84 | "patching_rect" : [ 202.0, 123.0, 24.0, 24.0 ] 85 | } 86 | 87 | } 88 | , { 89 | "box" : { 90 | "id" : "obj-30", 91 | "maxclass" : "button", 92 | "numinlets" : 1, 93 | "numoutlets" : 1, 94 | "outlettype" : [ "bang" ], 95 | "parameter_enable" : 0, 96 | "patching_rect" : [ 158.0, 123.0, 24.0, 24.0 ] 97 | } 98 | 99 | } 100 | , { 101 | "box" : { 102 | "id" : "obj-15", 103 | "maxclass" : "message", 104 | "numinlets" : 2, 105 | "numoutlets" : 1, 106 | "outlettype" : [ "" ], 107 | "patching_rect" : [ 230.5, 161.0, 79.0, 22.0 ], 108 | "text" : "history_down" 109 | } 110 | 111 | } 112 | , { 113 | "box" : { 114 | "id" : "obj-12", 115 | "maxclass" : "message", 116 | "numinlets" : 2, 117 | "numoutlets" : 1, 118 | "outlettype" : [ "" ], 119 | "patching_rect" : [ 158.5, 161.0, 64.0, 22.0 ], 120 | "text" : "history_up" 121 | } 122 | 123 | } 124 | , { 125 | "box" : { 126 | "id" : "obj-9", 127 | "maxclass" : "newobj", 128 | "numinlets" : 3, 129 | "numoutlets" : 3, 130 | "outlettype" : [ "bang", "bang", "" ], 131 | "patching_rect" : [ 158.5, 83.0, 57.0, 22.0 ], 132 | "text" : "sel 30 31" 133 | } 134 | 135 | } 136 | , { 137 | "box" : { 138 | "id" : "obj-23", 139 | "maxclass" : "newobj", 140 | "numinlets" : 1, 141 | "numoutlets" : 2, 142 | "outlettype" : [ "select", "clear" ], 143 | "patching_rect" : [ 97.0, 368.0, 76.0, 22.0 ], 144 | "text" : "t select clear" 145 | } 146 | 147 | } 148 | , { 149 | "box" : { 150 | "id" : "obj-22", 151 | "maxclass" : "newobj", 152 | "numinlets" : 2, 153 | "numoutlets" : 2, 154 | "outlettype" : [ "", "" ], 155 | "patching_rect" : [ 26.5, 176.0, 59.0, 22.0 ], 156 | "text" : "route text" 157 | } 158 | 159 | } 160 | , { 161 | "box" : { 162 | "fontname" : "Fira Code", 163 | "fontsize" : 14.0, 164 | "id" : "obj-20", 165 | "keymode" : 1, 166 | "maxclass" : "textedit", 167 | "numinlets" : 1, 168 | "numoutlets" : 4, 169 | "outlettype" : [ "", "int", "", "" ], 170 | "parameter_enable" : 0, 171 | "patching_rect" : [ 26.5, 46.0, 415.0, 25.0 ], 172 | "tabmode" : 0 173 | } 174 | 175 | } 176 | , { 177 | "box" : { 178 | "id" : "obj-19", 179 | "maxclass" : "newobj", 180 | "numinlets" : 1, 181 | "numoutlets" : 1, 182 | "outlettype" : [ "" ], 183 | "patching_rect" : [ 120.0, 443.0, 96.0, 22.0 ], 184 | "text" : "prepend append" 185 | } 186 | 187 | } 188 | , { 189 | "box" : { 190 | "id" : "obj-18", 191 | "maxclass" : "newobj", 192 | "numinlets" : 3, 193 | "numoutlets" : 1, 194 | "outlettype" : [ "int" ], 195 | "patching_rect" : [ 120.0, 414.0, 40.0, 22.0 ], 196 | "text" : "itoa" 197 | } 198 | 199 | } 200 | , { 201 | "box" : { 202 | "id" : "obj-17", 203 | "maxclass" : "newobj", 204 | "numinlets" : 1, 205 | "numoutlets" : 2, 206 | "outlettype" : [ "int", "" ], 207 | "patching_rect" : [ 211.0, 399.0, 38.0, 22.0 ], 208 | "text" : "t 13 s" 209 | } 210 | 211 | } 212 | , { 213 | "box" : { 214 | "id" : "obj-16", 215 | "maxclass" : "message", 216 | "numinlets" : 2, 217 | "numoutlets" : 1, 218 | "outlettype" : [ "" ], 219 | "patching_rect" : [ 468.0, 8.0, 35.0, 22.0 ], 220 | "text" : "clear" 221 | } 222 | 223 | } 224 | , { 225 | "box" : { 226 | "id" : "obj-14", 227 | "maxclass" : "newobj", 228 | "numinlets" : 1, 229 | "numoutlets" : 1, 230 | "outlettype" : [ "" ], 231 | "patching_rect" : [ 225.0, 431.0, 113.0, 22.0 ], 232 | "text" : "sprintf \"append %s\"" 233 | } 234 | 235 | } 236 | , { 237 | "box" : { 238 | "fontname" : "Fira Code", 239 | "fontsize" : 14.0, 240 | "id" : "obj-13", 241 | "keymode" : 1, 242 | "linecount" : 7, 243 | "maxclass" : "textedit", 244 | "numinlets" : 1, 245 | "numoutlets" : 4, 246 | "outlettype" : [ "", "int", "", "" ], 247 | "parameter_enable" : 0, 248 | "patching_rect" : [ 468.0, 46.0, 407.0, 172.0 ], 249 | "tabmode" : 0, 250 | "text" : "> 2 \r > 2019-02-08T19:22:10.966Z \r > 2 \r > 2019-02-08T19:22:18.086Z \r > 0.479426 \r > 1 \r > 1 \r" 251 | } 252 | 253 | } 254 | , { 255 | "box" : { 256 | "id" : "obj-10", 257 | "maxclass" : "newobj", 258 | "numinlets" : 3, 259 | "numoutlets" : 3, 260 | "outlettype" : [ "", "", "" ], 261 | "patching_rect" : [ 225.0, 368.0, 106.0, 22.0 ], 262 | "text" : "route stdout stderr" 263 | } 264 | 265 | } 266 | , { 267 | "box" : { 268 | "id" : "obj-7", 269 | "maxclass" : "newobj", 270 | "numinlets" : 1, 271 | "numoutlets" : 1, 272 | "outlettype" : [ "" ], 273 | "patching_rect" : [ 46.0, 284.0, 81.0, 22.0 ], 274 | "text" : "prepend stdin" 275 | } 276 | 277 | } 278 | , { 279 | "box" : { 280 | "bgmode" : 0, 281 | "border" : 0, 282 | "clickthrough" : 0, 283 | "enablehscroll" : 0, 284 | "enablevscroll" : 0, 285 | "id" : "obj-6", 286 | "lockeddragscroll" : 0, 287 | "maxclass" : "bpatcher", 288 | "name" : "n4m.monitor.maxpat", 289 | "numinlets" : 1, 290 | "numoutlets" : 0, 291 | "offset" : [ 0.0, 0.0 ], 292 | "patching_rect" : [ 475.0, 251.0, 400.0, 220.0 ], 293 | "viewvisibility" : 1 294 | } 295 | 296 | } 297 | , { 298 | "box" : { 299 | "id" : "obj-5", 300 | "maxclass" : "message", 301 | "numinlets" : 2, 302 | "numoutlets" : 1, 303 | "outlettype" : [ "" ], 304 | "patching_rect" : [ 237.0, 291.0, 63.0, 22.0 ], 305 | "text" : "script stop" 306 | } 307 | 308 | } 309 | , { 310 | "box" : { 311 | "id" : "obj-3", 312 | "maxclass" : "message", 313 | "numinlets" : 2, 314 | "numoutlets" : 1, 315 | "outlettype" : [ "" ], 316 | "patching_rect" : [ 157.0, 291.0, 64.0, 22.0 ], 317 | "text" : "script start" 318 | } 319 | 320 | } 321 | , { 322 | "box" : { 323 | "id" : "obj-1", 324 | "maxclass" : "newobj", 325 | "numinlets" : 1, 326 | "numoutlets" : 2, 327 | "outlettype" : [ "", "" ], 328 | "patching_rect" : [ 46.0, 329.0, 196.0, 22.0 ], 329 | "text" : "node.script repl-server.js @watch 1" 330 | } 331 | 332 | } 333 | ], 334 | "lines" : [ { 335 | "patchline" : { 336 | "destination" : [ "obj-10", 0 ], 337 | "order" : 0, 338 | "source" : [ "obj-1", 1 ] 339 | } 340 | 341 | } 342 | , { 343 | "patchline" : { 344 | "destination" : [ "obj-6", 0 ], 345 | "midpoints" : [ 232.5, 354.0, 462.0, 354.0, 462.0, 246.0, 484.5, 246.0 ], 346 | "order" : 1, 347 | "source" : [ "obj-1", 1 ] 348 | } 349 | 350 | } 351 | , { 352 | "patchline" : { 353 | "destination" : [ "obj-17", 0 ], 354 | "order" : 0, 355 | "source" : [ "obj-10", 0 ] 356 | } 357 | 358 | } 359 | , { 360 | "patchline" : { 361 | "destination" : [ "obj-23", 0 ], 362 | "midpoints" : [ 234.5, 393.0, 183.0, 393.0, 183.0, 363.0, 106.5, 363.0 ], 363 | "order" : 1, 364 | "source" : [ "obj-10", 0 ] 365 | } 366 | 367 | } 368 | , { 369 | "patchline" : { 370 | "destination" : [ "obj-33", 0 ], 371 | "midpoints" : [ 278.0, 402.0, 345.0, 402.0, 345.0, 363.0, 367.5, 363.0 ], 372 | "source" : [ "obj-10", 1 ] 373 | } 374 | 375 | } 376 | , { 377 | "patchline" : { 378 | "destination" : [ "obj-1", 0 ], 379 | "midpoints" : [ 168.0, 270.0, 33.0, 270.0, 33.0, 324.0, 55.5, 324.0 ], 380 | "source" : [ "obj-12", 0 ] 381 | } 382 | 383 | } 384 | , { 385 | "patchline" : { 386 | "destination" : [ "obj-13", 0 ], 387 | "midpoints" : [ 234.5, 465.0, 453.0, 465.0, 453.0, 42.0, 477.5, 42.0 ], 388 | "source" : [ "obj-14", 0 ] 389 | } 390 | 391 | } 392 | , { 393 | "patchline" : { 394 | "destination" : [ "obj-1", 0 ], 395 | "midpoints" : [ 240.0, 270.0, 33.0, 270.0, 33.0, 324.0, 55.5, 324.0 ], 396 | "source" : [ "obj-15", 0 ] 397 | } 398 | 399 | } 400 | , { 401 | "patchline" : { 402 | "destination" : [ "obj-13", 0 ], 403 | "source" : [ "obj-16", 0 ] 404 | } 405 | 406 | } 407 | , { 408 | "patchline" : { 409 | "destination" : [ "obj-14", 0 ], 410 | "source" : [ "obj-17", 1 ] 411 | } 412 | 413 | } 414 | , { 415 | "patchline" : { 416 | "destination" : [ "obj-18", 0 ], 417 | "source" : [ "obj-17", 0 ] 418 | } 419 | 420 | } 421 | , { 422 | "patchline" : { 423 | "destination" : [ "obj-19", 0 ], 424 | "source" : [ "obj-18", 0 ] 425 | } 426 | 427 | } 428 | , { 429 | "patchline" : { 430 | "destination" : [ "obj-13", 0 ], 431 | "midpoints" : [ 129.5, 477.0, 453.0, 477.0, 453.0, 42.0, 477.5, 42.0 ], 432 | "source" : [ "obj-19", 0 ] 433 | } 434 | 435 | } 436 | , { 437 | "patchline" : { 438 | "destination" : [ "obj-22", 0 ], 439 | "source" : [ "obj-20", 0 ] 440 | } 441 | 442 | } 443 | , { 444 | "patchline" : { 445 | "destination" : [ "obj-9", 0 ], 446 | "source" : [ "obj-20", 1 ] 447 | } 448 | 449 | } 450 | , { 451 | "patchline" : { 452 | "destination" : [ "obj-7", 0 ], 453 | "midpoints" : [ 36.0, 270.0, 55.5, 270.0 ], 454 | "source" : [ "obj-22", 0 ] 455 | } 456 | 457 | } 458 | , { 459 | "patchline" : { 460 | "destination" : [ "obj-20", 0 ], 461 | "midpoints" : [ 163.5, 393.0, 12.0, 393.0, 12.0, 42.0, 36.0, 42.0 ], 462 | "source" : [ "obj-23", 1 ] 463 | } 464 | 465 | } 466 | , { 467 | "patchline" : { 468 | "destination" : [ "obj-20", 0 ], 469 | "midpoints" : [ 106.5, 393.0, 12.0, 393.0, 12.0, 42.0, 36.0, 42.0 ], 470 | "source" : [ "obj-23", 0 ] 471 | } 472 | 473 | } 474 | , { 475 | "patchline" : { 476 | "destination" : [ "obj-1", 0 ], 477 | "source" : [ "obj-3", 0 ] 478 | } 479 | 480 | } 481 | , { 482 | "patchline" : { 483 | "destination" : [ "obj-12", 0 ], 484 | "source" : [ "obj-30", 0 ] 485 | } 486 | 487 | } 488 | , { 489 | "patchline" : { 490 | "destination" : [ "obj-15", 0 ], 491 | "source" : [ "obj-32", 0 ] 492 | } 493 | 494 | } 495 | , { 496 | "patchline" : { 497 | "destination" : [ "obj-34", 0 ], 498 | "source" : [ "obj-33", 0 ] 499 | } 500 | 501 | } 502 | , { 503 | "patchline" : { 504 | "destination" : [ "obj-35", 0 ], 505 | "source" : [ "obj-34", 0 ] 506 | } 507 | 508 | } 509 | , { 510 | "patchline" : { 511 | "destination" : [ "obj-20", 0 ], 512 | "midpoints" : [ 367.5, 444.0, 345.0, 444.0, 345.0, 210.0, 12.0, 210.0, 12.0, 42.0, 36.0, 42.0 ], 513 | "source" : [ "obj-35", 0 ] 514 | } 515 | 516 | } 517 | , { 518 | "patchline" : { 519 | "destination" : [ "obj-1", 0 ], 520 | "source" : [ "obj-5", 0 ] 521 | } 522 | 523 | } 524 | , { 525 | "patchline" : { 526 | "destination" : [ "obj-1", 0 ], 527 | "source" : [ "obj-7", 0 ] 528 | } 529 | 530 | } 531 | , { 532 | "patchline" : { 533 | "destination" : [ "obj-30", 0 ], 534 | "source" : [ "obj-9", 0 ] 535 | } 536 | 537 | } 538 | , { 539 | "patchline" : { 540 | "destination" : [ "obj-32", 0 ], 541 | "source" : [ "obj-9", 1 ] 542 | } 543 | 544 | } 545 | ], 546 | "dependency_cache" : [ { 547 | "name" : "n4m.monitor.maxpat", 548 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 549 | "type" : "JSON", 550 | "implicit" : 1 551 | } 552 | , { 553 | "name" : "resize_n4m_monitor_patcher.js", 554 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 555 | "type" : "TEXT", 556 | "implicit" : 1 557 | } 558 | , { 559 | "name" : "fit_jweb_to_bounds.js", 560 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 561 | "type" : "TEXT", 562 | "implicit" : 1 563 | } 564 | ], 565 | "autosave" : 0 566 | } 567 | 568 | } 569 | -------------------------------------------------------------------------------- /7-live-coded-patcher-scripting/filter-frequencies.maxpat: -------------------------------------------------------------------------------- 1 | {"patcher":{"fileversion":1,"appversion":{"major":7,"minor":3,"revision":4,"architecture":"x86","modernui":1},"rect":[20.0,20.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":"","boxes":[{"maxclass":"newobj","style":"","text":"mc.phasor~ 2","numinlets":2,"numoutlets":1,"id":"obj_1","patching_rect":[20,20,50,22]},{"maxclass":"newobj","style":"","text":"mc.*~ 2000","numinlets":2,"numoutlets":1,"id":"obj_2","patching_rect":[20,60,50,22]},{"maxclass":"newobj","style":"","text":"mc.+~ 200.","numinlets":2,"numoutlets":1,"id":"obj_3","patching_rect":[20,100,50,22]},{"maxclass":"newobj","style":"","text":"outlet ","numinlets":2,"numoutlets":0,"id":"obj_4","patching_rect":[20,140,50,22]}],"lines":[{"patchline":{"destination":["obj_4",0],"source":["obj_3",0]}},{"patchline":{"destination":["obj_3",0],"source":["obj_2",0]}},{"patchline":{"destination":["obj_2",0],"source":["obj_1",0]}}],"dependency_cache":[null],"autosave":0}} 2 | -------------------------------------------------------------------------------- /7-live-coded-patcher-scripting/filter-resonances.maxpat: -------------------------------------------------------------------------------- 1 | {"patcher":{"fileversion":1,"appversion":{"major":7,"minor":3,"revision":4,"architecture":"x86","modernui":1},"rect":[20.0,20.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":"","boxes":[{"maxclass":"newobj","style":"","text":"mc.cycle~ 5.","numinlets":2,"numoutlets":1,"id":"obj_1","patching_rect":[20,20,50,22]},{"maxclass":"newobj","style":"","text":"mc.*~ 0.45","numinlets":2,"numoutlets":1,"id":"obj_2","patching_rect":[20,60,50,22]},{"maxclass":"newobj","style":"","text":"mc.+~ 0.5","numinlets":2,"numoutlets":1,"id":"obj_3","patching_rect":[20,100,50,22]},{"maxclass":"newobj","style":"","text":"outlet ","numinlets":2,"numoutlets":0,"id":"obj_4","patching_rect":[20,140,50,22]}],"lines":[{"patchline":{"destination":["obj_4",0],"source":["obj_3",0]}},{"patchline":{"destination":["obj_3",0],"source":["obj_2",0]}},{"patchline":{"destination":["obj_2",0],"source":["obj_1",0]}}],"dependency_cache":[null],"autosave":0}} 2 | -------------------------------------------------------------------------------- /7-live-coded-patcher-scripting/live-coded-patcher-scripting.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 8, 6 | "minor" : 0, 7 | "revision" : 3, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "classnamespace" : "box", 13 | "rect" : [ 984.0, 92.0, 1030.0, 687.0 ], 14 | "bglocked" : 0, 15 | "openinpresentation" : 0, 16 | "default_fontsize" : 12.0, 17 | "default_fontface" : 0, 18 | "default_fontname" : "Arial", 19 | "gridonopen" : 1, 20 | "gridsize" : [ 15.0, 15.0 ], 21 | "gridsnaponopen" : 1, 22 | "objectsnaponopen" : 1, 23 | "statusbarvisible" : 2, 24 | "toolbarvisible" : 1, 25 | "lefttoolbarpinned" : 0, 26 | "toptoolbarpinned" : 0, 27 | "righttoolbarpinned" : 0, 28 | "bottomtoolbarpinned" : 0, 29 | "toolbars_unpinned_last_save" : 0, 30 | "tallnewobj" : 0, 31 | "boxanimatetime" : 200, 32 | "enablehscroll" : 1, 33 | "enablevscroll" : 1, 34 | "devicewidth" : 0.0, 35 | "description" : "", 36 | "digest" : "", 37 | "tags" : "", 38 | "style" : "", 39 | "subpatcher_template" : "", 40 | "boxes" : [ { 41 | "box" : { 42 | "id" : "obj-27", 43 | "maxclass" : "message", 44 | "numinlets" : 2, 45 | "numoutlets" : 1, 46 | "outlettype" : [ "" ], 47 | "patching_rect" : [ 338.0, 476.0, 94.0, 22.0 ], 48 | "text" : "deviate 100 400" 49 | } 50 | 51 | } 52 | , { 53 | "box" : { 54 | "bgmode" : 0, 55 | "border" : 1, 56 | "clickthrough" : 0, 57 | "enablehscroll" : 0, 58 | "enablevscroll" : 0, 59 | "id" : "obj-11", 60 | "lockeddragscroll" : 0, 61 | "maxclass" : "bpatcher", 62 | "name" : "filter-resonances.maxpat", 63 | "numinlets" : 0, 64 | "numoutlets" : 1, 65 | "offset" : [ 0.0, 0.0 ], 66 | "outlettype" : [ "multichannelsignal" ], 67 | "patching_rect" : [ 761.0, 527.0, 259.0, 163.0 ], 68 | "varname" : "filter-resonances", 69 | "viewvisibility" : 1 70 | } 71 | 72 | } 73 | , { 74 | "box" : { 75 | "bgmode" : 0, 76 | "border" : 1, 77 | "clickthrough" : 0, 78 | "enablehscroll" : 0, 79 | "enablevscroll" : 0, 80 | "id" : "obj-4", 81 | "lockeddragscroll" : 0, 82 | "maxclass" : "bpatcher", 83 | "name" : "filter-frequencies.maxpat", 84 | "numinlets" : 0, 85 | "numoutlets" : 1, 86 | "offset" : [ 0.0, 0.0 ], 87 | "outlettype" : [ "multichannelsignal" ], 88 | "patching_rect" : [ 640.0, 483.0, 259.0, 163.0 ], 89 | "varname" : "filter_frequencies", 90 | "viewvisibility" : 1 91 | } 92 | 93 | } 94 | , { 95 | "box" : { 96 | "id" : "obj-37", 97 | "maxclass" : "mc.ezdac~", 98 | "numinlets" : 1, 99 | "numoutlets" : 0, 100 | "patching_rect" : [ 427.0, 655.0, 45.0, 45.0 ] 101 | } 102 | 103 | } 104 | , { 105 | "box" : { 106 | "id" : "obj-36", 107 | "maxclass" : "newobj", 108 | "numinlets" : 2, 109 | "numoutlets" : 1, 110 | "outlettype" : [ "multichannelsignal" ], 111 | "patching_rect" : [ 435.0, 607.0, 68.0, 22.0 ], 112 | "text" : "mc.stereo~" 113 | } 114 | 115 | } 116 | , { 117 | "box" : { 118 | "id" : "obj-31", 119 | "maxclass" : "newobj", 120 | "numinlets" : 2, 121 | "numoutlets" : 1, 122 | "outlettype" : [ "multichannelsignal" ], 123 | "patching_rect" : [ 475.0, 562.0, 117.0, 22.0 ], 124 | "text" : "mc.*~ 0. @chans 16" 125 | } 126 | 127 | } 128 | , { 129 | "box" : { 130 | "id" : "obj-29", 131 | "maxclass" : "newobj", 132 | "numinlets" : 3, 133 | "numoutlets" : 1, 134 | "outlettype" : [ "multichannelsignal" ], 135 | "patching_rect" : [ 475.0, 516.0, 61.0, 22.0 ], 136 | "text" : "mc.lores~" 137 | } 138 | 139 | } 140 | , { 141 | "box" : { 142 | "id" : "obj-28", 143 | "maxclass" : "newobj", 144 | "numinlets" : 2, 145 | "numoutlets" : 1, 146 | "outlettype" : [ "multichannelsignal" ], 147 | "patching_rect" : [ 475.0, 476.0, 147.0, 22.0 ], 148 | "text" : "mc.saw~ 100. @chans 16" 149 | } 150 | 151 | } 152 | , { 153 | "box" : { 154 | "id" : "obj-21", 155 | "maxclass" : "newobj", 156 | "numinlets" : 1, 157 | "numoutlets" : 1, 158 | "outlettype" : [ "" ], 159 | "patching_rect" : [ 46.0, 476.0, 71.0, 22.0 ], 160 | "text" : "fromsymbol" 161 | } 162 | 163 | } 164 | , { 165 | "box" : { 166 | "bgmode" : 0, 167 | "border" : 1, 168 | "clickthrough" : 0, 169 | "enablehscroll" : 0, 170 | "enablevscroll" : 0, 171 | "id" : "obj-2", 172 | "lockeddragscroll" : 0, 173 | "maxclass" : "bpatcher", 174 | "name" : "pitches.maxpat", 175 | "numinlets" : 0, 176 | "numoutlets" : 1, 177 | "offset" : [ 0.0, 0.0 ], 178 | "outlettype" : [ "multichannelsignal" ], 179 | "patching_rect" : [ 150.0, 493.0, 259.0, 163.0 ], 180 | "varname" : "pitches", 181 | "viewvisibility" : 1 182 | } 183 | 184 | } 185 | , { 186 | "box" : { 187 | "id" : "obj-8", 188 | "maxclass" : "newobj", 189 | "numinlets" : 1, 190 | "numoutlets" : 2, 191 | "outlettype" : [ "", "" ], 192 | "patching_rect" : [ 46.0, 581.0, 67.0, 22.0 ], 193 | "save" : [ "#N", "thispatcher", ";", "#Q", "end", ";" ], 194 | "text" : "thispatcher" 195 | } 196 | 197 | } 198 | , { 199 | "box" : { 200 | "id" : "obj-35", 201 | "maxclass" : "newobj", 202 | "numinlets" : 1, 203 | "numoutlets" : 1, 204 | "outlettype" : [ "" ], 205 | "patching_rect" : [ 358.0, 421.0, 72.0, 22.0 ], 206 | "text" : "prepend set" 207 | } 208 | 209 | } 210 | , { 211 | "box" : { 212 | "id" : "obj-34", 213 | "maxclass" : "newobj", 214 | "numinlets" : 2, 215 | "numoutlets" : 2, 216 | "outlettype" : [ "", "" ], 217 | "patching_rect" : [ 358.0, 395.0, 75.0, 22.0 ], 218 | "text" : "route history" 219 | } 220 | 221 | } 222 | , { 223 | "box" : { 224 | "id" : "obj-33", 225 | "maxclass" : "newobj", 226 | "numinlets" : 1, 227 | "numoutlets" : 1, 228 | "outlettype" : [ "" ], 229 | "patching_rect" : [ 358.0, 368.0, 71.0, 22.0 ], 230 | "text" : "fromsymbol" 231 | } 232 | 233 | } 234 | , { 235 | "box" : { 236 | "id" : "obj-32", 237 | "maxclass" : "button", 238 | "numinlets" : 1, 239 | "numoutlets" : 1, 240 | "outlettype" : [ "bang" ], 241 | "parameter_enable" : 0, 242 | "patching_rect" : [ 202.0, 123.0, 24.0, 24.0 ] 243 | } 244 | 245 | } 246 | , { 247 | "box" : { 248 | "id" : "obj-30", 249 | "maxclass" : "button", 250 | "numinlets" : 1, 251 | "numoutlets" : 1, 252 | "outlettype" : [ "bang" ], 253 | "parameter_enable" : 0, 254 | "patching_rect" : [ 158.0, 123.0, 24.0, 24.0 ] 255 | } 256 | 257 | } 258 | , { 259 | "box" : { 260 | "id" : "obj-15", 261 | "maxclass" : "message", 262 | "numinlets" : 2, 263 | "numoutlets" : 1, 264 | "outlettype" : [ "" ], 265 | "patching_rect" : [ 230.5, 161.0, 79.0, 22.0 ], 266 | "text" : "history_down" 267 | } 268 | 269 | } 270 | , { 271 | "box" : { 272 | "id" : "obj-12", 273 | "maxclass" : "message", 274 | "numinlets" : 2, 275 | "numoutlets" : 1, 276 | "outlettype" : [ "" ], 277 | "patching_rect" : [ 158.5, 161.0, 64.0, 22.0 ], 278 | "text" : "history_up" 279 | } 280 | 281 | } 282 | , { 283 | "box" : { 284 | "id" : "obj-9", 285 | "maxclass" : "newobj", 286 | "numinlets" : 3, 287 | "numoutlets" : 3, 288 | "outlettype" : [ "bang", "bang", "" ], 289 | "patching_rect" : [ 158.5, 83.0, 57.0, 22.0 ], 290 | "text" : "sel 30 31" 291 | } 292 | 293 | } 294 | , { 295 | "box" : { 296 | "id" : "obj-23", 297 | "maxclass" : "newobj", 298 | "numinlets" : 1, 299 | "numoutlets" : 2, 300 | "outlettype" : [ "select", "clear" ], 301 | "patching_rect" : [ 97.0, 368.0, 76.0, 22.0 ], 302 | "text" : "t select clear" 303 | } 304 | 305 | } 306 | , { 307 | "box" : { 308 | "id" : "obj-22", 309 | "maxclass" : "newobj", 310 | "numinlets" : 2, 311 | "numoutlets" : 2, 312 | "outlettype" : [ "", "" ], 313 | "patching_rect" : [ 26.5, 176.0, 59.0, 22.0 ], 314 | "text" : "route text" 315 | } 316 | 317 | } 318 | , { 319 | "box" : { 320 | "fontname" : "Fira Code", 321 | "fontsize" : 14.0, 322 | "id" : "obj-20", 323 | "keymode" : 1, 324 | "maxclass" : "textedit", 325 | "numinlets" : 1, 326 | "numoutlets" : 4, 327 | "outlettype" : [ "", "int", "", "" ], 328 | "parameter_enable" : 0, 329 | "patching_rect" : [ 26.5, 46.0, 415.0, 25.0 ], 330 | "tabmode" : 0 331 | } 332 | 333 | } 334 | , { 335 | "box" : { 336 | "id" : "obj-19", 337 | "maxclass" : "newobj", 338 | "numinlets" : 1, 339 | "numoutlets" : 1, 340 | "outlettype" : [ "" ], 341 | "patching_rect" : [ 120.0, 443.0, 96.0, 22.0 ], 342 | "text" : "prepend append" 343 | } 344 | 345 | } 346 | , { 347 | "box" : { 348 | "id" : "obj-18", 349 | "maxclass" : "newobj", 350 | "numinlets" : 3, 351 | "numoutlets" : 1, 352 | "outlettype" : [ "int" ], 353 | "patching_rect" : [ 120.0, 414.0, 40.0, 22.0 ], 354 | "text" : "itoa" 355 | } 356 | 357 | } 358 | , { 359 | "box" : { 360 | "id" : "obj-17", 361 | "maxclass" : "newobj", 362 | "numinlets" : 1, 363 | "numoutlets" : 2, 364 | "outlettype" : [ "int", "" ], 365 | "patching_rect" : [ 211.0, 399.0, 38.0, 22.0 ], 366 | "text" : "t 13 s" 367 | } 368 | 369 | } 370 | , { 371 | "box" : { 372 | "id" : "obj-16", 373 | "maxclass" : "message", 374 | "numinlets" : 2, 375 | "numoutlets" : 1, 376 | "outlettype" : [ "" ], 377 | "patching_rect" : [ 468.0, 8.0, 35.0, 22.0 ], 378 | "text" : "clear" 379 | } 380 | 381 | } 382 | , { 383 | "box" : { 384 | "id" : "obj-14", 385 | "maxclass" : "newobj", 386 | "numinlets" : 1, 387 | "numoutlets" : 1, 388 | "outlettype" : [ "" ], 389 | "patching_rect" : [ 225.0, 431.0, 113.0, 22.0 ], 390 | "text" : "sprintf \"append %s\"" 391 | } 392 | 393 | } 394 | , { 395 | "box" : { 396 | "fontname" : "Fira Code", 397 | "fontsize" : 14.0, 398 | "id" : "obj-13", 399 | "keymode" : 1, 400 | "linecount" : 6, 401 | "maxclass" : "textedit", 402 | "numinlets" : 1, 403 | "numoutlets" : 4, 404 | "outlettype" : [ "", "int", "", "" ], 405 | "parameter_enable" : 0, 406 | "patching_rect" : [ 468.0, 46.0, 407.0, 172.0 ], 407 | "tabmode" : 0, 408 | "text" : "> reloaded pitches \r > reloaded pitches \r > reloaded bang \r > reloaded pitches \r > reloaded bang \r > reloaded pitches \r" 409 | } 410 | 411 | } 412 | , { 413 | "box" : { 414 | "id" : "obj-10", 415 | "maxclass" : "newobj", 416 | "numinlets" : 3, 417 | "numoutlets" : 3, 418 | "outlettype" : [ "", "", "" ], 419 | "patching_rect" : [ 225.0, 368.0, 106.0, 22.0 ], 420 | "text" : "route stdout stderr" 421 | } 422 | 423 | } 424 | , { 425 | "box" : { 426 | "id" : "obj-7", 427 | "maxclass" : "newobj", 428 | "numinlets" : 1, 429 | "numoutlets" : 1, 430 | "outlettype" : [ "" ], 431 | "patching_rect" : [ 46.0, 284.0, 81.0, 22.0 ], 432 | "text" : "prepend stdin" 433 | } 434 | 435 | } 436 | , { 437 | "box" : { 438 | "bgmode" : 0, 439 | "border" : 0, 440 | "clickthrough" : 0, 441 | "enablehscroll" : 0, 442 | "enablevscroll" : 0, 443 | "id" : "obj-6", 444 | "lockeddragscroll" : 0, 445 | "maxclass" : "bpatcher", 446 | "name" : "n4m.monitor.maxpat", 447 | "numinlets" : 1, 448 | "numoutlets" : 0, 449 | "offset" : [ 0.0, 0.0 ], 450 | "patching_rect" : [ 475.0, 251.0, 400.0, 220.0 ], 451 | "viewvisibility" : 1 452 | } 453 | 454 | } 455 | , { 456 | "box" : { 457 | "id" : "obj-5", 458 | "maxclass" : "message", 459 | "numinlets" : 2, 460 | "numoutlets" : 1, 461 | "outlettype" : [ "" ], 462 | "patching_rect" : [ 237.0, 291.0, 63.0, 22.0 ], 463 | "text" : "script stop" 464 | } 465 | 466 | } 467 | , { 468 | "box" : { 469 | "id" : "obj-3", 470 | "maxclass" : "message", 471 | "numinlets" : 2, 472 | "numoutlets" : 1, 473 | "outlettype" : [ "" ], 474 | "patching_rect" : [ 157.0, 291.0, 64.0, 22.0 ], 475 | "text" : "script start" 476 | } 477 | 478 | } 479 | , { 480 | "box" : { 481 | "id" : "obj-1", 482 | "maxclass" : "newobj", 483 | "numinlets" : 1, 484 | "numoutlets" : 2, 485 | "outlettype" : [ "", "" ], 486 | "patching_rect" : [ 46.0, 329.0, 196.0, 22.0 ], 487 | "saved_object_attributes" : { 488 | "autostart" : 0, 489 | "defer" : 0, 490 | "node" : "", 491 | "npm" : "", 492 | "watch" : 1 493 | } 494 | , 495 | "text" : "node.script repl-server.js @watch 1" 496 | } 497 | 498 | } 499 | ], 500 | "lines" : [ { 501 | "patchline" : { 502 | "destination" : [ "obj-10", 0 ], 503 | "order" : 0, 504 | "source" : [ "obj-1", 1 ] 505 | } 506 | 507 | } 508 | , { 509 | "patchline" : { 510 | "destination" : [ "obj-21", 0 ], 511 | "source" : [ "obj-1", 0 ] 512 | } 513 | 514 | } 515 | , { 516 | "patchline" : { 517 | "destination" : [ "obj-6", 0 ], 518 | "midpoints" : [ 232.5, 354.0, 462.0, 354.0, 462.0, 246.0, 484.5, 246.0 ], 519 | "order" : 1, 520 | "source" : [ "obj-1", 1 ] 521 | } 522 | 523 | } 524 | , { 525 | "patchline" : { 526 | "destination" : [ "obj-17", 0 ], 527 | "order" : 0, 528 | "source" : [ "obj-10", 0 ] 529 | } 530 | 531 | } 532 | , { 533 | "patchline" : { 534 | "destination" : [ "obj-23", 0 ], 535 | "midpoints" : [ 234.5, 393.0, 183.0, 393.0, 183.0, 363.0, 106.5, 363.0 ], 536 | "order" : 1, 537 | "source" : [ "obj-10", 0 ] 538 | } 539 | 540 | } 541 | , { 542 | "patchline" : { 543 | "destination" : [ "obj-33", 0 ], 544 | "midpoints" : [ 278.0, 402.0, 345.0, 402.0, 345.0, 363.0, 367.5, 363.0 ], 545 | "source" : [ "obj-10", 1 ] 546 | } 547 | 548 | } 549 | , { 550 | "patchline" : { 551 | "destination" : [ "obj-31", 1 ], 552 | "source" : [ "obj-11", 0 ] 553 | } 554 | 555 | } 556 | , { 557 | "patchline" : { 558 | "destination" : [ "obj-1", 0 ], 559 | "midpoints" : [ 168.0, 270.0, 33.0, 270.0, 33.0, 324.0, 55.5, 324.0 ], 560 | "source" : [ "obj-12", 0 ] 561 | } 562 | 563 | } 564 | , { 565 | "patchline" : { 566 | "destination" : [ "obj-13", 0 ], 567 | "midpoints" : [ 234.5, 465.0, 453.0, 465.0, 453.0, 42.0, 477.5, 42.0 ], 568 | "source" : [ "obj-14", 0 ] 569 | } 570 | 571 | } 572 | , { 573 | "patchline" : { 574 | "destination" : [ "obj-1", 0 ], 575 | "midpoints" : [ 240.0, 270.0, 33.0, 270.0, 33.0, 324.0, 55.5, 324.0 ], 576 | "source" : [ "obj-15", 0 ] 577 | } 578 | 579 | } 580 | , { 581 | "patchline" : { 582 | "destination" : [ "obj-13", 0 ], 583 | "source" : [ "obj-16", 0 ] 584 | } 585 | 586 | } 587 | , { 588 | "patchline" : { 589 | "destination" : [ "obj-14", 0 ], 590 | "source" : [ "obj-17", 1 ] 591 | } 592 | 593 | } 594 | , { 595 | "patchline" : { 596 | "destination" : [ "obj-18", 0 ], 597 | "source" : [ "obj-17", 0 ] 598 | } 599 | 600 | } 601 | , { 602 | "patchline" : { 603 | "destination" : [ "obj-19", 0 ], 604 | "source" : [ "obj-18", 0 ] 605 | } 606 | 607 | } 608 | , { 609 | "patchline" : { 610 | "destination" : [ "obj-13", 0 ], 611 | "midpoints" : [ 129.5, 477.0, 453.0, 477.0, 453.0, 42.0, 477.5, 42.0 ], 612 | "source" : [ "obj-19", 0 ] 613 | } 614 | 615 | } 616 | , { 617 | "patchline" : { 618 | "destination" : [ "obj-29", 2 ], 619 | "source" : [ "obj-2", 0 ] 620 | } 621 | 622 | } 623 | , { 624 | "patchline" : { 625 | "destination" : [ "obj-22", 0 ], 626 | "source" : [ "obj-20", 0 ] 627 | } 628 | 629 | } 630 | , { 631 | "patchline" : { 632 | "destination" : [ "obj-9", 0 ], 633 | "source" : [ "obj-20", 1 ] 634 | } 635 | 636 | } 637 | , { 638 | "patchline" : { 639 | "destination" : [ "obj-8", 0 ], 640 | "source" : [ "obj-21", 0 ] 641 | } 642 | 643 | } 644 | , { 645 | "patchline" : { 646 | "destination" : [ "obj-7", 0 ], 647 | "midpoints" : [ 36.0, 270.0, 55.5, 270.0 ], 648 | "source" : [ "obj-22", 0 ] 649 | } 650 | 651 | } 652 | , { 653 | "patchline" : { 654 | "destination" : [ "obj-20", 0 ], 655 | "midpoints" : [ 163.5, 393.0, 12.0, 393.0, 12.0, 42.0, 36.0, 42.0 ], 656 | "source" : [ "obj-23", 1 ] 657 | } 658 | 659 | } 660 | , { 661 | "patchline" : { 662 | "destination" : [ "obj-20", 0 ], 663 | "midpoints" : [ 106.5, 393.0, 12.0, 393.0, 12.0, 42.0, 36.0, 42.0 ], 664 | "source" : [ "obj-23", 0 ] 665 | } 666 | 667 | } 668 | , { 669 | "patchline" : { 670 | "destination" : [ "obj-28", 0 ], 671 | "source" : [ "obj-27", 0 ] 672 | } 673 | 674 | } 675 | , { 676 | "patchline" : { 677 | "destination" : [ "obj-29", 0 ], 678 | "source" : [ "obj-28", 0 ] 679 | } 680 | 681 | } 682 | , { 683 | "patchline" : { 684 | "destination" : [ "obj-31", 0 ], 685 | "source" : [ "obj-29", 0 ] 686 | } 687 | 688 | } 689 | , { 690 | "patchline" : { 691 | "destination" : [ "obj-1", 0 ], 692 | "source" : [ "obj-3", 0 ] 693 | } 694 | 695 | } 696 | , { 697 | "patchline" : { 698 | "destination" : [ "obj-12", 0 ], 699 | "source" : [ "obj-30", 0 ] 700 | } 701 | 702 | } 703 | , { 704 | "patchline" : { 705 | "destination" : [ "obj-36", 0 ], 706 | "source" : [ "obj-31", 0 ] 707 | } 708 | 709 | } 710 | , { 711 | "patchline" : { 712 | "destination" : [ "obj-15", 0 ], 713 | "source" : [ "obj-32", 0 ] 714 | } 715 | 716 | } 717 | , { 718 | "patchline" : { 719 | "destination" : [ "obj-34", 0 ], 720 | "source" : [ "obj-33", 0 ] 721 | } 722 | 723 | } 724 | , { 725 | "patchline" : { 726 | "destination" : [ "obj-35", 0 ], 727 | "source" : [ "obj-34", 0 ] 728 | } 729 | 730 | } 731 | , { 732 | "patchline" : { 733 | "destination" : [ "obj-20", 0 ], 734 | "midpoints" : [ 367.5, 444.0, 345.0, 444.0, 345.0, 210.0, 12.0, 210.0, 12.0, 42.0, 36.0, 42.0 ], 735 | "source" : [ "obj-35", 0 ] 736 | } 737 | 738 | } 739 | , { 740 | "patchline" : { 741 | "destination" : [ "obj-29", 1 ], 742 | "source" : [ "obj-4", 0 ] 743 | } 744 | 745 | } 746 | , { 747 | "patchline" : { 748 | "destination" : [ "obj-1", 0 ], 749 | "source" : [ "obj-5", 0 ] 750 | } 751 | 752 | } 753 | , { 754 | "patchline" : { 755 | "destination" : [ "obj-1", 0 ], 756 | "source" : [ "obj-7", 0 ] 757 | } 758 | 759 | } 760 | , { 761 | "patchline" : { 762 | "destination" : [ "obj-30", 0 ], 763 | "source" : [ "obj-9", 0 ] 764 | } 765 | 766 | } 767 | , { 768 | "patchline" : { 769 | "destination" : [ "obj-32", 0 ], 770 | "source" : [ "obj-9", 1 ] 771 | } 772 | 773 | } 774 | ], 775 | "dependency_cache" : [ { 776 | "name" : "repl-server.js", 777 | "bootpath" : "~/Documents/_CODE/art_music/znibbles/znibbles-github/10-node-in-max/7-live-coded-patcher-scripting", 778 | "patcherrelativepath" : ".", 779 | "type" : "TEXT", 780 | "implicit" : 1 781 | } 782 | , { 783 | "name" : "n4m.monitor.maxpat", 784 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 785 | "type" : "JSON", 786 | "implicit" : 1 787 | } 788 | , { 789 | "name" : "resize_n4m_monitor_patcher.js", 790 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 791 | "type" : "TEXT", 792 | "implicit" : 1 793 | } 794 | , { 795 | "name" : "fit_jweb_to_bounds.js", 796 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 797 | "type" : "TEXT", 798 | "implicit" : 1 799 | } 800 | , { 801 | "name" : "pitches.maxpat", 802 | "bootpath" : "~/Documents/_CODE/art_music/znibbles/znibbles-github/10-node-in-max/7-live-coded-patcher-scripting", 803 | "patcherrelativepath" : ".", 804 | "type" : "JSON", 805 | "implicit" : 1 806 | } 807 | , { 808 | "name" : "filter-frequencies.maxpat", 809 | "bootpath" : "~/Documents/_CODE/art_music/znibbles/znibbles-github/10-node-in-max/7-live-coded-patcher-scripting", 810 | "patcherrelativepath" : ".", 811 | "type" : "JSON", 812 | "implicit" : 1 813 | } 814 | , { 815 | "name" : "filter-resonances.maxpat", 816 | "bootpath" : "~/Documents/_CODE/art_music/znibbles/znibbles-github/10-node-in-max/7-live-coded-patcher-scripting", 817 | "patcherrelativepath" : ".", 818 | "type" : "JSON", 819 | "implicit" : 1 820 | } 821 | ], 822 | "autosave" : 0 823 | } 824 | 825 | } 826 | -------------------------------------------------------------------------------- /7-live-coded-patcher-scripting/pitches.maxpat: -------------------------------------------------------------------------------- 1 | {"patcher":{"fileversion":1,"appversion":{"major":7,"minor":3,"revision":4,"architecture":"x86","modernui":1},"rect":[20.0,20.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":"","boxes":[{"maxclass":"newobj","style":"","text":"mc.rand~ 10.","numinlets":2,"numoutlets":1,"id":"obj_1","patching_rect":[20,20,50,22]},{"maxclass":"newobj","style":"","text":"mc.*~ 0.5","numinlets":2,"numoutlets":1,"id":"obj_2","patching_rect":[20,60,50,22]},{"maxclass":"newobj","style":"","text":"mc.+~ 0.5","numinlets":2,"numoutlets":1,"id":"obj_3","patching_rect":[20,100,50,22]},{"maxclass":"newobj","style":"","text":"outlet ","numinlets":2,"numoutlets":0,"id":"obj_4","patching_rect":[20,140,50,22]}],"lines":[{"patchline":{"destination":["obj_4",0],"source":["obj_3",0]}},{"patchline":{"destination":["obj_3",0],"source":["obj_2",0]}},{"patchline":{"destination":["obj_2",0],"source":["obj_1",0]}}],"dependency_cache":[null],"autosave":0}} 2 | -------------------------------------------------------------------------------- /7-live-coded-patcher-scripting/repl-server.js: -------------------------------------------------------------------------------- 1 | const readline = require('readline'); 2 | const path = require('path'); 3 | const childProcess = require('child_process'); 4 | const Max = require('max-api'); 5 | const util = require('util'); 6 | const fs = require('fs'); 7 | 8 | const exec = util.promisify(childProcess.exec); 9 | const writeFile = util.promisify(fs.writeFile); 10 | 11 | const myHistory = []; 12 | let historyPos = -1; 13 | 14 | const rl = readline.createInterface({ 15 | input: process.stdin, 16 | output: process.stdout, 17 | }); 18 | 19 | const makePatcher = async (patcherName, patch) => { 20 | const output = await exec(`maxy-gen g '${patch}-outlet'`); 21 | return await writeFile(`${patcherName}.maxpat`, output.stdout); 22 | }; 23 | 24 | rl.prompt(); 25 | 26 | rl.on('line', (line) => { 27 | myHistory.unshift(line); 28 | 29 | try { 30 | [patcherName, patch] = line.trim().split(' '); 31 | Max.post(patch); 32 | process.env["PATH"] += `${path.delimiter}/Users/jrubisch/.rbenv/shims`; 33 | makePatcher(patcherName, patch).then(() => { Max.outlet(`script sendbox ${patcherName} replace ${patcherName}.maxpat`); }); 34 | console.log(`reloaded ${patcherName}`); 35 | 36 | } catch(e) { 37 | console.log(e); 38 | } 39 | 40 | historyPos = -1; 41 | rl.prompt(); 42 | }).on('close', () => { 43 | console.log('Have a great day!'); 44 | process.exit(0); 45 | }); 46 | 47 | Max.addHandler('history_up', () => { 48 | historyPos = historyPos < myHistory.length - 1 ? historyPos + 1 : myHistory.length - 1; 49 | console.error(`history ${myHistory[historyPos]}`); 50 | }); 51 | 52 | Max.addHandler('history_down', () => { 53 | historyPos = historyPos > 0 ? historyPos - 1 : 0; 54 | console.error(`history ${myHistory[historyPos]}`); 55 | }); 56 | -------------------------------------------------------------------------------- /8-express-js-sound-player/data/sound1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znibbles/10-node-in-max/2165b32dc6c5254b55b5ec3756059e6be8e8943d/8-express-js-sound-player/data/sound1.wav -------------------------------------------------------------------------------- /8-express-js-sound-player/data/sound2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znibbles/10-node-in-max/2165b32dc6c5254b55b5ec3756059e6be8e8943d/8-express-js-sound-player/data/sound2.wav -------------------------------------------------------------------------------- /8-express-js-sound-player/data/sound3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znibbles/10-node-in-max/2165b32dc6c5254b55b5ec3756059e6be8e8943d/8-express-js-sound-player/data/sound3.wav -------------------------------------------------------------------------------- /8-express-js-sound-player/data/sounds.csv: -------------------------------------------------------------------------------- 1 | filename 2 | sound1.wav 3 | sound2.wav 4 | sound3.wav 5 | -------------------------------------------------------------------------------- /8-express-js-sound-player/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "8-express-js-sound-player", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.7", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 10 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 11 | "requires": { 12 | "mime-types": "~2.1.24", 13 | "negotiator": "0.6.2" 14 | } 15 | }, 16 | "array-flatten": { 17 | "version": "1.1.1", 18 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 19 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 20 | }, 21 | "body-parser": { 22 | "version": "1.19.0", 23 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 24 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 25 | "requires": { 26 | "bytes": "3.1.0", 27 | "content-type": "~1.0.4", 28 | "debug": "2.6.9", 29 | "depd": "~1.1.2", 30 | "http-errors": "1.7.2", 31 | "iconv-lite": "0.4.24", 32 | "on-finished": "~2.3.0", 33 | "qs": "6.7.0", 34 | "raw-body": "2.4.0", 35 | "type-is": "~1.6.17" 36 | } 37 | }, 38 | "bytes": { 39 | "version": "3.1.0", 40 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 41 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 42 | }, 43 | "content-disposition": { 44 | "version": "0.5.3", 45 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 46 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 47 | "requires": { 48 | "safe-buffer": "5.1.2" 49 | } 50 | }, 51 | "content-type": { 52 | "version": "1.0.4", 53 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 54 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 55 | }, 56 | "cookie": { 57 | "version": "0.4.0", 58 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 59 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 60 | }, 61 | "cookie-signature": { 62 | "version": "1.0.6", 63 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 64 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 65 | }, 66 | "csv-parse": { 67 | "version": "4.8.3", 68 | "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.8.3.tgz", 69 | "integrity": "sha512-0GPxubzYzSn08lhNTWDCkcDKn8krmw0WuscqB2RrW6sugGGskbwaaEz7PCFFwbQ0phNGTTieiyfzzu3S/jZZ7Q==" 70 | }, 71 | "debug": { 72 | "version": "2.6.9", 73 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 74 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 75 | "requires": { 76 | "ms": "2.0.0" 77 | } 78 | }, 79 | "depd": { 80 | "version": "1.1.2", 81 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 82 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 83 | }, 84 | "destroy": { 85 | "version": "1.0.4", 86 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 87 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 88 | }, 89 | "ee-first": { 90 | "version": "1.1.1", 91 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 92 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 93 | }, 94 | "encodeurl": { 95 | "version": "1.0.2", 96 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 97 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 98 | }, 99 | "escape-html": { 100 | "version": "1.0.3", 101 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 102 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 103 | }, 104 | "etag": { 105 | "version": "1.8.1", 106 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 107 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 108 | }, 109 | "express": { 110 | "version": "4.17.1", 111 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 112 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 113 | "requires": { 114 | "accepts": "~1.3.7", 115 | "array-flatten": "1.1.1", 116 | "body-parser": "1.19.0", 117 | "content-disposition": "0.5.3", 118 | "content-type": "~1.0.4", 119 | "cookie": "0.4.0", 120 | "cookie-signature": "1.0.6", 121 | "debug": "2.6.9", 122 | "depd": "~1.1.2", 123 | "encodeurl": "~1.0.2", 124 | "escape-html": "~1.0.3", 125 | "etag": "~1.8.1", 126 | "finalhandler": "~1.1.2", 127 | "fresh": "0.5.2", 128 | "merge-descriptors": "1.0.1", 129 | "methods": "~1.1.2", 130 | "on-finished": "~2.3.0", 131 | "parseurl": "~1.3.3", 132 | "path-to-regexp": "0.1.7", 133 | "proxy-addr": "~2.0.5", 134 | "qs": "6.7.0", 135 | "range-parser": "~1.2.1", 136 | "safe-buffer": "5.1.2", 137 | "send": "0.17.1", 138 | "serve-static": "1.14.1", 139 | "setprototypeof": "1.1.1", 140 | "statuses": "~1.5.0", 141 | "type-is": "~1.6.18", 142 | "utils-merge": "1.0.1", 143 | "vary": "~1.1.2" 144 | } 145 | }, 146 | "finalhandler": { 147 | "version": "1.1.2", 148 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 149 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 150 | "requires": { 151 | "debug": "2.6.9", 152 | "encodeurl": "~1.0.2", 153 | "escape-html": "~1.0.3", 154 | "on-finished": "~2.3.0", 155 | "parseurl": "~1.3.3", 156 | "statuses": "~1.5.0", 157 | "unpipe": "~1.0.0" 158 | } 159 | }, 160 | "forwarded": { 161 | "version": "0.1.2", 162 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 163 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 164 | }, 165 | "fresh": { 166 | "version": "0.5.2", 167 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 168 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 169 | }, 170 | "http-errors": { 171 | "version": "1.7.2", 172 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 173 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 174 | "requires": { 175 | "depd": "~1.1.2", 176 | "inherits": "2.0.3", 177 | "setprototypeof": "1.1.1", 178 | "statuses": ">= 1.5.0 < 2", 179 | "toidentifier": "1.0.0" 180 | } 181 | }, 182 | "iconv-lite": { 183 | "version": "0.4.24", 184 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 185 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 186 | "requires": { 187 | "safer-buffer": ">= 2.1.2 < 3" 188 | } 189 | }, 190 | "inherits": { 191 | "version": "2.0.3", 192 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 193 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 194 | }, 195 | "ipaddr.js": { 196 | "version": "1.9.0", 197 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", 198 | "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" 199 | }, 200 | "media-typer": { 201 | "version": "0.3.0", 202 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 203 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 204 | }, 205 | "merge-descriptors": { 206 | "version": "1.0.1", 207 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 208 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 209 | }, 210 | "methods": { 211 | "version": "1.1.2", 212 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 213 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 214 | }, 215 | "mime": { 216 | "version": "1.6.0", 217 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 218 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 219 | }, 220 | "mime-db": { 221 | "version": "1.43.0", 222 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", 223 | "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" 224 | }, 225 | "mime-types": { 226 | "version": "2.1.26", 227 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", 228 | "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", 229 | "requires": { 230 | "mime-db": "1.43.0" 231 | } 232 | }, 233 | "ms": { 234 | "version": "2.0.0", 235 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 236 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 237 | }, 238 | "negotiator": { 239 | "version": "0.6.2", 240 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 241 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 242 | }, 243 | "on-finished": { 244 | "version": "2.3.0", 245 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 246 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 247 | "requires": { 248 | "ee-first": "1.1.1" 249 | } 250 | }, 251 | "parseurl": { 252 | "version": "1.3.3", 253 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 254 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 255 | }, 256 | "path-to-regexp": { 257 | "version": "0.1.7", 258 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 259 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 260 | }, 261 | "proxy-addr": { 262 | "version": "2.0.5", 263 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", 264 | "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", 265 | "requires": { 266 | "forwarded": "~0.1.2", 267 | "ipaddr.js": "1.9.0" 268 | } 269 | }, 270 | "qs": { 271 | "version": "6.7.0", 272 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 273 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 274 | }, 275 | "range-parser": { 276 | "version": "1.2.1", 277 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 278 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 279 | }, 280 | "raw-body": { 281 | "version": "2.4.0", 282 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 283 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 284 | "requires": { 285 | "bytes": "3.1.0", 286 | "http-errors": "1.7.2", 287 | "iconv-lite": "0.4.24", 288 | "unpipe": "1.0.0" 289 | } 290 | }, 291 | "safe-buffer": { 292 | "version": "5.1.2", 293 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 294 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 295 | }, 296 | "safer-buffer": { 297 | "version": "2.1.2", 298 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 299 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 300 | }, 301 | "send": { 302 | "version": "0.17.1", 303 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 304 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 305 | "requires": { 306 | "debug": "2.6.9", 307 | "depd": "~1.1.2", 308 | "destroy": "~1.0.4", 309 | "encodeurl": "~1.0.2", 310 | "escape-html": "~1.0.3", 311 | "etag": "~1.8.1", 312 | "fresh": "0.5.2", 313 | "http-errors": "~1.7.2", 314 | "mime": "1.6.0", 315 | "ms": "2.1.1", 316 | "on-finished": "~2.3.0", 317 | "range-parser": "~1.2.1", 318 | "statuses": "~1.5.0" 319 | }, 320 | "dependencies": { 321 | "ms": { 322 | "version": "2.1.1", 323 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 324 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 325 | } 326 | } 327 | }, 328 | "serve-static": { 329 | "version": "1.14.1", 330 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 331 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 332 | "requires": { 333 | "encodeurl": "~1.0.2", 334 | "escape-html": "~1.0.3", 335 | "parseurl": "~1.3.3", 336 | "send": "0.17.1" 337 | } 338 | }, 339 | "setprototypeof": { 340 | "version": "1.1.1", 341 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 342 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 343 | }, 344 | "statuses": { 345 | "version": "1.5.0", 346 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 347 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 348 | }, 349 | "toidentifier": { 350 | "version": "1.0.0", 351 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 352 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 353 | }, 354 | "type-is": { 355 | "version": "1.6.18", 356 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 357 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 358 | "requires": { 359 | "media-typer": "0.3.0", 360 | "mime-types": "~2.1.24" 361 | } 362 | }, 363 | "unpipe": { 364 | "version": "1.0.0", 365 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 366 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 367 | }, 368 | "utils-merge": { 369 | "version": "1.0.1", 370 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 371 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 372 | }, 373 | "vary": { 374 | "version": "1.1.2", 375 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 376 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 377 | } 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /8-express-js-sound-player/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "8-express-js-sound-player", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "csv-parse": "^4.8.3", 14 | "express": "^4.17.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /8-express-js-sound-player/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const fs = require("fs"); 3 | const parse = require("csv-parse"); 4 | const Max = require("max-api"); 5 | 6 | const app = express(); 7 | 8 | app.get("/sound/:id", (req, res) => { 9 | // res.header("Access-Control-Allow-Origin", "*"); 10 | // res.header( 11 | // "Access-Control-Allow-Headers", 12 | // "Origin, X-Requested-With, Content-Type, Accept" 13 | // ); 14 | 15 | const parser = parse({}, (_err, data) => { 16 | if (Max) Max.outlet({ [req.query.track]: data[req.params.id] }); 17 | 18 | res.sendStatus(200); 19 | }); 20 | 21 | fs.createReadStream(__dirname + "/data/sounds.csv").pipe(parser); 22 | }); 23 | 24 | app.listen(5000); 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 10-node-in-max 2 | -------------------------------------------------------------------------------- /transcripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znibbles/10-node-in-max/2165b32dc6c5254b55b5ec3756059e6be8e8943d/transcripts/.keep -------------------------------------------------------------------------------- /transcripts/01-watch-youtube-videos.md: -------------------------------------------------------------------------------- 1 | ## Connecting to the Outside World 2 | 3 | When I first heard that Max is getting a NodeJS integration, I thought to myself, what endless possibilities. I'm not the greatest supporter of the JavaScript language and the environment it lives in, but hey, the premise of re-using other software I had written in Max was just too tempting. 4 | 5 | That said, when it finally came out I was a little stunned not to find a more detailed introduction in how to set up a project with NPM, install modules etc. Maybe I didn't search intensely enough, but at least there wasn't something screaming at me: "TRY THIS". 6 | 7 | So well, while everybody else is freaking out about MC, here's my first take on Node in Max, a little script that allows you to play arbitrary YouTube videos in a `[jit.movie]`. When I asked on Twitter what could be a nice starter project, the answer I got was ["People will go nuts if NodeJS lets you open youtube tutos"](https://twitter.com/bl4ck_br41n_/status/1044741462682980354). So I just did it. 8 | 9 | ## Node Starter Kit 10 | 11 | First things first, there is a chance that many of you have been writing some Javascript code in Max but never had the need to install NodeJS. You can obtain it from the [website](https://nodejs.org/en/download/). I'd recommend installing the long term support (LTS) version, on this machine I'm using `node v8.11.2` and `npm v6.3.0`. You can check your installed versions like so. 12 | 13 | ## Defining the Interface 14 | 15 | Next, we're going to sketch how we'd like our Max patch to work, define how we'd like to interface with NodeJS. We'd like to send an `[open(` message with a URL, and if it's a valid YouTube URL obtain the actual `download_url` of the video, which we can then use to `read` in a `[jit.movie]` object, which supports opening files as well as remote URLs. 16 | 17 | ## Bootstrapping a Node Project 18 | 19 | So how can we go about this? First, let's check [npmjs.com](https://www.npmjs.com/) if there is some node package that supports downloading YouTube videos. The first hit, `ytdl-core`, is already a bullseye. Let us initialize a node project with `npm init` here... You can basically call this project what you like and leave most of the other fields blank. I'd just advise you to call the `main` script, which we'll have to create afterwards, in a sensible and rememberable manner. 20 | 21 | Next we say 22 | 23 | ``` 24 | $ npm install --save ytdl-core 25 | ``` 26 | 27 | This does two things: 28 | 29 | 1. it installs the module into the project's `node_modules` folder. 30 | 2. it writes the dependency info into the newly created `package.json` file so anybody coming to this project later on can simply issue `npm install` and get the appropriate modules installed from the package definition file. 31 | 32 | ## Hooking Up Node in Max 33 | 34 | Now on to the heart of the patch: the node script. We generate a JavaScript file and name it `watch-youtube.js`. Over in Max we load that into a `[node.script]` object which we set to `autostart` and `watch`, causing the Max object to reload the script whenever we make a change. Let's also fire up a `[node.debug]` object here. 35 | 36 | We require in the max Api and add an `open` handler. Let's confirm that this works by writing to a Max `outlet`. So there we get our URL back. 37 | 38 | ## Promises Kept 39 | 40 | Now let's take a step back and think through what is going to happen. The remote API we are calling is in fact returning a JavaScript [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), and this will be the case in almost every web request you make via node. This can be a little bit confusing to everyone who is used to synchronous programming, so let me elaborate. 41 | 42 | Actually, we are dealing with asynchronous code here, and we can mock what is going on under the hood by initializing a `Promise` object and setting the **resolve callback** to be triggered after a certain amount of time, 2000 milliseconds in this case. 43 | 44 | We then call the `then` method of the promise, which actually is the **event handler** of the resolve callback, so to speak. That means, it is going to be called when the promise resolves, be it in our case because the timeout is over, or because the remote server answers our request. To test this, we again just post what we get to a Max `outlet`. 45 | 46 | So back in Max we clean up our message here and click on the `open` message again. One, two, ... bang. Here it is. We now know our code is working asynchronous, promise-driven so to speak. 47 | 48 | ## YouTube Inquiry 49 | 50 | Now let's remove that and continue with what we actually want to accomplish. We ask the `ytdl` library for info on the url we are passing in. This will return a promise. So next we call `then` and say we want to choose a format. That's because YouTube, as you probably know, offers multiple resolutions etc. per video, the empty object I'm passing here merely tells `ytdl` to fetch the default one. Last we send the `format`'s url prefixed by `download_url` to the Max outlet. 51 | 52 | Because this is relying on an Internet connection, one thousand different things could go wrong, so let's prepare for that case with a `catch` handler, too. In this case we're just posting to the Max console that something went wrong. 53 | 54 | ## Playing from the Web 55 | 56 | So let's look at that here. Oh, as it seems I forgot to require the `ytdl` library, which will of course not work. This kind of thing happens when you don't use a linter, but that's another story. 57 | 58 | Back in Max we click on `[open https://youtu.be/foobar(` which logs an error to the console, as expected. Now we replace that with a valid URL and voila, we get a download URL from our `Promise`. So there we got our YouTube video running - with audio of course, which is muted here. Take it and transform it, pipe it into Vizzie or apply it to a GL texture, whatever you like. -------------------------------------------------------------------------------- /transcripts/02-static-d3js.md: -------------------------------------------------------------------------------- 1 | # Static D3JS 2 | 3 | I love [d3js](https://d3js.org/) for data visualizations, I mean, who doesn't? Sometimes, though, it'd be nice to obtain an SVG without having to embed the relevant code in a website, run it in a browser and copy-paste the result. With an npm package called [`d3-node`](https://www.npmjs.com/package/d3-node), this is possible, with a few limitations. 4 | 5 | For example, you cannot have interactive or animated graphics that way, and there are some other constraints pertaining to browser APIs that are absent in the `nodejs` environment, but we'll get to that later. 6 | 7 | We'll wrap this in a `node.script` and use it to render PNG files to be used in a Max patch. 8 | 9 | ## Rendering Static SVG or PNGs 10 | 11 | Let's quickly glance over the `nodejs` source code. In the `package.json` file we specify two dependencies: 12 | 13 | - `d3-node`, responsible for wrapping the `d3` library, and 14 | - `svg2png` for converting the vector format SVG to a rasterized PNG version 15 | 16 | Our `static-d3.js` is really minimalistic, but already provides some extension points, so let's go over it step by step. 17 | 18 | First we read CSS styles from a file in our mounted `data` folder. By default, it should be called `style.css`, but you can change this by calling the `style` handler up here from the Max patch. 19 | 20 | This `styles` text is then passed to the `D3Node` constructor. Next we provide margin, width and height parameters, which we'll use to create an `svg` root node here. We then pass the `d3` object, the created `svg`, and an object containing options (`width`, `height`, and `margin`) to the main graphics generating script, which also has to be specified at runtime as the handler's parameter. 21 | 22 | Last we render a PNG file of the specified dimensions, and send the absolute path as a message to the Max object's outlet. 23 | 24 | ## Differences to Browser-Rendered D3 25 | 26 | Let's look at the D3 rendering script for a moment. I basically copy-pasted this from Mike Bostock's [Grouped Bar Chart](https://bl.ocks.org/mbostock/3887051) example. There is one significant difference though, which shall not be dismissed here. 27 | 28 | The client-side implementation uses `d3.csv`, which employs the browser's `fetch` API to fetch and provide the data in the CSV file. Since `nodejs` has no implementation of that API, we cannot use that here. You could try to polyfill that, but since the polyfills only support absolute URLs, that, too, was not an option. 29 | 30 | But what, in contrast to a browser, you _can_ do is ask the filesystem to simply provide that CSV string, with a call to `readFileSync`, and afterwards `d3.csvParse`. For the rest of the script, please refer to the D3 documentation. 31 | 32 | 33 | ## Demo 34 | 35 | To get started, we first need to call `script npm install` to install the dependencies. It may take some time the first time to do this, because the `svg2png` package compiles some native code. Now it's done installing, our NPM status shows green, so that should have worked. 36 | 37 | Next we send the `script start` message. In the debug tool, we see that the process was indeed started. 38 | 39 | We click on `d3 groupchart.js`, thus specifying the filename of the input script. It takes some time to render the PNG image from the actual SVG output, but here it is, painted to a video plane. I've set the `erase_color` attribute of the scene to white, because the PNG actually contains an alpha channel rendering the background transparent, so if we left the background gray, we wouldn't be able to see that much. 40 | 41 | Anyways, a convenient way to generate beautiful infographics and use them in your installation/performance or whatever on the fly. With a few tweaks, we could even go on and supply some "real time" data. -------------------------------------------------------------------------------- /transcripts/03-socketio-client.md: -------------------------------------------------------------------------------- 1 | ## Socket.io Client 2 | 3 | A neat fact about `node.script` is that it gives us access to a bunch of Web APIs that are easy to implement using `nodejs`. The official Node4Max examples include one about creating and running a WebSocket server; I'm going to show you the opposite, how to make Max behave like a WebSocket client. 4 | 5 | Of course, for testing we will need a server ourselves, so let's start by creating one. We make up a `server` folder and call `npm init` to create a `package.json` file. We then install `express` as our application server and the `socket.io` package, which provides a simple WebSockets implementation. Let's also add an `npm start` script that runs our `index.js`, which we also need to create. 6 | 7 | In it, we start by requiring `express`, the built-in `http` module which we pass the express `app`. We need this to hook up `socket.io`, which we do next. 8 | 9 | To get this running, we need a root route, which we use to just serve an HTML file from our root directory. 10 | 11 | Now I'm going to paste in some HTML rather than typing it all from scratch. It's basically taken from the official [socket.io getting started guide](https://socket.io/get-started/chat/) and modified just a little bit for our purposes. 12 | 13 | There's just a big black container in here, with a little form where we can type text. Let's start our server and take a look. Here it is, pretty straightforward. 14 | 15 | ## Serving Up Sockets 16 | 17 | Next we need to actually create our WebSocket connection. This is done simply by implementing the `connection` event listener of the `io` object. For the moment, let's just spit out a log message when a new client connects. 18 | 19 | Of course, on the client we need to perform the mirrored action. We start by including the `socket.io.js` client script. One of the benefits of using this library in connection with an express server is that it automatically provides the client code at this URL. Then, after the DOM has loaded, we create a socket. That's it. Let's restart our server and see what happens. "a user connected" - hooray. 20 | 21 | ## Implementing a Client in Node4Max 22 | 23 | We call `npm init` in our root directory and install `socket.io-client`. In a file we call equally `socketio-client.js`, we require the `maxApi`, as always, and the socket.io client library. We declare a global `socket` variable, because we are going to need it in multiple Max handlers. 24 | 25 | First we add a `connect` handler, which we pass a `url` parameter. We tell `io` to connect to that URL. Next we create a Max patch where we load that script, start it, and call `connect`. And voila, in the server log we see that a second user has connected. 26 | 27 | To close the loop, let's add a `disconnect` handler. In it, we simply close the socket on the client side. And furthermore we add a `message` handler to actually send messages to the server. 28 | 29 | On the server, let's first listen for the `disconnect` event. We restart, connect and afterwards disconnect, and here is our message (the first `a user connected` message stems from the browser still running and connecting in the background). 30 | 31 | ## Forwarding Messages 32 | 33 | Alright, what do we do when a message arrives? Let's do the easiest thing possible and transmit it to all connected clients. Socket.io is capable of much more subtle ways of routing, but that's beyond the scope of this video. 34 | 35 | Now is also the time to focus our attention on the browser client again. When a `message` is received on the socket, we just set the `#message`'s div's `innerText` to that message. Alright, let's send a message from the Max patch, by simply prepending an integer with `message`. Woosh, it works! 36 | 37 | ## Talking Back 38 | 39 | There's one more tweak I'd like to show you. By attaching an event listener to the button, we can send a `talkback` message from the browser. Just don't forget to empty the input box afterwards! 40 | 41 | On the server, we need to provide a way of relaying those talkback messages, too. This time, we're calling `socket.broadcast.emit`, which means nothing else than "send to everybody but the sender". That way we can avoid unnecessary traffic. 42 | 43 | Back in the Node4Max javascript file, we just need to register a callback on the socket, like we have done so many times now. Basically we can choose the event string (`talkback`) quite freely, please refer to the documentation! In it we just send the message out the `node.script` object's outlet. 44 | 45 | Accordingly, we create a `[route]` object and look for `talkback` messages. Let's try it out, enter a message in the browser - and here it is! 46 | 47 | If you think this through, you can leverage all the awesomeness of WebSockets, e.g. scripting patchers from one to the other, even over the web if you like, etc. Socket.io will even let you transmit binary data, maybe I'll try that out in the future. -------------------------------------------------------------------------------- /transcripts/04-mqtt-subscriber.md: -------------------------------------------------------------------------------- 1 | ## MQTT Subscriber 2 | 3 | If you've never heard of MQTT, I don't blame you. It's been only a year since I was first confronted with it. But here's the clue: you probably already know most of how it works if you've ever worked with OSC. But let's take this step by step. 4 | 5 | MQTT is short for _Message Queueing Telemetry Transport_, and is a machine-to-machine communication protocol for the transmission in environments of high latencies and flaky networks. Why is that interesting? Because MQTT is the standard protocol for IoT applications, the Internet of Things' heartbeat so to speak. 6 | 7 | Chances are, if you have any smart home or sensor devices at home, they already transmit or offer MQTT at some endpoint, so here is an opportunity for a wide range of sensor and actor integrations which can be a great source for various artworks. 8 | 9 | So this is how MQTT works on a basic level - it's a publication/subscription system. Client A _subscribes_ to a certain _topic_, and Client B _publishes_ that topic. Between them, a _broker_ is responsible for handling the passing of messages from publishers to subscribers. As you can already see, the message syntax is very similar to OSC. 10 | 11 | ## Using Node 4 Max to Handle MQTT 12 | 13 | With Node4Max we have a powerful tool at hand to actually deal with such protocols and integrate them into installations or performances. Let's first `npm init` a project. Next we'll install the [`mqtt`](https://github.com/mqttjs/MQTT.js) npm module. We'll make two separate javascript files here, one named `mqtt-subscriber.js`, and one named `mqtt-publisher.js` respectively. 14 | 15 | In the Max patch, let's load them into `[node.script]` modules and ponder how we'd like to communicate with them. First of all, we'll need to connect to a broker, which will be running on our localhost machine for testing purposes, so we'll send a `connect` message to the object. Next, let us simulate a sensor transmitting data (pool temperature) with a `[metro]` and a `[drunk]` object. So here's the thing: We're not actually setting up a sensor, we're using our Max patch to emulate one. 16 | 17 | As for the subscriber, we'll also need to connect it to that same broker, and then _subscribe_ to that `temperature/pool` topic. 18 | 19 | ## Starting a Broker 20 | 21 | How do we get a broker running? Well, the easiest way is to use Docker for that purpose. From the Docker hub you can download and run the [eclipse mosquitto](https://hub.docker.com/_/eclipse-mosquitto) image like so. If you'd like to learn more about how you can leverage Docker for your projects, I've got a full course covering it over at [www.dockerforcreators.com](https://www.dockerforcreators.com). Suffice it to say here, that we're exposing port 1883 here, which is the standard MQTT port. This Docker container will then be responsible for handling subscriptions and relaying published messages to the subscribers. 22 | 23 | ## Implementing the Publisher 24 | 25 | Now, let's start fleshing out that publisher. We import the `maxApi` as always, plus `mqtt`. We add a `connect` handler and use it to instantiate a `client`. On it, we register a listener for the `connect` event, and just for testing purposes we'll post a message to the outlet. Let's see if that works - it does, and the broker also logs out this new connection. 26 | 27 | Next, we add a `publish` handler for our `[node.script]` object. In it we'll just `console.log` the `topic` and `value` for now. 28 | 29 | Ok, we have 'undefined' arriving here. Let's see. It turns out, I've misused the `$1` notation here, when what I actually need is a `[prepend]` object. Now this looks better. 30 | 31 | ## Implementing the Subscriber 32 | 33 | In the other file, we add a `subscript` handler and subscribe to the incoming `topic`. When a `message` arrives, we output this via the Max object's outlet. 34 | 35 | And here's the point where I finally realize that I've done it all backwards. Of course I need to put the subscriber's code into `mqtt-subscriber.js`, and the publisher's code into `mqtt-publisher.js`. 36 | 37 | Okay, now that that's sorted out, I need to actually cast the published message to a `string`, and do the same for the incoming message, otherwise the console will try to print the raw binary data. Now this looks correct. 38 | 39 | Imagine we had just connected a bunch of sensors to our Max patch and can now start making interactive visuals, sonifications, or what have you. How cool is that! 40 | 41 | ## MQTT Topic Wildcards 42 | 43 | There's just another small feature of MQTT I'd like point out. Let's say, we're actually sending the temperature of the _first_ pool, by appending a `/1` to the topic. Subscribing to our old topic now won't yield any messages, obviously. Let's verify that everything still works by using that same topic. 44 | 45 | MQTT allows for two types of wildcards, basically. A `#` at the end of a topic recursively subscribes to all sub-topics of that topic. A `+` in the middle of a topic will act as a wildcard for _that_ subtopic level, in our case `pool`. This subscription would also listen to `temperature/roof/1`, for example. -------------------------------------------------------------------------------- /transcripts/05-live-coding-repl.md: -------------------------------------------------------------------------------- 1 | ## Simple Live Coding REPL 2 | 3 | Lately I've been really impressed by Timo Hoogland's live coding patch which he put on [Gumroad](https://gumroad.com/tmhglnd), and while I'm still leaning on him to assemble a nice video tutorial for it, it sparked an exploration of how an efficient live coding shell could be set up with node4max. NodeJS already has a lot of the requirements covered, so we're going to take a deeper look at this. 4 | 5 | First, a definition: I'm going to talk about a so-called REPL, that's short for **R**ead, **E**valuate, **P**rint, **L**oop, and is nothing else than a sort of built-in shell for a programming runtime. PHP has one, Ruby has one, Elixir has one, and of course, Node does so too. Let's take a look. We open a terminal and type 6 | 7 | $ node 8 | 9 | after which we are greeted by a prompt: 10 | 11 | > 12 | 13 | We can now **read** in any valid JavaScript expression 14 | 15 | > 1 + 1 16 | > new Date() 17 | > Math.random() 18 | > [1, 2, 3].map(elem => Math.pow(elem, 3)) 19 | 20 | which it will **evaluate** and then **print**. When it is finished, it will **loop** over and present you a prompt again (hence the name). 21 | 22 | Now the thing is, Node will actually let you _extend_ its built-in REPL if you like (you can find the relevant docs [here](https://nodejs.org/api/repl.html)), but we will actually take a step back and do a more low-level implementation using [readline](https://nodejs.org/api/readline.html). The reasons for this choice will become clear in the next episode. 23 | 24 | ## Building the REPL in Pure Node 25 | 26 | Before we even consider opening Max, let's just build this pseudo-shell from scratch. We start with an empty `npm` project by typing 27 | 28 | $ npm init -y 29 | 30 | (the `-y` flag skips all questions asked in the init process). 31 | 32 | We create a file called `repl-server.js` and first of all require the `readline` module. We then create an interface with `readline.createInterface` and define the `STDIN` and `STDOUT` streams as input and output. If you're not familiar with this terms, let's simplify things by saying `STDIN` is your keyboard, and `STDOUT` is your screen. Afterwards we `prompt` for input. 33 | 34 | Let's give this a go: 35 | 36 | $ node repl-server.js 37 | 38 | So here's the prompt, we can input things but nothing happens. Unsurprisingly, because we haven't yet coded any logic. 39 | 40 | Next we need to listen for an event that's called by the runtime on the readline interface whenever the user presses ``. This is the `line` event, and here we get the actual input passed in as a parameter. 41 | 42 | We will look at this in a second, let's just also chain the `close` event on this and issue a friendly goodbye message when the user closes the REPL. 43 | 44 | In the `line` event, we just echo the input back. So that works, after the first prompt we just don't get a second opportunity to input something. So after echoing the input, we issue another `rl.prompt()`. 45 | 46 | Here's the first prompt, and there is the second - great. 47 | 48 | ## Using `node.script` with STDIN and STDOUT 49 | 50 | When we start our Max patch with a `[node.script]` object that references our `repl-server.js`, keep in mind that we haven't even yet included the Max API in our JavaScript! Instead, if we prepend our messages to the node object with `stdin`, it will simply be passed to the STDIN of the underlying JavaScript code. 51 | 52 | I find this a really neat move of the developers of `[node.script]`, because it ultimately helps loosen the coupling between Max and the JavaScript code, meaning you can take any project that accepts input at STDIN and outputs at STDOUT/STDERR and plug it into Max. 53 | 54 | The way we obtain the output is via the right outlet of `[node.script]`, where it says "Dump out: stdout and stderr". So if we patch a `[route]` object in sequence, we get the REPLs output here. Fine! 55 | 56 | ## Piping the Output into a `[textedit]` 57 | 58 | Now let's take this a step further and make it more "shell-like". We can append the output to a `[textedit]` object, by formatting the output as `"append %s"` with a `[sprintf]` object. If we connect that to the `[textedit]`, we can already see the output coming in, only that there is no newline appended. 59 | 60 | To fix this, I'm going to manually trigger a newline by sending a `13` into a `[itoa]` object. I wasn't able to do it with the `[sprintf]` - if you know of a way, please reach out to me. 61 | 62 | ## Using a `[textedit]` for STDIN 63 | 64 | Now we're going to duplicate this `[textedit]` and use it as the actual command line. If you configure it to output its text on `` (which I did earlier in the inspector), you will get its contents prepended by "text" on the left outlet. So, `[route text]`, and on into the STDIN. 65 | 66 | There's one more little tweak we have to make here: In order to clear its contents and reset the cursor after you've hit `` we have to trigger a `clear` and `select` message afterwards. All right, seems to work! 67 | 68 | ## A true JS REPL 69 | 70 | Finally, let's make the output a little more useful. We are going to exchange the simple echoing logic by a JavaScript evaluator. 71 | 72 | For that, we are going to write a `try/catch` block, because when a user inputs some garbage, we don't want the entire script to crash. So, on input, we call the `eval()` function, which is usually considered dangerous because it will readily execute ANY JavaScript you pass it. In our sandboxed environment here, though, it will be ok. 73 | 74 | When an error occurs, we'll just output that in our `catch` block, but the script will keep running. Because we haven't up till now required the `max-api`, we can just test this in the terminal! `1 + 1 = 2`, this is today's date, and here is a random float number. If we input some garbage, it tells us so but still waits for input. Great! 75 | 76 | Back in the Max patch, we confirm that it's working there, too. I'll leave you here with your own imagination of where this can lead, what applications it might have. We'll look at another usability tweak in the next episode. 77 | 78 | ## Max Compressed Representation 79 | 80 | ``` 81 | ----------begin_max5_patcher---------- 82 | 1086.3oc0XssihiCD84vWgkk12XPwNW.lmVoUZ9IlYUKCwP6tCIQ1F5t2Qy+ 83 | 9ZW1gNzMgjLDPZdAiq33pN0kiKmeNI.up7UtBi9J56nffeNIH.DYED3mGf2w 84 | dccNSAKCWveob0S3otGo4upAwZjhmyWqQqy4LY8iK2qy4Z8aUbmJvtEgmhvt 85 | 0g9W+JEYv1X15uPipe8h86DElM.zLwKrhoW+nnX6CR6NAa6x4yBmhhRWXGlm 86 | Z+kRmEdbyM6iyRfMhZE9qISr+L85PszrsbDLsMDaw54gI8rvj1NLooyRlhHN 87 | .lr7F.SKR3YhKiFQAD.aGXg3iXPx1w0b4C7B1pbXSB8OaSYgVI9OPFIdVs3m 88 | 4usqLi2LfaWYgYe.E7Mgjg9G6JpiErU0uP3.Sab9yXvcFSR.+YRq9y3wKsoR 89 | xq3EYHVkcncm847tjkCs5fPCADFGYGV1U4AY7voPWxZEc1znyBvEmEfQcCPH 90 | MBEGd+.nFQhPpKBw1pRHyGZbjRH.M2RnxOZwMn9eGWoXa4eBmWlT+73Kcnza 91 | wN9a3mnj6WPTUIMApMnefc0in+R8C7.Aa7fClT.gwQPLkPhtA3cTXyIQ+gvl 92 | 6Se7z4gPKAj4z6AetqM.kNyLZG3R4k6GnUmc3P4974Q9deHgcwtGM9mhY.rn 93 | XX0KCl6yEUoK.F9EjaPwxJPq7iAtCB9KGDJwJQtP+VSyqbyFE2aWvYMgMLj7 94 | x0Oyyxjrsp0xx77lYxqyEqeV+nIaY6iMk6Jmd7yuf6AG97CVs8iUIqJklztl 95 | RZ3tSGr6dtuaHhqXxejZXqd7iEq0U1Ew6lsqrvzAfblwOazAdDOOx3QDU1Rs 96 | xpgk3kL3dti.hD5RvSjdKno6DjLodXnLZnnjjbBJiueG9VXxim4QpgOI+KJt 97 | 7.WN6IE5uewZnHxvuZE42idIhBcVQ5rE4ZOohcfm8fQilM5AlVKEqLGDn7dA 98 | uaH.y1qKcQw2qTBvY7MmTvF.9B.C3iRp1cp.vkbLD3hA0QBvXw4hhOdgd.vV 99 | 4mFdTk6kqq8kdVYz6PNiqzhBlVTVzXMD2ZNaBPe0SxcROQ2I8P6ihr2Jtwh1 100 | IxpJM8d4CU1CtSr81CDLDpqQ+FShchRq++XXxjqyjMjg2USl1Cu77KYwNSgN 101 | GNMKIwZ8vjq0vB6S3mNBIZ1692cFczEiZFNN6G8XNP3Gm.wqOLiVKKYTBb1K 102 | z2sUubL7O8hAawXootper2J750T5.h4Wmlhu5rKZTr6SpkzLe5zYic1Uup95 103 | iUWSdsHpIUlelqyuZZZ6ri61wNuI2df7gL72a5+5TcORlSurGjB9kD2WfK0w 104 | 7exLp+h4K.eMLaz8f8olLraGnqQJVUkoQTk+0AkZ5n8oRX4KlBSEEku2BGVx 105 | sWXzsd3J6Xlzzoo1zc3doqytWSceoHr89axh8BOpMvch8BhltksWgRUwbHCZ 106 | pdxul7+.z2t8P 107 | -----------end_max5_patcher----------- 108 | ``` -------------------------------------------------------------------------------- /transcripts/06-repl-with-history.md: -------------------------------------------------------------------------------- 1 | ## REPL with History 2 | 3 | Let's consider a nice little addition to our REPL. All modern shells that are in use today provide a means of accessing the history of commands issued to it by typing the up and down arrow keys. `readline` actually does this natively, but because we are dealing with an external environment here, we have to do a little extra setup. 4 | 5 | Let's start this exploration by finally requiring in the `max-api` module. We add two handlers called `history_up` and `history_down` to traverse our history. Let's just initialize them with an empty function for now. 6 | 7 | The easiest way to store commands is via a simple JavaScript array that we call `myHistory` - please do find a more creative variable name. We also need a variable to store our current position in the history, and initialize it with `-1`. 8 | 9 | ## Populating the History 10 | 11 | Whenever we enter something into our little shell, we want to add that command to the beginning of our array. That's what the `Array.unshift` method is for - it inserts entries at the beginning whereas `Array.push` would insert entries at the end. 12 | 13 | One other thing we need to do here - whenever we enter something new, we want to reset the history position variable, because we usually want to start traversing it from the beginning again. 14 | 15 | ## Implementing the Handlers 16 | 17 | Now in our handlers, we are going to set our `historyPos` to a new value, and output the history entry at that position. But because our history can be of any length, we need to be careful here as to not exceed the boundaries of our array. 18 | 19 | When pushing the up arrow key, we want to increase the position, but only until the maximum length of the array is reached, which is at `myHistory.length - 1`, because array indexes start counting at 0. And that's basically what this ternary expression does: if the current history position is smaller than the array length, increase it by 1, else just clip it at the array length. 20 | 21 | When that is done, we hijack the `STDOUT` stream to output the command referenced by the new `historyPos` in the history array. We'll see why we do this in a second. 22 | 23 | In the `history_down` handler, we do the opposite: We decrease the `historyPos` counter until it has reached 0. That ternary expression is much easier to write and read. 24 | 25 | ## Wire It Up in Max 26 | 27 | Because we are sending it to the `STDERR` stream we can leave the rest of the logic totally untouched and just need to care for what comes out of this second `[route]` outlet. 28 | 29 | And to send the handler messages to the `[node.script]` object, we take the input `[textobject]`'s second outlet and select the `30` and `31` ASCII keycodes, which are the up and down arrows keys, respectively. 30 | 31 | Let's take a look. Let's input some commands first. When we push the up and down keys, we see the respective history entries flashing up in that message box. Great! 32 | 33 | Let's insert two buttons here to observe when a key is pressed. Now let's turn this symbol into a list with `[fromsymbol]` and append `[route history]`. Afterwards we need to `[prepend set]` and connect back to our `[textobject]`. 34 | 35 | So let's give this a spin. Input some commands, and we can retrieve them again by pressing the up and down arrows. This little addition to our REPL will help us out a lot in the future. -------------------------------------------------------------------------------- /transcripts/07-live-coded-patcher-scripting.md: -------------------------------------------------------------------------------- 1 | ## Live Coded Patcher Scripting 2 | 3 | A while ago, I drafted a little [command-line tool called `maxy-gen`](https://github.com/julianrubisch/maxy-gen) to allow the creation of simple Max patches from the command line. It's written as a RubyGem, so you'll need the Ruby interpreter installed on your machine (if by any chance you're working on a Mac, that should automatically be the case). 4 | 5 | Let's first install this gem with `gem install maxy-gen`. If you have already installed it, please update it to the latest version, 0.4.1, because I have made significant changes to make the examples in this screencast work. If you'd like to know more about how to use it, please refer to the [README on GitHub](https://github.com/julianrubisch/maxy-gen). 6 | 7 | After installing it, we call `maxy-gen install`, which will ask for the location of your Max installation's refpages, since it will construct the objects list from there. Eventually, we need to find out where our `maxy-gen` executable is located for later use. Please note that this will be in a different location in your environment. 8 | 9 | ### Preparing for Patch Generation 10 | 11 | Now let's open our REPL server file. What I intend to do in this tutorial, is make parts of our actual patcher live-scriptable by exchanging the contents of a `[bpatcher]`. 12 | 13 | Let's first sketch how we want to achieve this. We are going to input commands to our repl that first tell it which bpatcher to use, e.g. `cutoffs`, and then the maxy-gen representation of a patch. 14 | 15 | So we are going to split the input into two strings and destructure them into a JavaScript array, `[patcherName patch]`. 16 | 17 | Next we need to prepare the `PATH` used in the script, because chances are your `maxy-gen` executable cannot be found when running it in the context of a Max patch. So we add the path we discovered earlier to our PATH like so. Of course, we also need to require the `path` object from the NodeJS standard library. 18 | 19 | Let's actually move these statements into the `try...catch` block. 20 | 21 | Now, let's sketch a function we want to invoke to actually create our patch with `maxy-gen`. Let's call it `makePatcher`, and it will take the `patcherName` and the `patch` from our input. 22 | 23 | ### Create a Patch from Inside NodeJS 24 | 25 | Let's add three more imports, the `util`‌, `childProcess`, and `fs` modules. 26 | 27 | We are going to use the `promisify` method from `util` to wrap the calls to `childProcess.exec` and `fs.writeFile` in Promises, otherwise we would need to use callbacks, which are ugly to read and debug. Just bear with me please :) 28 | 29 | Alright, now on to the heart of this episode, the `makePatcher` function. Basically, we're using `exec` to make our Node script invoke the `maxy-gen` executable with the arguments given to it from our REPL (connected to an outlet by default). This will return a JSON representation of our Max patch, so let's capture it in the `output` variable. Since we wrapped it in a Promise, we need to `await` its output. 30 | 31 | The second step is to actually write it to the file specified by `patcherName`, which is also called asynchronously and must be awaited. As the whole method thus is executed asynchronously, we must flag it as `async`. If you're not familiar with this new way of handling Promises, there are a million blogposts and tutorials out there explaining it in detail. 32 | 33 | ### Preparing the Main Patch for Scripting 34 | 35 | Let's return to our main patch for a second. We need to create a bpatcher and give it a name, `cutoffs` in this case. 36 | 37 | And there's another thing we need to do. When we're done creating the new bpatcher internals in the Node script, we need to exchange its contents, and this is done by sending a message to `[thispatcher]`. (Actually I made a mistake here in the video, it needs to be connected to the `[node.script]`'s first outlet, but since it's being echoed back through the textinput, it worked nonetheless. Sorry for the confusion 🙃.) 38 | 39 | ### Scripting the Patcher from Inside Node 40 | 41 | Alright, now we need to return to the place where we actually invoke that `makePatcher` function. Since it returns a Promise, we call `then` so when it's resolved, we can send a `script sendbox` message to the `[thispatcher]` object telling it to replace the context of the patcher named `patcherName` with the newly overwritten file. You can see now why I promisified this, because it ensures that the sending of the message is done only **after** the executable is done writing! 42 | 43 | ### Live Coding 44 | 45 | Okay, let's give it a spin. We'll first preload our bpatcher with a `cutoffs` file. Now we specify different contents of the bpatcher and you can instantly hear the changes. 46 | 47 | Let's make a second one for resonances, called `res`, and create it from our REPL, load it into the bpatcher and connect it. 48 | 49 | Last but not least we do something similar for the amplitude modulation, and call the bpatcher `amps`. 50 | 51 | So here we are, live-coding parts of our actual Max environment from a REPL. Thanks to [Darwin Grosse](https://twitter.com/darwingrosse) for helping me out with the replacing of the bpatcher contents via `thispatcher` 👍. 52 | 53 | ### Improvements TODO 54 | 55 | Our REPL is still crashing on invalid inputs, which isn't any good in a live coding environment, so we need to make it more resilient, but we'll deal with that in a later episode. 56 | -------------------------------------------------------------------------------- /transcripts/08-express-js-sound-player.md: -------------------------------------------------------------------------------- 1 | ## Express JS Sound Player 2 | 3 | Let's consider a scenario where you get a bunch of audio files along with a CSV file containing metadata, for example from an automated analysis. Let's furthermore assume we want to expose a web API to start multi-track playback of these files via standard HTTP GET requests. 4 | 5 | ### A Simple Playback Server 6 | 7 | We are going to set up our Max patch as follows: To demonstrate multi-track playback, let's just add three `[buffer~]`s with three `[wave~]` objects, along with a `[phasor~]` to trigger playback. Of course we will also need a `[node.script]` object for this. 8 | 9 | We initialize a node project and add two dependencies: `express` itself, and `csv-parse` for parsing of CSV files. 10 | 11 | $ npm i -S express csv-parse 12 | 13 | Let's create a `server.js` file and open it. First we need to set up our express server, which we do by requiring the module and instantiating an `app`. We then define an ExpressJS `route` like this: 14 | 15 | ```javascript 16 | app.get("/sound/:id", (req, res) => { 17 | }); 18 | ``` 19 | 20 | The `":id"` part of the route denotes a _dynamic segment_, and will be replaced by whatever `id` you specify in your GET request. We'll get to that later. 21 | 22 | ### Parsing CSV Data 23 | 24 | Now here's the trickiest part of the JavaScript part: parsing the supplied CSV file and returning the relevant row. For that we are going to use an npm package called [`csv-parse`](https://www.npmjs.com/package/csv-parse). It's used as follows: We are going to require the `parse` method and instantiate a `parser`. It receives an empty options object and a callback containing the parsed `data` as a function argument. Let's leave the function body empty for a second and `pipe` the CSV file's contents into the `parser` using `fs.createReadStream`. 25 | 26 | What are the contents of that file, anyway? To demonstrate this, I've just listed the sound files' names in it. 27 | 28 | Within the function body, first we make sure that we return an `OK` HTTP response to the request. We do not need to return more than that, since all we want to do is issue a command to `Max`, which we will do next. At the `[node.script]`'s outlet we are going to provide a JSON object containing the track number, which we pass to the request as a query parameter, as a key, and the row corresponding to the specified sound id. In this case this simply matches the row number, so we can call `data[req.params.id]`. 29 | 30 | ```javascript 31 | const parser = parse({}, (_err, data) => { 32 | Max.outlet({ [req.query.track]: data[req.params.id] }); 33 | 34 | res.sendStatus(200); 35 | }); 36 | 37 | fs.createReadStream(__dirname + "/data/sounds.csv").pipe(parser); 38 | ``` 39 | 40 | Below we'll start the server and have it listen on port `5000`. 41 | 42 | ### A Little `[dict]` Exercise 43 | 44 | In the Max patch, the created JSON object is represented as a `[dict]`. Let's just connect that and make sure things work so far. Ok, great. Now we need to come up with some custom `[dict]` logic here. With every call to our Express server, we'd like to _merge_ this `[dict]` into the already existing `[dict]`, so as to not stop playback but simply start another track. There is actually a quite simple way to do this with `[dict.join]`, but we need to add a timing tweak. If we take a look at the `[dict.join]` help patch, it says 45 | 46 | > if two dictionaries are joined, and both contain the same key, the key in the dictionary being joined (right inlet) overrides the key for the dictionary input (left inlet) 47 | 48 | Now this is exactly our situation: When a new sound should be played back for a certain track, we need to _override_ the key, i.e. pass the new `[dict]` in on the right hand side, and create a new `[dict]` to store the current contents of the merged `[dict]`s. That object's outlet goes into the _left (hot)_ inlet of `[dict.join]`. Now our patch is set up to _replace_ old keys with new ones while retaining the rest of the `[dict]`s contents. All we need to do now is _trigger_ the update process of `[dict.join]` with a bang to the "storage" `[dict]`, and we are going to do this with a `[t b l]`, that way we can ensure that first the new JSON object from the `[node.script]` is passed to `[dict.join]`, and only afterwards is the merge triggered. We can even delete this intermediary `[dict]` now since it is only passed as a `list`. 49 | 50 | ### Sounding Out 51 | 52 | Okay, last but not least, we'd like to pass the sound file names to the `[buffer~]`s, and we're going to use `[dict.iter]` to iterate over the contents of our `[dict]`. Using `[route "0" "1" "2"]` (the quotes are necessary in this case), we send them to the appropriate paths in our patcher (of course, you could also use a `[poly~]` object here). I've prepared a little subpatch returning the absolute path of the soundfile, then `[prepend replace]`, and off we go. Let's open a browser and see if we get some sound output. 53 | 54 | ### Conclusion 55 | 56 | So using some basic backend JavaScript and `[dict]` magic, we've created a versatile sound player. The obvious reason why you'd like to do something like that is that you can plug any frontend that speaks HTTP to this, be it a HTML website, a command line tool, another web service, or whatever. 57 | 58 | ### Max Representation 59 | 60 | ``` 61 | ----------begin_max5_patcher---------- 62 | 2392.3oc6bszjiSCD9bleEtLWCAK42vE3NPUTbbWpTJwJIZwwxksRlYgB9si 63 | dX6Xm3GJLZxrE3CiSrjsZ80pU+Rcl+7oE1anufKss9VqOXsXwe9zhExlDMrn 64 | 59E1GQurMEUJeL6L7yzMexdopKF9ElrYl0Fqz5VyNcjdhkhYxWAV0ppI1myw 65 | J5YuAks2dokss0uU8HjD4fwIvWCBpGsbDa6AR190E3sL0q5GuxYoEvGH9.Fu 66 | xmeEtxoYb3S.RVM8Ah19qmdRbY4qCkoTThZV2GPAiBz9.o2HXLH.JQmii3C+ 67 | H4MOBPlag1TRSOwvqKomxRVymbGtSD2+RJXD35F5JPnmq7Cf5iQva6QBWTgx 68 | JXtvdGIEeFWTRnYsd5E1n77VMun0qH3MehJGnnkMMQxTMAZZp.elb8vxG2BN 69 | dXbvbpPxBreIndsULLzDbQ1IRyBkbUpZJIWOxPGwk4nspWVrrU2cKABkTuGH 70 | T7QfmTtvKx4BKhufuOkt82wRdtScizbbFIKu.WhyXHV0juo6D7NzoT15czLV 71 | I4OjSAfP1qm92UME6sSAHjy+enffRaPv9BRBMSLI5rTHZtlbefuWVsitMXjO 72 | QFJumWlKNw4KCzYIGjmJ2fJDqTaRwsTBwEyozztc07do3crptyIYYWwEYz7g 73 | 6rfr+vHu6FJuyiiM1xdJWeJS06ZtPAacI5bWtMCklVsqs6v+BJibDwvLhZIf 74 | q0ntSbFhCzCkaKnoocvqpmy8zSBWHeK9YRBeiufPsEF3ONIuVHxtYUNgrGWx 75 | 51FCsuraKkrOqX5sZ5zlpMwqY3i4obTz8A5Xhp8N11525z9X545pqqvph1sz 76 | wMrdtIz0ci1cu1iXelwjagAJM7gwWqw6VsdN0sK0zWoGw7bksziaHY317Fqu 77 | IAwPeikzhfP6p02y3B864JSsfCy3fix3VNByyMPOlG2Pgv5eHXZtm6ig6Ua8 78 | 7sPjx0UOtRnTcZX3zLEfAYJxQ036hlRP.FH8MRYOzs45H6hZ0AWT+H2pXGMN 79 | xIPVB9ESyeTfdXFjy.bfPsVzg9g5xA.uVNvEizobEECncVNSE82OqgqJoXa8 80 | xe8JsU2YM2bCij03yxGtnavBZ8aZszb2yBu6YV37FMKb0lW39VNKb0bVHDQG 81 | XVT0XsCu1BmZRVqbfaMhwshrgqrr7ZO3uKGM3d8tAkVYtpwMzd7C4oKStuzC 82 | VxYjfkfff4fklCVZNXo4fklCVZNXo4fklCVZNXo4fklCVZNXoujCV5YNZ9ak 83 | UQ3cFiTIYeF2M49hTJHdrSJTc5f9gR0dQAiGnj6aBVAFDqQSeDZugXcWJkOF 84 | 0SgNw.4V61qHNPxQ9Swa8qgPGemK8fdotmtcz+AFuiVvcSWzRPKJ1afS86uT 85 | 2ycMGUveUF20Yku7sLhzw.h+XAd6I4vddBgpHkGCtOhyj8.pjV72Sv46W95N 86 | XZiI4ENMaQI3EDONaA9ufsjRNiWsGQxZXAmQWzccauc4D9Sf2ks+l3OtbNWv 87 | aoXjEZOGSBplKKhAc6ADOdzzs7M8r1RWzBR6jI.5g65Oc1dBfR+Tg.Iy1KbP 88 | 9aGaGMFMFw7wYT5ILc2sY44BfOxIfbGc3k3aa2OWwv9gWOtZnPxk5fdGnxCz 89 | BlliT8JYeiyoLBqI.ZumZan8ekIM7ejf1Nf7UOZQBb0XAMVUxC90WM29koMJ 90 | 4XPiRfow5i2.bdANGmkXw+HUjHNCjS1.mQs9BEFE7TAWG6+3pWGyCT+XMR97 91 | +I.ZzjUY1iGmaNsaGtXbGkGrn5ZLcMXQm4GNsTruiDtQwuOfFXdPGngNp2WP 92 | 6XdP6Ooz8iGyuYmXlmF6kmOvr4CLa9.ylOvr4CLa9.ylOvr4CLa9.ylOvr4C 93 | La9.y9B8.yJ6wAhoy8n2X4dLDn1AKO1.qHf4i6iGqQIZO91.+LTbdvXM9Uy4 94 | J0Z6BGGdFL0prCjx5vEuu33G7m.ILPCfpNyufP8Bns1meNY+peVP3NSaK6uS 95 | b4q9EwUbVhpE0VLyvkJ3nGa8QamOZyuBjWgezteNl2nbrkiv4hmLS.tJGhim 96 | P92ybPOgrkshGEegQ1CLM.AQ0GSzCJwNB.dmKjhWgqBFU74IWRgSmKKnLuBB 97 | +UF82CL79LUfOt4p3oG4fj6bNWn77ltMtog2Xt8X+xjAJWA+eG6d0mnjr6b2 98 | aKdduU4+zajgApTwEadSYCYoV4ajUIilaBkUicdC.tBJ+Z+PB7LOF2bkQ4yD 99 | 7yxzyQRIrO2AL61UhqlVRVdqfbrUI9Lof6qWSBspc8ZaJY6uyNvsss+P61uI 100 | 2Xc637scrYuHYtcZgVjnR.83E4QiaoYdGWcjlQXzhUbFQNhYe2+aIXzyJwEb 101 | wwQOUxkfPmGgCjMhknBlIjKcmbu2amX4.pYx3K+qpwIt3LtX0mJMk+jfo+Op 102 | ATBX.vWe2I0PmN5DipV0tJ49cjrWHQ+5MjL0QB0IHqr7iCzyyBvLfRe4ruaR 103 | DTKNWGpZ0RzMgnF2NB0ghNM5Rzo2HEbODBNAgD++yvHDBLEgbL.gB0f0YB.E 104 | nyZjn9pZ8PMJUAOHRCtkzNuNRG89g5n2OTqiP0UqJGII4b+1XUZ.TYzOnx20 105 | pBwpyc9UkQqabycuO3HRab3p7Qr6cU3vMRUQG8iCvC.G.swAzAzBGU2UgCHz 106 | eDb.ec3vWGb3ZBsU9Zn+URIvqkRf2OMDf2OMDNZPZ+PCrR5qixe+.SPIcT35 107 | 6a.J4oEkLg+NdZsiyDqSdZPHXrAHj6CxgGnNBdPSH3IGjoTV4YB0hPcDGDIK 108 | oOMFuNkUPOcI8qFjZPnQ8cIHRXAzUkr+V27JmW.cVlglP+BHPGQWOSPIntTB 109 | LN2FpJ7WfmJI.ctCDoJmv3nl6Ls8L8vwzBMvH+KBMxaL89HfVF8MQjk.srwa 110 | BU5.cbVyPzYRUP2RHU5MtppSEz3ppM8pJM81pLc3JL85pKUdP6pBs6pTpbo9 111 | LOkPn+prJIW+S3rSpL0TWKmc4Da1uijltklRuoHaqynksp2lBZs9YEIoEF6A 112 | .QhL05BbCgAxuw+hueq0sp2AT+Rd9dwNPwi5E.C87keKB5561IkuUuF7Bsb. 113 | wJJ3DG43o9FuI.mVseMT19ppBs0uUJ67BZNsno7XW4FurU1w1WfRHU0IS27X 114 | srRPpf26002n8QNCkT+NsE7ZVLXn7w3+sKrVtb1OhXTqeTTsoMm568Pek73. 115 | kb7S+0S+CMzCvkB 116 | -----------end_max5_patcher----------- 117 | ``` 118 | --------------------------------------------------------------------------------