├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── cors-service ├── README.md ├── app.js ├── cors-service.js ├── cors-service.maxpat ├── package.json ├── public │ └── stylesheets │ │ └── style.css ├── routes │ └── index.js ├── testpage.html └── views │ ├── error.ejs │ └── index.ejs ├── dog-ceo ├── README.md ├── dogceo.js └── dogceo.maxpat ├── echo ├── README.md ├── n4m.echo.js └── n4m.echo.maxpat ├── express ├── README.md ├── express-node │ ├── package-lock.json │ ├── package.json │ └── server.js ├── express.maxproj └── patchers │ └── express.maxpat ├── file-upload ├── .gitignore ├── README.md ├── app.js ├── file-upload.js ├── file-upload.maxpat ├── package-lock.json ├── package.json ├── public │ └── stylesheets │ │ └── style.css ├── routes │ ├── index.js │ └── upload.js └── views │ ├── error.ejs │ ├── index.ejs │ └── status.ejs ├── freesound ├── .env-template ├── .gitignore ├── README.md ├── attribution.md ├── freesound-search.maxpat ├── freesound-sequencer.maxpat ├── freesound.jpg ├── fs-index.js ├── hat.png ├── kick.png ├── package-lock.json ├── package.json └── snare.png ├── giphy ├── .env-template ├── .gitignore ├── README.md ├── attribution.md ├── giphy-demo.maxpat ├── giphy.js ├── new.png ├── package-lock.json ├── package.json └── powered_by_giphy.gif ├── package-lock.json ├── package.json ├── routeServer ├── README.md ├── app.js ├── external │ └── Max8Logo.png ├── js │ ├── helpers.js │ └── message_broker.js ├── max_routeServer.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ └── style.css ├── routeServer.maxpat ├── routes │ ├── content.js │ └── max.js └── views │ ├── error.ejs │ ├── max_data.ejs │ └── max_html.ejs ├── sockets ├── README.md ├── max_sockets.js ├── max_sockets.maxpat ├── package-lock.json ├── package.json ├── public │ ├── css │ │ └── style.css │ ├── img │ │ ├── sl-green.png │ │ ├── sl-red.png │ │ └── sl-yellow.png │ └── js │ │ └── mySockets.js ├── routes │ └── index.js └── views │ ├── error.ejs │ └── index.ejs ├── tonal-chord-builder ├── README.md ├── chord-builder.maxpat ├── n4m.chords.js ├── package-lock.json ├── package.json └── poly.phatness.maxpat ├── twitter ├── .env-template ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── twitter.js └── twitter.maxpat └── typescript ├── .gitignore ├── README.md ├── n4m.ts.index.js ├── n4m.ts.maxpat ├── package-lock.json ├── package.json ├── src └── n4m.typescript.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | typescript/lib -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const OFF = 0; 2 | const ERROR = 2; 3 | 4 | module.exports = { 5 | "extends": "c74", 6 | "rules" : { 7 | 8 | }, 9 | "env" : { 10 | "es6": true, 11 | "node": true, 12 | "browser": true 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 8, 16 | "sourceType": "module" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | #### Nature of issue? 10 | 11 | - [ ] Found a bug in an example 12 | - [ ] Existing example enhancement 13 | - [ ] Suggest a new example 14 | 15 | 16 | #### Details about the bug: 17 | 18 | - Max version: 19 | - Operating System: 20 | - Name of example: 21 | - Steps to reproduce this: 22 | 23 | 24 | 25 | #### Existing example enhancement details: 26 | 27 | 28 | 29 | #### New example details: 30 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | I have verified that this pull request: 2 | 3 | * [ ] there are no linting errors -- `npm run lint`, from the root of the n4m-examples repository 4 | * [ ] is from a uniquely-named feature branch and has been rebased on top of the latest master. (If I was asked to make more changes, I have made sure to rebase onto master then too) 5 | * [ ] is descriptively named and links to an issue number, i.e. `Fixes #123` 6 | 7 | Thank you! 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_STORE -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 26 | * Trolling, insulting/derogatory comments, and personal or political attacks 27 | * Public or private harassment 28 | * Publishing others' private information, such as a physical or electronic 29 | address, without explicit permission 30 | * Other conduct which could reasonably be considered inappropriate in a 31 | professional setting 32 | 33 | ## Our Responsibilities 34 | 35 | Project maintainers are responsible for clarifying the standards of acceptable 36 | behavior and are expected to take appropriate and fair corrective action in 37 | response to any instances of unacceptable behavior. 38 | 39 | Project maintainers have the right and responsibility to remove, edit, or 40 | reject comments, commits, code, wiki edits, issues, and other contributions 41 | that are not aligned to this Code of Conduct, or to ban temporarily or 42 | permanently any contributor for other behaviors that they deem inappropriate, 43 | threatening, offensive, or harmful. 44 | 45 | ## Scope 46 | 47 | This Code of Conduct applies both within project spaces and in public spaces 48 | when an individual is representing the project or its community. Examples of 49 | representing a project or community include using an official project e-mail 50 | address, posting via an official social media account, or acting as an appointed 51 | representative at an online or offline event. Representation of a project may be 52 | further defined and clarified by project maintainers. 53 | 54 | ## Enforcement 55 | 56 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 57 | reported by contacting Cycling '74 support via [http://cycling74.com](http://cycling74.com/contact-support/) .All 58 | complaints will be reviewed and investigated and will result in a response that 59 | is deemed necessary and appropriate to the circumstances. The project team is 60 | obligated to maintain confidentiality with regard to the reporter of an incident. 61 | Further details of specific enforcement policies may be posted separately. 62 | 63 | Project maintainers who do not follow or enforce the Code of Conduct in good 64 | faith may face temporary or permanent repercussions as determined by other 65 | members of the project's leadership. 66 | 67 | ## Attribution 68 | 69 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 70 | available at [http://contributor-covenant.org/version/1/4][version] 71 | 72 | [homepage]: http://contributor-covenant.org 73 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | The Node for Max Examples repository is an open source project, and contributions and help from the community is strongly encouraged and important to improve the software. Contributions are therefore always welcome, no matter how large or small. Here are some things we'd like you to keep in mind in order to help with keeping the process smooth and organized. Please also read our [Code of Conduct](CODE_OF_CONDUCT.md). 4 | 5 | ## Bug Reports / Example Suggestions 6 | 7 | If you've come across a bug in one of the examples, would like to suggest a new example, suggest an enhancement to a current example, or just ask a question, please use the GitHub Issues section for [n4m-examples][issues]. In order to make things easier for the maintainers and others the following would be helpful: 8 | 9 | * **Use the search.** It's possible that someone already filed the issue or asked the question you have in mind, so please try to avoid duplicates. 10 | * **Share Info** Please try to share as much helpful info as possible. 11 | * **Distinct test case** Please try to provide detailed info about your bug, example suggestion, feature request, or question. In the case of a bug please try to share clear reproducible steps or ideally even an isolated, reproducible test case. 12 | 13 | ## Contributing Changes / Pull Requests 14 | 15 | We are happy to accept your contributions in the form of pull requests from the GitHub Community. Please make sure your contributions are well-formatted, pass the tests (use `npm run test`) and make use of commonly understood commit messages. 16 | 17 | ## Quick Code Style Guide 18 | 19 | * Use tab characters for spacing 20 | * No trailing whitespace and also blank lines should have no whitespace 21 | * Make use of strict equals === unless type coercion is intended 22 | * Follow conventions already established in project's source code 23 | * Validate changes with eslint and build/test the project to make sure you didn't break anything 24 | 25 | This project also uses eslint. So please feel free to use `npm run lint` to check the formatting or `npm run fix` to have eslint auto format where it can. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, Cycling'74 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included 11 | in all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Node For Max 3 |

4 | 5 | # Node For Max Examples 6 | [![Build Status](https://travis-ci.org/Cycling74/n4m-examples.svg?branch=master)](https://travis-ci.org/Cycling74/n4m-examples) 7 | 8 | Hey Maxers! This repository contains many examples of how to use the Node For Max package in your Max patches created by Cycling '74. 9 | 10 | ## Other Resources 11 | 12 | If you created your own example you would like to showcase, please submit it to the [community repository](https://github.com/Cycling74/n4m-community). We love community contributions! 13 | 14 | Looking for more basic examples to learn the core concepts of Node For Max? The [n4m-core-examples](https://github.com/Cycling74/n4m-core-examples) repository has a growing list that can help learning the core techniques and principles of using Node For Max. 15 | 16 | ## List of Examples 17 | 18 | * [cors-service](./cors-service): Create a CORS (Cross-Origin Resource Sharing) capable web server. By [Cycling '74](https://github.com/Cycling74). 19 | * [dog-ceo](./dog-ceo): Downloads files from a remote API, in this case a repository of dogs. By [Cycling '74](https://github.com/Cycling74). 20 | * [echo](./echo): Simply outputs the input, a pass-through. Shows how to use a list of arguments of unspecified length. By [Cycling '74](https://github.com/Cycling74). 21 | * [express](./express): A small example Express application. By [Cycling '74](https://github.com/Cycling74). 22 | * [file-upload](./file-upload): Upload a file from a browser, play it in Max. By [Cycling '74](https://github.com/Cycling74). 23 | * [freesound](./freesound): Use the Freesound API with Max. By [Cycling '74](https://github.com/Cycling74). 24 | * [giphy](./giphy): Use the Giphy API with Max. By [Cycling '74](https://github.com/Cycling74). 25 | * [routeServer](./routeServer): Another small Express application example, which gets data from Max. By [Cycling '74](https://github.com/Cycling74). 26 | * [sockets](./sockets): An example creating a web server with websockets. By [Cycling '74](https://github.com/Cycling74). 27 | * [tonal-chord-builder](./tonal-chord-builder): Generate different chords based on a root note. By [Cycling '74](https://github.com/Cycling74). 28 | * [twitter](./twitter): Communicate with Twitter from Max. By [Cycling '74](https://github.com/Cycling74). 29 | 30 | ## Contributing 31 | 32 | The main purpose of this repository is to show complete yet limited in scope projects that show potential use-cases of Node For Max. We are grateful to the community for contributing bufixes and improvements. 33 | 34 | Note that we are not accepting community-created examples in this repository; however, we are taking suggestions for examples you would like to see Cycling '74 create. If you'd like to showcase your work please visit the [community repository](https://github.com/Cycling74/n4m-community) 35 | 36 | ### Contributing Guide 37 | 38 | You might find an error in an example, or have a request for a example you would like to see. You can report this by [submitting an issue](https://github.com/Cycling74/n4m-examples/issues/new) to this repository. Note that you will need to have a GitHub account to submit an issue. See the full [Contributing Guide](./CONTRIBUTING.md) for more details on how to participate in this project. 39 | 40 | ### Code of Conduct 41 | 42 | We have adopted a Code of Conduct that we expect every participant to adhere to. You can find the full text [here](./CODE_OF_CONDUCT.md). 43 | 44 | ## LICENSE 45 | 46 | [MIT](./LICENSE) -------------------------------------------------------------------------------- /cors-service/README.md: -------------------------------------------------------------------------------- 1 | # cors-service 2 | 3 | Provide a CORS-Capable service. 4 | 5 | Note: Before you run this example, make sure that you run the npm install embedded into the patcher file, by clicking on the [script npm install] message. 6 | 7 | *** 8 | 9 | ## Files 10 | 11 | `cors-service.maxpat` : The Max patch to run the example.
12 | `cors-service.js` : The launcher JS for the NodeJS script.
13 | `app.js` : The NodeJS/Express script that runs the application.

14 | `testpage.html` : A web page used to test the service.
15 | `package.json` : The Node package file.
16 | `README.md` : This file!
17 | 18 | ## Folders 19 | 20 | `/public` : The browser-facing content served up by Node.
21 | `/routes` : The Express routing functions for endpoints.
22 | `/views` : The HTML/EJS templates used by the routing function.
23 | 24 | *** 25 | 26 | ## Usage 27 | 28 | 1. Launch the `cors-service.maxpat` Max patch. 29 | 2. (First time only...) Click on the [script npm install] message at the top-left to load the required packages and libraries. 30 | 3. Click on the [script start] message at the top-left to start the Node process running. 31 | 4. Click on the "Open the test page" message to launch a browser window with the example HTML page. Click on the button on that page to send a random number to the Max patch, and have it return a variety of Max-generated information. 32 | -------------------------------------------------------------------------------- /cors-service/app.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------ 2 | // app.js - This is a generic Node application provide by the Express cli, 3 | // routing to both the default and "content" functional locations. 4 | // NOTE: This does use EJS-based templating; if you choose to use 5 | // something else (like Angular or React), you will need to change 6 | // the view engine. 7 | // ------------------------------------------------------------------------ 8 | 9 | 10 | var express = require("express"); 11 | var path = require("path"); 12 | var logger = require("morgan"); 13 | var cookieParser = require("cookie-parser"); 14 | var bodyParser = require("body-parser"); 15 | 16 | // These are the additional packages we need for this application 17 | var maxAPI = require("max-api"); 18 | var cors = require("cors"); 19 | 20 | // This is the route that contains the application logic 21 | var index = require("./routes/index"); 22 | 23 | // set up the express app 24 | var app = express(); 25 | 26 | // view engine setup 27 | app.set("views", path.join(__dirname, "views")); 28 | app.set("view engine", "ejs"); 29 | app.use(logger("dev")); 30 | app.use(bodyParser.json()); 31 | app.use(bodyParser.urlencoded({ 32 | extended: false 33 | })); 34 | app.use(cookieParser()); 35 | app.use(express.static(path.join(__dirname, "public"))); 36 | 37 | // this is the middleware that allows CORS 38 | app.use(cors()); 39 | 40 | // this is where we handle the index request 41 | // ----------------------------------------- 42 | app.use("/", index); 43 | 44 | // and this is where we respond to the info request 45 | // obviously, you might want to do more error checking! 46 | // ---------------------------------------------------- 47 | app.get("/info/:id", (req, res) => { 48 | let id = 0; 49 | 50 | let doComplete = (data) => { 51 | let outObj = { 52 | value: data[0], 53 | sqrt: data[1], 54 | rando: data[2], 55 | msg: data[3] 56 | }; 57 | res.json(outObj); 58 | }; 59 | 60 | let doFailure = () => { 61 | res.json({ 62 | value: 0, 63 | sqrt: 0, 64 | rando: 0, 65 | msg: "System Failure" 66 | }); 67 | }; 68 | 69 | let handler = (args) => { 70 | maxAPI.removeHandlers("info"); 71 | doComplete(args); 72 | }; 73 | 74 | try { 75 | if (isNaN(req.params.id)) { 76 | throw (new Error("Not a number")); 77 | } 78 | 79 | id = parseInt(req.params.id, 10); 80 | maxAPI.addHandler("info", (...args) => { 81 | console.log(args); 82 | handler(args); 83 | }); 84 | 85 | maxAPI.outlet(id); 86 | } catch (err) { 87 | console.log("Error: Value must be an integer!"); 88 | doFailure(); 89 | return; 90 | } 91 | }); 92 | 93 | // catch 404 and forward to error handler 94 | app.use((req, res, next) => { 95 | var err = new Error("Not Found"); 96 | err.status = 404; 97 | next(err); 98 | }); 99 | 100 | // error handler 101 | app.use((err, req, res) => { 102 | // set locals, only providing error in development 103 | res.locals.message = err.message; 104 | res.locals.error = req.app.get("env") === "development" ? err : {}; 105 | 106 | // render the error page 107 | res.status(err.status || 500); 108 | res.render("error"); 109 | }); 110 | 111 | module.exports = app; 112 | -------------------------------------------------------------------------------- /cors-service/cors-service.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // --------------------------------------------------------------------- 4 | // cors-service.js - the starter JS for the cors-service Max project (as 5 | // created by the Express CLI). 6 | // 7 | // For the node app, check out the app.js at the top level directory. 8 | // 9 | // --------------------------------------------------------------------- 10 | 11 | 12 | /** 13 | * Module dependencies. 14 | */ 15 | 16 | var app = require("./app"); 17 | var debug = require("debug")("service:server"); 18 | var http = require("http"); 19 | 20 | /** 21 | * Normalize a port into a number, string, or false. 22 | */ 23 | 24 | function normalizePort(val) { 25 | var port = parseInt(val, 10); 26 | 27 | if (isNaN(port)) { 28 | // named pipe 29 | return val; 30 | } 31 | 32 | if (port >= 0) { 33 | // port number 34 | return port; 35 | } 36 | 37 | return false; 38 | } 39 | 40 | /** 41 | * Get port from environment and store in Express. 42 | */ 43 | 44 | var port = normalizePort(process.env.PORT || "3000"); 45 | app.set("port", port); 46 | 47 | /** 48 | * Create HTTP server. 49 | */ 50 | 51 | var server = http.createServer(app); 52 | 53 | /** 54 | * Event listener for HTTP server "error" event. 55 | */ 56 | 57 | function onError(error) { 58 | if (error.syscall !== "listen") { 59 | throw error; 60 | } 61 | 62 | var bind = typeof port === "string" 63 | ? "Pipe " + port 64 | : "Port " + port; 65 | 66 | // handle specific listen errors with friendly messages 67 | switch (error.code) { 68 | case "EACCES": 69 | console.error(bind + " requires elevated privileges"); 70 | process.exit(1); 71 | break; 72 | case "EADDRINUSE": 73 | console.error(bind + " is already in use"); 74 | process.exit(1); 75 | break; 76 | default: 77 | throw error; 78 | } 79 | } 80 | 81 | /** 82 | * Event listener for HTTP server "listening" event. 83 | */ 84 | 85 | function onListening() { 86 | var addr = server.address(); 87 | var bind = typeof addr === "string" 88 | ? "pipe " + addr 89 | : "port " + addr.port; 90 | debug("Listening on " + bind); 91 | } 92 | 93 | 94 | /** 95 | * Listen on provided port, on all network interfaces. 96 | */ 97 | 98 | server.listen(port); 99 | server.on("error", onError); 100 | server.on("listening", onListening); 101 | -------------------------------------------------------------------------------- /cors-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "service", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.17.1", 10 | "cookie-parser": "~1.4.3", 11 | "cors": "^2.8.4", 12 | "debug": "~2.6.3", 13 | "ejs": "~2.5.6", 14 | "express": "~4.15.2", 15 | "morgan": "~1.8.1", 16 | "serve-favicon": "~2.4.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /cors-service/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /cors-service/routes/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var express = require("express"); 4 | var router = new express.Router(); 5 | 6 | /* GET home page. */ 7 | router.get("/", function (req, res, next) { 8 | res.render("index", { title: "cors-service example" }); 9 | }); 10 | 11 | module.exports = router; 12 | -------------------------------------------------------------------------------- /cors-service/testpage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | Service Test 12 | 13 | 14 | 15 |
16 |

Getting To A Service...

17 |
18 |
19 |

20 | This is an example of getting a request from a web-based service. In most 21 | cases, servers will prevent a request from a page/program that was not served 22 | by itself - this is called a 'Cross-Origin Resource Sharing Violation'. 23 |

24 |

25 | How do we get around it? There is a 'dance' that the server and requestor 26 | go through in order to validate that a request is coming from a valid 27 | service. By setting our server to allow CORS, we can get service content 28 | from any location. We allow CORS through the server by using the cors node package. 29 |

30 |

31 | To see this in action - click this button: 32 |

33 |

34 | 35 |

36 |

(no data received...)

37 |
38 | 39 | 40 | 41 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /cors-service/views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /cors-service/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 |

CORS Service Example

9 |
10 |

11 | If you got to this page, you aren't doing it right! This example 12 | shows how to handle a webpage directly accessing a service from 13 | your Node for Max application. To correctly run this example, do 14 | the following steps: 15 |

16 |
    17 |
  1. Launch the cors-service.maxpat Max patch.
  2. 18 |
  3. (First time) Click the [script npm install] message to load 19 | the required libraries and packages.
  4. 20 |
  5. Click the [script start] message to start the Node process.
  6. 21 |
  7. Load the testpage.html page into a browser. You can 22 | normally do that by double-clicking on the file in your 23 | computer's file browser.
  8. 24 |
  9. If things are running correctly, you should see the results 25 | in your browser window. If not, check the Max window for errors.
  10. 26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /dog-ceo/README.md: -------------------------------------------------------------------------------- 1 | # Dog CEO 2 | 3 | Download files from a remote API, in this case a repository of dogs. 4 | Demonstrates the use of asynchronous functions in Node 5 | 6 | *** 7 | 8 | ## Files 9 | 10 | `dogceo.maxpat` : The Max patch to run the example.
11 | `dogceo.js` : The launcher JS for the NodeJS script.
12 | `README.md` : This file
13 | 14 | *** 15 | 16 | ## Usage 17 | 18 | 1. Launch the `dogceo.maxpat` Max patch. 19 | 2. Click on any of the dog breeds at the bottom of the patcher 20 | 3. A download should begin automatically and display in the jit.pwindow 21 | -------------------------------------------------------------------------------- /dog-ceo/dogceo.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | // fs is a Node module for working with the file system 4 | const fs = require("fs"); 5 | 6 | // https is a Node module. It simplifies the process of making https requests. 7 | const https = require("https"); 8 | 9 | // os is also a Node module. It lets us get operating system information, like 10 | // the path to the temporary directory. 11 | const os = require("os"); 12 | 13 | // Yet another Node module, path helps with things like resolving relative paths 14 | const path = require("path"); 15 | 16 | // Another node module! URL if for working with URL's of course. 17 | // You'll notice a slightly different syntax here, that actually assigns the value 18 | // of the variable URL to the URL property of the url module. For more info, see 19 | // https://nodejs.org/docs/latest-v8.x/api/url.html#url_url_pathname 20 | const { 21 | URL 22 | } = require("url"); 23 | 24 | // max-api is only available when running this script from Max. 25 | const maxApi = require("max-api"); 26 | 27 | const BASE_URL = "https://dog.ceo/api/"; 28 | 29 | // Promises are a useful JavaScript construct for handling functions that do 30 | // not return until some asynchronous process is completed. 31 | function request(requrl) { 32 | return new Promise((resolve, reject) => { 33 | let data = ""; 34 | https.get(requrl, (res) => { 35 | res.on("data", (d) => { 36 | data = data + d; 37 | }); 38 | 39 | res.on("end", () => { 40 | resolve(JSON.parse(data)); 41 | }); 42 | }).on("error", e => { 43 | reject(e); 44 | }); 45 | }); 46 | } 47 | 48 | function download(uri, filepath) { 49 | return new Promise((resolve, reject) => { 50 | https.get(uri, (resp) => { 51 | const headers = JSON.stringify(resp.headers); 52 | if (resp.statusCode === 200) { 53 | const ws = fs.createWriteStream(filepath); 54 | resp 55 | .on("data", chunk => ws.write(chunk)) 56 | .on("end", () => { 57 | ws.end(); 58 | resolve(filepath); 59 | }) 60 | .on("error", e => reject(e)); 61 | } else if (resp.statusCode === 307) { // redirect 62 | const redirectURL = headers.location; 63 | resolve(download(redirectURL, filepath)); 64 | } else { 65 | reject("Failed to download file: " + resp.statusMessage); 66 | } 67 | }); 68 | }); 69 | } 70 | 71 | // addHandler (and the convenient addHandlers) allow you to call functions from 72 | // Max. Here, "breed" is bound to an anonymous function that retrieves images 73 | // of dogs, categorized by breed. 74 | maxApi.addHandler("breed", (name) => { 75 | const apiurl = `${BASE_URL}breed/${name}/images/random`; 76 | 77 | // This .then .then syntax is JavaScript Promise syntax. It lets us do 78 | // a sequence of asynchronous tasks (tasks that don't immediately return) 79 | // one after the other, only starting when the previous task completes. 80 | // This first function tries to fetch the URL of a random dog photo 81 | request(apiurl) 82 | .then((data) => { 83 | if (data.status === "success") { 84 | return data.message; 85 | } 86 | throw new Error("Error fetching dog image: " + data.message); 87 | }) 88 | 89 | // Next, we try to download the image to the temporary directory. 90 | // We could send the URL straight to Max--on OS X anyway the jit.movie 91 | // object can read URL's directly. However, this won't work on windows 92 | // (as of the time of writing this comment, anyway). So intsead, we 93 | // download the image to a temporary directory, then send Max the file. 94 | .then((imgurl) => { 95 | const imgurlPath = (new URL(imgurl)).pathname; 96 | const filename = path.basename(imgurlPath); 97 | return download(imgurl, path.join(os.tmpdir(), filename)); 98 | }) 99 | 100 | // Finally, send the full path back to Max, with the dog selector 101 | .then(pathname => maxApi.outlet("dog", pathname)) 102 | 103 | // If something went wrong, post an error to the Max console 104 | .catch(e => maxApi.post(e.message, maxApi.POST_LEVELS.WARN)); 105 | }); 106 | -------------------------------------------------------------------------------- /dog-ceo/dogceo.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" : [ 300.0, 80.0, 793.0, 966.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 | "bubble" : 1, 43 | "bubblepoint" : 0.25, 44 | "id" : "obj-13", 45 | "linecount" : 5, 46 | "maxclass" : "comment", 47 | "numinlets" : 1, 48 | "numoutlets" : 0, 49 | "patching_rect" : [ 216.0, 85.0, 150.0, 78.0 ], 50 | "text" : "Double-click to reveal the file, which shows how to use asynchronous functions" 51 | } 52 | 53 | } 54 | , { 55 | "box" : { 56 | "id" : "obj-11", 57 | "maxclass" : "jit.pwindow", 58 | "numinlets" : 1, 59 | "numoutlets" : 2, 60 | "outlettype" : [ "", "" ], 61 | "patching_rect" : [ 89.666656494140625, 228.191497802734375, 626.66668701171875, 558.30853271484375 ] 62 | } 63 | 64 | } 65 | , { 66 | "box" : { 67 | "id" : "obj-10", 68 | "maxclass" : "newobj", 69 | "numinlets" : 1, 70 | "numoutlets" : 2, 71 | "outlettype" : [ "jit_matrix", "" ], 72 | "patching_rect" : [ 45.0, 163.636367797851562, 125.0, 22.0 ], 73 | "text" : "jit.movie @autostart 1" 74 | } 75 | 76 | } 77 | , { 78 | "box" : { 79 | "id" : "obj-7", 80 | "maxclass" : "newobj", 81 | "numinlets" : 2, 82 | "numoutlets" : 2, 83 | "outlettype" : [ "", "" ], 84 | "patching_rect" : [ 45.0, 99.636367797851562, 59.0, 22.0 ], 85 | "text" : "route dog" 86 | } 87 | 88 | } 89 | , { 90 | "box" : { 91 | "id" : "obj-6", 92 | "maxclass" : "newobj", 93 | "numinlets" : 1, 94 | "numoutlets" : 1, 95 | "outlettype" : [ "" ], 96 | "patching_rect" : [ 45.0, 20.136367797851562, 87.0, 22.0 ], 97 | "text" : "prepend breed" 98 | } 99 | 100 | } 101 | , { 102 | "box" : { 103 | "activebgoncolor" : [ 0.741176, 0.827451, 0.039216, 1.0 ], 104 | "fontname" : "Lato Medium", 105 | "fontsize" : 22.0, 106 | "id" : "obj-2", 107 | "lcdcolor" : [ 0.741176, 0.827451, 0.039216, 1.0 ], 108 | "maxclass" : "live.tab", 109 | "num_lines_patching" : 4, 110 | "num_lines_presentation" : 0, 111 | "numinlets" : 1, 112 | "numoutlets" : 3, 113 | "outlettype" : [ "", "", "float" ], 114 | "parameter_enable" : 1, 115 | "patching_rect" : [ 45.0, 800.0, 716.0, 117.0 ], 116 | "saved_attribute_attributes" : { 117 | "valueof" : { 118 | "parameter_enum" : [ "affenpinscher", "beagle", "boxer", "chihuahua", "dachshund", "husky", "labrador", "papillon", "pomeranian", "puggle", "retriever", "rottweiler", "samoyed", "schnauzer", "shiba", "terrier" ], 119 | "parameter_type" : 2, 120 | "parameter_unitstyle" : 0, 121 | "parameter_longname" : "live.tab", 122 | "parameter_shortname" : "live.tab" 123 | } 124 | 125 | } 126 | , 127 | "varname" : "live.tab" 128 | } 129 | 130 | } 131 | , { 132 | "box" : { 133 | "fontname" : "Lato Light", 134 | "fontsize" : 27.741391846000681, 135 | "id" : "obj-5", 136 | "maxclass" : "comment", 137 | "numinlets" : 1, 138 | "numoutlets" : 0, 139 | "patching_rect" : [ 350.333343505859375, 20.136367797851562, 366.0, 40.0 ], 140 | "text" : "Dog pictures on demand", 141 | "textcolor" : [ 0.0, 0.0, 0.0, 1.0 ], 142 | "textjustification" : 1 143 | } 144 | 145 | } 146 | , { 147 | "box" : { 148 | "color" : [ 0.741176, 0.827451, 0.039216, 1.0 ], 149 | "id" : "obj-1", 150 | "maxclass" : "newobj", 151 | "numinlets" : 1, 152 | "numoutlets" : 2, 153 | "outlettype" : [ "", "" ], 154 | "patching_rect" : [ 45.0, 54.136367797851562, 251.0, 22.0 ], 155 | "saved_object_attributes" : { 156 | "autostart" : 1, 157 | "defer" : 0, 158 | "node" : "", 159 | "npm" : "", 160 | "watch" : 1 161 | } 162 | , 163 | "text" : "node.script dogceo.js @autostart 1 @watch 1" 164 | } 165 | 166 | } 167 | , { 168 | "box" : { 169 | "id" : "obj-15", 170 | "maxclass" : "message", 171 | "numinlets" : 2, 172 | "numoutlets" : 1, 173 | "outlettype" : [ "" ], 174 | "patching_rect" : [ 45.0, 129.636367797851562, 83.0, 22.0 ], 175 | "text" : "read $1, bang" 176 | } 177 | 178 | } 179 | ], 180 | "lines" : [ { 181 | "patchline" : { 182 | "destination" : [ "obj-7", 0 ], 183 | "source" : [ "obj-1", 0 ] 184 | } 185 | 186 | } 187 | , { 188 | "patchline" : { 189 | "destination" : [ "obj-11", 0 ], 190 | "midpoints" : [ 54.5, 205.913932800292969, 99.166656494140625, 205.913932800292969 ], 191 | "source" : [ "obj-10", 0 ] 192 | } 193 | 194 | } 195 | , { 196 | "patchline" : { 197 | "destination" : [ "obj-10", 0 ], 198 | "source" : [ "obj-15", 0 ] 199 | } 200 | 201 | } 202 | , { 203 | "patchline" : { 204 | "destination" : [ "obj-6", 0 ], 205 | "midpoints" : [ 403.0, 934.0, 25.0, 934.0, 25.0, 10.5, 54.5, 10.5 ], 206 | "source" : [ "obj-2", 1 ] 207 | } 208 | 209 | } 210 | , { 211 | "patchline" : { 212 | "destination" : [ "obj-1", 0 ], 213 | "source" : [ "obj-6", 0 ] 214 | } 215 | 216 | } 217 | , { 218 | "patchline" : { 219 | "destination" : [ "obj-15", 0 ], 220 | "source" : [ "obj-7", 0 ] 221 | } 222 | 223 | } 224 | ], 225 | "parameters" : { 226 | "obj-2" : [ "live.tab", "live.tab", 0 ], 227 | "parameterbanks" : { 228 | 229 | } 230 | 231 | } 232 | , 233 | "dependency_cache" : [ ], 234 | "autosave" : 0, 235 | "styles" : [ { 236 | "name" : "light", 237 | "default" : { 238 | "textcolor_inverse" : [ 0.0, 0.0, 0.0, 1.0 ], 239 | "bgfillcolor" : { 240 | "type" : "color", 241 | "color1" : [ 1.0, 1.0, 1.0, 1.0 ], 242 | "color2" : [ 0.290196, 0.309804, 0.301961, 1.0 ], 243 | "color" : [ 0.65098, 0.666667, 0.662745, 1.0 ], 244 | "angle" : 270.0, 245 | "proportion" : 0.39, 246 | "autogradient" : 0.0 247 | } 248 | , 249 | "fontsize" : [ 32.0 ] 250 | } 251 | , 252 | "parentstyle" : "", 253 | "multi" : 0 254 | } 255 | ] 256 | } 257 | 258 | } 259 | -------------------------------------------------------------------------------- /echo/README.md: -------------------------------------------------------------------------------- 1 | # echo 2 | 3 | A pass-through script. Any message input to the script will be output to the node.script outlet, and then printed to the Max console. 4 | 5 | *** 6 | 7 | ## Files 8 | 9 | `n4m.echo.maxpat` : The Max patch to run the example.
10 | `n4m.echo.js` : The launcher JS for the NodeJS script.
11 | `README.md` : This file!
12 | 13 | *** 14 | 15 | ## Usage 16 | 17 | 1. Launch the `n4m.echo.maxpat` Max patch. 18 | 2. Click on the [script start] message at the top-left to start the Node process. 19 | 3. Type in the [textedit] object and hit Enter. You will see this text in the Max Console. 20 | -------------------------------------------------------------------------------- /echo/n4m.echo.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | // Require the max-api module to connect to Max via node.script 4 | const maxAPI = require("max-api"); 5 | 6 | // When node.script gets the symbol "text", the remainder will be passed to this function. 7 | // The "..." is the spread operator. All of the arguments to this function will go into args as an array. 8 | maxAPI.addHandler("text", (...args) => { 9 | 10 | // 11 | // The outlet function sends the arguments right back to Max. Hence, echo. 12 | maxAPI.outlet(...args); 13 | }); 14 | -------------------------------------------------------------------------------- /echo/n4m.echo.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, 787.0, 540.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 | "bubble" : 1, 43 | "bubbleside" : 0, 44 | "id" : "obj-24", 45 | "linecount" : 2, 46 | "maxclass" : "comment", 47 | "numinlets" : 1, 48 | "numoutlets" : 0, 49 | "patching_rect" : [ 40.0, 328.0, 139.0, 52.0 ], 50 | "presentation_linecount" : 2, 51 | "text" : "Anything you send shoudl be echoed here" 52 | } 53 | 54 | } 55 | , { 56 | "box" : { 57 | "bubble" : 1, 58 | "id" : "obj-23", 59 | "linecount" : 2, 60 | "maxclass" : "comment", 61 | "numinlets" : 1, 62 | "numoutlets" : 0, 63 | "patching_rect" : [ 214.0, 105.5, 111.0, 37.0 ], 64 | "presentation_linecount" : 2, 65 | "text" : "Write your text here" 66 | } 67 | 68 | } 69 | , { 70 | "box" : { 71 | "id" : "obj-14", 72 | "maxclass" : "newobj", 73 | "numinlets" : 1, 74 | "numoutlets" : 0, 75 | "patching_rect" : [ 55.0, 298.0, 120.0, 22.0 ], 76 | "text" : "print echo @popup 1" 77 | } 78 | 79 | } 80 | , { 81 | "box" : { 82 | "id" : "obj-7", 83 | "keymode" : 1, 84 | "maxclass" : "textedit", 85 | "numinlets" : 1, 86 | "numoutlets" : 4, 87 | "outlettype" : [ "", "int", "", "" ], 88 | "parameter_enable" : 0, 89 | "patching_rect" : [ 94.0, 99.0, 100.0, 50.0 ], 90 | "text" : "satars" 91 | } 92 | 93 | } 94 | , { 95 | "box" : { 96 | "bgmode" : 0, 97 | "border" : 0, 98 | "clickthrough" : 0, 99 | "enablehscroll" : 0, 100 | "enablevscroll" : 0, 101 | "id" : "obj-6", 102 | "lockeddragscroll" : 0, 103 | "maxclass" : "bpatcher", 104 | "name" : "n4m.monitor.maxpat", 105 | "numinlets" : 1, 106 | "numoutlets" : 0, 107 | "offset" : [ 0.0, 0.0 ], 108 | "patching_rect" : [ 316.0, 244.0, 400.0, 220.0 ], 109 | "viewvisibility" : 1 110 | } 111 | 112 | } 113 | , { 114 | "box" : { 115 | "bubble" : 1, 116 | "id" : "obj-5", 117 | "linecount" : 2, 118 | "maxclass" : "comment", 119 | "numinlets" : 1, 120 | "numoutlets" : 0, 121 | "patching_rect" : [ 136.0, 40.5, 111.0, 37.0 ], 122 | "presentation_linecount" : 2, 123 | "text" : "Remember to start the script" 124 | } 125 | 126 | } 127 | , { 128 | "box" : { 129 | "id" : "obj-3", 130 | "maxclass" : "message", 131 | "numinlets" : 2, 132 | "numoutlets" : 1, 133 | "outlettype" : [ "" ], 134 | "patching_rect" : [ 55.0, 48.0, 64.0, 22.0 ], 135 | "text" : "script start" 136 | } 137 | 138 | } 139 | , { 140 | "box" : { 141 | "id" : "obj-1", 142 | "maxclass" : "newobj", 143 | "numinlets" : 1, 144 | "numoutlets" : 2, 145 | "outlettype" : [ "", "" ], 146 | "patching_rect" : [ 55.0, 183.0, 109.0, 22.0 ], 147 | "saved_object_attributes" : { 148 | "autostart" : 0, 149 | "defer" : 0, 150 | "node" : "", 151 | "npm" : "", 152 | "watch" : 0 153 | } 154 | , 155 | "text" : "node.script n4m.echo.js" 156 | } 157 | 158 | } 159 | ], 160 | "lines" : [ { 161 | "patchline" : { 162 | "destination" : [ "obj-14", 0 ], 163 | "source" : [ "obj-1", 0 ] 164 | } 165 | 166 | } 167 | , { 168 | "patchline" : { 169 | "destination" : [ "obj-6", 0 ], 170 | "source" : [ "obj-1", 1 ] 171 | } 172 | 173 | } 174 | , { 175 | "patchline" : { 176 | "destination" : [ "obj-1", 0 ], 177 | "source" : [ "obj-3", 0 ] 178 | } 179 | 180 | } 181 | , { 182 | "patchline" : { 183 | "destination" : [ "obj-1", 0 ], 184 | "source" : [ "obj-7", 0 ] 185 | } 186 | 187 | } 188 | ], 189 | "dependency_cache" : [ { 190 | "name" : "n4m.monitor.maxpat", 191 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 192 | "type" : "JSON", 193 | "implicit" : 1 194 | } 195 | , { 196 | "name" : "resize_n4m_monitor_patcher.js", 197 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 198 | "type" : "TEXT", 199 | "implicit" : 1 200 | } 201 | ], 202 | "autosave" : 0 203 | } 204 | 205 | } 206 | -------------------------------------------------------------------------------- /express/README.md: -------------------------------------------------------------------------------- 1 | # express 2 | 3 | An example of a small express application. 4 | 5 | Note: Before you run this example, make sure that you run the npm install embedded into the patcher file, by clicking on the [script npm install] message. 6 | 7 | *** 8 | 9 | ## Files 10 | 11 | `express.maxproj` : The Max project for the example.
12 | `patchers/express.maxpat` : The Max patch to run the example.
13 | `express-node/package.json` : The Node package file.
14 | `express-node/server.js` : The NodeJS/Express script that runs the application.
15 | 16 | ## Folders 17 | 18 | `/patchers` : All Max patches for this example. 19 | `/express-node` : All files needed for the Express server. 20 | 21 | *** 22 | 23 | ## Usage 24 | 25 | 1. Launch the `express.maxproj` Max project. 26 | 2. (First time only...) Click on the [script npm install] message at the upper-left to load the required packages and libraries. 27 | 3. Click on the [script start] message at the lower-left to start the Node process running. 28 | 4. In the [jweb] object in the Max patch, you will see a website served by the Express application. 29 | 5. You can move the [slider] and see the slider value being sent to the Express application. 30 | -------------------------------------------------------------------------------- /express/express-node/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-node", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", 9 | "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", 10 | "requires": { 11 | "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", 12 | "negotiator": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz" 13 | } 14 | }, 15 | "array-flatten": { 16 | "version": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 17 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 18 | }, 19 | "body-parser": { 20 | "version": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", 21 | "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", 22 | "requires": { 23 | "bytes": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 24 | "content-type": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 25 | "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 26 | "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 27 | "http-errors": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 28 | "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", 29 | "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 30 | "qs": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 31 | "raw-body": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", 32 | "type-is": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz" 33 | } 34 | }, 35 | "bytes": { 36 | "version": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 37 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 38 | }, 39 | "content-disposition": { 40 | "version": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 41 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 42 | }, 43 | "content-type": { 44 | "version": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 45 | "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=" 46 | }, 47 | "cookie": { 48 | "version": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 49 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 50 | }, 51 | "cookie-signature": { 52 | "version": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 53 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 54 | }, 55 | "debug": { 56 | "version": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 57 | "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", 58 | "requires": { 59 | "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" 60 | } 61 | }, 62 | "depd": { 63 | "version": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 64 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" 65 | }, 66 | "destroy": { 67 | "version": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 68 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 69 | }, 70 | "ee-first": { 71 | "version": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 72 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 73 | }, 74 | "encodeurl": { 75 | "version": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 76 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" 77 | }, 78 | "escape-html": { 79 | "version": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 80 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 81 | }, 82 | "etag": { 83 | "version": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 84 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 85 | }, 86 | "express": { 87 | "version": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", 88 | "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", 89 | "requires": { 90 | "accepts": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", 91 | "array-flatten": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 92 | "body-parser": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", 93 | "content-disposition": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 94 | "content-type": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 95 | "cookie": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 96 | "cookie-signature": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 97 | "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 98 | "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 99 | "encodeurl": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 100 | "escape-html": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 101 | "etag": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 102 | "finalhandler": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", 103 | "fresh": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 104 | "merge-descriptors": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 105 | "methods": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 106 | "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 107 | "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 108 | "path-to-regexp": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 109 | "proxy-addr": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", 110 | "qs": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 111 | "range-parser": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 112 | "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 113 | "send": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", 114 | "serve-static": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", 115 | "setprototypeof": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 116 | "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 117 | "type-is": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", 118 | "utils-merge": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 119 | "vary": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" 120 | } 121 | }, 122 | "finalhandler": { 123 | "version": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", 124 | "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", 125 | "requires": { 126 | "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 127 | "encodeurl": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 128 | "escape-html": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 129 | "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 130 | "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 131 | "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 132 | "unpipe": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" 133 | } 134 | }, 135 | "forwarded": { 136 | "version": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 137 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 138 | }, 139 | "fresh": { 140 | "version": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 141 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 142 | }, 143 | "http-errors": { 144 | "version": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 145 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", 146 | "requires": { 147 | "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 148 | "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 149 | "setprototypeof": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 150 | "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz" 151 | }, 152 | "dependencies": { 153 | "setprototypeof": { 154 | "version": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 155 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 156 | } 157 | } 158 | }, 159 | "iconv-lite": { 160 | "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", 161 | "integrity": "sha1-90aPYBNfXl2tM5nAqBvpoWA6CCs=" 162 | }, 163 | "inherits": { 164 | "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 165 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 166 | }, 167 | "ipaddr.js": { 168 | "version": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz", 169 | "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A=" 170 | }, 171 | "media-typer": { 172 | "version": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 173 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 174 | }, 175 | "merge-descriptors": { 176 | "version": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 177 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 178 | }, 179 | "methods": { 180 | "version": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 181 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 182 | }, 183 | "mime": { 184 | "version": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 185 | "integrity": "sha1-Eh+evEnjdm8xGnbh+hyAA8SwOqY=" 186 | }, 187 | "mime-db": { 188 | "version": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", 189 | "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" 190 | }, 191 | "mime-types": { 192 | "version": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", 193 | "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", 194 | "requires": { 195 | "mime-db": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz" 196 | } 197 | }, 198 | "ms": { 199 | "version": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 200 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 201 | }, 202 | "negotiator": { 203 | "version": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 204 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 205 | }, 206 | "on-finished": { 207 | "version": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 208 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 209 | "requires": { 210 | "ee-first": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" 211 | } 212 | }, 213 | "parseurl": { 214 | "version": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 215 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 216 | }, 217 | "path-to-regexp": { 218 | "version": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 219 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 220 | }, 221 | "proxy-addr": { 222 | "version": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", 223 | "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=", 224 | "requires": { 225 | "forwarded": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 226 | "ipaddr.js": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz" 227 | } 228 | }, 229 | "qs": { 230 | "version": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 231 | "integrity": "sha1-NJzfbu+J7EXBLX1es/wMhwNDptg=" 232 | }, 233 | "range-parser": { 234 | "version": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 235 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 236 | }, 237 | "raw-body": { 238 | "version": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", 239 | "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", 240 | "requires": { 241 | "bytes": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 242 | "http-errors": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 243 | "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", 244 | "unpipe": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" 245 | } 246 | }, 247 | "safe-buffer": { 248 | "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 249 | "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" 250 | }, 251 | "send": { 252 | "version": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", 253 | "integrity": "sha1-pw4coh0TgsEdDZ9iMd6ygQgNerM=", 254 | "requires": { 255 | "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 256 | "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 257 | "destroy": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 258 | "encodeurl": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 259 | "escape-html": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 260 | "etag": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 261 | "fresh": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 262 | "http-errors": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 263 | "mime": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 264 | "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 265 | "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 266 | "range-parser": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 267 | "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz" 268 | } 269 | }, 270 | "serve-static": { 271 | "version": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", 272 | "integrity": "sha1-TFfVNASnYdjy58HooYpH2/J4pxk=", 273 | "requires": { 274 | "encodeurl": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 275 | "escape-html": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 276 | "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 277 | "send": "https://registry.npmjs.org/send/-/send-0.16.1.tgz" 278 | } 279 | }, 280 | "setprototypeof": { 281 | "version": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 282 | "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=" 283 | }, 284 | "statuses": { 285 | "version": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 286 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 287 | }, 288 | "type-is": { 289 | "version": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", 290 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", 291 | "requires": { 292 | "media-typer": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 293 | "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz" 294 | } 295 | }, 296 | "unpipe": { 297 | "version": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 298 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 299 | }, 300 | "utils-merge": { 301 | "version": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 302 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 303 | }, 304 | "vary": { 305 | "version": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 306 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 307 | } 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /express/express-node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-node", 3 | "version": "1.0.0", 4 | "description": "Run an express server from Max", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "server", 11 | "express" 12 | ], 13 | "author": "Sam Tarakajian, Florian Demmer", 14 | "license": "ISC", 15 | "dependencies": { 16 | "express": "^4.16.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /express/express-node/server.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const express = require("express"); 4 | const app = express(); 5 | const Max = require("max-api"); 6 | 7 | function anypost(str) { 8 | if (Max) { 9 | Max.post(str); 10 | } else { 11 | console.log(str); 12 | } 13 | } 14 | 15 | app.get("/", function (req, res) { 16 | let responseText = ""; 17 | if (Max) { 18 | Max.getDict("pagestats") 19 | .then((dict) => { 20 | dict.accesses = dict.accesses ? dict.accesses + 1 : 1; 21 | Max.updateDict("pagestats", "accesses", dict.accesses); 22 | responseText += "

Hello, Max!

"; 23 | responseText += `

24 | This page has been loaded ${dict.accesses} ${dict.accesses === 1 ? "time" : "times"}. 25 |

`; 26 | responseText += `

27 | The slider is at ${dict.slider ? dict.slider : 0} 28 |

`; 29 | res.send(responseText); 30 | }) 31 | .catch((err) => { 32 | responseText += "

Had trouble connecting to Max

"; 33 | responseText += `

${err}

`; 34 | res.send(responseText); 35 | }); 36 | } else { 37 | res.send("

Hello! This simple server is not running inside of Max.

"); 38 | } 39 | }); 40 | 41 | app.listen(3000, function () { 42 | anypost("Example app listening on port 3000!"); 43 | if (Max) Max.outlet("ready"); 44 | }); 45 | -------------------------------------------------------------------------------- /express/express.maxproj: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "express", 3 | "version" : 1, 4 | "creationdate" : 3592207919, 5 | "modificationdate" : 3592207948, 6 | "viewrect" : [ 25.0, 70.0, 300.0, 500.0 ], 7 | "autoorganize" : 0, 8 | "hideprojectwindow" : 0, 9 | "showdependencies" : 1, 10 | "autolocalize" : 0, 11 | "contents" : { 12 | "patchers" : { 13 | "express.maxpat" : { 14 | "kind" : "patcher", 15 | "local" : 1, 16 | "toplevel" : 1 17 | } 18 | 19 | } 20 | 21 | } 22 | , 23 | "layout" : { 24 | 25 | } 26 | , 27 | "searchpath" : { 28 | 29 | } 30 | , 31 | "detailsvisible" : 0, 32 | "amxdtype" : 1633771873, 33 | "readonly" : 0, 34 | "devpathtype" : 0, 35 | "devpath" : ".", 36 | "sortmode" : 0 37 | } 38 | -------------------------------------------------------------------------------- /file-upload/.gitignore: -------------------------------------------------------------------------------- 1 | _filesin 2 | -------------------------------------------------------------------------------- /file-upload/README.md: -------------------------------------------------------------------------------- 1 | # file-upload 2 | 3 | Handle an uploaded audio file. 4 | 5 | Note: Before you run this example, make sure that you run the npm install embedded into the patcher file, by clicking on the [script npm install] message. 6 | 7 | *** 8 | 9 | ## Files 10 | 11 | `file-upload.maxpat` : The Max patch to run the example.
12 | `file-upload.js` : The launcher JS for the NodeJS script.
13 | `app.js` : The NodeJS/Express script that runs the application.
14 | `package.json` : The Node package file.
15 | `README.md` : This file!
16 | 17 | ## Folders 18 | 19 | `/public` : The browser-facing content served up by Node.
20 | `/routes` : The Express routing functions for each endpoint.
21 | `/views` : The HTML/EJS templates used by the routing functions.
22 | 23 | *** 24 | 25 | ## Usage 26 | 27 | 1. Launch the `file-upload.maxpat` Max patch. 28 | 2. (First time only...) Click on the [script npm install] message at the lower-right to load the required packages and libraries. 29 | 3. Click on the [script start] message at the top-left to start the Node process running. 30 | 4. Launch a browser, and type in the URL "localhost:3000". You should see a file upload form. 31 | 5. Select an audio file and upload it. You should hear it play from the patch. If you do not hear the file play, check the Max window for any errors. 32 | 33 | Note: The files are moved to a folder named "_filesin" inside the package folder, and are not deleted after they are played. You may wish to occasionally clear out that folder if you use this patch a lot! 34 | -------------------------------------------------------------------------------- /file-upload/app.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------ 2 | // app.js - This is a generic Node application provide by the Express cli, 3 | // routing to both the default and 'content' functional locations. 4 | // NOTE: This does use EJS-based templating; if you choose to use 5 | // something else (like Angular or React), you will need to change 6 | // the view engine. 7 | // ------------------------------------------------------------------------ 8 | 9 | 10 | var express = require("express"); 11 | var path = require("path"); 12 | var logger = require("morgan"); 13 | var cookieParser = require("cookie-parser"); 14 | var bodyParser = require("body-parser"); 15 | 16 | // This is the additional package we need for uploading 17 | const fileUpload = require("express-fileupload"); 18 | 19 | // These are the routes that contain the application logic 20 | var index = require("./routes/index"); 21 | var upload = require("./routes/upload"); 22 | 23 | var app = express(); 24 | 25 | // view engine setup 26 | app.set("views", path.join(__dirname, "views")); 27 | app.set("view engine", "ejs"); 28 | 29 | // deal with the Express middleware 30 | app.use(logger("dev")); 31 | app.use(bodyParser.json()); 32 | app.use(bodyParser.urlencoded({ extended: false })); 33 | app.use(cookieParser()); 34 | app.use(express.static(path.join(__dirname, "public"))); 35 | app.use(fileUpload()); 36 | 37 | // These are the routes used based on the incoming URL. 38 | app.use("/", index); 39 | app.use("/upload", upload); 40 | 41 | // catch 404 and forward to error handler 42 | app.use(function (req, res, next) { 43 | var err = new Error("Not Found"); 44 | err.status = 404; 45 | next(err); 46 | }); 47 | 48 | // error handler 49 | app.use(function (err, req, res, next) { 50 | // set locals, only providing error in development 51 | res.locals.message = err.message; 52 | res.locals.error = req.app.get("env") === "development" ? err : {}; 53 | 54 | // render the error page 55 | res.status(err.status || 500); 56 | res.render("error"); 57 | }); 58 | 59 | module.exports = app; 60 | -------------------------------------------------------------------------------- /file-upload/file-upload.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // --------------------------------------------------------------------- 4 | // file-upload.js - the starter JS for the file-upload Max project (as 5 | // created by the Express CLI). 6 | // 7 | // For the node app, check out the app.js at the top level directory. 8 | // For the functionality, check upload.js in the routes directory. 9 | // 10 | // --------------------------------------------------------------------- 11 | 12 | 13 | // Module dependencies 14 | // ------------------- 15 | var app = require("./app"); 16 | var debug = require("debug")("uploader:server"); 17 | var http = require("http"); 18 | 19 | 20 | // Set up any internal-use folders that are required 21 | // ------------------------------------------------- 22 | var fs = require("fs"); 23 | if (!fs.existsSync("./_filesin")) { 24 | fs.mkdirSync("./_filesin"); 25 | } 26 | 27 | // Normalize a port into a number, string, or false 28 | // ------------------------------------------------ 29 | function normalizePort(val) { 30 | var port = parseInt(val, 10); 31 | 32 | if (isNaN(port)) { 33 | // named pipe 34 | return val; 35 | } 36 | 37 | if (port >= 0) { 38 | // port number 39 | return port; 40 | } 41 | 42 | return false; 43 | } 44 | 45 | // Get port from environment and store in Express 46 | // ---------------------------------------------- 47 | var port = normalizePort(process.env.PORT || "3000"); 48 | app.set("port", port); 49 | 50 | // Create HTTP server 51 | // ------------------ 52 | var server = http.createServer(app); 53 | 54 | // Event listener for HTTP server "error" event 55 | // -------------------------------------------- 56 | function onError(error) { 57 | if (error.syscall !== "listen") { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === "string" 62 | ? "Pipe " + port 63 | : "Port " + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case "EACCES": 68 | console.error(bind + " requires elevated privileges"); 69 | process.exit(1); 70 | break; 71 | case "EADDRINUSE": 72 | console.error(bind + " is already in use"); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | // Event listener for HTTP server "listening" event 81 | // ------------------------------------------------ 82 | function onListening() { 83 | var addr = server.address(); 84 | var bind = typeof addr === "string" 85 | ? "pipe " + addr 86 | : "port " + addr.port; 87 | debug("Listening on " + bind); 88 | } 89 | 90 | // Listen on provided port, on all network interfaces 91 | // -------------------------------------------------- 92 | server.listen(port); 93 | server.on("error", onError); 94 | server.on("listening", onListening); 95 | -------------------------------------------------------------------------------- /file-upload/file-upload.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" : [ 275.0, 243.0, 802.0, 518.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-16", 43 | "maxclass" : "message", 44 | "numinlets" : 2, 45 | "numoutlets" : 1, 46 | "outlettype" : [ "" ], 47 | "patching_rect" : [ 53.0, 222.62713623046875, 65.0, 22.0 ], 48 | "text" : "open $1, 1" 49 | } 50 | 51 | } 52 | , { 53 | "box" : { 54 | "id" : "obj-14", 55 | "maxclass" : "newobj", 56 | "numinlets" : 1, 57 | "numoutlets" : 1, 58 | "outlettype" : [ "" ], 59 | "patching_rect" : [ 144.0, 317.0, 70.0, 22.0 ], 60 | "text" : "loadmess 1" 61 | } 62 | 63 | } 64 | , { 65 | "box" : { 66 | "id" : "obj-18", 67 | "maxclass" : "newobj", 68 | "numinlets" : 1, 69 | "numoutlets" : 0, 70 | "patching_rect" : [ 130.0, 222.62713623046875, 76.0, 22.0 ], 71 | "text" : "print FILE-->" 72 | } 73 | 74 | } 75 | , { 76 | "box" : { 77 | "id" : "obj-12", 78 | "maxclass" : "newobj", 79 | "numinlets" : 2, 80 | "numoutlets" : 0, 81 | "patching_rect" : [ 53.0, 384.0, 35.0, 22.0 ], 82 | "text" : "dac~" 83 | } 84 | 85 | } 86 | , { 87 | "box" : { 88 | "id" : "obj-11", 89 | "maxclass" : "message", 90 | "numinlets" : 2, 91 | "numoutlets" : 1, 92 | "outlettype" : [ "" ], 93 | "patching_rect" : [ 91.0, 317.0, 40.0, 22.0 ], 94 | "text" : "fclose" 95 | } 96 | 97 | } 98 | , { 99 | "box" : { 100 | "id" : "obj-9", 101 | "maxclass" : "newobj", 102 | "numinlets" : 1, 103 | "numoutlets" : 2, 104 | "outlettype" : [ "", "" ], 105 | "patching_rect" : [ 53.0, 159.0, 31.0, 22.0 ], 106 | "text" : "t s s" 107 | } 108 | 109 | } 110 | , { 111 | "box" : { 112 | "id" : "obj-8", 113 | "maxclass" : "newobj", 114 | "numinlets" : 2, 115 | "numoutlets" : 3, 116 | "outlettype" : [ "signal", "signal", "bang" ], 117 | "patching_rect" : [ 53.0, 275.5, 57.0, 22.0 ], 118 | "saved_object_attributes" : { 119 | "basictuning" : 440, 120 | "followglobaltempo" : 0, 121 | "formantcorrection" : 0, 122 | "mode" : "basic", 123 | "originallength" : [ 0.0, "ticks" ], 124 | "originaltempo" : 120.0, 125 | "pitchcorrection" : 0, 126 | "quality" : "basic", 127 | "timestretch" : [ 0 ] 128 | } 129 | , 130 | "text" : "sfplay~ 2" 131 | } 132 | 133 | } 134 | , { 135 | "box" : { 136 | "id" : "obj-25", 137 | "maxclass" : "newobj", 138 | "numinlets" : 1, 139 | "numoutlets" : 0, 140 | "patching_rect" : [ 635.5, 461.0, 61.0, 22.0 ], 141 | "text" : "s to_node" 142 | } 143 | 144 | } 145 | , { 146 | "box" : { 147 | "id" : "obj-23", 148 | "maxclass" : "message", 149 | "numinlets" : 2, 150 | "numoutlets" : 1, 151 | "outlettype" : [ "" ], 152 | "patching_rect" : [ 635.5, 430.0, 98.0, 22.0 ], 153 | "text" : "script npm install" 154 | } 155 | 156 | } 157 | , { 158 | "box" : { 159 | "fontface" : 1, 160 | "id" : "obj-13", 161 | "maxclass" : "comment", 162 | "numinlets" : 1, 163 | "numoutlets" : 0, 164 | "patching_rect" : [ 402.5, 431.0, 231.0, 20.0 ], 165 | "text" : "Click this to install necessary libraries:" 166 | } 167 | 168 | } 169 | , { 170 | "box" : { 171 | "id" : "obj-33", 172 | "linecount" : 3, 173 | "maxclass" : "comment", 174 | "numinlets" : 1, 175 | "numoutlets" : 0, 176 | "patching_rect" : [ 402.5, 366.0, 349.0, 47.0 ], 177 | "presentation_linecount" : 3, 178 | "text" : "When you upload a file, it is placed in the _filesin folder, and a message is sent with the full path to the file. You can use that to load, play or otherwise manipulate the file within Max." 179 | } 180 | 181 | } 182 | , { 183 | "box" : { 184 | "id" : "obj-31", 185 | "linecount" : 3, 186 | "maxclass" : "comment", 187 | "numinlets" : 1, 188 | "numoutlets" : 0, 189 | "patching_rect" : [ 402.5, 317.0, 351.0, 47.0 ], 190 | "presentation_linecount" : 3, 191 | "text" : "This Max patch is the supporting system for an online file uploader. When the script is running, you can get to a webpage at localhost:3000." 192 | } 193 | 194 | } 195 | , { 196 | "box" : { 197 | "fontsize" : 24.0, 198 | "id" : "obj-29", 199 | "maxclass" : "comment", 200 | "numinlets" : 1, 201 | "numoutlets" : 0, 202 | "patching_rect" : [ 402.5, 274.0, 119.0, 33.0 ], 203 | "text" : "file-upload", 204 | "underline" : 1 205 | } 206 | 207 | } 208 | , { 209 | "box" : { 210 | "id" : "obj-24", 211 | "maxclass" : "newobj", 212 | "numinlets" : 0, 213 | "numoutlets" : 1, 214 | "outlettype" : [ "" ], 215 | "patching_rect" : [ 114.0, 73.0, 59.0, 22.0 ], 216 | "text" : "r to_node" 217 | } 218 | 219 | } 220 | , { 221 | "box" : { 222 | "id" : "obj-4", 223 | "maxclass" : "message", 224 | "numinlets" : 2, 225 | "numoutlets" : 1, 226 | "outlettype" : [ "" ], 227 | "patching_rect" : [ 83.5, 41.0, 63.0, 22.0 ], 228 | "text" : "script stop" 229 | } 230 | 231 | } 232 | , { 233 | "box" : { 234 | "id" : "obj-3", 235 | "maxclass" : "message", 236 | "numinlets" : 2, 237 | "numoutlets" : 1, 238 | "outlettype" : [ "" ], 239 | "patching_rect" : [ 53.0, 13.0, 64.0, 22.0 ], 240 | "text" : "script start" 241 | } 242 | 243 | } 244 | , { 245 | "box" : { 246 | "bgmode" : 0, 247 | "border" : 0, 248 | "clickthrough" : 0, 249 | "enablehscroll" : 0, 250 | "enablevscroll" : 0, 251 | "id" : "obj-6", 252 | "lockeddragscroll" : 0, 253 | "maxclass" : "bpatcher", 254 | "name" : "n4m.monitor.maxpat", 255 | "numinlets" : 1, 256 | "numoutlets" : 0, 257 | "offset" : [ 0.0, 0.0 ], 258 | "patching_rect" : [ 353.5, 30.627120971679688, 400.0, 220.0 ], 259 | "viewvisibility" : 1 260 | } 261 | 262 | } 263 | , { 264 | "box" : { 265 | "id" : "obj-1", 266 | "maxclass" : "newobj", 267 | "numinlets" : 1, 268 | "numoutlets" : 2, 269 | "outlettype" : [ "", "" ], 270 | "patching_rect" : [ 53.0, 112.0, 138.0, 22.0 ], 271 | "saved_object_attributes" : { 272 | "autostart" : 0, 273 | "defer" : 0, 274 | "node" : "", 275 | "npm" : "", 276 | "watch" : 0 277 | } 278 | , 279 | "text" : "node.script file-upload.js" 280 | } 281 | 282 | } 283 | ], 284 | "lines" : [ { 285 | "patchline" : { 286 | "destination" : [ "obj-6", 0 ], 287 | "midpoints" : [ 181.5, 143.999999971679699, 282.75, 143.999999971679699, 282.75, 12.499999971679689, 363.0, 12.499999971679689 ], 288 | "source" : [ "obj-1", 1 ] 289 | } 290 | 291 | } 292 | , { 293 | "patchline" : { 294 | "destination" : [ "obj-9", 0 ], 295 | "source" : [ "obj-1", 0 ] 296 | } 297 | 298 | } 299 | , { 300 | "patchline" : { 301 | "destination" : [ "obj-8", 0 ], 302 | "midpoints" : [ 100.5, 349.000015202148461, 39.5, 349.000015202148461, 39.5, 264.500015202148461, 62.5, 264.500015202148461 ], 303 | "source" : [ "obj-11", 0 ] 304 | } 305 | 306 | } 307 | , { 308 | "patchline" : { 309 | "destination" : [ "obj-12", 0 ], 310 | "source" : [ "obj-14", 0 ] 311 | } 312 | 313 | } 314 | , { 315 | "patchline" : { 316 | "destination" : [ "obj-8", 0 ], 317 | "source" : [ "obj-16", 0 ] 318 | } 319 | 320 | } 321 | , { 322 | "patchline" : { 323 | "destination" : [ "obj-25", 0 ], 324 | "source" : [ "obj-23", 0 ] 325 | } 326 | 327 | } 328 | , { 329 | "patchline" : { 330 | "destination" : [ "obj-1", 0 ], 331 | "source" : [ "obj-24", 0 ] 332 | } 333 | 334 | } 335 | , { 336 | "patchline" : { 337 | "destination" : [ "obj-1", 0 ], 338 | "source" : [ "obj-3", 0 ] 339 | } 340 | 341 | } 342 | , { 343 | "patchline" : { 344 | "destination" : [ "obj-1", 0 ], 345 | "source" : [ "obj-4", 0 ] 346 | } 347 | 348 | } 349 | , { 350 | "patchline" : { 351 | "destination" : [ "obj-11", 0 ], 352 | "source" : [ "obj-8", 2 ] 353 | } 354 | 355 | } 356 | , { 357 | "patchline" : { 358 | "destination" : [ "obj-12", 1 ], 359 | "source" : [ "obj-8", 1 ] 360 | } 361 | 362 | } 363 | , { 364 | "patchline" : { 365 | "destination" : [ "obj-12", 0 ], 366 | "source" : [ "obj-8", 0 ] 367 | } 368 | 369 | } 370 | , { 371 | "patchline" : { 372 | "destination" : [ "obj-16", 0 ], 373 | "source" : [ "obj-9", 0 ] 374 | } 375 | 376 | } 377 | , { 378 | "patchline" : { 379 | "destination" : [ "obj-18", 0 ], 380 | "source" : [ "obj-9", 1 ] 381 | } 382 | 383 | } 384 | ], 385 | "dependency_cache" : [ { 386 | "name" : "n4m.monitor.maxpat", 387 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 388 | "type" : "JSON", 389 | "implicit" : 1 390 | } 391 | , { 392 | "name" : "resize_n4m_monitor_patcher.js", 393 | "bootpath" : "C74:/packages/Node For Max/patchers/debug-monitor", 394 | "type" : "TEXT", 395 | "implicit" : 1 396 | } 397 | ], 398 | "autosave" : 0 399 | } 400 | 401 | } 402 | -------------------------------------------------------------------------------- /file-upload/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "file-upload", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./file-upload.js" 7 | }, 8 | "dependencies": { 9 | "body-parser": "^1.18.3", 10 | "cookie-parser": "~1.4.3", 11 | "debug": "~2.6.3", 12 | "ejs": "~2.5.6", 13 | "express": "^4.16.4", 14 | "express-fileupload": "^0.3.0", 15 | "morgan": "^1.9.1", 16 | "serve-favicon": "~2.4.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /file-upload/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | /* The core style sheet for our test site */ 2 | 3 | head {} 4 | 5 | body { 6 | background-color: #333333; 7 | font-family: "Trebuchet MS", Helvetica, sans-serif; 8 | font-size: 1em; 9 | background-image: url('images/page-bg.gif'); 10 | margin: 0; 11 | } 12 | 13 | #header { 14 | background-color: #6666AA; 15 | background-position: 10px center; 16 | padding-right: 20px; 17 | padding-left: 20px; 18 | padding-top: 0px; 19 | padding-bottom: 0px; 20 | color: #62666e; 21 | text-align: right; 22 | font-size: 20pt; 23 | font-family: "Trebuchet MS", Helvetica, sans-serif; 24 | margin: 20px; 25 | -webkit-border-radius: 20px; 26 | -moz-border-radius: 20px; 27 | border-radius: 20px; 28 | border: 4px solid; 29 | border-color: #FFFFFF } 30 | 31 | .top { 32 | padding-top: 5px; 33 | font-size: 2.5em; 34 | margin: 0px; 35 | text-align: left; 36 | color: #ffffff; } 37 | 38 | .bottom { 39 | padding-bottom: 15px; 40 | font-style: italic; 41 | font-size: 0.7em; 42 | margin: 0px; 43 | text-align: left; 44 | color: #ffffff; } 45 | 46 | #content { 47 | width: auto; } 48 | 49 | #navbar { 50 | position: relative; 51 | float: right; 52 | border: 1px none; 53 | -webkit-border-radius: 15px; 54 | -moz-border-radius: 15px; 55 | border-radius: 15px; 56 | background-color: #6666aa; 57 | margin-right: 24px; 58 | margin-top: 4px; 59 | padding-bottom: 20px; 60 | font-size: 1.2em; 61 | margin-left: 10px; } 62 | 63 | #navbar ul li { 64 | list-style-type: none; 65 | padding-top: 0px; 66 | padding-right: 20px; 67 | padding-bottom: 0px; 68 | padding-left: 20px; 69 | margin-top: 10px; 70 | font-size: 0.8em; 71 | color: #ffffff; } 72 | 73 | #navbar ul li a:link, #navbar ul li a:visited { 74 | display: block; 75 | font-weight: bold; 76 | padding-top: 0px; 77 | padding-bottom: 0px; 78 | padding-left: 0px; 79 | padding-right: 20px; 80 | color: #cccccc; 81 | text-decoration: none; 82 | font-size: 1.0em; 83 | font-style: italic; } 84 | 85 | #navbar ul li a:hover { 86 | text-decoration: underline; 87 | } 88 | 89 | #navbar ul li a:active { 90 | } 91 | 92 | #navbar ul { 93 | margin: 0px; 94 | padding: 0px; 95 | } 96 | 97 | 98 | #main-text { 99 | padding: 20px; 100 | z-index: 1; 101 | border: 4px #6666aa solid; 102 | -webkit-border-radius: 20px; 103 | -moz-border-radius: 20px; 104 | border-radius: 20px; 105 | background-color: #ffffff; 106 | margin-left: 20px; 107 | margin-right: 20px; } 108 | 109 | 110 | h1 { 111 | font-size: 1.5em; 112 | text-align: left; 113 | font-style: normal; 114 | color: #ffffff; 115 | background-color: #6666aa; 116 | text-decoration: none; 117 | width: 50%; 118 | padding-left: 10px; 119 | line-height: 1.5em; 120 | margin-left: -20px; } 121 | 122 | h2 { 123 | font-style: italic; 124 | font-size: 1em; 125 | } 126 | 127 | p { 128 | } 129 | 130 | p.footer { 131 | font-size: 1.0em; 132 | text-align: center; 133 | font-weight: bold; } 134 | 135 | .footer {} 136 | 137 | a {} 138 | -------------------------------------------------------------------------------- /file-upload/routes/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var express = require("express"); 4 | var router = new express.Router(); 5 | 6 | /* GET home page. */ 7 | router.get("/", function (req, res, next) { 8 | res.render("index", {status: ""}); 9 | }); 10 | 11 | module.exports = router; 12 | -------------------------------------------------------------------------------- /file-upload/routes/upload.js: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------- 2 | // 3 | // upload.js - the route handler for an upload 'request'. 4 | // 5 | // -------------------------------------------------------------------------- 6 | 7 | 8 | var express = require("express"); 9 | var router = new express.Router(); 10 | const path = require("path"); 11 | const Max = require("max-api"); 12 | 13 | 14 | // deal with a post request to the / route 15 | // --------------------------------------- 16 | router.post("/", function (req, res, next) { 17 | // bail if there are no files... 18 | if (!req.files) { 19 | res.render("status", {status: "No files uploaded"}); 20 | return; 21 | } 22 | 23 | // The name of the input field (i.e. "sampleFile") is used to 24 | // retrieve the uploaded file 25 | let sampleFile = req.files.sampleFile; 26 | let newPath = path.join(process.cwd(), "_filesin", sampleFile.name); 27 | 28 | // Move the file to a specific location (to make it easy to handle) 29 | sampleFile.mv(newPath, function (err) { 30 | if (err) { 31 | res.render("status", {status: err}); 32 | } else { 33 | Max.outlet(newPath); 34 | res.render("status", {status: sampleFile.name + " upload complete."}); 35 | } 36 | }); 37 | }); 38 | 39 | module.exports = router; 40 | -------------------------------------------------------------------------------- /file-upload/views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /file-upload/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | File-Upload 5 | 6 | 7 | 8 | 9 | 13 |
14 |
15 |
20 | 21 |

22 | 23 |
24 |
25 |
26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /file-upload/views/status.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | File-Upload 5 | 6 | 7 | 8 | 9 | 13 |
14 |
15 | Upload Status: 16 |

<%- status %>

17 |

Return to main form

18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /freesound/.env-template: -------------------------------------------------------------------------------- 1 | FREESOUND_CLIENT_KEY=your_key_here 2 | FREESOUND_CLIENT_SECRET=your_secret_here -------------------------------------------------------------------------------- /freesound/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /freesound/README.md: -------------------------------------------------------------------------------- 1 | # Freesound 2 | Search and download from Freesound, using the API 3 | 4 | ## Getting an API key 5 | Before you can do anything with Freesound, you'll need an API key. 6 | 7 | 1. Get a Freesound account. It's free. Go to www.freesound.org and sign up. 8 | 9 | 2. After you've logged in go to https://freesound.org/apiv2/apply/ 10 | 11 | 3. Fill in the form to apply for a new set of API keys. 12 | 13 | 4. You may notice that Freesound has fewer keys than Twitter. That's because 14 | Freesound only hands out client keys, not access keys. So, whereas using the 15 | access keys someone could post tweets from your Twitter account, the same 16 | isn't true for Freesound. 17 | 18 | 5. Anyway, note down the Client ID and API key. 19 | 20 | 6. In this freesound folder, you should see a file named .env-template. This is 21 | an environment file, it contains key-value pairs that other applications, like 22 | Node, can load before running. 23 | 24 | 7. Duplicate the .env-template file to a new file named ".env". It must be named 25 | ".env", not "my.env" or anything like that. Fill in this file using the API credentials 26 | you got from Freesound. 27 | 28 | 8. You should be all set. Open the Max patcher freesound.maxpat and see! 29 | -------------------------------------------------------------------------------- /freesound/attribution.md: -------------------------------------------------------------------------------- 1 | # Attribution 2 | 3 | ## Icons 4 | 5 |
Icons made by Smashicons from www.flaticon.com is licensed by CC 3.0 BY
6 |
Icons made by Freepik from www.flaticon.com is licensed by CC 3.0 BY
7 | -------------------------------------------------------------------------------- /freesound/freesound.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cycling74/n4m-examples/25073187cd13221bb673c95f3e29771fbc9a32ea/freesound/freesound.jpg -------------------------------------------------------------------------------- /freesound/fs-index.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------- 2 | // fs-index.js - Search and download from Freesound.org 3 | // 4 | // Check the README for information on how to get a Freesound API key, 5 | // which you'll need in order to get any of this to work. 6 | // --------------------------------------------------------------------- 7 | 8 | const maxAPI = require("max-api"); 9 | 10 | // Attempt to load the dotenv module, which is needed to load the .env file containing the Freesound API keys. 11 | let dotenv_module; 12 | try { 13 | dotenv_module = require("dotenv"); 14 | dotenv_module.config(); 15 | } catch (e) { 16 | maxAPI.post(e, maxAPI.POST_LEVELS.ERROR); 17 | maxAPI.post("Could not load the dotenv module. Please be sure to send the message 'script npm install' to the node.script object to download node modules", maxAPI.POST_LEVELS.ERROR); 18 | process.exit(1); 19 | } 20 | 21 | // Make sure that the API keys are loaded. Dotenv will put them in process.env if they are. 22 | if (!process.env.FREESOUND_CLIENT_ID) { 23 | maxAPI.post("No value for key FREESOUND_CLIENT_ID in .env file. Please make sure to create a file called .env with a Freesound API Client ID.", maxAPI.POST_LEVELS.ERROR); 24 | process.exit(1); 25 | } 26 | 27 | if (!process.env.FREESOUND_CLIENT_KEY) { 28 | maxAPI.post("No value for key FREESOUND_CLIENT_KEY in .env file. Please make sure to create a file called .env with a Freesound API Key.", maxAPI.POST_LEVELS.ERROR); 29 | process.exit(1); 30 | } 31 | 32 | // Create an Axios HTTP Request instance using the API keys from the process. 33 | const axios = require("axios").default; 34 | const freesoundRequest = axios.create({ 35 | baseURL: "https://freesound.org/apiv2", 36 | headers: { 37 | Authorization: `Token ${process.env.FREESOUND_CLIENT_KEY}` 38 | } 39 | }); 40 | 41 | const fs = require("fs"); 42 | const tmp = require("tmp"); 43 | const path = require("path"); 44 | const { promisify } = require("util"); 45 | 46 | const tmpName = promisify(tmp.tmpName); 47 | 48 | // Freesound won't let us download directly to a Max buffer, so we have to put the file somewhere. 49 | async function saveToFile(url, filepath) { 50 | const writer = fs.createWriteStream(filepath); 51 | const response = await axios.get(url, { responseType: "stream" }); 52 | response.data.pipe(writer); 53 | return new Promise((resolve, reject) => { 54 | writer.on("finish", resolve); 55 | writer.on("error", reject); 56 | }); 57 | } 58 | 59 | // Declare handlers 60 | maxAPI.addHandlers({ 61 | search: async (query, duration) => { 62 | try { 63 | // See https://freesound.org/docs/api/resources_apiv2.html#search-resources 64 | // For Details on the Freesound API Query Params 65 | const { data } = await freesoundRequest.get("/search/text/", { 66 | params: { 67 | filter: duration ? `duration:[0.0 TO ${duration}]` : null, 68 | fields: "id,name,url,previews", 69 | page: 1, 70 | query, 71 | sort: "score" 72 | } 73 | }); 74 | 75 | const results = data.results; 76 | // Output the results as a list 77 | await maxAPI.outlet(["search", query, results]); 78 | } catch (err) { 79 | await maxAPI.post("Failed to search Freesound:", maxAPI.POST_LEVELS.ERROR); 80 | await maxAPI.post(err.message, maxAPI.POST_LEVELS.ERROR); 81 | } 82 | }, 83 | preview: async (key, url) => { 84 | try { 85 | await maxAPI.outlet("preview", key, "start", url); 86 | const dlPath = await tmpName({ postfix: ".mp3" }); 87 | await saveToFile(url, dlPath); 88 | await maxAPI.outlet("preview", key, "complete", url, dlPath); 89 | } catch (err) { 90 | await maxAPI.post(`Failed to preview file: ${key}`, maxAPI.POST_LEVELS.WARN); 91 | await maxAPI.post(err.message, maxAPI.POST_LEVELS.WARN); 92 | } 93 | }, 94 | download: async (key, name, url, dlPath) => { 95 | try { 96 | await maxAPI.outlet("download", key, "start", url, dlPath); 97 | const outpath = path.join(dlPath, `${name}.mp3`); 98 | await saveToFile(url, outpath); 99 | await maxAPI.outlet("download", key, "complete", url, dlPath); 100 | } catch (err) { 101 | await maxAPI.post(err.message, maxAPI.POST_LEVELS.WARN); 102 | } 103 | } 104 | }); 105 | -------------------------------------------------------------------------------- /freesound/hat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cycling74/n4m-examples/25073187cd13221bb673c95f3e29771fbc9a32ea/freesound/hat.png -------------------------------------------------------------------------------- /freesound/kick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cycling74/n4m-examples/25073187cd13221bb673c95f3e29771fbc9a32ea/freesound/kick.png -------------------------------------------------------------------------------- /freesound/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "n4m-freesound", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "n4m-freesound", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "axios": "^1.3.4", 13 | "dotenv": "^6.0.0", 14 | "tmp": "0.0.33" 15 | } 16 | }, 17 | "node_modules/asynckit": { 18 | "version": "0.4.0", 19 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 20 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 21 | }, 22 | "node_modules/axios": { 23 | "version": "1.3.4", 24 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", 25 | "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", 26 | "dependencies": { 27 | "follow-redirects": "^1.15.0", 28 | "form-data": "^4.0.0", 29 | "proxy-from-env": "^1.1.0" 30 | } 31 | }, 32 | "node_modules/combined-stream": { 33 | "version": "1.0.8", 34 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 35 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 36 | "dependencies": { 37 | "delayed-stream": "~1.0.0" 38 | }, 39 | "engines": { 40 | "node": ">= 0.8" 41 | } 42 | }, 43 | "node_modules/delayed-stream": { 44 | "version": "1.0.0", 45 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 46 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 47 | "engines": { 48 | "node": ">=0.4.0" 49 | } 50 | }, 51 | "node_modules/dotenv": { 52 | "version": "6.0.0", 53 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.0.0.tgz", 54 | "integrity": "sha512-FlWbnhgjtwD+uNLUGHbMykMOYQaTivdHEmYwAKFjn6GKe/CqY0fNae93ZHTd20snh9ZLr8mTzIL9m0APQ1pjQg==", 55 | "engines": { 56 | "node": ">=6" 57 | } 58 | }, 59 | "node_modules/follow-redirects": { 60 | "version": "1.15.2", 61 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", 62 | "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", 63 | "funding": [ 64 | { 65 | "type": "individual", 66 | "url": "https://github.com/sponsors/RubenVerborgh" 67 | } 68 | ], 69 | "engines": { 70 | "node": ">=4.0" 71 | }, 72 | "peerDependenciesMeta": { 73 | "debug": { 74 | "optional": true 75 | } 76 | } 77 | }, 78 | "node_modules/form-data": { 79 | "version": "4.0.0", 80 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 81 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 82 | "dependencies": { 83 | "asynckit": "^0.4.0", 84 | "combined-stream": "^1.0.8", 85 | "mime-types": "^2.1.12" 86 | }, 87 | "engines": { 88 | "node": ">= 6" 89 | } 90 | }, 91 | "node_modules/mime-db": { 92 | "version": "1.52.0", 93 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 94 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 95 | "engines": { 96 | "node": ">= 0.6" 97 | } 98 | }, 99 | "node_modules/mime-types": { 100 | "version": "2.1.35", 101 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 102 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 103 | "dependencies": { 104 | "mime-db": "1.52.0" 105 | }, 106 | "engines": { 107 | "node": ">= 0.6" 108 | } 109 | }, 110 | "node_modules/os-tmpdir": { 111 | "version": "1.0.2", 112 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 113 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", 114 | "engines": { 115 | "node": ">=0.10.0" 116 | } 117 | }, 118 | "node_modules/proxy-from-env": { 119 | "version": "1.1.0", 120 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 121 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 122 | }, 123 | "node_modules/tmp": { 124 | "version": "0.0.33", 125 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", 126 | "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", 127 | "dependencies": { 128 | "os-tmpdir": "~1.0.2" 129 | }, 130 | "engines": { 131 | "node": ">=0.6.0" 132 | } 133 | } 134 | }, 135 | "dependencies": { 136 | "asynckit": { 137 | "version": "0.4.0", 138 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 139 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 140 | }, 141 | "axios": { 142 | "version": "1.3.4", 143 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", 144 | "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", 145 | "requires": { 146 | "follow-redirects": "^1.15.0", 147 | "form-data": "^4.0.0", 148 | "proxy-from-env": "^1.1.0" 149 | } 150 | }, 151 | "combined-stream": { 152 | "version": "1.0.8", 153 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 154 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 155 | "requires": { 156 | "delayed-stream": "~1.0.0" 157 | } 158 | }, 159 | "delayed-stream": { 160 | "version": "1.0.0", 161 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 162 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" 163 | }, 164 | "dotenv": { 165 | "version": "6.0.0", 166 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.0.0.tgz", 167 | "integrity": "sha512-FlWbnhgjtwD+uNLUGHbMykMOYQaTivdHEmYwAKFjn6GKe/CqY0fNae93ZHTd20snh9ZLr8mTzIL9m0APQ1pjQg==" 168 | }, 169 | "follow-redirects": { 170 | "version": "1.15.2", 171 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", 172 | "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" 173 | }, 174 | "form-data": { 175 | "version": "4.0.0", 176 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 177 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 178 | "requires": { 179 | "asynckit": "^0.4.0", 180 | "combined-stream": "^1.0.8", 181 | "mime-types": "^2.1.12" 182 | } 183 | }, 184 | "mime-db": { 185 | "version": "1.52.0", 186 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 187 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 188 | }, 189 | "mime-types": { 190 | "version": "2.1.35", 191 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 192 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 193 | "requires": { 194 | "mime-db": "1.52.0" 195 | } 196 | }, 197 | "os-tmpdir": { 198 | "version": "1.0.2", 199 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 200 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" 201 | }, 202 | "proxy-from-env": { 203 | "version": "1.1.0", 204 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 205 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 206 | }, 207 | "tmp": { 208 | "version": "0.0.33", 209 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", 210 | "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", 211 | "requires": { 212 | "os-tmpdir": "~1.0.2" 213 | } 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /freesound/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "n4m-freesound", 3 | "version": "1.0.0", 4 | "description": "Examples using the freesound API with Node for Max", 5 | "main": "fs-index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Cycling74/n4m-examples.git" 12 | }, 13 | "keywords": [ 14 | "n4m", 15 | "Node", 16 | "for", 17 | "Max", 18 | "freesound" 19 | ], 20 | "author": "Cycling '74", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/Cycling74/n4m-examples/issues" 24 | }, 25 | "homepage": "https://github.com/Cycling74/n4m-examples#readme", 26 | "dependencies": { 27 | "axios": "^1.3.4", 28 | "dotenv": "^6.0.0", 29 | "tmp": "0.0.33" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /freesound/snare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cycling74/n4m-examples/25073187cd13221bb673c95f3e29771fbc9a32ea/freesound/snare.png -------------------------------------------------------------------------------- /giphy/.env-template: -------------------------------------------------------------------------------- 1 | GIPHY_API_KEY=your_key_here -------------------------------------------------------------------------------- /giphy/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /giphy/README.md: -------------------------------------------------------------------------------- 1 | # Giphy 2 | Search and download from Giphy, using the API 3 | 4 | ## Getting an API key 5 | Before you can do anything with Giphy, you'll need an API key. 6 | 7 | 1. Get a Giphy account. It's free. Go to www.giphy.com. 8 | 9 | 2. After you've logged in go to https://developers.giphy.com/ 10 | 11 | 3. Click "Create an App." 12 | 13 | 4. Click "Create an App" again at the top of the window—this will bring up a 14 | simple form. Fill in whatever you like and click "Create New App." 15 | 16 | 5. Giphy only has the one API key, unlike Twitter and Freesound. Copy it down. 17 | 18 | 6. In this giphy folder, you should see a file named .env-template. This is 19 | n environment file, it contains key-value pairs that other applications, like 20 | Node, can load before running. 21 | 22 | 7. Duplicate the .env-template file to a new file named ".env". It must be named 23 | ".env", not "my.env" or anything like that. Fill in this file using the key 24 | you got from Giphy. 25 | 26 | 8. You should be all set. Open the Max patcher giphy-demo.maxpat and see! 27 | -------------------------------------------------------------------------------- /giphy/attribution.md: -------------------------------------------------------------------------------- 1 | # Attribution 2 | 3 | ## Icons 4 | 5 |
Icons made by Roundicons from www.flaticon.com is licensed by CC 3.0 BY
6 | -------------------------------------------------------------------------------- /giphy/giphy-demo.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 | "rect" : [ 132.0, 80.0, 1320.0, 966.0 ], 13 | "bgcolor" : [ 0.898039, 0.898039, 0.898039, 1 ], 14 | "editing_bgcolor" : [ 0.898039, 0.898039, 0.898039, 1 ], 15 | "bglocked" : 0, 16 | "openinpresentation" : 1, 17 | "default_fontsize" : 12.0, 18 | "default_fontface" : 0, 19 | "default_fontname" : "Arial", 20 | "gridonopen" : 1, 21 | "gridsize" : [ 15.0, 15.0 ], 22 | "gridsnaponopen" : 1, 23 | "objectsnaponopen" : 1, 24 | "statusbarvisible" : 2, 25 | "toolbarvisible" : 1, 26 | "lefttoolbarpinned" : 0, 27 | "toptoolbarpinned" : 0, 28 | "righttoolbarpinned" : 0, 29 | "bottomtoolbarpinned" : 0, 30 | "toolbars_unpinned_last_save" : 0, 31 | "tallnewobj" : 0, 32 | "boxanimatetime" : 200, 33 | "enablehscroll" : 1, 34 | "enablevscroll" : 1, 35 | "devicewidth" : 0.0, 36 | "description" : "", 37 | "digest" : "", 38 | "tags" : "", 39 | "style" : "", 40 | "subpatcher_template" : "", 41 | "boxes" : [ { 42 | "box" : { 43 | "bubble" : 1, 44 | "bubbleside" : 0, 45 | "id" : "obj-2", 46 | "linecount" : 2, 47 | "maxclass" : "comment", 48 | "numinlets" : 1, 49 | "numoutlets" : 0, 50 | "patching_rect" : [ 607.5, 161.0, 150.0, 52.0 ], 51 | "presentation" : 1, 52 | "presentation_linecount" : 2, 53 | "presentation_rect" : [ 611.0, 158.0, 99.0, 52.0 ], 54 | "style" : "", 55 | "text" : "2. Press to see what's new." 56 | } 57 | 58 | } 59 | , { 60 | "box" : { 61 | "bubble" : 1, 62 | "bubbleside" : 2, 63 | "id" : "obj-7", 64 | "maxclass" : "comment", 65 | "numinlets" : 1, 66 | "numoutlets" : 0, 67 | "patching_rect" : [ 381.5, 812.0, 150.0, 39.0 ], 68 | "presentation" : 1, 69 | "presentation_rect" : [ 400.5, 825.0, 99.0, 39.0 ], 70 | "style" : "", 71 | "text" : "1. start the script" 72 | } 73 | 74 | } 75 | , { 76 | "box" : { 77 | "bubble" : 1, 78 | "bubbleside" : 2, 79 | "id" : "obj-5", 80 | "linecount" : 2, 81 | "maxclass" : "comment", 82 | "numinlets" : 1, 83 | "numoutlets" : 0, 84 | "patching_rect" : [ 463.0, 797.0, 150.0, 52.0 ], 85 | "presentation" : 1, 86 | "presentation_linecount" : 2, 87 | "presentation_rect" : [ 228.5, 812.0, 139.0, 52.0 ], 88 | "style" : "", 89 | "text" : "0. Install dependencies (one time)" 90 | } 91 | 92 | } 93 | , { 94 | "box" : { 95 | "autofit" : 1, 96 | "forceaspect" : 1, 97 | "id" : "obj-44", 98 | "maxclass" : "fpic", 99 | "numinlets" : 1, 100 | "numoutlets" : 1, 101 | "outlettype" : [ "jit_matrix" ], 102 | "patching_rect" : [ 779.0, 180.03125, 195.522385000000014, 40.937499359375003 ], 103 | "pic" : "/Users/starakaj/git/n4m-examples/giphy/powered_by_giphy.gif", 104 | "presentation" : 1, 105 | "presentation_rect" : [ 767.0, 118.0, 195.522384643554688, 40.937499284744263 ] 106 | } 107 | 108 | } 109 | , { 110 | "box" : { 111 | "fontsize" : 32.0, 112 | "id" : "obj-19", 113 | "maxclass" : "comment", 114 | "numinlets" : 1, 115 | "numoutlets" : 0, 116 | "patching_rect" : [ 779.0, 51.0, 371.0, 42.0 ], 117 | "presentation" : 1, 118 | "presentation_rect" : [ 383.5, 66.0, 187.0, 42.0 ], 119 | "style" : "", 120 | "text" : "See what's" 121 | } 122 | 123 | } 124 | , { 125 | "box" : { 126 | "fontsize" : 32.0, 127 | "id" : "obj-13", 128 | "maxclass" : "comment", 129 | "numinlets" : 1, 130 | "numoutlets" : 0, 131 | "patching_rect" : [ 779.0, 108.0, 371.0, 42.0 ], 132 | "presentation" : 1, 133 | "presentation_rect" : [ 767.0, 66.0, 216.0, 42.0 ], 134 | "style" : "", 135 | "text" : "and trending!" 136 | } 137 | 138 | } 139 | , { 140 | "box" : { 141 | "handoff" : "", 142 | "hltcolor" : [ 1.0, 1.0, 1.0, 0.501961 ], 143 | "id" : "obj-11", 144 | "maxclass" : "ubutton", 145 | "numinlets" : 1, 146 | "numoutlets" : 4, 147 | "outlettype" : [ "bang", "bang", "", "int" ], 148 | "parameter_enable" : 0, 149 | "patching_rect" : [ 562.0, 98.0, 128.0, 128.0 ], 150 | "presentation" : 1, 151 | "presentation_rect" : [ 596.5, 23.0, 128.0, 128.0 ] 152 | } 153 | 154 | } 155 | , { 156 | "box" : { 157 | "autofit" : 1, 158 | "id" : "obj-10", 159 | "maxclass" : "fpic", 160 | "numinlets" : 1, 161 | "numoutlets" : 1, 162 | "outlettype" : [ "jit_matrix" ], 163 | "patching_rect" : [ 562.0, 98.0, 128.0, 128.0 ], 164 | "pic" : "Macintosh HD:/Users/starakaj/git/n4m-examples/giphy/new.png", 165 | "presentation" : 1, 166 | "presentation_rect" : [ 596.5, 23.0, 128.0, 128.0 ] 167 | } 168 | 169 | } 170 | , { 171 | "box" : { 172 | "id" : "obj-46", 173 | "linecount" : 2, 174 | "maxclass" : "message", 175 | "numinlets" : 2, 176 | "numoutlets" : 1, 177 | "outlettype" : [ "" ], 178 | "patching_rect" : [ 1108.0, 183.0, 223.0, 35.0 ], 179 | "presentation_linecount" : 2, 180 | "presentation_rect" : [ 1108.0, 183.0, 223.0, 35.0 ], 181 | "style" : "", 182 | "text" : "window size 300 80 1620 1046, window exec" 183 | } 184 | 185 | } 186 | , { 187 | "box" : { 188 | "id" : "obj-42", 189 | "maxclass" : "newobj", 190 | "numinlets" : 1, 191 | "numoutlets" : 2, 192 | "outlettype" : [ "", "" ], 193 | "patching_rect" : [ 1108.0, 231.0, 100.0, 22.0 ], 194 | "presentation_rect" : [ 1108.0, 231.0, 100.0, 22.0 ], 195 | "save" : [ "#N", "thispatcher", ";", "#Q", "end", ";" ], 196 | "style" : "", 197 | "text" : "thispatcher" 198 | } 199 | 200 | } 201 | , { 202 | "box" : { 203 | "id" : "obj-54", 204 | "maxclass" : "jit.pwindow", 205 | "numinlets" : 1, 206 | "numoutlets" : 2, 207 | "outlettype" : [ "", "" ], 208 | "patching_rect" : [ 58.0, 311.0, 585.0, 441.839783000000011 ], 209 | "presentation" : 1, 210 | "presentation_rect" : [ 319.263397216796875, 224.770233154296875, 682.47320556640625, 515.45953369140625 ] 211 | } 212 | 213 | } 214 | , { 215 | "box" : { 216 | "id" : "obj-41", 217 | "maxclass" : "newobj", 218 | "numinlets" : 2, 219 | "numoutlets" : 1, 220 | "outlettype" : [ "bang" ], 221 | "patching_rect" : [ 140.5, 273.0, 119.0, 22.0 ], 222 | "presentation_rect" : [ 140.5, 273.0, 119.0, 22.0 ], 223 | "style" : "", 224 | "text" : "qmetro 30 @active 1" 225 | } 226 | 227 | } 228 | , { 229 | "box" : { 230 | "id" : "obj-40", 231 | "maxclass" : "message", 232 | "numinlets" : 2, 233 | "numoutlets" : 1, 234 | "outlettype" : [ "" ], 235 | "patching_rect" : [ 58.0, 196.0, 49.0, 22.0 ], 236 | "presentation_rect" : [ 58.0, 196.0, 49.0, 22.0 ], 237 | "style" : "", 238 | "text" : "read $1" 239 | } 240 | 241 | } 242 | , { 243 | "box" : { 244 | "id" : "obj-38", 245 | "maxclass" : "newobj", 246 | "numinlets" : 1, 247 | "numoutlets" : 2, 248 | "outlettype" : [ "jit_matrix", "" ], 249 | "patching_rect" : [ 58.0, 273.0, 53.0, 22.0 ], 250 | "presentation_rect" : [ 58.0, 273.0, 53.0, 22.0 ], 251 | "style" : "", 252 | "text" : "jit.movie" 253 | } 254 | 255 | } 256 | , { 257 | "box" : { 258 | "id" : "obj-31", 259 | "maxclass" : "newobj", 260 | "numinlets" : 2, 261 | "numoutlets" : 2, 262 | "outlettype" : [ "", "" ], 263 | "patching_rect" : [ 58.0, 145.0, 53.0, 22.0 ], 264 | "presentation_rect" : [ 58.0, 145.0, 53.0, 22.0 ], 265 | "style" : "", 266 | "text" : "route url" 267 | } 268 | 269 | } 270 | , { 271 | "box" : { 272 | "bgcolor" : [ 1.0, 1.0, 1.0, 1.0 ], 273 | "bgcolor2" : [ 1.0, 1.0, 1.0, 1.0 ], 274 | "bgfillcolor_angle" : 270.0, 275 | "bgfillcolor_autogradient" : 0.0, 276 | "bgfillcolor_color" : [ 0.65098, 0.666667, 0.662745, 1.0 ], 277 | "bgfillcolor_color1" : [ 1.0, 1.0, 1.0, 1.0 ], 278 | "bgfillcolor_color2" : [ 0.290196, 0.309804, 0.301961, 1.0 ], 279 | "bgfillcolor_proportion" : 0.39, 280 | "bgfillcolor_type" : "color", 281 | "gradient" : 1, 282 | "id" : "obj-8", 283 | "maxclass" : "message", 284 | "numinlets" : 2, 285 | "numoutlets" : 1, 286 | "outlettype" : [ "" ], 287 | "patching_rect" : [ 255.0, 28.0, 124.0, 44.0 ], 288 | "presentation_rect" : [ 255.0, 28.0, 124.0, 44.0 ], 289 | "style" : "light", 290 | "text" : "trending" 291 | } 292 | 293 | } 294 | , { 295 | "box" : { 296 | "id" : "obj-6", 297 | "maxclass" : "message", 298 | "numinlets" : 2, 299 | "numoutlets" : 1, 300 | "outlettype" : [ "" ], 301 | "patching_rect" : [ 144.0, 28.0, 98.0, 22.0 ], 302 | "presentation" : 1, 303 | "presentation_rect" : [ 249.0, 873.0, 98.0, 22.0 ], 304 | "style" : "", 305 | "text" : "script npm install" 306 | } 307 | 308 | } 309 | , { 310 | "box" : { 311 | "id" : "obj-3", 312 | "maxclass" : "message", 313 | "numinlets" : 2, 314 | "numoutlets" : 1, 315 | "outlettype" : [ "" ], 316 | "patching_rect" : [ 58.0, 28.0, 64.0, 22.0 ], 317 | "presentation" : 1, 318 | "presentation_rect" : [ 418.0, 877.0, 64.0, 22.0 ], 319 | "style" : "", 320 | "text" : "script start" 321 | } 322 | 323 | } 324 | , { 325 | "box" : { 326 | "bgcolor" : [ 0.862745, 0.870588, 0.878431, 1.0 ], 327 | "id" : "obj-1", 328 | "maxclass" : "newobj", 329 | "numinlets" : 1, 330 | "numoutlets" : 2, 331 | "outlettype" : [ "", "" ], 332 | "patching_rect" : [ 58.0, 91.0, 432.0, 44.0 ], 333 | "presentation" : 1, 334 | "presentation_rect" : [ 543.0, 855.0, 432.0, 44.0 ], 335 | "saved_object_attributes" : { 336 | "autostart" : 0, 337 | "defer" : 0, 338 | "node" : "", 339 | "npm" : "", 340 | "running" : 1, 341 | "watch" : 1 342 | } 343 | , 344 | "style" : "light", 345 | "text" : "node.script giphy.js @watch 1" 346 | } 347 | 348 | } 349 | , { 350 | "box" : { 351 | "bgmode" : 0, 352 | "border" : 0, 353 | "clickthrough" : 0, 354 | "enablehscroll" : 0, 355 | "enablevscroll" : 0, 356 | "id" : "obj-4", 357 | "lockeddragscroll" : 0, 358 | "maxclass" : "bpatcher", 359 | "name" : "n4m.monitor.maxpat", 360 | "numinlets" : 1, 361 | "numoutlets" : 0, 362 | "offset" : [ 0.0, 0.0 ], 363 | "patching_rect" : [ 771.0, 334.0, 400.0, 220.0 ], 364 | "presentation_rect" : [ 771.0, 334.0, 400.0, 220.0 ], 365 | "viewvisibility" : 1 366 | } 367 | 368 | } 369 | , { 370 | "box" : { 371 | "angle" : 270.0, 372 | "bgcolor" : [ 0.862745, 0.870588, 0.878431, 1.0 ], 373 | "id" : "obj-18", 374 | "maxclass" : "panel", 375 | "mode" : 0, 376 | "numinlets" : 1, 377 | "numoutlets" : 0, 378 | "patching_rect" : [ 278.0, 776.0, 128.0, 128.0 ], 379 | "presentation" : 1, 380 | "presentation_rect" : [ 297.5, 204.770233154296875, 128.0, 128.0 ], 381 | "proportion" : 0.39, 382 | "shape" : 2, 383 | "style" : "", 384 | "vertical_direction" : 2 385 | } 386 | 387 | } 388 | , { 389 | "box" : { 390 | "angle" : 270.0, 391 | "bgcolor" : [ 0.862745, 0.870588, 0.878431, 1.0 ], 392 | "horizontal_direction" : 1, 393 | "id" : "obj-17", 394 | "maxclass" : "panel", 395 | "mode" : 0, 396 | "numinlets" : 1, 397 | "numoutlets" : 0, 398 | "patching_rect" : [ 730.0, 782.0, 128.0, 128.0 ], 399 | "presentation" : 1, 400 | "presentation_rect" : [ 895.0, 204.770233154296875, 128.0, 128.0 ], 401 | "proportion" : 0.39, 402 | "shape" : 2, 403 | "style" : "", 404 | "vertical_direction" : 2 405 | } 406 | 407 | } 408 | , { 409 | "box" : { 410 | "angle" : 270.0, 411 | "bgcolor" : [ 0.862745, 0.870588, 0.878431, 1.0 ], 412 | "horizontal_direction" : 1, 413 | "id" : "obj-16", 414 | "maxclass" : "panel", 415 | "mode" : 0, 416 | "numinlets" : 1, 417 | "numoutlets" : 0, 418 | "patching_rect" : [ 578.0, 782.0, 128.0, 128.0 ], 419 | "presentation" : 1, 420 | "presentation_rect" : [ 895.0, 630.770263671875, 128.0, 128.0 ], 421 | "proportion" : 0.39, 422 | "shape" : 2, 423 | "style" : "" 424 | } 425 | 426 | } 427 | , { 428 | "box" : { 429 | "angle" : 270.0, 430 | "bgcolor" : [ 0.862745, 0.870588, 0.878431, 1.0 ], 431 | "id" : "obj-15", 432 | "maxclass" : "panel", 433 | "mode" : 0, 434 | "numinlets" : 1, 435 | "numoutlets" : 0, 436 | "patching_rect" : [ 433.0, 782.0, 128.0, 128.0 ], 437 | "presentation" : 1, 438 | "presentation_rect" : [ 303.0, 630.770263671875, 128.0, 128.0 ], 439 | "proportion" : 0.39, 440 | "shape" : 2, 441 | "style" : "" 442 | } 443 | 444 | } 445 | , { 446 | "box" : { 447 | "angle" : 270.0, 448 | "background" : 1, 449 | "bgcolor" : [ 1.0, 0.394308, 0.375508, 1.0 ], 450 | "id" : "obj-14", 451 | "maxclass" : "panel", 452 | "mode" : 0, 453 | "numinlets" : 1, 454 | "numoutlets" : 0, 455 | "patching_rect" : [ -586.0, 5.0, 1321.0, 965.0 ], 456 | "presentation" : 1, 457 | "presentation_rect" : [ 0.0, 0.0, 1321.0, 965.0 ], 458 | "proportion" : 0.39, 459 | "style" : "" 460 | } 461 | 462 | } 463 | ], 464 | "lines" : [ { 465 | "patchline" : { 466 | "destination" : [ "obj-31", 0 ], 467 | "source" : [ "obj-1", 0 ] 468 | } 469 | 470 | } 471 | , { 472 | "patchline" : { 473 | "color" : [ 0.65, 0.65, 0.65, 0.9 ], 474 | "destination" : [ "obj-4", 0 ], 475 | "source" : [ "obj-1", 1 ] 476 | } 477 | 478 | } 479 | , { 480 | "patchline" : { 481 | "color" : [ 0.65, 0.65, 0.65, 0.9 ], 482 | "destination" : [ "obj-8", 0 ], 483 | "source" : [ "obj-11", 0 ] 484 | } 485 | 486 | } 487 | , { 488 | "patchline" : { 489 | "destination" : [ "obj-1", 0 ], 490 | "source" : [ "obj-3", 0 ] 491 | } 492 | 493 | } 494 | , { 495 | "patchline" : { 496 | "destination" : [ "obj-40", 0 ], 497 | "source" : [ "obj-31", 0 ] 498 | } 499 | 500 | } 501 | , { 502 | "patchline" : { 503 | "destination" : [ "obj-54", 0 ], 504 | "source" : [ "obj-38", 0 ] 505 | } 506 | 507 | } 508 | , { 509 | "patchline" : { 510 | "destination" : [ "obj-38", 0 ], 511 | "source" : [ "obj-40", 0 ] 512 | } 513 | 514 | } 515 | , { 516 | "patchline" : { 517 | "destination" : [ "obj-38", 0 ], 518 | "midpoints" : [ 150.0, 305.0, 127.5, 305.0, 127.5, 262.0, 67.5, 262.0 ], 519 | "source" : [ "obj-41", 0 ] 520 | } 521 | 522 | } 523 | , { 524 | "patchline" : { 525 | "destination" : [ "obj-42", 0 ], 526 | "source" : [ "obj-46", 0 ] 527 | } 528 | 529 | } 530 | , { 531 | "patchline" : { 532 | "destination" : [ "obj-1", 0 ], 533 | "source" : [ "obj-6", 0 ] 534 | } 535 | 536 | } 537 | , { 538 | "patchline" : { 539 | "destination" : [ "obj-1", 0 ], 540 | "source" : [ "obj-8", 0 ] 541 | } 542 | 543 | } 544 | ], 545 | "dependency_cache" : [ { 546 | "name" : "n4m.monitor.maxpat", 547 | "bootpath" : "~/Documents/Max 8/Packages/Node For Max/patchers/debug-monitor", 548 | "patcherrelativepath" : "../../../Documents/Max 8/Packages/Node For Max/patchers/debug-monitor", 549 | "type" : "JSON", 550 | "implicit" : 1 551 | } 552 | , { 553 | "name" : "resize_n4m_monitor_patcher.js", 554 | "bootpath" : "~/Documents/Max 8/Packages/Node For Max/patchers/debug-monitor", 555 | "patcherrelativepath" : "../../../Documents/Max 8/Packages/Node For Max/patchers/debug-monitor", 556 | "type" : "TEXT", 557 | "implicit" : 1 558 | } 559 | , { 560 | "name" : "new.png", 561 | "bootpath" : "~/git/n4m-examples/giphy", 562 | "patcherrelativepath" : ".", 563 | "type" : "PNG", 564 | "implicit" : 1 565 | } 566 | , { 567 | "name" : "powered_by_giphy.gif", 568 | "bootpath" : "~/git/n4m-examples/giphy", 569 | "patcherrelativepath" : ".", 570 | "type" : "GIFf", 571 | "implicit" : 1 572 | } 573 | ], 574 | "autosave" : 0, 575 | "styles" : [ { 576 | "name" : "light", 577 | "default" : { 578 | "textcolor_inverse" : [ 0.0, 0.0, 0.0, 1.0 ], 579 | "bgfillcolor" : { 580 | "type" : "color", 581 | "color1" : [ 1.0, 1.0, 1.0, 1.0 ], 582 | "color2" : [ 0.290196, 0.309804, 0.301961, 1.0 ], 583 | "color" : [ 0.65098, 0.666667, 0.662745, 1.0 ], 584 | "angle" : 270.0, 585 | "proportion" : 0.39, 586 | "autogradient" : 0.0 587 | } 588 | , 589 | "fontsize" : [ 32.0 ] 590 | } 591 | , 592 | "parentstyle" : "", 593 | "multi" : 0 594 | } 595 | ] 596 | } 597 | 598 | } 599 | -------------------------------------------------------------------------------- /giphy/giphy.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------- 2 | // giphy.js - Download Giphy gifs to maxAPI 3 | // 4 | // Check the README for information on how to get a Giphy API key, 5 | // which you'll need in order to get any of this to work. 6 | // 7 | // This script uses a fork of the giphy-api NPM package, availiable here: 8 | // https://github.com/austinkelleher/giphy-api 9 | // 10 | // --------------------------------------------------------------------- 11 | 12 | // Begin loading modules 13 | 14 | 15 | const maxAPI = require("max-api"); 16 | 17 | let dotenv_module; 18 | try { 19 | dotenv_module = require("dotenv"); 20 | dotenv_module.config(); 21 | } catch (e) { 22 | maxAPI.post(e, maxAPI.POST_LEVELS.ERROR); 23 | maxAPI.post("Could not load the dotenv module. Please be sure to send the message 'script npm install' to the node.script object to download node modules", maxAPI.POST_LEVELS.ERROR); 24 | process.exit(1); 25 | } 26 | 27 | if (!process.env.GIPHY_API_KEY) { 28 | maxAPI.post("No value for key GIPHY_API_KEY in .env file. Please make sure to create a file called .env with a GIPHY API key.", maxAPI.POST_LEVELS.ERROR); 29 | process.exit(1); 30 | } 31 | 32 | const giphy = require("giphy-api")(process.env.GIPHY_API_KEY); 33 | 34 | function trimPreview(previewFilename) { 35 | return previewFilename.replace("-preview", ""); 36 | } 37 | 38 | // Declare handlers 39 | 40 | maxAPI.addHandlers({ 41 | random: (tag) => { 42 | giphy.random(tag).then((res) => { 43 | const preview_file = maxAPI.outlet(res.data.images.preview.mp4); 44 | const filename = trimPreview(preview_file); 45 | maxAPI.outlet(["url", filename]); 46 | }); 47 | }, 48 | 49 | trending: () => { 50 | giphy.trending({ 51 | limit: 25, 52 | rating: "pg", 53 | fmt: "json" 54 | }).then((res) => { 55 | const idx = Math.floor(Math.random() * 25); 56 | const preview_file = res.data[idx].images.preview.mp4; 57 | const filename = trimPreview(preview_file); 58 | maxAPI.outlet(["url", filename]); 59 | }); 60 | } 61 | }); 62 | -------------------------------------------------------------------------------- /giphy/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cycling74/n4m-examples/25073187cd13221bb673c95f3e29771fbc9a32ea/giphy/new.png -------------------------------------------------------------------------------- /giphy/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "giphy", 3 | "version": "0.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "dotenv": { 8 | "version": "5.0.1", 9 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz", 10 | "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==" 11 | }, 12 | "giphy-api": { 13 | "version": "1.2.7", 14 | "resolved": "https://registry.npmjs.org/giphy-api/-/giphy-api-1.2.7.tgz", 15 | "integrity": "sha1-gr7eRRBTZ1scmLY0BxG3iVeaA70=" 16 | }, 17 | "os-tmpdir": { 18 | "version": "1.0.2", 19 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 20 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" 21 | }, 22 | "tmp": { 23 | "version": "0.0.33", 24 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", 25 | "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", 26 | "requires": { 27 | "os-tmpdir": "1.0.2" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /giphy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "giphy", 3 | "version": "0.0.0", 4 | "description": "Download images from Giphy using their API", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+ssh://git@github.com/Cycling74/n4m-examples.git" 8 | }, 9 | "author": "Cycling '74", 10 | "license": "ISC", 11 | "bugs": { 12 | "url": "https://github.com/Cycling74/n4m-examples/issues" 13 | }, 14 | "homepage": "https://github.com/Cycling74/n4m-examples#readme", 15 | "dependencies": { 16 | "dotenv": "^5.0.1", 17 | "giphy-api": "^1.2.7", 18 | "tmp": "0.0.33" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /giphy/powered_by_giphy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cycling74/n4m-examples/25073187cd13221bb673c95f3e29771fbc9a32ea/giphy/powered_by_giphy.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "n4m-examples", 3 | "version": "1.0.0", 4 | "description": "Example patchers and node scripts for the Node for Max package", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npm run lint", 8 | "lint": "eslint ./ ", 9 | "lint-fix": "eslint ./ --fix" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/Cycling74/n4m-examples.git" 14 | }, 15 | "keywords": [ 16 | "max", 17 | "node", 18 | "n4m", 19 | "max8" 20 | ], 21 | "author": { 22 | "name": "Cycling '74", 23 | "url": "https://cycling74.com" 24 | }, 25 | "contributors": [ 26 | "Florian Demmer ", 27 | "Sam Tarakajian ", 28 | "Cassie Tarakajian " 29 | ], 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/Cycling74/n4m-examples/issues" 33 | }, 34 | "homepage": "https://github.com/Cycling74/n4m-examples#readme", 35 | "devDependencies": { 36 | "eslint": "^5.7.0", 37 | "eslint-config-c74": "^1.0.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /routeServer/README.md: -------------------------------------------------------------------------------- 1 | # routeServer 2 | 3 | Another example Express application. Uses Max to do the routing and build the HTML. 4 | 5 | Note: Before you run this example, make sure that you run the npm install embedded into the patcher file, by clicking on the [script npm install] message. 6 | 7 | *** 8 | 9 | ## Files 10 | 11 | `routeServer.maxpat` : The Max patch to run the example.
12 | `max_routeServer.js` : The launcher JS for the NodeJS script.
13 | `app.js` : The NodeJS/Express script that runs the application.
14 | `package.json` : The Node package file.
15 | `README.md` : This file!
16 | 17 | ## Folders 18 | 19 | `/public` : The browser-facing content served up by Node.
20 | `/routes` : The Express routing functions for each endpoint.
21 | `/views` : The HTML/EJS templates used by the routing functions.
22 | `/external` : The files that are not JS/HTML/CSS. 23 | `/js` : The JavaScript helper/utility files. 24 | 25 | *** 26 | 27 | ## Usage 28 | 29 | 1. Launch the `routeServer.maxpat`. 30 | 2. (First time only...) Click on the [script npm install] message at the upper-right to load the required packages and libraries. 31 | 3. Click on the [script start] message at the top-left to start the Node process running. 32 | 4. Launch a browser, and type in the URL "localhost:3000". You should see a simple index page. 33 | 5. If you type in the URL "localhost:3000/makeData", you should see some data. 34 | 6. If you type in the URL "localhost:3000/makeHTML", you should see some HTML, generated by Max, containing a value from Max. 35 | -------------------------------------------------------------------------------- /routeServer/app.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------ 2 | // app.js - a generic app provided by the Express cli, updated to provide 3 | // routing to both the default and 'content' functional locations. 4 | // NOTE: This does use EJS-based templating; if you choose to use 5 | // something else (like Angular or React), you will need to change 6 | // the view engine. 7 | // ------------------------------------------------------------------------ 8 | 9 | 10 | let express = require("express"); 11 | let path = require("path"); 12 | let logger = require("morgan"); 13 | let cookieParser = require("cookie-parser"); 14 | let bodyParser = require("body-parser"); 15 | 16 | let content = require("./routes/content"); 17 | let max = require("./routes/max"); 18 | 19 | let app = express(); 20 | 21 | // view engine setup 22 | app.set("views", path.join(__dirname, "views")); 23 | app.set("view engine", "ejs"); 24 | 25 | app.use(logger("dev")); 26 | app.use(bodyParser.json()); 27 | app.use(bodyParser.urlencoded({ extended: false })); 28 | 29 | app.use(cookieParser()); 30 | app.use(express.static(path.join(__dirname, "public"))); 31 | 32 | app.use("/content", content); // check for /content first ... 33 | app.use("/", max); // then everything else ... 34 | 35 | // catch 404 and forward to error handler 36 | app.use(function (req, res, next) { 37 | let err = new Error("Not Found"); 38 | err.status = 404; 39 | next(err); 40 | }); 41 | 42 | // error handler 43 | app.use(function (err, req, res, next) { 44 | // set locals, only providing error in development 45 | res.locals.message = err.message; 46 | res.locals.error = req.app.get("env") === "development" ? err : {}; 47 | 48 | // render the error page 49 | res.status(err.status || 500); 50 | res.render("error"); 51 | }); 52 | 53 | module.exports = app; 54 | -------------------------------------------------------------------------------- /routeServer/external/Max8Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cycling74/n4m-examples/25073187cd13221bb673c95f3e29771fbc9a32ea/routeServer/external/Max8Logo.png -------------------------------------------------------------------------------- /routeServer/js/helpers.js: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------- 2 | // 3 | // helpers.js : provide some message-based helper functions. 4 | // 5 | // -------------------------------------------------------------------------- 6 | 7 | 8 | const MaxMSP = require("max-api"); 9 | 10 | let Helpers = { 11 | doCWD: function () { 12 | MaxMSP.outlet(["cwd", process.cwd()]); 13 | } 14 | }; 15 | 16 | MaxMSP.addHandler("cwd", () => { 17 | Helpers.doCWD(); 18 | }); 19 | 20 | module.exports = Helpers; 21 | -------------------------------------------------------------------------------- /routeServer/js/message_broker.js: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------- 2 | // 3 | // message_broker.js : given a message, transmit it to any registered 4 | // listeners. Keep track of attachments, and kill 5 | // any listeners that go 'dead'. 6 | // 7 | // -------------------------------------------------------------------------- 8 | 9 | 10 | let uuidv1 = require("uuid/v1"); 11 | 12 | let MessageBroker = { 13 | listeners: [], 14 | 15 | // add a listener with a callback 16 | // ------------------------------ 17 | addListener: function (func) { 18 | let tmp = { 19 | uuid: uuidv1(), 20 | callback: func 21 | }; 22 | this.listeners.push(tmp); 23 | return tmp.uuid; 24 | }, 25 | 26 | // find and remove a listener by id 27 | // -------------------------------- 28 | removeListener: function (id) { 29 | this.listeners.forEach((v, i, a) => { 30 | if (v.uuid === id) { 31 | a.splice(i, 1); 32 | } 33 | }); 34 | }, 35 | 36 | // When a message comes in, send it to the listener that has a matching 37 | // uuid field. Reject the message if it is not the proper type, or if 38 | // it doesn't have a uuid field. 39 | brokerMessage: function (args = []) { 40 | // we only accept dictionaries as input 41 | if (args[0] !== "dict" || args.length !== 2) { 42 | console.log("invalid message - only dictionaries accepted as messages"); 43 | return; 44 | } 45 | 46 | const content = args[1]; 47 | 48 | // the object has to have a uuid property 49 | if (!content.hasOwnProperty("uuid")) { 50 | console.log("invalid message - dictionary must contain a uuid value"); 51 | return; 52 | } 53 | 54 | // the uuid has to be 'all', or be in the current list of listeners 55 | if ((content.uuid !== "all") && !this.listeners.some(e => e.uuid === content.uuid)) { 56 | console.log("invalid message - the passed uuid does not match any listeners"); 57 | return; 58 | } 59 | 60 | // spin through the listeners and send out matches 61 | this.listeners.forEach((v, i) => { 62 | try { 63 | if ((content.uuid === "all") || (content.uuid === v.uuid)) { 64 | v.callback(content); 65 | } 66 | } 67 | catch (e) { 68 | console.log(`removing listener ${v.uuid}, error ${e}`); 69 | this.removeListener(v.uuid); 70 | } 71 | }); 72 | } 73 | 74 | }; 75 | 76 | module.exports = MessageBroker; 77 | -------------------------------------------------------------------------------- /routeServer/max_routeServer.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // ------------------------------------------------------------------------ 3 | // max_routeServer.js - a generic app launcher provided by the Express cli, 4 | // but renamed to prevent searchpath clashes. Calls 5 | // the local app.js to actually run the application. 6 | // ------------------------------------------------------------------------ 7 | 8 | 9 | let app = require("./app"); 10 | let debug = require("debug")("node-routetest:server"); 11 | let http = require("http"); 12 | 13 | let server; 14 | let port; 15 | 16 | // ---------------------------- 17 | // Normalize the port properly 18 | // ---------------------------- 19 | 20 | function normalizePort(val) { 21 | let localPort = parseInt(val, 10); 22 | 23 | // named pipe 24 | if (isNaN(localPort)) { 25 | return val; 26 | } 27 | 28 | // port number 29 | if (localPort >= 0) { 30 | return localPort; 31 | } 32 | 33 | return false; 34 | } 35 | 36 | port = normalizePort(process.env.PORT || "3000"); 37 | app.set("port", port); 38 | 39 | // ------------------------------------ 40 | // Watch for HTTP server error events. 41 | // ------------------------------------ 42 | 43 | function onError(error) { 44 | if (error.syscall !== "listen") { 45 | throw error; 46 | } 47 | 48 | let bind = typeof port === "string" 49 | ? "Pipe " + port 50 | : "Port " + port; 51 | 52 | // handle specific listen errors with friendly messages 53 | switch (error.code) { 54 | case "EACCES": 55 | console.error(bind + " requires elevated privileges"); 56 | process.exit(1); 57 | break; 58 | case "EADDRINUSE": 59 | console.error(bind + " is already in use"); 60 | process.exit(1); 61 | break; 62 | default: 63 | throw error; 64 | } 65 | } 66 | 67 | // --------------------------------------- 68 | // Watch for HTTP server listening events 69 | // --------------------------------------- 70 | 71 | function onListening() { 72 | let addr = server.address(); 73 | let bind = typeof addr === "string" 74 | ? "pipe " + addr 75 | : "port " + addr.port; 76 | debug("Listening on " + bind); 77 | } 78 | 79 | // --------------------- 80 | // Create the server... 81 | // --------------------- 82 | server = http.createServer(app); 83 | server.listen(port); 84 | server.on("error", onError); 85 | server.on("listening", onListening); 86 | -------------------------------------------------------------------------------- /routeServer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "max_routeServer", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/max_routeServer.js" 7 | }, 8 | "dependencies": { 9 | "body-parser": "^1.18.3", 10 | "cookie-parser": "~1.4.3", 11 | "debug": "~2.6.3", 12 | "ejs": "~2.5.6", 13 | "express": "^4.16.4", 14 | "morgan": "^1.9.1", 15 | "serve-favicon": "~2.4.2", 16 | "uuid": "^3.1.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /routeServer/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cycling74/n4m-examples/25073187cd13221bb673c95f3e29771fbc9a32ea/routeServer/public/favicon.ico -------------------------------------------------------------------------------- /routeServer/public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 25px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /routeServer/routes/content.js: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------- 2 | // 3 | // content.js : deal with a 'contentfolder' that can contain static files 4 | // to be served to the browser. Note: if the contentFolder 5 | // variable is null, the route is ignored; hence, to use this, 6 | // the application needs to receive a valid 'contentfolder' 7 | // message with a file path. 8 | // 9 | // -------------------------------------------------------------------------- 10 | 11 | 12 | const MaxMSP = require("max-api"); 13 | 14 | let express = require("express"); 15 | let router = new express.Router(); 16 | let path = require("path"); 17 | 18 | let contentFolder = null; 19 | 20 | // deal with a 'contentfolder' message. This message either needs to have a 21 | // valid filepath for a folder, or it needs to have the word 'clear' (which 22 | // will remove any existing path). 23 | // ------------------------------------------------------------------------ 24 | MaxMSP.addHandler("contentfolder", (args) => { 25 | if (typeof(args) === "string") { 26 | if (args === "clear") { 27 | contentFolder = null; 28 | console.log("Content folder cleared"); 29 | } else { 30 | if (args[0] === ".") { 31 | contentFolder = path.join(process.cwd(), args); 32 | console.log(`Content folder set to ${contentFolder}`); 33 | } else { 34 | contentFolder = args; 35 | } 36 | } 37 | } else { 38 | console.err("bad folder name"); 39 | } 40 | }); 41 | 42 | // Assuming that the contentFolder has been set up, this route will serve up 43 | // the filename that is provided. If contentFolder is not set up, it will 44 | // pass to the next express middleware. 45 | // ------------------------------------------------------------------------- 46 | router.get("/:name", function (req, res, next) { 47 | if (!contentFolder) { 48 | next(); 49 | } else { 50 | var fileName = path.join(contentFolder, req.params.name); 51 | console.log("content request providing " + fileName); 52 | 53 | var options = { 54 | headers: { 55 | "x-timestamp": Date.now(), 56 | "x-sent": true 57 | } 58 | }; 59 | 60 | res.sendFile(fileName, options, function (err) { 61 | if (err) { 62 | next(err); 63 | } else { 64 | console.log("Sent:", fileName); 65 | } 66 | }); 67 | } 68 | }); 69 | 70 | module.exports = router; 71 | -------------------------------------------------------------------------------- /routeServer/routes/max.js: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------- 2 | // 3 | // max.js : deal with the interaction between Max and the browser. This uses 4 | // the brokering mechanism provided in the message_broker.js source. 5 | // 6 | // -------------------------------------------------------------------------- 7 | 8 | 9 | let express = require("express"); 10 | let router = new express.Router(); 11 | 12 | const MaxMSP = require("max-api"); 13 | let broker = require("../js/message_broker"); 14 | 15 | // ------------------------------------------------------------------------ 16 | // incoming messages are brokered, because they should have be responses 17 | // to previous requests - or are system broadcast messages that have to 18 | // go to all listeners... 19 | // ------------------------------------------------------------------------ 20 | 21 | MaxMSP.addHandler("test", (args) => console.log(args)); 22 | MaxMSP.addHandler(MaxMSP.MESSAGE_TYPES.ALL, (handled, ...args) => { 23 | if (!handled) { 24 | broker.brokerMessage(args); 25 | } 26 | }); 27 | 28 | // ------------------------ 29 | // Convenience functions! 30 | // ------------------------ 31 | 32 | let hasOwnProperty = Object.prototype.hasOwnProperty; 33 | function isEmpty(obj) { 34 | if (obj === null) return true; 35 | if (obj.length > 0) return false; 36 | if (obj.length === 0) return true; 37 | if (typeof obj !== "object") return true; 38 | for (let key in obj) { 39 | if (hasOwnProperty.call(obj, key)) return false; 40 | } 41 | return true; 42 | } 43 | 44 | // ------------------------------------------------------------------------ 45 | // In the event that we have an object, we need to format it into an 46 | // easy-to-digest set of html lines - a nice way of taking an object and 47 | // making it usefully visible on our generic web page... 48 | // ------------------------------------------------------------------------ 49 | 50 | function decodeKeys(obj, ind) { 51 | let outContent = ""; 52 | let indentLevel = (ind || 0); 53 | let tmp = "    ".repeat(indentLevel); 54 | 55 | let theKeys = Object.keys(obj); 56 | if (theKeys.length < 1) { 57 | outContent = tmp + "empty
"; 58 | return outContent; 59 | } 60 | 61 | for (let i = 0; i < theKeys.length; i++) { 62 | let typ = typeof(obj[theKeys[i]]); 63 | if (typ !== "object") { 64 | outContent += tmp + "" + theKeys[i] + ": " + obj[theKeys[i]] + "
"; 65 | if (indentLevel < 1) { 66 | outContent += "
"; 67 | } 68 | } else { 69 | outContent += tmp + "" + theKeys[i] + ":
"; 70 | outContent += decodeKeys(obj[theKeys[i]], indentLevel + 1); 71 | outContent += "
"; 72 | } 73 | } 74 | return outContent; 75 | } 76 | 77 | // ------------------------------------------------------------------------ 78 | // Handle the incoming GET request, create the dictionary for Max, then 79 | // deal with the return information that Max provides. 80 | // ------------------------------------------------------------------------ 81 | 82 | router.all("/*", function (req, res, next) { 83 | 84 | // tear apart the request for useful information 85 | let tmpTokens = req.path.split("/").filter(e => !isEmpty(e)); 86 | let outDict = { 87 | uuid: "", 88 | ip: req.ip, 89 | url: req.path, 90 | tokens: tmpTokens.length ? tmpTokens : "/", 91 | verb: req.method, 92 | body: req.body, 93 | cookies: req.cookies, 94 | query: req.query 95 | }; 96 | 97 | // then deal with the whole thing in a promise 98 | let reqFunc = new Promise((resolve, reject) => { 99 | let myID = ""; 100 | 101 | // time out if more than two seconds 102 | let intrHandle = setTimeout(function () { 103 | reject("timeout"); 104 | }, 2000); 105 | 106 | // set up a response function 107 | let resFunction = (val) => { 108 | clearTimeout(intrHandle); 109 | broker.removeListener(myID); 110 | 111 | // figure out what kind of message we have... 112 | if (val.hasOwnProperty("httpStatus")) { 113 | // deal with an http status message 114 | resolve({ 115 | kind: "httpStatus", 116 | content: val.httpStatus 117 | }); 118 | } else if (val.hasOwnProperty("htmlContent")) { 119 | // deal with an html content message 120 | resolve({ 121 | kind: "htmlContent", 122 | title: val.htmlTitle || "From Max", 123 | content: val.htmlContent 124 | }); 125 | } else { 126 | // the generic case 127 | delete val.uuid; 128 | resolve({kind: "generic", content: val}); 129 | } 130 | }; 131 | 132 | // listen for the result, then send the message 133 | myID = broker.addListener(resFunction); 134 | outDict.uuid = myID; 135 | MaxMSP.outlet(outDict); 136 | }); 137 | 138 | // manage the result of the promise by responding with 139 | // an appropriate rendering and content set, and deal 140 | // with errors using a 500 error. 141 | reqFunc.then((obj) => { 142 | if (obj.kind === "httpStatus") { 143 | res.writeHead(obj.content); 144 | res.end(); 145 | } else if (obj.kind === "htmlContent") { 146 | res.render("max_html", { 147 | title: obj.title, 148 | html: obj.content 149 | }); 150 | } else if (obj.kind === "generic") { 151 | res.render("max_data", { 152 | html: decodeKeys(obj.content) 153 | }); 154 | } 155 | }).catch((err) => { 156 | res.writeHead(500); 157 | res.end(); 158 | }); 159 | 160 | }); 161 | 162 | module.exports = router; 163 | -------------------------------------------------------------------------------- /routeServer/views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /routeServer/views/max_data.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Max Data 5 | 6 | 7 | 8 |

Max Data:

9 |

<%- html %>

10 | Return 11 | 12 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /routeServer/views/max_html.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 | <%- html %> 9 | 10 | 11 | -------------------------------------------------------------------------------- /sockets/README.md: -------------------------------------------------------------------------------- 1 | # max_socket 2 | 3 | Realtime data transfer to the browser. 4 | 5 | Note: Before you run this example, make sure that you run the npm install embedded into the patcher file, by clicking on the [script npm install] message. 6 | 7 | *** 8 | 9 | ## Files 10 | 11 | `max_sockets.maxpat` : The Max patch to run the example.
12 | `max_sockets.js` : The NodeJS script.
13 | `package.json` : The Node package file.
14 | `README.md` : This file!
15 | 16 | ## Folders 17 | 18 | `/public` : The browser-facing content served up by Node.
19 | `/routes` : The Express routing functions for each endpoint.
20 | `/views` : The HTML/EJS templates used by the routing functions.
21 | 22 | *** 23 | 24 | ## Usage 25 | 26 | 1. Launch the `max_sockets.maxpat` Max patch. 27 | 2. (First time only...) Click on the [script npm install] message to load the required packages and libraries. 28 | 3. Click on the [script start] message to start the Node process running. 29 | 4. Click on the toggle to begin generating live data. 30 | 5. Click on the "Open test page" message to launch a browser to the test form. The graphics will show the results of the data feed from the Max patch. 31 | -------------------------------------------------------------------------------- /sockets/max_sockets.js: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------- 2 | // max_sockets.js - a generic Node/Express application that serves up the 3 | // requested web pages, and manages a socket connection 4 | // with any requesting pages. This is part of the Node for 5 | // Max system for Max 8. 6 | // -------------------------------------------------------------------------- 7 | 8 | const express = require("express"); 9 | const http = require("http"); 10 | const path = require("path"); 11 | 12 | const cookieParser = require("cookie-parser"); 13 | const bodyParser = require("body-parser"); 14 | const WebSocket = require("ws"); 15 | 16 | const Max = require("max-api"); 17 | 18 | var index = require("./routes/index"); 19 | var app = express(); 20 | 21 | // view engine setup 22 | app.set("views", path.join(__dirname, "views")); 23 | app.set("view engine", "ejs"); 24 | 25 | // uncomment after placing your favicon in /public 26 | // app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 27 | // app.use(logger('dev')); 28 | app.use(bodyParser.json()); 29 | app.use(bodyParser.urlencoded({ extended: false })); 30 | app.use(cookieParser()); 31 | app.use(express.static(path.join(__dirname, "public"))); 32 | 33 | app.use("/", index); 34 | 35 | // catch 404 and forward to error handler 36 | app.use(function (req, res, next) { 37 | var err = new Error("Not Found"); 38 | err.status = 404; 39 | next(err); 40 | }); 41 | 42 | // error handler 43 | app.use(function (err, req, res, next) { 44 | // set locals, only providing error in development 45 | res.locals.message = err.message; 46 | res.locals.error = req.app.get("env") === "development" ? err : {}; 47 | 48 | // render the error page 49 | res.status(err.status || 500); 50 | res.render("error"); 51 | }); 52 | 53 | // handle the web socket server here (using the ws package...) 54 | // Note: Replace this with your own customer socket handler 55 | // if you are creating a custom websockets implementation 56 | // ------------------------------------------------------------- 57 | let server = http.createServer(app); 58 | 59 | const wss = new WebSocket.Server({ port: 7474 }); 60 | 61 | wss.on("connection", function connection(ws, req) { 62 | 63 | ws.on("message", function incoming(message) { 64 | console.log("received: %s", message); 65 | }); 66 | 67 | ws.on("close", function stop() { 68 | Max.removeHandlers("send"); 69 | console.log("Connection closed"); 70 | 71 | ws.terminate(); 72 | }); 73 | 74 | const sender = function (a, b, c) { 75 | ws.send(JSON.stringify({ 76 | "value_1": a, 77 | "value_2": b, 78 | "value_3": c 79 | })); 80 | }; 81 | 82 | // Handle the Max interactions here... 83 | Max.addHandler("send", (...args) => { 84 | console.log("send args: " + args); 85 | if (args.length === 3) { 86 | sender(args[0], args[1], args[2]); 87 | } 88 | }); 89 | }); 90 | 91 | Max.addHandler(Max.MESSAGE_TYPES.ALL, (handled, ...args) => { 92 | if (!handled) { 93 | // Max.post('No client connected.') 94 | // just consume the message 95 | } 96 | }); 97 | 98 | console.log("setting up max handlers"); 99 | 100 | server.listen(8080, function listening() { 101 | console.log("Listening on %d", server.address().port); 102 | }); 103 | -------------------------------------------------------------------------------- /sockets/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "max-sockets", 3 | "version": "0.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 | "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 | "async-limiter": { 22 | "version": "1.0.0", 23 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", 24 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" 25 | }, 26 | "basic-auth": { 27 | "version": "1.1.0", 28 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", 29 | "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=" 30 | }, 31 | "body-parser": { 32 | "version": "1.17.2", 33 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz", 34 | "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=", 35 | "requires": { 36 | "bytes": "2.4.0", 37 | "content-type": "~1.0.2", 38 | "debug": "2.6.7", 39 | "depd": "~1.1.0", 40 | "http-errors": "~1.6.1", 41 | "iconv-lite": "0.4.15", 42 | "on-finished": "~2.3.0", 43 | "qs": "6.4.0", 44 | "raw-body": "~2.2.0", 45 | "type-is": "~1.6.15" 46 | }, 47 | "dependencies": { 48 | "debug": { 49 | "version": "2.6.7", 50 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", 51 | "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=", 52 | "requires": { 53 | "ms": "2.0.0" 54 | } 55 | } 56 | } 57 | }, 58 | "bytes": { 59 | "version": "2.4.0", 60 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", 61 | "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" 62 | }, 63 | "content-disposition": { 64 | "version": "0.5.2", 65 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 66 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 67 | }, 68 | "content-type": { 69 | "version": "1.0.4", 70 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 71 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 72 | }, 73 | "cookie": { 74 | "version": "0.3.1", 75 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 76 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 77 | }, 78 | "cookie-parser": { 79 | "version": "1.4.3", 80 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz", 81 | "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=", 82 | "requires": { 83 | "cookie": "0.3.1", 84 | "cookie-signature": "1.0.6" 85 | } 86 | }, 87 | "cookie-signature": { 88 | "version": "1.0.6", 89 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 90 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 91 | }, 92 | "debug": { 93 | "version": "2.6.9", 94 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 95 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 96 | "requires": { 97 | "ms": "2.0.0" 98 | } 99 | }, 100 | "depd": { 101 | "version": "1.1.2", 102 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 103 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 104 | }, 105 | "destroy": { 106 | "version": "1.0.4", 107 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 108 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 109 | }, 110 | "ee-first": { 111 | "version": "1.1.1", 112 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 113 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 114 | }, 115 | "ejs": { 116 | "version": "2.5.9", 117 | "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.9.tgz", 118 | "integrity": "sha512-GJCAeDBKfREgkBtgrYSf9hQy9kTb3helv0zGdzqhM7iAkW8FA/ZF97VQDbwFiwIT8MQLLOe5VlPZOEvZAqtUAQ==" 119 | }, 120 | "encodeurl": { 121 | "version": "1.0.2", 122 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 123 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 124 | }, 125 | "escape-html": { 126 | "version": "1.0.3", 127 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 128 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 129 | }, 130 | "etag": { 131 | "version": "1.8.1", 132 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 133 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 134 | }, 135 | "express": { 136 | "version": "4.15.5", 137 | "resolved": "https://registry.npmjs.org/express/-/express-4.15.5.tgz", 138 | "integrity": "sha1-ZwI1ypWYiQpa6BcLg9tyK4Qu2Sc=", 139 | "requires": { 140 | "accepts": "~1.3.3", 141 | "array-flatten": "1.1.1", 142 | "content-disposition": "0.5.2", 143 | "content-type": "~1.0.2", 144 | "cookie": "0.3.1", 145 | "cookie-signature": "1.0.6", 146 | "debug": "2.6.9", 147 | "depd": "~1.1.1", 148 | "encodeurl": "~1.0.1", 149 | "escape-html": "~1.0.3", 150 | "etag": "~1.8.0", 151 | "finalhandler": "~1.0.6", 152 | "fresh": "0.5.2", 153 | "merge-descriptors": "1.0.1", 154 | "methods": "~1.1.2", 155 | "on-finished": "~2.3.0", 156 | "parseurl": "~1.3.1", 157 | "path-to-regexp": "0.1.7", 158 | "proxy-addr": "~1.1.5", 159 | "qs": "6.5.0", 160 | "range-parser": "~1.2.0", 161 | "send": "0.15.6", 162 | "serve-static": "1.12.6", 163 | "setprototypeof": "1.0.3", 164 | "statuses": "~1.3.1", 165 | "type-is": "~1.6.15", 166 | "utils-merge": "1.0.0", 167 | "vary": "~1.1.1" 168 | }, 169 | "dependencies": { 170 | "qs": { 171 | "version": "6.5.0", 172 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.0.tgz", 173 | "integrity": "sha512-fjVFjW9yhqMhVGwRExCXLhJKrLlkYSaxNWdyc9rmHlrVZbk35YHH312dFd7191uQeXkI3mKLZTIbSvIeFwFemg==" 174 | }, 175 | "setprototypeof": { 176 | "version": "1.0.3", 177 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 178 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 179 | }, 180 | "statuses": { 181 | "version": "1.3.1", 182 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 183 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 184 | } 185 | } 186 | }, 187 | "finalhandler": { 188 | "version": "1.0.6", 189 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.6.tgz", 190 | "integrity": "sha1-AHrqM9Gk0+QgF/YkhIrVjSEvgU8=", 191 | "requires": { 192 | "debug": "2.6.9", 193 | "encodeurl": "~1.0.1", 194 | "escape-html": "~1.0.3", 195 | "on-finished": "~2.3.0", 196 | "parseurl": "~1.3.2", 197 | "statuses": "~1.3.1", 198 | "unpipe": "~1.0.0" 199 | }, 200 | "dependencies": { 201 | "statuses": { 202 | "version": "1.3.1", 203 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 204 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 205 | } 206 | } 207 | }, 208 | "forwarded": { 209 | "version": "0.1.2", 210 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 211 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 212 | }, 213 | "fresh": { 214 | "version": "0.5.2", 215 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 216 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 217 | }, 218 | "http-errors": { 219 | "version": "1.6.3", 220 | "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 221 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 222 | "requires": { 223 | "depd": "~1.1.2", 224 | "inherits": "2.0.3", 225 | "setprototypeof": "1.1.0", 226 | "statuses": ">= 1.4.0 < 2" 227 | } 228 | }, 229 | "iconv-lite": { 230 | "version": "0.4.15", 231 | "resolved": "http://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", 232 | "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=" 233 | }, 234 | "inherits": { 235 | "version": "2.0.3", 236 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 237 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 238 | }, 239 | "ipaddr.js": { 240 | "version": "1.4.0", 241 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.4.0.tgz", 242 | "integrity": "sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA=" 243 | }, 244 | "media-typer": { 245 | "version": "0.3.0", 246 | "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 247 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 248 | }, 249 | "merge-descriptors": { 250 | "version": "1.0.1", 251 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 252 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 253 | }, 254 | "methods": { 255 | "version": "1.1.2", 256 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 257 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 258 | }, 259 | "mime": { 260 | "version": "1.3.4", 261 | "resolved": "http://registry.npmjs.org/mime/-/mime-1.3.4.tgz", 262 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" 263 | }, 264 | "mime-db": { 265 | "version": "1.37.0", 266 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", 267 | "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" 268 | }, 269 | "mime-types": { 270 | "version": "2.1.21", 271 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", 272 | "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", 273 | "requires": { 274 | "mime-db": "~1.37.0" 275 | } 276 | }, 277 | "morgan": { 278 | "version": "1.8.2", 279 | "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.8.2.tgz", 280 | "integrity": "sha1-eErHc05KRTqcbm6GgKkyknXItoc=", 281 | "requires": { 282 | "basic-auth": "~1.1.0", 283 | "debug": "2.6.8", 284 | "depd": "~1.1.0", 285 | "on-finished": "~2.3.0", 286 | "on-headers": "~1.0.1" 287 | }, 288 | "dependencies": { 289 | "debug": { 290 | "version": "2.6.8", 291 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", 292 | "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", 293 | "requires": { 294 | "ms": "2.0.0" 295 | } 296 | } 297 | } 298 | }, 299 | "ms": { 300 | "version": "2.0.0", 301 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 302 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 303 | }, 304 | "negotiator": { 305 | "version": "0.6.1", 306 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 307 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 308 | }, 309 | "on-finished": { 310 | "version": "2.3.0", 311 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 312 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 313 | "requires": { 314 | "ee-first": "1.1.1" 315 | } 316 | }, 317 | "on-headers": { 318 | "version": "1.0.1", 319 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", 320 | "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" 321 | }, 322 | "parseurl": { 323 | "version": "1.3.2", 324 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 325 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 326 | }, 327 | "path-to-regexp": { 328 | "version": "0.1.7", 329 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 330 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 331 | }, 332 | "proxy-addr": { 333 | "version": "1.1.5", 334 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz", 335 | "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=", 336 | "requires": { 337 | "forwarded": "~0.1.0", 338 | "ipaddr.js": "1.4.0" 339 | } 340 | }, 341 | "qs": { 342 | "version": "6.4.0", 343 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", 344 | "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" 345 | }, 346 | "range-parser": { 347 | "version": "1.2.0", 348 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 349 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 350 | }, 351 | "raw-body": { 352 | "version": "2.2.0", 353 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", 354 | "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=", 355 | "requires": { 356 | "bytes": "2.4.0", 357 | "iconv-lite": "0.4.15", 358 | "unpipe": "1.0.0" 359 | } 360 | }, 361 | "safe-buffer": { 362 | "version": "5.1.1", 363 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 364 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 365 | }, 366 | "send": { 367 | "version": "0.15.6", 368 | "resolved": "https://registry.npmjs.org/send/-/send-0.15.6.tgz", 369 | "integrity": "sha1-IPI6nJJbdiq4JwX+L52yUqzkfjQ=", 370 | "requires": { 371 | "debug": "2.6.9", 372 | "depd": "~1.1.1", 373 | "destroy": "~1.0.4", 374 | "encodeurl": "~1.0.1", 375 | "escape-html": "~1.0.3", 376 | "etag": "~1.8.1", 377 | "fresh": "0.5.2", 378 | "http-errors": "~1.6.2", 379 | "mime": "1.3.4", 380 | "ms": "2.0.0", 381 | "on-finished": "~2.3.0", 382 | "range-parser": "~1.2.0", 383 | "statuses": "~1.3.1" 384 | }, 385 | "dependencies": { 386 | "statuses": { 387 | "version": "1.3.1", 388 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 389 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 390 | } 391 | } 392 | }, 393 | "serve-favicon": { 394 | "version": "2.4.5", 395 | "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.4.5.tgz", 396 | "integrity": "sha512-s7F8h2NrslMkG50KxvlGdj+ApSwaLex0vexuJ9iFf3GLTIp1ph/l1qZvRe9T9TJEYZgmq72ZwJ2VYiAEtChknw==", 397 | "requires": { 398 | "etag": "~1.8.1", 399 | "fresh": "0.5.2", 400 | "ms": "2.0.0", 401 | "parseurl": "~1.3.2", 402 | "safe-buffer": "5.1.1" 403 | } 404 | }, 405 | "serve-static": { 406 | "version": "1.12.6", 407 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.6.tgz", 408 | "integrity": "sha1-uXN3P2NEmTTaVOW+ul4x2fQhFXc=", 409 | "requires": { 410 | "encodeurl": "~1.0.1", 411 | "escape-html": "~1.0.3", 412 | "parseurl": "~1.3.2", 413 | "send": "0.15.6" 414 | } 415 | }, 416 | "setprototypeof": { 417 | "version": "1.1.0", 418 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 419 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" 420 | }, 421 | "statuses": { 422 | "version": "1.5.0", 423 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 424 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 425 | }, 426 | "type-is": { 427 | "version": "1.6.16", 428 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", 429 | "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", 430 | "requires": { 431 | "media-typer": "0.3.0", 432 | "mime-types": "~2.1.18" 433 | } 434 | }, 435 | "unpipe": { 436 | "version": "1.0.0", 437 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 438 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 439 | }, 440 | "utils-merge": { 441 | "version": "1.0.0", 442 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", 443 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 444 | }, 445 | "vary": { 446 | "version": "1.1.2", 447 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 448 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 449 | }, 450 | "ws": { 451 | "version": "4.1.0", 452 | "resolved": "http://registry.npmjs.org/ws/-/ws-4.1.0.tgz", 453 | "integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==", 454 | "requires": { 455 | "async-limiter": "~1.0.0", 456 | "safe-buffer": "~5.1.0" 457 | } 458 | } 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /sockets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "max-sockets", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node max_sockets.js" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.17.1", 10 | "cookie-parser": "~1.4.3", 11 | "debug": "~2.6.3", 12 | "ejs": "~2.5.6", 13 | "express": "~4.15.2", 14 | "morgan": "~1.8.1", 15 | "serve-favicon": "~2.4.2", 16 | "ws": "^4.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sockets/public/css/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | font: 1em "Lucida Grande", Helvetica, Arial, sans-serif; 3 | text-align: center; 4 | } 5 | 6 | body { 7 | padding: 50px; 8 | font: 1.5em "Lucida Grande", Helvetica, Arial, sans-serif; 9 | } 10 | 11 | table { 12 | text-align: center; 13 | margin-left: auto; 14 | margin-right: auto; 15 | } 16 | 17 | th, td { 18 | border: 1px solid black; 19 | } 20 | 21 | a { 22 | color: #00B7FF; 23 | } 24 | -------------------------------------------------------------------------------- /sockets/public/img/sl-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cycling74/n4m-examples/25073187cd13221bb673c95f3e29771fbc9a32ea/sockets/public/img/sl-green.png -------------------------------------------------------------------------------- /sockets/public/img/sl-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cycling74/n4m-examples/25073187cd13221bb673c95f3e29771fbc9a32ea/sockets/public/img/sl-red.png -------------------------------------------------------------------------------- /sockets/public/img/sl-yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cycling74/n4m-examples/25073187cd13221bb673c95f3e29771fbc9a32ea/sockets/public/img/sl-yellow.png -------------------------------------------------------------------------------- /sockets/public/js/mySockets.js: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------- 2 | // This is the javascript required for interactive data retrieval from 3 | // the Max-based Node host via websockets. It uses fairly standard jQuery 4 | // to perform its thing... 5 | // -------------------------------------------------------------------------- 6 | /* global $ */ 7 | 8 | 9 | var exampleSocket = new WebSocket("ws://localhost:7474"); 10 | 11 | exampleSocket.onopen = function (event) { 12 | console.log("sending data..."); 13 | exampleSocket.send("Ready, willing and able!"); 14 | }; 15 | 16 | exampleSocket.onmessage = function (event) { 17 | let e = JSON.parse(event.data); 18 | 19 | // Stoplight legend: 20 | // -.9 thru +.9 = green 21 | // +/- .9 thru +/- 1.5 = yellow 22 | // greater than +/- 1.5 = red 23 | let v = (a) => { 24 | let m = Math.abs(a); 25 | if (m > 1.5) { 26 | return "sl-red"; 27 | } else if (m > 0.9) { 28 | return "sl-yellow"; 29 | } 30 | return"sl-green"; 31 | 32 | }; 33 | 34 | $("#value_1").text(Math.round(e.value_1 * 100) / 100); 35 | $("#status_1").html(``); 36 | $("#value_2").text(Math.round(e.value_2 * 100) / 100); 37 | $("#status_2").html(``); 38 | $("#value_3").text(Math.round(e.value_3 * 100) / 100); 39 | $("#status_3").html(``); 40 | }; 41 | 42 | // Managing the interaction 43 | 44 | $(window).on("beforeunload", function () { 45 | exampleSocket.close(); 46 | }); 47 | -------------------------------------------------------------------------------- /sockets/routes/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var express = require("express"); 4 | var router = new express.Router(); 5 | 6 | /* GET home page. */ 7 | router.get("/", function (req, res, next) { 8 | res.render("index", { 9 | title: "Web Sockets Test" 10 | }); 11 | }); 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /sockets/views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /sockets/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 |

<%= title %>

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
ItemStatusValue
Random0.00
Sine Wave0.00
Square Wave0.00
31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tonal-chord-builder/README.md: -------------------------------------------------------------------------------- 1 | # Tonal Chord Builder 2 | Uses the npm library Tonal to generate different chords based on a root note. 3 | 4 | Note: Before you run this example, make sure that you run the npm install embedded into the patcher file, by clicking on the [script npm install] message. 5 | 6 | *** 7 | 8 | ## Files 9 | 10 | `chord-builder.maxpat` : The Max patch to run the example.
11 | `n4m.chords.js` : The launcher JS for the NodeJS script.
12 | `poly.phatness.maxpat` : The Max patch for the [poly~] synthesizer.
13 | `package.json` : The Node package file.
14 | `README.md` : This file!
15 | 16 | ## Usage 17 | 18 | 1. Launch the `chord-builder.maxpat` Max patch. 19 | 2. (First time only...) Click on the [script npm install] message at the lower-right to load the required packages and libraries. 20 | 3. Click on the [script start] message at the top-middle to start the Node process running. 21 | 4. Turn on the chord generation by clicking the "Chords Off" button, or pressing the spacebar. 22 | 5. Press a note on the upper keyboard. It should generate the "Maj7" version of that chord on the lower keyboard, and you should hear it. 23 | 6. Change the chord by clicking on the chord buttons, or using the number keys to switch between them. -------------------------------------------------------------------------------- /tonal-chord-builder/n4m.chords.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const maxApi = require("max-api"); 4 | const { Chord, Note } = require("tonal"); 5 | 6 | maxApi.addHandler("chord", (midiRoot, name) => { 7 | const root = Note.fromMidi(midiRoot); 8 | const chord = Chord.notes(root, name); 9 | const midiNotes = chord.map(Note.midi); 10 | maxApi.outlet(midiNotes); 11 | }); 12 | -------------------------------------------------------------------------------- /tonal-chord-builder/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "detect", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "tonal": { 8 | "version": "2.0.0", 9 | "resolved": "https://registry.npmjs.org/tonal/-/tonal-2.0.0.tgz", 10 | "integrity": "sha512-sWcvoPQOMvTusk3uc+IpO50YqXCA7OhLhGQA7D6gMUUudQ5pQERdmvuXhgMDhp+95JtpNyaOBYmW9JxbDLNQXg==", 11 | "requires": { 12 | "tonal-array": "2.0.0", 13 | "tonal-chord": "2.0.0", 14 | "tonal-dictionary": "2.0.0", 15 | "tonal-distance": "2.0.0", 16 | "tonal-interval": "2.0.0", 17 | "tonal-key": "2.0.0", 18 | "tonal-note": "2.0.0", 19 | "tonal-pcset": "2.0.0", 20 | "tonal-scale": "2.0.0" 21 | } 22 | }, 23 | "tonal-array": { 24 | "version": "2.0.0", 25 | "resolved": "https://registry.npmjs.org/tonal-array/-/tonal-array-2.0.0.tgz", 26 | "integrity": "sha512-SqbPkmHXEmAZEPmwELfKAyddAzpDST346MgH8dFnUcE5lEg5nQwr4YsjPRI3KDfNBVDd2/2u4KohF5c8PY7Fqg==", 27 | "requires": { 28 | "tonal-note": "2.0.0" 29 | } 30 | }, 31 | "tonal-chord": { 32 | "version": "2.0.0", 33 | "resolved": "https://registry.npmjs.org/tonal-chord/-/tonal-chord-2.0.0.tgz", 34 | "integrity": "sha512-wMuSw05Md02/ncXjGKhRIyv/jcD1UGmcd0c7E59FyllKyxlpHaSxAaFVWaupGQQI3iBdy7/WGE9f3bo65OQXDQ==", 35 | "requires": { 36 | "tonal-dictionary": "2.0.0", 37 | "tonal-distance": "2.0.0", 38 | "tonal-note": "2.0.0", 39 | "tonal-pcset": "2.0.0" 40 | } 41 | }, 42 | "tonal-detect": { 43 | "version": "2.0.0", 44 | "resolved": "https://registry.npmjs.org/tonal-detect/-/tonal-detect-2.0.0.tgz", 45 | "integrity": "sha512-vrPZVfPTF7b2TkxEtsMLY103s4PGjly5A2dBo53gKXSLgu16sifL0AokdUO0a4pdk5LUbrAinNQMaSZI7YKayQ==", 46 | "requires": { 47 | "tonal-array": "2.0.0", 48 | "tonal-dictionary": "2.0.0", 49 | "tonal-note": "2.0.0", 50 | "tonal-pcset": "2.0.0" 51 | } 52 | }, 53 | "tonal-dictionary": { 54 | "version": "2.0.0", 55 | "resolved": "https://registry.npmjs.org/tonal-dictionary/-/tonal-dictionary-2.0.0.tgz", 56 | "integrity": "sha512-zqpm91T6jClSadzH1dvYSAkBFBzjWTYW9/96DVuQ78Mzxyqr3RLWHwpClJ6jIIDAnwGXpiZWfs8s+SL2uWCqRQ==", 57 | "requires": { 58 | "tonal-array": "2.0.0", 59 | "tonal-note": "2.0.0", 60 | "tonal-pcset": "2.0.0" 61 | } 62 | }, 63 | "tonal-distance": { 64 | "version": "2.0.0", 65 | "resolved": "https://registry.npmjs.org/tonal-distance/-/tonal-distance-2.0.0.tgz", 66 | "integrity": "sha512-RnY3r4FQwjyXE0eR3qjUG8hfs6vBPXThk9y94eU7wMQ2mLKinP2dW/DdgUF6ORnJ36jnTCyYLEAPp23yxrfXDw==", 67 | "requires": { 68 | "tonal-interval": "2.0.0", 69 | "tonal-note": "2.0.0" 70 | } 71 | }, 72 | "tonal-interval": { 73 | "version": "2.0.0", 74 | "resolved": "https://registry.npmjs.org/tonal-interval/-/tonal-interval-2.0.0.tgz", 75 | "integrity": "sha512-pXobehztDC/dlZsQT5KOca/7wszBjrAULXFlvpzzWPgsCv3808fdN6mR3g/dTZOEG47AP6CpzAxZzNBgbJbCXg==" 76 | }, 77 | "tonal-key": { 78 | "version": "2.0.0", 79 | "resolved": "https://registry.npmjs.org/tonal-key/-/tonal-key-2.0.0.tgz", 80 | "integrity": "sha512-rIXxnG3b9Qc/KQcx7GhDX6xt+Whm4fzpP7/L+diS99LJ/DfiVOTvrC36DBoOa3A+jY/3LWKesfAfwIDoBjPuug==", 81 | "requires": { 82 | "tonal-array": "2.0.0", 83 | "tonal-distance": "2.0.0", 84 | "tonal-note": "2.0.0" 85 | } 86 | }, 87 | "tonal-note": { 88 | "version": "2.0.0", 89 | "resolved": "https://registry.npmjs.org/tonal-note/-/tonal-note-2.0.0.tgz", 90 | "integrity": "sha512-ZUeAloZQE2L3lhobVn7yCX7tT0x+iim26EIkZt8P7f5i4LCxbw/NifS2MgdcCL0Dh+CnaoGD/YMH9QhRmX7Tig==" 91 | }, 92 | "tonal-pcset": { 93 | "version": "2.0.0", 94 | "resolved": "https://registry.npmjs.org/tonal-pcset/-/tonal-pcset-2.0.0.tgz", 95 | "integrity": "sha512-liEq3QvGF13O8PerJ+Xu+NEM3o/ByU+TsZX8JsResozf+McY/pYhSrhTYBn9zmyBFTbppMNkvfIarOb+JPA9zA==", 96 | "requires": { 97 | "tonal-array": "2.0.0", 98 | "tonal-interval": "2.0.0", 99 | "tonal-note": "2.0.0" 100 | } 101 | }, 102 | "tonal-scale": { 103 | "version": "2.0.0", 104 | "resolved": "https://registry.npmjs.org/tonal-scale/-/tonal-scale-2.0.0.tgz", 105 | "integrity": "sha512-g1WqDRSkB67nh+uuxosA6rgAqcHOGzNXDRgZ0fgwDWGLZhGYECgPX4fK/5mDYpmV+ySYGZscItbNBw6FWDCBnA==", 106 | "requires": { 107 | "tonal-array": "2.0.0", 108 | "tonal-dictionary": "2.0.0", 109 | "tonal-distance": "2.0.0", 110 | "tonal-note": "2.0.0", 111 | "tonal-pcset": "2.0.0" 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /tonal-chord-builder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "detect", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "chord-detect.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "tonal": "^2.0.0", 13 | "tonal-detect": "^2.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /twitter/.env-template: -------------------------------------------------------------------------------- 1 | TWITTER_CONSUMER_KEY=your_key_here 2 | TWITTER_CONSUMER_SECRET=your_consumer_secret_here 3 | TWITTER_ACCESS_TOKEN=your_access_token_here 4 | TWITTER_ACCESS_TOKEN_SECRET=your_access_token_secret_here -------------------------------------------------------------------------------- /twitter/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /twitter/README.md: -------------------------------------------------------------------------------- 1 | # Twitter 2 | Communicate with Twitter from Max, using the API. 3 | 4 | ## Getting an API key 5 | Before you can do anything with Twitter, you'll need an API key. Way back in 6 | the day, you could do basic stuff like search tweets or view the global 7 | homepage, even if you didn't have an API key. No longer. The process is a bit 8 | annoying but not too bad. 9 | 10 | 1. Get a Twitter account. If you're reading this, probably you've got one already. 11 | 12 | 2. Go to https://apps.twitter.com/ to create a Twitter App. This app will 13 | contain all of the keys that you need in order to connect your patch. 14 | 15 | 3. Click the "Create New App" button to create a new app. 16 | 17 | 4. Fill out the new app form. Nothing you put here really matters, assuming 18 | you just want Twitter to work from Max, so put whatever you want. 19 | 20 | 5. Once you've created the app, you should be on the app settings page. Near 21 | the top, find the tab "Keys and Access Tokens" and click it. 22 | 23 | 6. Note the Consumer Key and Consumer Secret (aka API Key and API Secret). 24 | You'll need these. 25 | 26 | 7. Near the bottom of this page, click the button "Create my access token". 27 | This is the token that the app will use to do things like send tweets from 28 | you account. 29 | 30 | 8. After you click the button, wait a bit. The Access Token and Access Token 31 | Secret should appear. Note these down as well. 32 | 33 | 9. In this twitter folder, you should see a file name .env-template. This is 34 | an environment file, it contains key-value pairs that other applications, like 35 | Node, can load before running. 36 | 37 | 10. Duplicate the .env-template file to a new file named ".env". It must be named 38 | ".env", not "my.env" or anything like that. Fill in this file using the keys 39 | you got from Twitter. 40 | 41 | 11. BE CAREFUL! Anyone with these keys can tweet from your account, follow and 42 | unfollow people—if they have these keys then they are you on Twitter. Try not 43 | to share these keys accidentally, for example by checking the .env file into 44 | version control. 45 | 46 | 12. You should be all set. Open the Max patcher twitter.maxpat and see! 47 | -------------------------------------------------------------------------------- /twitter/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twitter-node", 3 | "version": "0.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ajv": { 8 | "version": "6.6.1", 9 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", 10 | "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", 11 | "requires": { 12 | "fast-deep-equal": "^2.0.1", 13 | "fast-json-stable-stringify": "^2.0.0", 14 | "json-schema-traverse": "^0.4.1", 15 | "uri-js": "^4.2.2" 16 | } 17 | }, 18 | "asn1": { 19 | "version": "0.2.4", 20 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 21 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 22 | "requires": { 23 | "safer-buffer": "~2.1.0" 24 | } 25 | }, 26 | "assert-plus": { 27 | "version": "1.0.0", 28 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 29 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 30 | }, 31 | "asynckit": { 32 | "version": "0.4.0", 33 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 34 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 35 | }, 36 | "aws-sign2": { 37 | "version": "0.7.0", 38 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 39 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 40 | }, 41 | "aws4": { 42 | "version": "1.8.0", 43 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", 44 | "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" 45 | }, 46 | "bcrypt-pbkdf": { 47 | "version": "1.0.2", 48 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 49 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 50 | "requires": { 51 | "tweetnacl": "^0.14.3" 52 | } 53 | }, 54 | "caseless": { 55 | "version": "0.12.0", 56 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 57 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 58 | }, 59 | "combined-stream": { 60 | "version": "1.0.7", 61 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", 62 | "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", 63 | "requires": { 64 | "delayed-stream": "~1.0.0" 65 | } 66 | }, 67 | "core-util-is": { 68 | "version": "1.0.2", 69 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 70 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 71 | }, 72 | "dashdash": { 73 | "version": "1.14.1", 74 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 75 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 76 | "requires": { 77 | "assert-plus": "^1.0.0" 78 | } 79 | }, 80 | "deep-extend": { 81 | "version": "0.6.0", 82 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 83 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" 84 | }, 85 | "delayed-stream": { 86 | "version": "1.0.0", 87 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 88 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 89 | }, 90 | "dotenv": { 91 | "version": "5.0.1", 92 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz", 93 | "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==" 94 | }, 95 | "ecc-jsbn": { 96 | "version": "0.1.2", 97 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 98 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 99 | "requires": { 100 | "jsbn": "~0.1.0", 101 | "safer-buffer": "^2.1.0" 102 | } 103 | }, 104 | "extend": { 105 | "version": "3.0.2", 106 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 107 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 108 | }, 109 | "extsprintf": { 110 | "version": "1.3.0", 111 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 112 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 113 | }, 114 | "fast-deep-equal": { 115 | "version": "2.0.1", 116 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 117 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" 118 | }, 119 | "fast-json-stable-stringify": { 120 | "version": "2.0.0", 121 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 122 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 123 | }, 124 | "forever-agent": { 125 | "version": "0.6.1", 126 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 127 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 128 | }, 129 | "form-data": { 130 | "version": "2.3.3", 131 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 132 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 133 | "requires": { 134 | "asynckit": "^0.4.0", 135 | "combined-stream": "^1.0.6", 136 | "mime-types": "^2.1.12" 137 | } 138 | }, 139 | "getpass": { 140 | "version": "0.1.7", 141 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 142 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 143 | "requires": { 144 | "assert-plus": "^1.0.0" 145 | } 146 | }, 147 | "har-schema": { 148 | "version": "2.0.0", 149 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 150 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 151 | }, 152 | "har-validator": { 153 | "version": "5.1.3", 154 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", 155 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", 156 | "requires": { 157 | "ajv": "^6.5.5", 158 | "har-schema": "^2.0.0" 159 | } 160 | }, 161 | "http-signature": { 162 | "version": "1.2.0", 163 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 164 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 165 | "requires": { 166 | "assert-plus": "^1.0.0", 167 | "jsprim": "^1.2.2", 168 | "sshpk": "^1.7.0" 169 | } 170 | }, 171 | "is-typedarray": { 172 | "version": "1.0.0", 173 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 174 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 175 | }, 176 | "isstream": { 177 | "version": "0.1.2", 178 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 179 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 180 | }, 181 | "jsbn": { 182 | "version": "0.1.1", 183 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 184 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 185 | }, 186 | "json-schema": { 187 | "version": "0.2.3", 188 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 189 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 190 | }, 191 | "json-schema-traverse": { 192 | "version": "0.4.1", 193 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 194 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 195 | }, 196 | "json-stringify-safe": { 197 | "version": "5.0.1", 198 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 199 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 200 | }, 201 | "jsprim": { 202 | "version": "1.4.1", 203 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 204 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 205 | "requires": { 206 | "assert-plus": "1.0.0", 207 | "extsprintf": "1.3.0", 208 | "json-schema": "0.2.3", 209 | "verror": "1.10.0" 210 | } 211 | }, 212 | "mime-db": { 213 | "version": "1.37.0", 214 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", 215 | "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" 216 | }, 217 | "mime-types": { 218 | "version": "2.1.21", 219 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", 220 | "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", 221 | "requires": { 222 | "mime-db": "~1.37.0" 223 | } 224 | }, 225 | "oauth-sign": { 226 | "version": "0.9.0", 227 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 228 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" 229 | }, 230 | "performance-now": { 231 | "version": "2.1.0", 232 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 233 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 234 | }, 235 | "psl": { 236 | "version": "1.1.29", 237 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", 238 | "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" 239 | }, 240 | "punycode": { 241 | "version": "2.1.1", 242 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 243 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 244 | }, 245 | "qs": { 246 | "version": "6.5.2", 247 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 248 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 249 | }, 250 | "request": { 251 | "version": "2.88.0", 252 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", 253 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", 254 | "requires": { 255 | "aws-sign2": "~0.7.0", 256 | "aws4": "^1.8.0", 257 | "caseless": "~0.12.0", 258 | "combined-stream": "~1.0.6", 259 | "extend": "~3.0.2", 260 | "forever-agent": "~0.6.1", 261 | "form-data": "~2.3.2", 262 | "har-validator": "~5.1.0", 263 | "http-signature": "~1.2.0", 264 | "is-typedarray": "~1.0.0", 265 | "isstream": "~0.1.2", 266 | "json-stringify-safe": "~5.0.1", 267 | "mime-types": "~2.1.19", 268 | "oauth-sign": "~0.9.0", 269 | "performance-now": "^2.1.0", 270 | "qs": "~6.5.2", 271 | "safe-buffer": "^5.1.2", 272 | "tough-cookie": "~2.4.3", 273 | "tunnel-agent": "^0.6.0", 274 | "uuid": "^3.3.2" 275 | } 276 | }, 277 | "safe-buffer": { 278 | "version": "5.1.2", 279 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 280 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 281 | }, 282 | "safer-buffer": { 283 | "version": "2.1.2", 284 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 285 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 286 | }, 287 | "sshpk": { 288 | "version": "1.15.2", 289 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", 290 | "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", 291 | "requires": { 292 | "asn1": "~0.2.3", 293 | "assert-plus": "^1.0.0", 294 | "bcrypt-pbkdf": "^1.0.0", 295 | "dashdash": "^1.12.0", 296 | "ecc-jsbn": "~0.1.1", 297 | "getpass": "^0.1.1", 298 | "jsbn": "~0.1.0", 299 | "safer-buffer": "^2.0.2", 300 | "tweetnacl": "~0.14.0" 301 | } 302 | }, 303 | "tough-cookie": { 304 | "version": "2.4.3", 305 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", 306 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", 307 | "requires": { 308 | "psl": "^1.1.24", 309 | "punycode": "^1.4.1" 310 | }, 311 | "dependencies": { 312 | "punycode": { 313 | "version": "1.4.1", 314 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 315 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 316 | } 317 | } 318 | }, 319 | "tunnel-agent": { 320 | "version": "0.6.0", 321 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 322 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 323 | "requires": { 324 | "safe-buffer": "^5.0.1" 325 | } 326 | }, 327 | "tweetnacl": { 328 | "version": "0.14.5", 329 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 330 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 331 | }, 332 | "twitter": { 333 | "version": "1.7.1", 334 | "resolved": "https://registry.npmjs.org/twitter/-/twitter-1.7.1.tgz", 335 | "integrity": "sha1-B2I3jx3BwFDkj2ZqypBOJLGpYvQ=", 336 | "requires": { 337 | "deep-extend": "^0.5.0", 338 | "request": "^2.72.0" 339 | }, 340 | "dependencies": { 341 | "deep-extend": { 342 | "version": "0.5.1", 343 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", 344 | "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==" 345 | } 346 | } 347 | }, 348 | "uri-js": { 349 | "version": "4.2.2", 350 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 351 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 352 | "requires": { 353 | "punycode": "^2.1.0" 354 | } 355 | }, 356 | "uuid": { 357 | "version": "3.3.2", 358 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 359 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" 360 | }, 361 | "verror": { 362 | "version": "1.10.0", 363 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 364 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 365 | "requires": { 366 | "assert-plus": "^1.0.0", 367 | "core-util-is": "1.0.2", 368 | "extsprintf": "^1.2.0" 369 | } 370 | } 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /twitter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twitter-node", 3 | "version": "0.0.0", 4 | "description": "Connect to twitter from your Max patcher, using OAuth and Node", 5 | "main": "twitter.js", 6 | "author": "Cycling '74", 7 | "license": "ISC", 8 | "dependencies": { 9 | "dotenv": "^5.0.1", 10 | "twitter": "^1.7.1", 11 | "deep-extend": ">=0.5.1" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /twitter/twitter.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------- 2 | // twitter.js - Tweet from your patch 3 | // 4 | // Check the README for information on how to get a Twitter API key, 5 | // which you'll need in order to get any of this to work. 6 | // 7 | // This script uses the twitter NPM package, availiable here: 8 | // https://github.com/desmondmorris/node-twitter 9 | // 10 | // --------------------------------------------------------------------- 11 | 12 | 13 | const maxAPI = require("max-api"); 14 | 15 | // Attempt to load the dotenv module, which is needed to load the .env file containing the Twitter API keys. 16 | let dotenv_module; 17 | try { 18 | dotenv_module = require("dotenv"); 19 | dotenv_module.config(); 20 | } catch (e) { 21 | maxAPI.post(e, maxAPI.POST_LEVELS.ERROR); 22 | maxAPI.post("Could not load the dotenv module. Please be sure to send the message 'script npm install' to the node.script object to download node modules", maxAPI.POST_LEVELS.ERROR); 23 | process.exit(1); // Exit with an error if dotenv is not installed. 24 | } 25 | 26 | // Make sure that the API keys are loaded. Dotenv will put them in process.env if they are. 27 | ["TWITTER_CONSUMER_KEY", "TWITTER_CONSUMER_SECRET", "TWITTER_ACCESS_TOKEN", "TWITTER_ACCESS_TOKEN_SECRET"].forEach(key => { 28 | if (!process.env[key]) { 29 | maxAPI.post(`No value for ${key} in .env file. Please make sure to create a file called .env with the appropriate key-value pair.`, maxAPI.POST_LEVELS.ERROR); 30 | process.exit(0); // Exit without an error if the keys are missing 31 | } 32 | }); 33 | 34 | const Twitter = require("twitter"); 35 | 36 | // Create a twitter client object, using the keys from the process. 37 | const client = new Twitter({ 38 | consumer_key: process.env.TWITTER_CONSUMER_KEY, 39 | consumer_secret: process.env.TWITTER_CONSUMER_SECRET, 40 | access_token_key: process.env.TWITTER_ACCESS_TOKEN, 41 | access_token_secret: process.env.TWITTER_ACCESS_TOKEN_SECRET 42 | }); 43 | 44 | // Add the handlers. This defines how the running Node script will respond to messages from maxAPI. 45 | const handlers = { 46 | 47 | // When the script gets the message "getTimeline", call this function. 48 | getTimeline: () => { 49 | client.get("statuses/user_timeline", {}, (error, tweets) => { 50 | if (!error) { 51 | const output = ["timeline"]; 52 | tweets.forEach(tweet => { 53 | output.push(tweet.text); 54 | }); 55 | maxAPI.outlet(output); 56 | } else { 57 | maxAPI.post(error, maxAPI.POST_LEVELS.ERROR); 58 | } 59 | }); 60 | }, 61 | 62 | // When the script gets the message "postStatus", call this function. 63 | // The status argument will be whatever comes after the postStatus message. In maxAPI, we use the tosymbol object to 64 | // turn a list of symbols into a single symbol. 65 | postStatus: (status) => { 66 | const params = { 67 | status 68 | }; 69 | if (status) { 70 | client.post("statuses/update", params, (error, tweet) => { 71 | if (!error) { 72 | maxAPI.outlet("tweet", tweet); 73 | } else { 74 | maxAPI.post(error, maxAPI.POST_LEVELS.ERROR); 75 | } 76 | }); 77 | } 78 | } 79 | }; 80 | 81 | maxAPI.addHandlers(handlers); 82 | -------------------------------------------------------------------------------- /typescript/.gitignore: -------------------------------------------------------------------------------- 1 | lib -------------------------------------------------------------------------------- /typescript/README.md: -------------------------------------------------------------------------------- 1 | # Typescript 2 | Use [Typescript](https://www.typescriptlang.org/) to author your Node for Max scripts. 3 | 4 | ## Usage 5 | This simple example showcases how to use Typescript and the `@types/max-api` package in order to author 6 | your Node for Max code. 7 | 8 | 1. The `tsconfig.json` file provides a simply configuration in order to be able to compile the TS code of this example. 9 | Note that the configuration targets Node v16 which is the current version shipped with Node for Max. 10 | 2. The source code is placed in `src/n4m.typescript.ts` 11 | 3. Open `n4m.ts.maxpat` which has more instructions 12 | 13 | 14 | ## Disclaimer 15 | 16 | The `n4m.ts.index.js` file is used as a simple entrypoint here in order to avoid headaches with having to copy the 17 | `lib` folder into your search path for people that didn't clone the whole repo into their Search Path. 18 | 19 | You might not need to do this in your patch, which is probably either part of a Max project or placed in your Search Path already. 20 | -------------------------------------------------------------------------------- /typescript/n4m.ts.index.js: -------------------------------------------------------------------------------- 1 | require("./lib/n4m.typescript.js"); 2 | -------------------------------------------------------------------------------- /typescript/n4m.ts.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 8, 6 | "minor" : 5, 7 | "revision" : 2, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "classnamespace" : "box", 13 | "rect" : [ 109.0, 106.0, 1102.0, 812.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 | "assistshowspatchername" : 0, 41 | "boxes" : [ { 42 | "box" : { 43 | "id" : "obj-2", 44 | "maxclass" : "message", 45 | "numinlets" : 2, 46 | "numoutlets" : 1, 47 | "outlettype" : [ "" ], 48 | "patching_rect" : [ 35.0, 240.0, 63.0, 22.0 ], 49 | "text" : "script stop" 50 | } 51 | 52 | } 53 | , { 54 | "box" : { 55 | "id" : "obj-49", 56 | "maxclass" : "newobj", 57 | "numinlets" : 2, 58 | "numoutlets" : 4, 59 | "outlettype" : [ "dictionary", "", "", "" ], 60 | "patching_rect" : [ 826.5, 55.5, 99.0, 22.0 ], 61 | "saved_object_attributes" : { 62 | "embed" : 0, 63 | "parameter_enable" : 0, 64 | "parameter_mappable" : 0 65 | } 66 | , 67 | "text" : "dict n4m.test.dict" 68 | } 69 | 70 | } 71 | , { 72 | "box" : { 73 | "id" : "obj-48", 74 | "maxclass" : "newobj", 75 | "numinlets" : 1, 76 | "numoutlets" : 0, 77 | "patching_rect" : [ 485.191918253898621, 226.0, 43.0, 22.0 ], 78 | "text" : "s tests" 79 | } 80 | 81 | } 82 | , { 83 | "box" : { 84 | "id" : "obj-47", 85 | "maxclass" : "newobj", 86 | "numinlets" : 0, 87 | "numoutlets" : 1, 88 | "outlettype" : [ "" ], 89 | "patching_rect" : [ 58.0, 291.5, 41.0, 22.0 ], 90 | "text" : "r tests" 91 | } 92 | 93 | } 94 | , { 95 | "box" : { 96 | "id" : "obj-46", 97 | "maxclass" : "message", 98 | "numinlets" : 2, 99 | "numoutlets" : 1, 100 | "outlettype" : [ "" ], 101 | "patching_rect" : [ 826.5, 128.0, 71.0, 22.0 ], 102 | "text" : "update_dict" 103 | } 104 | 105 | } 106 | , { 107 | "box" : { 108 | "id" : "obj-45", 109 | "maxclass" : "message", 110 | "numinlets" : 2, 111 | "numoutlets" : 1, 112 | "outlettype" : [ "" ], 113 | "patching_rect" : [ 826.5, 90.5, 59.0, 22.0 ], 114 | "text" : "write_dict" 115 | } 116 | 117 | } 118 | , { 119 | "box" : { 120 | "id" : "obj-44", 121 | "maxclass" : "newobj", 122 | "numinlets" : 1, 123 | "numoutlets" : 1, 124 | "outlettype" : [ "" ], 125 | "patching_rect" : [ 647.5, 165.0, 132.0, 22.0 ], 126 | "text" : "append \"message test\"" 127 | } 128 | 129 | } 130 | , { 131 | "box" : { 132 | "id" : "obj-43", 133 | "maxclass" : "message", 134 | "numinlets" : 2, 135 | "numoutlets" : 1, 136 | "outlettype" : [ "" ], 137 | "patching_rect" : [ 647.5, 128.0, 34.0, 22.0 ], 138 | "text" : "error" 139 | } 140 | 141 | } 142 | , { 143 | "box" : { 144 | "id" : "obj-41", 145 | "maxclass" : "message", 146 | "numinlets" : 2, 147 | "numoutlets" : 1, 148 | "outlettype" : [ "" ], 149 | "patching_rect" : [ 647.5, 55.5, 29.5, 22.0 ], 150 | "text" : "info" 151 | } 152 | 153 | } 154 | , { 155 | "box" : { 156 | "id" : "obj-39", 157 | "maxclass" : "message", 158 | "numinlets" : 2, 159 | "numoutlets" : 1, 160 | "outlettype" : [ "" ], 161 | "patching_rect" : [ 647.5, 90.5, 35.0, 22.0 ], 162 | "text" : "warn" 163 | } 164 | 165 | } 166 | , { 167 | "box" : { 168 | "id" : "obj-37", 169 | "maxclass" : "newobj", 170 | "numinlets" : 1, 171 | "numoutlets" : 0, 172 | "patching_rect" : [ 28.0, 363.0, 91.0, 22.0 ], 173 | "text" : "print @popup 1" 174 | } 175 | 176 | } 177 | , { 178 | "box" : { 179 | "id" : "obj-35", 180 | "maxclass" : "number", 181 | "numinlets" : 1, 182 | "numoutlets" : 2, 183 | "outlettype" : [ "", "bang" ], 184 | "parameter_enable" : 0, 185 | "patching_rect" : [ 485.191918253898621, 165.0, 50.0, 22.0 ] 186 | } 187 | 188 | } 189 | , { 190 | "box" : { 191 | "id" : "obj-33", 192 | "maxclass" : "button", 193 | "numinlets" : 1, 194 | "numoutlets" : 1, 195 | "outlettype" : [ "bang" ], 196 | "parameter_enable" : 0, 197 | "patching_rect" : [ 485.191918253898621, 128.0, 24.0, 24.0 ] 198 | } 199 | 200 | } 201 | , { 202 | "box" : { 203 | "id" : "obj-31", 204 | "maxclass" : "message", 205 | "numinlets" : 2, 206 | "numoutlets" : 1, 207 | "outlettype" : [ "" ], 208 | "patching_rect" : [ 485.191918253898621, 90.5, 61.0, 22.0 ], 209 | "text" : "list_levels" 210 | } 211 | 212 | } 213 | , { 214 | "box" : { 215 | "id" : "obj-30", 216 | "maxclass" : "message", 217 | "numinlets" : 2, 218 | "numoutlets" : 1, 219 | "outlettype" : [ "" ], 220 | "patching_rect" : [ 485.191918253898621, 55.5, 85.0, 22.0 ], 221 | "text" : "list_messages" 222 | } 223 | 224 | } 225 | , { 226 | "box" : { 227 | "bubble" : 1, 228 | "id" : "obj-24", 229 | "linecount" : 2, 230 | "maxclass" : "comment", 231 | "numinlets" : 1, 232 | "numoutlets" : 0, 233 | "patching_rect" : [ 241.436083614826202, 316.5, 251.0, 37.0 ], 234 | "text" : "Note the @watch 1\" which forces our script to restart whenever we run \"build\"" 235 | } 236 | 237 | } 238 | , { 239 | "box" : { 240 | "fontsize" : 18.0, 241 | "id" : "obj-23", 242 | "maxclass" : "comment", 243 | "numinlets" : 1, 244 | "numoutlets" : 0, 245 | "patching_rect" : [ 466.0, 19.285714745521545, 211.0, 27.0 ], 246 | "text" : "2. Tests", 247 | "textcolor" : [ 0.0, 0.0, 0.0, 1.0 ] 248 | } 249 | 250 | } 251 | , { 252 | "box" : { 253 | "fontsize" : 18.0, 254 | "id" : "obj-21", 255 | "maxclass" : "comment", 256 | "numinlets" : 1, 257 | "numoutlets" : 0, 258 | "patching_rect" : [ 35.0, 19.285714745521545, 211.0, 27.0 ], 259 | "text" : "1. Setup", 260 | "textcolor" : [ 0.062745098039216, 0.058823529411765, 0.058823529411765, 1.0 ] 261 | } 262 | 263 | } 264 | , { 265 | "box" : { 266 | "bubble" : 1, 267 | "id" : "obj-17", 268 | "maxclass" : "comment", 269 | "numinlets" : 1, 270 | "numoutlets" : 0, 271 | "patching_rect" : [ 169.436083614826202, 186.0, 220.0, 24.0 ], 272 | "text" : "3) Start the script" 273 | } 274 | 275 | } 276 | , { 277 | "box" : { 278 | "bubble" : 1, 279 | "id" : "obj-15", 280 | "linecount" : 3, 281 | "maxclass" : "comment", 282 | "numinlets" : 1, 283 | "numoutlets" : 0, 284 | "patching_rect" : [ 169.436083614826202, 113.5, 228.0, 51.0 ], 285 | "text" : "2) Compile your Typescript sources (run this anytime you changed your .ts files)" 286 | } 287 | 288 | } 289 | , { 290 | "box" : { 291 | "bubble" : 1, 292 | "id" : "obj-16", 293 | "linecount" : 2, 294 | "maxclass" : "comment", 295 | "numinlets" : 1, 296 | "numoutlets" : 0, 297 | "patching_rect" : [ 169.436083614826202, 55.5, 228.0, 37.0 ], 298 | "text" : "1) Install all NPM dependencies (you only have to do this once)." 299 | } 300 | 301 | } 302 | , { 303 | "box" : { 304 | "id" : "obj-10", 305 | "maxclass" : "message", 306 | "numinlets" : 2, 307 | "numoutlets" : 1, 308 | "outlettype" : [ "" ], 309 | "patching_rect" : [ 35.0, 128.0, 128.0, 22.0 ], 310 | "text" : "script npm run build" 311 | } 312 | 313 | } 314 | , { 315 | "box" : { 316 | "id" : "obj-7", 317 | "maxclass" : "message", 318 | "numinlets" : 2, 319 | "numoutlets" : 1, 320 | "outlettype" : [ "" ], 321 | "patching_rect" : [ 35.0, 63.0, 128.0, 22.0 ], 322 | "text" : "script npm install" 323 | } 324 | 325 | } 326 | , { 327 | "box" : { 328 | "bgmode" : 0, 329 | "border" : 0, 330 | "clickthrough" : 0, 331 | "enablehscroll" : 0, 332 | "enablevscroll" : 0, 333 | "id" : "obj-5", 334 | "lockeddragscroll" : 0, 335 | "lockedsize" : 0, 336 | "maxclass" : "bpatcher", 337 | "name" : "n4m.monitor.maxpat", 338 | "numinlets" : 1, 339 | "numoutlets" : 1, 340 | "offset" : [ 0.0, 0.0 ], 341 | "outlettype" : [ "bang" ], 342 | "patching_rect" : [ 216.0, 394.0, 400.0, 220.0 ], 343 | "viewvisibility" : 1 344 | } 345 | 346 | } 347 | , { 348 | "box" : { 349 | "id" : "obj-3", 350 | "maxclass" : "message", 351 | "numinlets" : 2, 352 | "numoutlets" : 1, 353 | "outlettype" : [ "" ], 354 | "patching_rect" : [ 35.0, 188.0, 128.0, 22.0 ], 355 | "text" : "script start" 356 | } 357 | 358 | } 359 | , { 360 | "box" : { 361 | "id" : "obj-1", 362 | "maxclass" : "newobj", 363 | "numinlets" : 1, 364 | "numoutlets" : 2, 365 | "outlettype" : [ "", "" ], 366 | "patching_rect" : [ 28.0, 324.0, 207.0, 22.0 ], 367 | "saved_object_attributes" : { 368 | "autostart" : 0, 369 | "defer" : 0, 370 | "node_bin_path" : "", 371 | "npm_bin_path" : "", 372 | "watch" : 1 373 | } 374 | , 375 | "text" : "node.script n4m.ts.index.js @watch 1" 376 | } 377 | 378 | } 379 | , { 380 | "box" : { 381 | "angle" : 270.0, 382 | "bgcolor" : [ 0.752941176470588, 0.76078431372549, 0.752941176470588, 1.0 ], 383 | "bordercolor" : [ 0.807843137254902, 0.898039215686275, 0.909803921568627, 1.0 ], 384 | "id" : "obj-19", 385 | "maxclass" : "panel", 386 | "mode" : 0, 387 | "numinlets" : 1, 388 | "numoutlets" : 0, 389 | "patching_rect" : [ 28.0, 5.571428418159485, 385.5, 268.0 ], 390 | "proportion" : 0.39 391 | } 392 | 393 | } 394 | , { 395 | "box" : { 396 | "angle" : 270.0, 397 | "bgcolor" : [ 0.752941176470588, 0.76078431372549, 0.752941176470588, 1.0 ], 398 | "bordercolor" : [ 0.807843137254902, 0.898039215686275, 0.909803921568627, 1.0 ], 399 | "id" : "obj-50", 400 | "maxclass" : "panel", 401 | "mode" : 0, 402 | "numinlets" : 1, 403 | "numoutlets" : 0, 404 | "patching_rect" : [ 454.0, 5.571428418159485, 492.5, 268.0 ], 405 | "proportion" : 0.39 406 | } 407 | 408 | } 409 | ], 410 | "lines" : [ { 411 | "patchline" : { 412 | "destination" : [ "obj-37", 0 ], 413 | "source" : [ "obj-1", 0 ] 414 | } 415 | 416 | } 417 | , { 418 | "patchline" : { 419 | "destination" : [ "obj-5", 0 ], 420 | "source" : [ "obj-1", 1 ] 421 | } 422 | 423 | } 424 | , { 425 | "patchline" : { 426 | "destination" : [ "obj-1", 0 ], 427 | "midpoints" : [ 44.5, 174.0, 21.0, 174.0, 21.0, 309.0, 37.5, 309.0 ], 428 | "source" : [ "obj-10", 0 ] 429 | } 430 | 431 | } 432 | , { 433 | "patchline" : { 434 | "destination" : [ "obj-1", 0 ], 435 | "midpoints" : [ 44.5, 309.0, 37.5, 309.0 ], 436 | "source" : [ "obj-2", 0 ] 437 | } 438 | 439 | } 440 | , { 441 | "patchline" : { 442 | "destination" : [ "obj-1", 0 ], 443 | "midpoints" : [ 44.5, 225.0, 21.0, 225.0, 21.0, 309.0, 37.5, 309.0 ], 444 | "source" : [ "obj-3", 0 ] 445 | } 446 | 447 | } 448 | , { 449 | "patchline" : { 450 | "destination" : [ "obj-48", 0 ], 451 | "midpoints" : [ 494.691918253898621, 78.0, 471.0, 78.0, 471.0, 208.0, 494.691918253898621, 208.0 ], 452 | "source" : [ "obj-30", 0 ] 453 | } 454 | 455 | } 456 | , { 457 | "patchline" : { 458 | "destination" : [ "obj-48", 0 ], 459 | "midpoints" : [ 494.691918253898621, 114.0, 471.0, 114.0, 471.0, 208.0, 494.691918253898621, 208.0 ], 460 | "source" : [ "obj-31", 0 ] 461 | } 462 | 463 | } 464 | , { 465 | "patchline" : { 466 | "destination" : [ "obj-48", 0 ], 467 | "midpoints" : [ 494.691918253898621, 153.0, 471.0, 153.0, 471.0, 208.0, 494.691918253898621, 208.0 ], 468 | "source" : [ "obj-33", 0 ] 469 | } 470 | 471 | } 472 | , { 473 | "patchline" : { 474 | "destination" : [ "obj-48", 0 ], 475 | "midpoints" : [ 494.691918253898621, 219.0, 494.691918253898621, 219.0 ], 476 | "source" : [ "obj-35", 0 ] 477 | } 478 | 479 | } 480 | , { 481 | "patchline" : { 482 | "destination" : [ "obj-44", 0 ], 483 | "midpoints" : [ 657.0, 114.0, 633.0, 114.0, 633.0, 159.0, 657.0, 159.0 ], 484 | "source" : [ "obj-39", 0 ] 485 | } 486 | 487 | } 488 | , { 489 | "patchline" : { 490 | "destination" : [ "obj-44", 0 ], 491 | "midpoints" : [ 657.0, 78.0, 633.0, 78.0, 633.0, 159.0, 657.0, 159.0 ], 492 | "source" : [ "obj-41", 0 ] 493 | } 494 | 495 | } 496 | , { 497 | "patchline" : { 498 | "destination" : [ "obj-44", 0 ], 499 | "midpoints" : [ 657.0, 153.0, 657.0, 153.0 ], 500 | "source" : [ "obj-43", 0 ] 501 | } 502 | 503 | } 504 | , { 505 | "patchline" : { 506 | "destination" : [ "obj-48", 0 ], 507 | "midpoints" : [ 657.0, 205.0, 494.691918253898621, 205.0 ], 508 | "source" : [ "obj-44", 0 ] 509 | } 510 | 511 | } 512 | , { 513 | "patchline" : { 514 | "destination" : [ "obj-48", 0 ], 515 | "midpoints" : [ 836.0, 114.0, 812.80473518371582, 114.0, 812.80473518371582, 205.0, 494.691918253898621, 205.0 ], 516 | "source" : [ "obj-45", 0 ] 517 | } 518 | 519 | } 520 | , { 521 | "patchline" : { 522 | "destination" : [ "obj-48", 0 ], 523 | "midpoints" : [ 836.0, 205.0, 494.691918253898621, 205.0 ], 524 | "source" : [ "obj-46", 0 ] 525 | } 526 | 527 | } 528 | , { 529 | "patchline" : { 530 | "destination" : [ "obj-1", 0 ], 531 | "source" : [ "obj-47", 0 ] 532 | } 533 | 534 | } 535 | , { 536 | "patchline" : { 537 | "destination" : [ "obj-1", 0 ], 538 | "midpoints" : [ 44.5, 114.0, 21.0, 114.0, 21.0, 309.0, 37.5, 309.0 ], 539 | "source" : [ "obj-7", 0 ] 540 | } 541 | 542 | } 543 | ], 544 | "dependency_cache" : [ { 545 | "name" : "fit_jweb_to_bounds.js", 546 | "bootpath" : "C74:/packages/Node for Max/patchers/debug-monitor", 547 | "type" : "TEXT", 548 | "implicit" : 1 549 | } 550 | , { 551 | "name" : "n4m.monitor.maxpat", 552 | "bootpath" : "C74:/packages/Node for Max/patchers/debug-monitor", 553 | "type" : "JSON", 554 | "implicit" : 1 555 | } 556 | , { 557 | "name" : "n4m.ts.index.js", 558 | "bootpath" : "~/c74/n4m-examples/typescript", 559 | "patcherrelativepath" : ".", 560 | "type" : "TEXT", 561 | "implicit" : 1 562 | } 563 | , { 564 | "name" : "resize_n4m_monitor_patcher.js", 565 | "bootpath" : "C74:/packages/Node for Max/patchers/debug-monitor", 566 | "type" : "TEXT", 567 | "implicit" : 1 568 | } 569 | ], 570 | "autosave" : 0 571 | } 572 | 573 | } 574 | -------------------------------------------------------------------------------- /typescript/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "n4m-ts", 3 | "version": "0.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "n4m-ts", 9 | "version": "0.0.0", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@types/max-api": "^2.0.0", 13 | "@types/node": "^16.18.23", 14 | "typescript": "^5.0.3" 15 | } 16 | }, 17 | "node_modules/@types/max-api": { 18 | "version": "2.0.0", 19 | "resolved": "https://registry.npmjs.org/@types/max-api/-/max-api-2.0.0.tgz", 20 | "integrity": "sha512-0CX7CzBuSkfphE0Zanmz/Ehpgcd4xS8okP1elII9nx4U7EJ7U9MhuymObISjHxDMwX8HhKbLQoPWXoHEsZRHlA==", 21 | "dev": true 22 | }, 23 | "node_modules/@types/node": { 24 | "version": "16.18.23", 25 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.23.tgz", 26 | "integrity": "sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g==", 27 | "dev": true 28 | }, 29 | "node_modules/typescript": { 30 | "version": "5.0.3", 31 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.3.tgz", 32 | "integrity": "sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA==", 33 | "dev": true, 34 | "bin": { 35 | "tsc": "bin/tsc", 36 | "tsserver": "bin/tsserver" 37 | }, 38 | "engines": { 39 | "node": ">=12.20" 40 | } 41 | } 42 | }, 43 | "dependencies": { 44 | "@types/max-api": { 45 | "version": "2.0.0", 46 | "resolved": "https://registry.npmjs.org/@types/max-api/-/max-api-2.0.0.tgz", 47 | "integrity": "sha512-0CX7CzBuSkfphE0Zanmz/Ehpgcd4xS8okP1elII9nx4U7EJ7U9MhuymObISjHxDMwX8HhKbLQoPWXoHEsZRHlA==", 48 | "dev": true 49 | }, 50 | "@types/node": { 51 | "version": "16.18.23", 52 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.23.tgz", 53 | "integrity": "sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g==", 54 | "dev": true 55 | }, 56 | "typescript": { 57 | "version": "5.0.3", 58 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.3.tgz", 59 | "integrity": "sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA==", 60 | "dev": true 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "n4m-ts", 3 | "version": "0.0.0", 4 | "description": "Use N4M with Typescript", 5 | "author": "Cycling '74", 6 | "license": "ISC", 7 | "scripts": { 8 | "build": "tsc -b" 9 | }, 10 | "devDependencies": { 11 | "@types/max-api": "^2.0.0", 12 | "@types/node": "^16.18.23", 13 | "typescript": "^5.0.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /typescript/src/n4m.typescript.ts: -------------------------------------------------------------------------------- 1 | import maxAPI from "max-api"; 2 | 3 | maxAPI.post(`Running in MAX_ENV: ${process.env.MAX_ENV as string}`); 4 | 5 | maxAPI.addHandler(maxAPI.MESSAGE_TYPES.BANG, async () => { 6 | await maxAPI.outletBang(); 7 | }); 8 | 9 | maxAPI.addHandler(maxAPI.MESSAGE_TYPES.NUMBER, async (num: number) => { 10 | await maxAPI.outlet("num", `${num}`); 11 | }); 12 | 13 | maxAPI.addHandler("list_messages", async () => { 14 | await maxAPI.post(`MESSAGE_TYPES:\n${Object.keys(maxAPI.MESSAGE_TYPES).join("\n")}`); 15 | }); 16 | 17 | maxAPI.addHandler("list_levels", async () => { 18 | await maxAPI.post(`POST_LEVELS:\n${Object.keys(maxAPI.POST_LEVELS).join("\n")}`); 19 | }); 20 | 21 | const levelHandlers: Record = {}; 22 | for (const level of Object.values(maxAPI.POST_LEVELS)) { 23 | levelHandlers[level] = async (msg: string) => { 24 | await maxAPI.post(`echo ${msg}`, level); 25 | }; 26 | } 27 | 28 | maxAPI.addHandlers(levelHandlers); 29 | 30 | const dictID = "n4m.test.dict"; 31 | 32 | maxAPI.addHandler("write_dict", async () => { 33 | const d = await maxAPI.getDict(dictID); 34 | await maxAPI.setDict(dictID, { counter: (d?.counter as number || 0) + 1, timestamp: new Date().toISOString() }); 35 | }); 36 | 37 | maxAPI.addHandler("update_dict", async () => { 38 | const d = await maxAPI.getDict(dictID); 39 | await maxAPI.updateDict(dictID, "counter", (d?.counter as number || 0) + 1); 40 | await maxAPI.updateDict(dictID, "timestamp", new Date().toISOString()); 41 | }); 42 | -------------------------------------------------------------------------------- /typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2021", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "noImplicitAny": false, 7 | "strict": true, 8 | "incremental": true, 9 | "esModuleInterop": true, 10 | "outDir": "lib" 11 | }, 12 | "include": [ 13 | "./src" 14 | ] 15 | } 16 | --------------------------------------------------------------------------------