Welcome to the second in a series of â€śdrive-byâ€ť tutorials â€“ quick introductions to the world of Max Gen objects. While the Gen world is deep and subtle and you may eventually find yourself thinking about things like polar coordinate conversion, dot products, and other bits of arcana, you can still do some rewarding things armed with a few basic concepts and the will to play.
- Download the patches used in this tutorial.
- Read the other tutorials in this series:
Reading This Tutorial (And The Next One)
Youâ€™ll notice that this is Gen Tutorial 2a, and that it showed up alongside its fraternal twin Gen Tutorial 2b on the very same day. Theyâ€™re closely related, but very different. In this first tutorial, weâ€™re going to introduce some basic concepts about working in the Jitter Gen environment in ways that closely resemble your experience in the Max world.
Our suggestion is that you go through Tutorial 2a, learn about swizzling, play around with the patches and then dive right back into Tutorial 2b. The second part of the tutorial takes the concepts you learned and the patches we showed you and rewrites them with more efficiency by making use of the vector nature of the Jitter Gen environment.
The Lay of the Land
While the programming you do inside of any Gen object looks and feels like regular Max patching (connecting object boxes together with patch cords in ways that show you the flow of data), there are some important differences that are useful to know when you begin.
The Jitter Gen objects are no exception. Theyâ€™re even a little different than the gen~ object you use for audio processing, in fact.
- The things that happen inside of a Gen object are synchronous â€“ there are no triggers and thereâ€™s no notion about the order in which things happen. In the Jitter world, the basic unit of data is a vector, which weâ€™ll describe in greater detail in a minute. For now, think of this vector as a way to collectively describe a group of single pixels rather than a frame of video. Actually, itâ€™s more correct to think of it as though youâ€™re performing the same operation on every single individual pixel (unit of data) in the entire matrix at once.
- To provide user input used for calculations and transformations inside a Jitter Gen object, we use the Gen param operator.
- Most of the Jitter family of Gen objects only allow you to have a single outlet for your objects â€“ you can have as many single matrix inputs as youâ€™d like (each of which represents a single matrix), but only one output. The jit.gen object is the only exception to this rule.
- Thereâ€™s no way to retain the value of the pixel youâ€™re working on from one operation to the next – if youâ€™re familiar with the gen~ object, thereâ€™s no such thing as a history operator for Jitter Gen objects that youâ€™d use for interpolation or filtering or delays (Donâ€™t worry â€“ there are ways to do what youâ€™d use a history object for, which weâ€™ll talk about in another tutorial).
Meet the family
The first thing youâ€™ll notice about using Gen in the Jitter world is that there are three objects instead of one. Hereâ€™s how they differ:
The jit.pix and jit.gl.pix objects are made for standard Jitter 2d image processing. They work with input matrices with 1 to 4 planes, and treat incoming data like image data. The jit.gl.pix differs from the jit.pix object in that does all its work on the GPU (Graphics Processing Unit) instead of your CPU.
The jit.gen object is a general matrix processing object that works with any type of data that a matrix can contain (e.g. OpenGL).
In (both halves of) this tutorial, weâ€™re going to be working with the jit.pix object.
Maps Make the Journey More Interesting
As weâ€™ve said before, when you work with Jitter Gen objects, itâ€™s easiest to think of it as performing the same operation on every single individual pixel (unit of data) in the entire matrix at once.
At a very low level, thatâ€™s how things like the jit.op or jit.expr objects work â€“ a single operation is performed on each and every individual cell of the matrix or some part of the matrix, and the result of the calculations inside of your Jitter Gen object pops out the outlet of the object as a single matrix.
Letâ€™s start with a really simple Jitter Gen patch. Iâ€™m a big fan of color mapping as a way to generate visual variety, so I’m going to start with a simple Gen patch that uses a cosine function to do some color remapping.
The inside of the jit.pix patch is much simpler than you might think from watching its output, but thatâ€™s because the relatively straightforward calculations on the inside of the patch are being applied to each and every pixel in the image (actually, the calculations are being performed on each and every pixel in the alpha, red, green, and blue planes).
Note: If youâ€™re accustomed to only working with color in Jitter as char data in the range of 0 â€“ 255, you might be wondering where the color processing is. Jitter actually has an additional way of specifying and working with color values – floating point values in the range of 0 – 1.0. In genland, colors are always specified using floating-point numbers.
Each input pixel is multiplied by an input value and then used as the input to a cosine function. Since we know that cosine functions output values in the range from -1.0 to 1.0, weâ€™ll multiply the cosine functionâ€™s output by 0.5 and then add 0.5 to produce values in the 0. â€“ 1.0 range that we expect for colors. The result is quick and easy color remapping – I particularly like the kind of results you get from using large values for the amp parameter.
Of course, we could replace the cos operator with any other kind of calculation we could think of (a sin operator would also work well, since its outputs fall into the same range as that of the cos operator), as long as we know in advance what the maximum and minimum values for the output range is so that we can coerce them into the 0. â€“ 1.0 range.
In fact, the tutorial patch colormap_2a does precisely that. The patch not only adds the ability to pass the output unchanged, to select from either cosine or sine mappings, but also lets you choose to pass the data unchanged or invert its output for the passed or processed images. Hereâ€™s what the patch inside our jit.pix object looks like now: