Gen Tutorial 2b: Adventures in Vectorland
The jit.pix-based patches we created in our last tutorial do cool things and use patching techniques that will probably be accessible to the average Max user, they're not all that they could or should be as Jitter Gen patches.
Don't get me wrong - they make sense and introduce the idea of swizzling data from a vector in the Jitter Gen world. And they work just fine as patches. But one of the things that's really cool about working with Jitter Gen objects has to do with working with vectors as you patch.
In the Jitter Gen universe, the vector reigns supreme, and we're going to take this tutorial to get you used to the idea of thinking in terms of vectors as collections of data (as opposed to the individual bits of data we worked with in the first of these two tutorials), and show you how to patch more fluently and efficiently.
All tutorials in this series: Part 1, gen~: The Garden of Earthly Delays, Part 2a: The Joy of Swiz, Part 2b: Adventures in Vectorland
If you're a beginner, thinking in terms of vectors involves developing some simple habits of thought about how to combine, split, and recombine data as you patch. To help you along, we're going to take some of the patches we created in Gen Tutorial 2a and rewrite them so that they make use of vectors and are more “gen-like” and efficient. In many cases, the resulting patches will be a good deal more compact.
One of the first things we mentioned in describing the Jitter family of Gen objects is that the jit.pix and jit.gl.pix objects both operate on matrices that represent 2d images.
As a result of their image orientation, these two objects allow us to do something interesting - when we work with vectors, we often don't need to use the vec operator at all when patching. In some cases, we don’t even need to swiz out individual bits of data. If we operate on aggregate data, we can take example of some special features of the jit.pix and jit.gl.pix objects.
My First Vectorization
In the Gen Tutorial patch colormap_3a, we took a patch that originally operated on aggregate input data and swizzed out the individual color values (r, g, b, and a) in order to be able to set parameters for the red, green, and blue planes individually for greater variety. Here’s what’s inside of the colormap_3a tutorial patch’s jit.pix object:
The patch is composed of three sections, each of which has a swiz operator at the top. When a new matrix/vector arrives in the in 1 input object, the red, green, and blue values are unpacked/swizzled.
Each of these values is a floating-point number in the range 0. – 1.0, and the three portions of the patch process each color value independently of the others using the same logic.
At the very bottom of the patch, you’ll see a vec operator, which we used to pack the individually manipulated parameter values back together into a vector for output.
When we did all this duplication, all we needed to do to accommodate the additional channel processing was to change the names of the param operators when we duplicated the functionality from our earlier tutorial patch.
That’s fine as far as it goes, but it doesn’t go far enough.
So far, I've talked about the swiz/vector pair as operators that work like the Max pack and unpack objects - they provide a way of taking a group of data and breaking it up into individual pieces that you can modify individually and then repack and send on their way. Introducing the process in terms that most Max users can quickly grasp may make experimenting in Gen seem a bit less scary, but it's not the whole story. The patches in Tutorial 2a were correct and worked properly, but they didn’t take full advantage of the ability of working with vectors in Gen.
It’s been my experience to date that people encountering Jitter Gen objects for the first time initially find vectorization confusing, but it’s really not that difficult to get used to. The original patch is a perfectly legitimate way to do color mapping and it will work fine when used inside of a Gen patch. But look more closely - you'll notice that there are three collections of Gen operators that are performing the same kinds of operations on the red, green, and blue portions of each pixel in the image.
Even though we might be working with different parameter values and settings for function and inversions for each of the three colors, the calculations we perform and send to the vec operator are the same in each case – only the parameter names are different.
When we work with vectors in Jitter Gen objects, we can combine the set of three similar calculations into one calculation where all three color channels are processed in tandem by treating them as a vector (you might want to think of the vector at this point as a list that contains three values). When we do that, each Gen operator down the line then performs a calculation on each of the color values contained in the vector. Once we regroup things in this way, each parameter in our our patch will take three three-item lists as inputs, where each list contains the parameter values associated with each of the three colors.
So how do we do that? First, we modify the param objects that provide the interface to the patch. In place of the three param objects for the modes we want to apply for each color, we create a single param object with three arguments (param mode 0. 0. 0. ). This operator describes the vector we're working with as having three items and also sets their default values. Similarly, we group the amp parameters that set the values we'll feed to the sine or cosine operator (param amp 2. 2. 2. ). Finally, we can replace the inversion parameters with a single param object with three args that will, by default, set our patch to pass its input in unaltered form (param invert 0. 0. 0.).
Tutorial patch colormap_3b shows what our vectorized patch looks like. It’s a lot simpler, once you understand how we can combine operations into vectors.
In addition to our combining operations into vectors, you’ll notice that the vectorized patch no longer contains swiz or vec operators. You’re seeing a neat feature of the jit.pix and jit.gl.pix objects in action – since we’re swizzing out the red, green, and blue values as a single vector and operating on that vector, we don’t really need to unpack (swiz) or pack (vec) it up – we just pass the vector through the patch logic and output the result.
The clever reader may be wondering why it is that there’s no reference to the image’s alpha channel in sight anywhere in the patch. That’s because the jit.pix object has a particularly useful feature - If you send a three-element RGB vector to the out 1 operator, the jit.pix object will convert it to an RGBA value by automatically adding an alpha channel with a value of 1.0. Sure makes things easier, doesn’t it?
Are there any changes in the actual Max patch itself once we’ve vectorized it? Let’s take a look:
In the tutorial patch, you'll notice that the inputs to the jit.pix object are now created using a trio of pak objects rather than the multiple messages we had before. Apart from that, they're functionally equivalent.
Let’s take a look at some of the other swizzling we did in our last tutorial with an eye toward vectorization.
Where Am I?
Although it’s a new way of thinking for many Max users, properly vectorizing a Jitter Gen patch isn’t that hard to get the hang of – you try to identify what portions of what happens in the patch can be done by grouping elements into lists (vectors) that can be processed. Here’s the insides of the xyswizzle_1a tutorial patch we used last time to introduce the notion of working with x and y positions in a Jitter Gen patch:
Let’s consider vectorizing this patch. As before, you’ll notice the duplicated effort for the two offset parameters. We could vectorize them and perform the offset calculations in a single operation.
And, as before, working with vectors allows us to do something that’s might begin to feel a familiar to us (even as it might have baffled you before reading this tutorial) – since we’re working with a jit.pix object that assumes we’re only dealing with 2d video images, we don’t even need to use swiz and vec operators – the sample operator assumes that it’s going to be working with a pixel coordinate (a 2d vector), and the norm operator outputs the x and y values of the current pixel as a pair of floating-point numbers. So all we really need to do is to take one-half of our original patch and add another argument to the param object (e.g. param offset 0. 0.) to indicate we’re working with a vector composed of a pair of numbers.
Wow. That was easy, wasn’t it? And, as before, the only change we need to make to the main patch is to replace the two separate offset messages with a single message in the form offset x y.
Mirror, Mirror on the wall….
Let’s try something a bit more challenging: the contents of our mirroring tutorial patch xy_swizzle_2a, which used the swizzled x and y values and offsets to create an image mirrored with respect to the x and y axes.
In our original tutorial patch, we swizzled out the x and y position values, added offset values to them depending on which kind of reflection we wanted, and then used the sample operator to do the mirroring. Here’s what the insides of the jit.pix object that did the calculations looked like:
So far, we’ve thought about vectorizing our patches as a process of looking for places where there is duplicated activity in our patch – places where we could replace multiple operators with a single operator that performed operations on a vector (a group of values). Obviously, those two param operators at the top right are likely candidates. If you look at the summing and folding for the part of the patch labeled image over on the left, you can see why. If we could find a way to perform our x and y value calculations on the two values as a vector, we could really simplify our patch – by keeping the x and y values together as a vector, we wouldn’t need to use the swiz operator at all – we’d simply be adding one vector (the output of the norm operator) to another (the vector containing the x and y offsets). In fact, that’s exactly what we did when we vectorized the xyswizzle_1 tutorial patch just a minute ago.
Similarly, taking a look at the x and y reflection portion of the patch also reveals a part of the patch where we’re performing the same operations – always an opportunity for the savvy Jitter Gen programmer to start thinking about vectorization!
In fact, the x reflection and y reflection parts of the patch also resemble that x and y reflection part of the patch too, with the difference being that we’re performing the sequence of multiplication and subtraction on only one part of the vector. What if we could modify our patch to let us vectorize that part of it, too?
The next step demonstrates something that hard-core Jitter Gen vectorizers do all the time but may not be as obvious to you if you’re just starting out: We can add a few Jitter operators to our patch that will reveal the way we can vectorize all three of the mirroring calculations.
It works like this: Imagine what objects you could add to the patch so that that every one of the reflections (the pair of + operators and vec operators) would be preceded by a single * operator and one + operator, each of which operated on a pair of values.
Of course, you’d normally only be doing that for the part of the vector you needed to reflect – the other + operator would take the input from the swiz and param operators and add them together.
But what if you took those inputs and multiplied them by one and added a zero? Technically, you wouldn’t be doing anything at all that changed the values you were handling. But when we add those operators, take a look at how our patch looks now:
I’ll bet that you can see the opportunity to vectorize the patch a lot more clearly now, can’t you? It’s like solving a Rubik’s cube: sometimes you need to take one step back before you take a great leap forward.
So, let’s vectorize and simplify our patch. The Jitter Gen mathematical operators work as easily on vectors as they do on single values - all you need to do is to provide an argument that specifies the numbers you’re multiplying by or adding for each element in the vector. If you’re adding two vectors, it’s even easier than that – you just send each vector to the input of an operator.
Since we’re not working with individually swizzled x/y values, one final amazing thing happens: Since the sample operator expects its right inlet to be a vector, we don’t need to have any vec operators in our patch – we’re working with x/y vector values all through the patch!
And for our last trick, we’re going to have the sample operator do the heavy lifting when handling out-of-range values by adding something new - the @boundmode mirror attribute of the sample operator will automatically mirror out-of-range values rather than folding them.
Here’s what the final vectorized version of our patch looks like once we’ve set things up to work with vector pairs:
It makes quite a difference in how our patch looks, doesn’t it? You might want to take a few moments to meditate on what we’ve just done once again. While the purpose of showing you this patch modification in detail in this way was to approach the problem as though you were an ordinary Max programmer who’d never worked with vectors very much, you’ll discover as you work with Jitter Gen objects that you’ll find yourself learning to think of vectors right from the start.
And by the way – if you’ve looked at the Jitter Gen examples folder patches in the past and had a hard time figuring out how the sample patches worked, you might want to conclude your meditations by going back and having another look at those patches. I’ll bet they’ll seem a little clearer now.
The xyswizzle_2b tutorial patch differs from the unvectorized original only in that it uses pak objects to send offsets as vector pairs.
Our final patch swizzlemap_1b provides a fully vectorized version of the original swizzlemap_1a patch. I hope that your new understanding adds to the richness of the experience of playing with it.
We’ve now arrived at the end of the second leg of our Jitter Gen tutorial. I hope that you’ve gotten a better sense of the power of working in the world of Jitter Gen patches, and that you’ve made your first steps toward being someone who thinks in terms of vectors when patching. As the Romans would have said if they’d had Jitter Gen objects to play with,“To the vectors go the spoils.”