JavaScript
Using JavaScript, you can script the behavior of Max by writing text code. Most of the things that you could do with a Max external, you can also do with a JavaScript object.
- Receive messages from inlets
- Manage internal state
- Send messages to outlets
- Perform custom drawing and handle mouse events
- Schedule events
- Query the state of the patcher
- Other functions exposed through the JavaScript API
JavaScript Objects
The JavaScript family of objects v8 and v8ui (js and jsui using the older JavaScript engine) let you define a custom Max object using JavaScript. The JavaScript code that you write to define the behavior of your object can be loaded as a separate .js
file, or embedded in your patcher.
There are two JavaScript objects: v8 and v8ui. The main difference between the two is that v8 is a simple Max object, while v8ui provides a drawing and interaction context. Use v8 to implement a generic Max object using JavaScript, and use v8ui to implement a user interface object. You can also replace the default drawing behavior of any UI object using the @jspainter
attribute. See **Custom UI Objects for a full description of working with @jspainter
and jsui.
jsthis
Before calling into your JavaScript code, Max binds an instance of jsthis to this
. Doing so puts a number of Max-specific functions into scope. Using this
is optional when referring to these functions.
function bang() {
// The `post` function is a method of jsthis
post("hi\n");
// Using "this" is optional, but makes explicit the reference
// to the bound instance of jsthis
this.post("nice to see you\n");
}
See the JS API Docs for a full list.
Number of Inlets and Outlets
Set the number of inlets and outlets on your JavaScript object by setting the value of inlets
and outlets
on jsthis
.
inlets = 3; // object will have three intles
outlets = 2; // object will have two outlets
function bang() {
outlet(1, "hi"); // send "hi" out the second outlet.
}
Arguments
Arguments supplied to the js or v8 object after the filename will be passed to the JavaScript code as arguments. These will be available in a jsthis
property called jsarguments
.
// The first argument will always be the name of the file
const filename = jsarguments[0];
const argumentsLength = jsarguments.length;
// Get an array of arguments supplied by the user
const userArguments = jsarguments.slice(1, argumentsLength);
// Fancy way to do the same thing with array destructuring
const [filename1, ...userArguments1] = jsarguments;
Input
A message received in the inlet of a v8 or v8ui object will invoke a function with the same name. Arguments following the message name will be passed to the JavaScript function. This method would print "1, 2, 3" in response to the message foo 1 2 3
.
function foo(a, b, c) {
post(a, b, c);
}
With v8
you can use destructuring assignment to get the arguments to the function as an array.
function foo(...args) {
post(args.length);
}
You can even get the first argument as a single value and the rest as an array.
function foo(first, ...rest) {
post(first, ...rest);
};
Special Functions
You can define a number of functions with special names to respond to specific hooks from Max.
bang
Invoked in response to a bang message.
msg_int, msg_float
Invoked in response to an integer or a float, respectively.
function msg_int(a) {
post(`received an int: ${a}\n`);
}
function msg_float(a) {
post(`received a float: ${a}\n`);
}
If you define only msg_int
, any float received will be truncated and passed to msg_int
. Similarly, if only msg_float
exists, an int received will be passed to the msg_float
function.
list
Invoked in response to a list (a message with more than one element that starts with a number).
function list(...elements) {
post(`elements length: ${elements.length}\n`);
post(`first element: ${elements[0]}\n`);
}
anything
You can define an anything
function that will run if no specific function is found to match the message symbol received by the v8 or v8ui object. If you want to know the name of the message that invoked the function, use the messagename
property. If you want to know what inlet received the message, use the inlet
property.
function anything(...args) {
post(`message: ${this.messagename}\n`);
post(`inlet: ${this.inlet}\n`);
post(`arguments: ${args}\n`);
}
loadbang
Invoked when the patcher file containing the v8 or v8ui object is loaded. This function will not be called when you instantiate a new v8 or v8ui object and add it to a patcher; it will only be called when a pre-existing patcher file containing a JavaScript object is loaded.
function loadbang() {
post("loadbang\n");
}
getvalueof
Defining a getvalueof
function lets your JavaScript object participate in the pattr system, enabling Max to save the state of your JavaScript object using pattr and pattrstorage. The return value of getvalueof
can be a number
, a string
, an Array
of number
and string
, or a Max Dict.
let myvalue = 0.25
function getvalueof() {
return myvalue;
}
setvalueof
If you've defined a getvalueof
function, you can define a setvalueof
function to enable pattr and related object to restore the state of your JavaScript object from a preset. Like the return value of getvalueof
, the arguments to setvalueof
can be a number
, a string
, an Array
of number
and string
, or a Max Dict.
let myvalue;
function setvalueof(v) {
myvalue = v;
}
save
Defining a function called save
allows your script to embed state in a patcher file containing your JavaScript object. Max will automatically restore your saved state when the patcher is loaded.
Saving your state consists of storing a set of messages that your script will receive shortly after the JavaScript object is recreated. These messages are stored using a special global function called embedmessage
that only works inside the save
function.
Suppose you have a function cowbells
that sets the number of cowbells your object currently has:
let numcowbells = 1
function cowbells(a) {
numcowbells = a
}
When the patcher containing the JavaScript object is saved, you would like to preserve the current number of cowbells, so you define a save
function as follows:
function save() {
embedmessage("cowbells", numcowbells)
}
The first argument to embedmessage is the name of the function you want to call as a string. Additional arguments to embedmessage supply the arguments to this function. These additional arguments will typically be the values of the state you want to save. For each call to embedmessage
, Max will call that function with the provided arguments as it restores the state of your JavaScript object.
notifydeleted
The notifydeleted
method is called when the JavaScript object is freed.
Reserved names
The v8 and v8ui objects already do something in response to the compile
message. So, if you define a function named "compile" in your JavaScript code, there will be no way to call that function from Max. However, you can still call the function locally, from your own JavaScript code.
Output
Call the jsthis
method outlet
to send messages out of a given outlet.
function bang() {
outlet(0, "bang");
}
Global Code and Initialization
When the JavaScript object is loaded, Max will execute the script once from beginning to end. Variables defined in the global scope will persist through the life of the object, and you can use these to hold on to internal state.
let counter = 0;
function count() {
post(`count: ${++counter}`);
}
In fact, Max will attach global variables to the this
context when executing functions from an inlet. The above code is equivalent to the following.
this.counter = 0;
function count() {
this.counter++;
post(`count: ${this.counter}`);
}
require
Use require
to include code from other JavaScript files.
const lib = require("my-lib.js");
function call(a) {
const computedValue = lib.compute(a);
outlet(0, computedValue);
}
The included file should be a CommonJS module. To export functions and variables, it should set the properties of exports
or else replace module.exports
with an object containing exported properties.
// An example implementation of my-lib
function compute(a) {
return a + 10;
}
module.exports = {
compute: compute
};
Private (Local) Functions
If you want to use a function locally, but you don't want it to be called from Max, you can set its local
property to 1
. For example, suppose the function foo
is not something we wish to expose to the outside world.
foo.local = 1
function foo() {
post("what does Pd *really* stand for?");
}
Now, when we send the message foo
to the JavaScript object, we see the following error in the Max window:
error: js: function foo is private
Available APIs
Since the Max JavaScript engine is not running inside of a web browser, certain APIs may not be available.
- JSON serialization with
JSON.stringify
andJSON.parse
are available. - Timing functions like
setImmediate
andsetTimeout
are not available. Use Task instead. - There is no DOM, so document methods like
document.getElementById
are not available.
JavaScript, Threading, and Priority
Max schedules events using a high priority thread for events that require high timing accuracy, like MIDI, and a low priority thread for long-running operations like decompressing video. The JavaScript engine in Max always executes code in the low priority thread. That means that if a JavaScript object receives a message from a MIDI object, or from any other object that schedules events on the high priority queue, that event will be deferred to the low priority queue.