├── .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 |
3 |
4 |
5 | # Node For Max Examples
6 | [](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 | Request Data from /info/random()
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 | Launch the cors-service.maxpat Max patch.
18 | (First time) Click the [script npm install] message to load
19 | the required libraries and packages.
20 | Click the [script start] message to start the Node process.
21 | 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.
24 | If things are running correctly, you should see the results
25 | in your browser window. If not, check the Max window for errors.
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 |
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 |
6 |
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 |
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 | Item
12 | Status
13 | Value
14 |
15 |
16 | Random
17 |
18 | 0.00
19 |
20 |
21 | Sine Wave
22 |
23 | 0.00
24 |
25 |
26 | Square Wave
27 |
28 | 0.00
29 |
30 |
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 |
--------------------------------------------------------------------------------