Many people already know that Jitter can be a fantastic tool for video processing, but what about audio? Used with a bit of cleverness, a matrix can be just the thing for that patching impasse. Following is a set of simple examples to get you started thinking about a matrix when you've run thin on patching ideas or need a more elegant way to manage your numbers.
To start, you will want to download the patches for this tutorial here.
First, let's have a look at the patch called 01Basics. This patch shows a couple of useful ways to get data (specifically lists) into and out of a matrix. The first section shows the use of jit.fill to fill a matrix with list values and jit.spill to empty it out into a list. It may help to think of a matrix as a container for data that is filled and poured out into other containers.
Another way to pour out the data in your matrix is to use jit.iter. This will send out the cell values, one cell at a time through the left outlet, accompanied by the coordinates of the cell coming out the middle outlet. These are simply two different ways to access the information, which are useful in different ways.
To the right is a demonstration of using multislider as an interface to matrix data. Note that multislider is unique among Max objects in that it can generate lists longer than 256 values.
Moving on, we will open our first audio example, 02SlidingCycles. You will notice that we have the familiar multislider-->jit.fill combination from before, which fills a 1-plane float32 matrix. Start the patch by turning on the toggle switch above the metro and turning on the ezdac~. Once you can hear the sine-waves, try changing the values of the multislider. This will cause the individual frequencies to slowly glide up and down.
The reason for this glide is the use of jit.slide, which does temporal interpolation (low-pass filtering) of the input. The jit.op objects are used to multiply and offset the float32 values (0.-1.) so that they are in a useable range. We then use jit.spill to convert this matrix data into a list that can be unpacked and sent to the cycle~ objects.
Note how much easier it is to do this than patching together all the necessary number boxes, line objects and math operators.
The next patch we will look at is called 03SequenceLoop. This patch performs some interesting sample reordering by making use of 2d.wave~ and Jitter's newest audio powerhouse, jit.buffer~.
To start, try loading a short sample into our buffer~ and changing the values of the multisliders. The MSP portion of this patch should be fairly straightforward to those familiar with index~ and 2d.wave~. The index~ objects are looking up values from the jit.buffer~ depending on a signal value between 0 and 31, which we then use to drive the speed and loop position of the 2d.wave~ playback.
Once again, we are using jit.fill to load values into a matrix, only this time we are using a 2-plane matrix. By sending this 2-plane matrix on to the jit.buffer~, we can easily load a buffer with the values from our user interface.
Note: Just like the buffer~ object, jit.buffer~ is limited to 4 channels/planes. If you need to use more than that, you'll have to create multiple jit.buffer~ objects. The approach used in this patch can be used in many different ways, as we will soon see.
Now, let's open up the next patch, 04SequenceSynth. This patch is very similar to the last one, but we are now using the values in our jit.buffer~ to drive a square-wave synthesizer with a lowpass filter. Once again, this patch could easily be expanded with controls for various processes and effects.
Now that we've seen how easy it is to control audio processing with Jitter objects, we will now delve into some slightly more complex stuff as we take our matrix into another dimension.
We will now look at the patch called 05Storage. You will recognize all of the controls from the previous synth patch, but we've hidden the MSP stuff to make room for our preset storage mechanism.
First, let's have a look at the storage section on the far left. As you fill up a matrix with values using the multislider, the matrix is now also getting sent over to a matrix with @thru 0. This means that the matrix will accept new data sets, but won't output unless you explicitly bang it. Before we bang the matrix, we have to decide which row of our "bank" matrix to store it to. This is accomplished using the "dstdim" messages. The way the patch is set up, we can store up to 10 preset sequences in our matrix bank. This number could easily be increased by changing the dimensions of the matrix.
Now that we have a named matrix with all of our presets stored, we can recall each preset by creating another jit.matrix object and calling it "bank" as well. This will reference named matrix that we created for storage. Since we only want to recall one row of this matrix, we use jit.submatrix to sample a row of the larger one. We define which row we want by using the "offset $1" message. The trigger object here (t b i) is making sure that the offset message is sent before we bang the matrix.
You will also notice that your presets are recalled in the multisliders as well. This is accomplished by using a jit.spill object for each multislder. This has the rather inefficient side-effect of also refilling the "sigl" matrix with the preset values again, but it is pretty handy for editing your sequences.
Once you have a set of presets that you are happy with, simply send a write message to the "bank" matrix. This will write a jitter binary file that can be read back without any data loss. To read it in, simply use the "read" message.
Yet Another Dimension
As if this weren't enough for you to play with, we'll take this one step further by adding yet another dimension to our matrix. Open the patch called 06DeepStorage. This patch is very similar to 05Storage, but this time the preset bank is much larger, and has a 3rd dimension. This works almost exactly the same way, but has a second value for the location of each preset. This allows us to do a lookup of presets in 2 dimensions, thus making it possible to create a map of sonic parameters.
Since you probably don't have the patience or time to fill in 16,384 presets (128x128), now is a good time to get to know the jit.bfg object. This object allows you to easily create a multidimensional map of values using one of many math, noise, or fractal functions. It is not as fast as, say jit.noise, but allows for a great deal more intelligent data generation. For more info on using jit.bfg, check out the help patch and Jitter documentation. Another option would be to use a smaller 3d matrix that is upscaled and interpolated to create transitional presets.
Next up, we'll have a quick look at 07InterpolatingWaves. So far, we've been mostly looking at jit.buffer~ as a way of storing parameters for signal-rate control. With this patch, we begin to see a way that Jitter can be used for generating actual audio information. Here we use the storage/recall paradigm set forth in previous patches to create a set of audio samples for use with wavetable FM synthesis.
Using multislider, you could create a whole bank of wavetable samples, but for the sake of ease, we'll just use jit.noise. Since we want our waves to be continuous, we add an empty cell at each end of the matrix before upsampling and interpolation.
Because we want to smoothly transition between waves, we can use jit.xfade to interpolate between rows. This is done by using a second jit.submatrix object to load the next row, and then using modulo arithmetic to define the xfade amount. This gives us a behavior similar to pattrstorage, but with the benefit of working with the large datasets that are necessary for audio samples.
What I've provided here is just a small sample of what is possible when Jitter and MSP get together and play. If this topic interests you, I encourage you to check out the Jitter Recipe Book for a couple of ideas, the extensive Jitter and MSP documentation, and stay tuned for future articles. If you have any questions, post them to the Jitter or MaxMSP forum and we'll discuss. Happy Patching!