Now that I've got a nice generative patch and a way to hear it, I thought it'd be nice to make a few improvements and extensions that would let me begin to specify larger structures - to generate instructions to my generative patch, as it were. While I'm sure that the world is full of people who want ways to have the same thing happen again and again, I'd like to do this in ways that offer a little more freedom than that. This short tutorial will add a modest number of these kinds of changes.
All LFO tutorials in this series:
- LFO Tutorial 1: The Zen of the Silent Patch
- LFO Tutorial 2: Making Some Noise
- LFO Tutorial 3: Extending Our Generators
- LFO Tutorial 4: Building Complexity
- LFO Tutorial 5: LFO Child Slight Return
- LFO Tutorial 6: Live If You Want It
- LFO Tutorial 7: Rattle and Hmmm
- LFO Tutorial 8: Valediction
Click here for a full-sized version of the patch image.
In the course of playing around with the LFOur - LFOur_vst and LFOur_out patches, I decided that - although I had considerable control over the patterns I could generate from a small number of pitches stored in a coll object - I would like to allow for more variety. In some cases (and working with VST drumboxes what what got me thinking about this), it was great to have a very small number of choices. I could limit those choices by changing the contents of the coll object I used when mapping output or I could experiment with different settings for the notevalue tempi of each of the four LFOs in combination with the rate at which I sampled the LFO output. In the course of experimenting with this, I realized that I could add the ability to work with multiple coll objects to my patch, or I could also consider changing the size of the range of notes in the coll object that I used when sampling (e.g., for an ostinato, the coll would only have two choices when scaling its output).
After some thought, I decided that since neither addition was very complicated, I'd add both options.
First, I'd like to have the opportunity to use more than one coll object in order to vary the set of pitches I use when mapping the LFOur patch's output. I added a single receive object connected to the coll.
Whenever I send the message refer followed by the name of a coll object in my patch to an s ref object, I'll switch the coll that's used when mapping output.
Adding the ability to select a range within the coll object itself involved the addition of two new inlet objects to the notemap subpatch and the addition of a pair of number boxes to the top level of the patch that specify the low and high output mapping values used by the scale object that is used to map LFO outputs to MIDI note numbers. A nice side effect of this choice is that I no longer care how large the coll object I'm using with the patch is - I can set it to any set of high and low values to work with different lengths for the coll objects' contents (just change the high number) or map subsets of the coll object contents by changing both values (0-15, 8-23, 60-63, etc.). And I have added the two number boxes that set the low and high scaling values to the pattr registry in the patch by giving each number box a scripting name using the Inspector, so that my presets now automatically recall my mapping ranges as part of the patch preset.
A Tale of Two Objects
The values I'm sending to the two newly added rightmost inlets of the notemap subpatch are used to set the arguments to a scaleobject. As any reasonably seasoned Max user will tell you, there are two different Max objects commonly used for linear mapping: the scale and zmap objects (just in case you need to be reminded again that there is nearly always another way to do nearly anything using Max!). While they'll both go a fine job of performing linear mapping of one set of values in a range to values within a different range - in this case, mapping values in the range 0-127 that the LFOur patch sends me to a range that matches the size of my coll object - there's a reason I prefer using the scale object: I can invert the values used when mapping outputs, which lets me count “backwards” through a coll object's contents. So I can also now read my coll object forward or backward.
Note: Another difference between the scale and zmap objects is equally subtle: although the scale object will let you reverse the range values when mapping output, it won't clip output values so that the fall within that range - if you feed it a very high value, it'll faithfully map it waay outside the output range you think you've specified.
The zmap object does clip its outputs, although it won't invert output ranges (Is this a bit of fabled “secret” Max arcana? Not really, but I'm always surprised at the number of people who haven't noticed this). Since I'm using a scale object, I've used the minimum and maximum attributes of the number box object using the object ‘s Inspector to automatically clip my input ranges in my patch. Once set, you cannot type a number less than 0 or greater than 63 into either range number box. This is a trick I use often when patching.
Steering the Generative Bus
Each of these four timepoint objects takes as its argument a time point, specified in bars, beats, and units - one of the timing formats that Max uses. The objects' function itself is pretty straight-ahead: when the transport reaches a timepoint specified by the argument, the timepoint object sends a bang message. In this patch, The timepoint objects control a couple of things:
- The first timepoint object increments a counter used to step through the presets for the LFOurGenerator patch itself. These presets keep track of the sources, notevalues, and range of values from the LFOur patch to be sampled and used as inputs.
- The second timepoint object uses a counter to step through a sequence of numbers used to set the sampling rate of the LFOur patch itself by sending a value to the receive destination L4samp_rate in the LFOur patch. Setting a greater sampling rate means that there is more variety in the sequence generated by any LFO.
- The third timepoint object uses a textbutton object as a toggle, and will alternately send the message refer sequence_1 and refer sequence_2 to the receive object located in our notemap subpatcher, changing which coll object in our patch is used for note values.
- The final timepoint object serves several different functions: The first and perhaps most interesting thing it does is to reset the transport (by sending the message 0. to the right inlet of the transport object), effectively creating an 8-bar “loop” which cycles over and over. But it also controls a counter and coll object pair that sends messages that change the presets used in the LFOur patch to change the operation of the LFOur object while the patch runs. Finally, when a complete cycle of the counter is finished, a bang is sent out the “carry count” outlet of the counter object and used to turn off the transport.
You'll notice that I added a trigger object directly below the large toggle to initialize the state of all my counters and toggles whenever I turn the transport on, and another toggle that actually turns on the transport. You might want to have a look at the patching around the transport object a little more closely.
If you unlock the patch, you'll also see that I've connected the third outlet of the preset object to the umenu and textbutton objects attached to the second and third timepoint objects in order to have them not be affected when the first timepoint object changes patch presets. The one other thing you might stop to wonder about is why there's a metro object attached to the transport, and why doesn't it have a toggle switch of its own? The answer is that in order to get a current bar/beat/unit output from the transport object, it needs to receive its own bang message, and that's what this metro object provides. Notice that the metro object sets two attributes: autostart and autostarttime. Setting the autostart attribute to one means that this metro will start any time that the transport itself starts, and the autostarttime attribute tells this metro to always start counting from timepoint 0 when it's started up. This metro is the reason that we can see the bars and beats display updating.
A Final Word
So this patch lets us configure a larger scale generative structure - it uses timepoint objects to set the point at which changes will occur, and uses messages to modify not only the patch itself (using messages to the preset object), but also to modify and set the behavior of the LFOur patch itself.
Of course, you don't have to stop there - you can route the same control information to other parts of your Max patch to control how various VST instruments are mixed, to adjust plug-in settings, and so on. Here's an MP3 example that uses this patch and some things I've added that I'll let you imagine. Sorry about the reverb - I've recently downloaded an Altiverb impulse response from Gol Gumbaz in India that I'm in love with. It seemed like just the thing.
I hope this trio of articles has been interesting and enlightening. While your mileage on the examples I've provided may vary, I'm sure you can create interesting kinds of mayhem of your own based on what's here. So don't fare well, fare forward.