Tutorial 45: Introduction to using Jitter within JavaScript
The Max js object, introduced in Max 4.5, allows us to use procedural code written in the JavaScript language within Max. In addition to implementing the core JavaScript 1.5 language, the Max js object, contains a number of additional objects and methods specific to Max, e.g. the Patcher object (designed for interfacing with the Max patcher) or the post() method (for printing messages into the Max Console). There are a number of extensions to the js object that allow us to perform Jitter functions directly from the JavaScript language when working with Jitter. For example, the Jitter extensions to js allow us to:
Before beginning this tutorial, you should review the basics of using JavaScript in Max by looking at the JavaScript tutorials starting with Basic JavaScripting and JavaScript Scripting. These tutorials cover the basics of instantiating and controlling a function chain of Jitter objects within js JavaScript code.
The tutorial patch shows us two columns of Max objects side-by-side. The right column contains a patch that processes a movie through an effect and displays it. All of this is done using Jitter objects connected within the Max patch between the qmetro object and the jit.pwindow object. The left column shows the qmetro and jit.pwindow objects, but contains only a js object loading the JavaScript file 45jWakefilter.js in between. As we will learn, the two sides of the patch do pretty much the exact same thing. First, we'll look at the patch on the right side to see what's happening.
Waking Up
This side of the patch plays back a movie (using a jit.movie object) into an edge detection object (jit.robcross), which is then multiplied by the output of a named matrix called (using jit.op). The matrix output by jit.op is then processed by a jit.wake object, which applies a temporal feedback and spatial convolution to the matrix, the parameters of which can be controlled independently for each plane. The output of the jit.wake object is then brightened slightly (with a jit.brcosa object) and then stored back into our named matrix ( ). The output of our effects chain as seen in the jit.pwindow is the output of the jit.wake object.
The key to the variation of our processing algorithm is this subpatch, containing twelve random objects that are controlling different parameters of the jit.wake object in the main processing chain. The output of these random objects is scaled for us (with scale objects) to convert our integer random numbers ( to ) into floating-point values in the range to . These values are then smoothed with the two * objects and the + object, implementing a simple one-pole filter:
yn = 0.01xn + 0.99yn-1
These smoothed values then set the attributes of jit.wake that control how much bleed occurs in different directions (up, down, left, right) in different planes (specified as the color channels red, green, and blue). You'll notice that the smoothing algorithm is such that the values in all of the number box objects showing the smoothed output tend to hover around (or half of ). Our Jitter algorithm exhibits a slowly varying (random) color shift because of the minute differences between these sets of attributes.
The Javascript Route
Click the message box that reads on the left side of the patch.
The video on the left side of the patch looks strikingly familiar to that displayed on the right side. This is because the js object on the left side of the patch contains all the objects and instructions necessary to read in our movie and perform the matrix processing for our effect.
Our JavaScript code contains the familiar comment block at the top, describing the file, followed by a block of global code (executed when the js object is instantiated) followed by a number of functions we've defined, most of which respond to various messages sent into the js object from the Max patcher.
Creating Matrices
Our code begins with the familiar statement of how many inlets and outlets we'd like in our js object:
// inlets and outlets
inlets = 1
outlets = 1
Following this, we have a number of statements we may never have seen before:
// Jitter matrices to work with (declared globally)
var mymatrix = new JitterMatrix(4, "char", 320, 240)
var mywakematrix = new JitterMatrix(4, "char", 320, 240)
var myfbmatrix = new JitterMatrix(4, "char", 320, 240)
// initialize feedback matrix to all maximum values
myfbmatrix.setall(255, 255, 255, 255)
This block of code defines that we will be working with a number of Jitter matrices within our js object. The variables mymatrix, mywakematrix, and myfbmatrix are defined to be instances of the JitterMatrix object, much as we would declare an Array, Task, or instance of the jsui sketch object. The arguments to our new JitterMatrix objects are exactly the same as would be used as arguments to a jit.matrix object, i.e. an optional , a , a , and a list of values for the .
All three of our JitterMatrix objects are created with the same typology. On the fourth line of this part of our code, we take the JitterMatrix myfbmatrix and set all its values to jit.matrix object would. In fact, all of the messages and attributes used by the jit.matrix object are exposed as methods and properties of the JitterMatrix object within JavaScript. A few examples:
. The setall() method of the JitterMatrix object does this for us, much as the message to a
// set all the values in our matrix to 0:
mymatrix.clear()
// set the variable foo to the value of cell (40,40):
var foo = mymatrix.getcell(40, 40)
// set cell (30,20) to the values (255,255,0,0):
mymatrix.setcell2d(30, 20, 255, 255, 0, 0)
Creating Objects
// Jitter objects to use (also declared globally)
var myqtmovie = new JitterObject("jit.movie", 320, 240)
var myrobcross = new JitterObject("jit.robcross")
var mywake = new JitterObject("jit.wake")
var mybrcosa = new JitterObject("jit.brcosa")
These four lines create instances of the JitterObject objects. We need four of them (myqtmovie, myrobcross, mywake, and mybrcosa) corresponding to the four equivalent objects on the right side of our Max patch (jit.movie, jit.robcross, jit.wake, and jit.brcosa). These JitterObject objects behave just as the equivalent Jitter objects would in a Max patcher, as we'll see a bit later on. The first argument when we instantiate a JitterObject is the class of Jitter object we'd like it to load (e.g. "jit.movie" will give us a jit.movie object loaded into JavaScript). Further arguments to the object can be passed just as they would in Max patchers, so that we can tell our new jit.movie JitterObject to have a of 320x240 by supplying those values as arguments.
Just as we would initialize attributes by typing them into the object box following the object's name (e.g. jit.brcosa ), we can use our global JavaScript code to initialize attributes of our the JitterObject objects we've created:
myrobcross.thresh = 0.14 // set edge detection threshold
mywake.rfb = 0.455 // set wake feedback for red channel
mywake.gfb = 0.455 // set wake feedback for green channel
mywake.bfb = 0.455 // set wake feedback for blue channel
mybrcosa.brightness = 1.5 // set brightness for feedback stage
Note that the properties of a JitterObject correspond directly to the attributes used by the Jitter object loaded into it, e.g. a JitterObject loading a jit.brcosa object will have properties for brightness, contrast, and saturation. In our code above, we initialize the property of the JitterObject myrobcross to , mirroring the jit.robcross object on the right side of our patch. In the same way, we initialize attributes for our mywake and mybrcosa objects as well.
JavaScript Functions calling Jitter Object Methods
function read(filename) // read a movie
{
if(arguments.length="=0)" {
// no movie specified, so open a dialog
myqtmovie.read();
}
else { // read the movie specified
myqtmovie.read(filename);
}
// initialize feedback matrix to all maximum values
myfbmatrix.setall(255, 255, 255, 255);
}
Our read() function parses the arguments to the js object. If no arguments appear, it will call the read() method of our myqtmovie object with no arguments. If an argument is specified, our myqtmovie object will be told to read that argument as a filename.
message sent to ourIf we wanted to, we could have looked at the Array returned by the read() method to ensure that it didn't fail. For right now, however, we'll trust that the arguments to the js object are legitimate filenames of movies in the search path.
message sent to ourAfter we read in our movie (or instruct our myqtmovie object to open a jit.movie "Open Document" dialog), we once again intialize our JitterMatrix myfbmatrix to values of all .
The Perform Routine
Just as a typical Jitter processing chain might run from jit.movie to output through a series of Jitter objects in response to a qmetro, our JavaScript Jitter algorithm performs one loop of its processing algorithm (outputting a single matrix) in response to a from an outside source.
function bang() {
// perform one iteration of the playback / processing loop
// setup
// calculate bleed coefficients for new matrix:
calccoeffs()
// process
// get new matrix from movie ([jit.movie]):
myqtmovie.matrixcalc(mymatrix, mymatrix)
// perform edge detection ([jit.robcross]):
myrobcross.matrixcalc(mymatrix, mymatrix)
// multiply with previous (brightened) output
mymatrix.op("*", myfbmatrix)
// process wake effect (can't process in place) ([jit.wake]):
mywake.matrixcalc(mymatrix, mywakematrix)
// brighten and copy into feedback matrix ([jit.brcosa]):
mybrcosa.matrixcalc(mywakematrix, myfbmatrix)
// output processed matrix into Max
outlet(0, "jit_matrix", mywakematrix.name)
}
The calccoeffs() function called first in the bang() function sets up the properties of our mywake object (more on this below). Following this is the processing chain of Jitter objects that take a new matrix from our myqtmovie object and transform it. The matrixcalc() method of a JitterObject is the equivalent to sending a Jitter object in Max a jit.xfade), we would supply our matrixcalc() method with Arrays of matrices set inside brackets ([, ]).
(in the case of Jitter objects which generate matrices) or a message (in Jitter objects which process or display matrices). The arguments to the matrixcalc() method are the input matrix followed by the output matrix. Our myqtmovie object has a redundant argument for its input matrix that is ignored; we simply provide the name of a valid JitterMatrix. If we were working with a Jitter object that needs more than one input or output (e.g.The op() method of a JitterMatrix object is the equivalent of running the matrix through a jit.op object, with arguments corresponding to the attribute and the scalar ( ) or matrix to act as the second operand. In a narrative form, therefore, the following things are happening in the "process" section of our bang() function:
Our processed matrix (the output of the mywake object stored in the mywakematrix matrix) is then sent out to the patcher by using an outlet() function:
outlet(0, "jit_matrix", mywakematrix.name)
We use the
property of our JitterMatrix in this call to send the matrix's (uxxxxxxxxx) to the receiving object in the Max patch.Other Functions
function calccoeffs() {
// computes the 12 bleed coefficients for the convolution state of the [jit.wake] object
// red channel
mywake.rupbleed *= 0.99
mywake.rupbleed += Math.random() * 0.006
mywake.rdownbleed *= 0.99
mywake.rdownbleed += Math.random() * 0.006
mywake.rleftbleed *= 0.99
mywake.rleftbleed += Math.random() * 0.006
mywake.rrightbleed *= 0.99
mywake.rrightbleed += Math.random() * 0.006
// green channel
mywake.gupbleed *= 0.99
mywake.gupbleed += Math.random() * 0.006
mywake.gdownbleed *= 0.99
mywake.gdownbleed += Math.random() * 0.006
mywake.gleftbleed *= 0.99
mywake.gleftbleed += Math.random() * 0.006
mywake.grightbleed *= 0.99
mywake.grightbleed += Math.random() * 0.006
// blue channel
mywake.bupbleed *= 0.99
mywake.bupbleed += Math.random() * 0.006
mywake.bdownbleed *= 0.99
mywake.bdownbleed += Math.random() * 0.006
mywake.bleftbleed *= 0.99
mywake.bleftbleed += Math.random() * 0.006
mywake.brightbleed *= 0.99
mywake.brightbleed += Math.random() * 0.006
}
calccoeffs.local = 1 // can't call from the patcher
We see that the calccoeffs() function literally duplicates the functionality of the patcher on the right side of our patch. It sets a variety of properties of the mywake JitterObject, corresponding to the various attributes of the jit.wake object it contains. Notice that we can use these properties as ordinary variables, getting their values as well as setting them. This allows us to change their values using in place operators, e.g.:
mywake.rupbleed *= 0.99
mywake.rupbleed += Math.random() * 0.006
This code (replicated twelve times for different properties of the mywake object) uses the current value of the
property of mywake as a starting point, multiplies it by , and adds a small random value (between and ) to it.Summary
You can use JavaScript code within Max to define procedural systems using Jitter matrices and objects. The JitterMatrix object within js allows you to create, set, and query attributes of Jitter matrices from within JavaScript—the setall() method of JitterMatrix, sets all of its cells to a certain value, for example. You can also apply mathematical operations to a JitterMatrix "in place" using the op() method, which contains the complete set of mathematical operators used in the jit.op object. Jitter objects can be loaded as classes into the JitterObject object. Upon instantiation, a JitterObject acquires properties and methods equivalent to the Jitter object's messages and attributes. The matrixcalc() method of a JitterObject performs the equivalent of sending the Jitter object a or a message, whichever is relevant for that class of object. This allows you to port complex function graphs of Jitter processes into JavaScript.
In the next two Tutorials, we'll look at other ways to use JavaScript to expand the possibilities when working with Jitter.
Code Listing
// 45jWakefilter.js
//
// a video playback processing chain demonstrating the use of
// Jitter objects and matrices within [js].
//
// rld, 6.05
//
// inlets and outlets
inlets = 1;
outlets = 1;
// Jitter matrices to work with (declared globally)
var mymatrix = new JitterMatrix(4, "char", 320, 240);
var mywakematrix = new JitterMatrix(4, "char", 320, 240);
var myfbmatrix = new JitterMatrix(4, "char", 320, 240);
// initialize feedback matrix to all maximum values
myfbmatrix.setall(255, 255, 255, 255);
// Jitter objects to use (also declared globally)
var myqtmovie = new JitterObject("jit.movie", 320, 240);
var myrobcross = new JitterObject("jit.robcross");
var mywake = new JitterObject("jit.wake");
var mybrcosa = new JitterObject("jit.brcosa");
// set some initial attributes for our JitterObjects
myrobcross.thresh = 0.14; // set edge detection threshold
mywake.rfb = 0.455; // set wake feedback for red channel
mywake.gfb = 0.455; // set wake feedback for green channel
mywake.bfb = 0.455; // set wake feedback for blue channel
mybrcosa.brightness = 1.5; // set brightness for feedback stage
function read(filename) // read a movie
{
if(arguments.length="=0)" {
// no movie specified, so open a dialog
myqtmovie.read();
}
else { // read the movie specified
myqtmovie.read(filename);
}
// initialize feedback matrix to all maximum values
myfbmatrix.setall(255, 255, 255, 255);
}
function bang()
// perform one iteration of the playback / processing loop
{
// setup
// calculate bleed coefficients for new matrix:
calccoeffs();
// process
// get new matrix from movie ([jit.movie]):
myqtmovie.matrixcalc(mymatrix, mymatrix);
// perform edge detection ([jit.robcross]):
myrobcross.matrixcalc(mymatrix, mymatrix);
// multiply with previous (brightened) output
mymatrix.op("*", myfbmatrix);
// process wake effect (can't process in place) ([jit.wake]):
mywake.matrixcalc(mymatrix, mywakematrix);
// brighten and copy into feedback matrix ([jit.brcosa]):
mybrcosa.matrixcalc(mywakematrix,myfbmatrix);
// output processed matrix into Max
outlet(0, "jit_matrix", mywakematrix.name);
}
function calccoeffs() // computes the 12 bleed coefficients for the convolution state of the [jit.wake] object
{
// red channel
mywake.rupbleed*=0.99;
mywake.rupbleed+=Math.random()*0.006;
mywake.rdownbleed*=0.99;
mywake.rdownbleed+=Math.random()*0.006;
mywake.rleftbleed*=0.99;
mywake.rleftbleed+=Math.random()*0.006;
mywake.rrightbleed*=0.99;
mywake.rrightbleed+=Math.random()*0.006;
// green channel
mywake.gupbleed*=0.99;
mywake.gupbleed+=Math.random()*0.006;
mywake.gdownbleed*=0.99;
mywake.gdownbleed+=Math.random()*0.006;
mywake.gleftbleed*=0.99;
mywake.gleftbleed+=Math.random()*0.006;
mywake.grightbleed*=0.99;
mywake.grightbleed+=Math.random()*0.006;
// blue channel
mywake.bupbleed*=0.99;
mywake.bupbleed+=Math.random()*0.006;
mywake.bdownbleed*=0.99;
mywake.bdownbleed+=Math.random()*0.006;
mywake.bleftbleed*=0.99;
mywake.bleftbleed+=Math.random()*0.006;
mywake.brightbleed*=0.99;
mywake.brightbleed+=Math.random()*0.006;
}
calccoeffs.local = 1; // can't call from the patcher
See Also
Name | Description |
---|---|
Working with Video in Jitter | Working with Video in Jitter |
JavaScript Usage | JavaScript Usage |
jit.brcosa | Adjust image brightness/contrast/saturation |
jit.matrix | The Jitter Matrix! |
jit.op | Apply binary or unary operators |
jit.pwindow | Display Jitter data and images |
jit.movie | Play a movie |
jit.robcross | Robert's Cross edge detection |
jit.wake | Feedback with convolution stage |
js | Execute Javascript |
qmetro | Queue-based metronome |
random | Generate a random number |