In an earlier article, Andrew Benson and Ben Bracken went through the process of connecting a guitar to a Max-based processing system, and creating a few guitar-oriented effects patches. In this series of articles, I will be building a Max-based guitar processing “rig”, and will give you the opportunity to look over my shoulder as I design and implement this system.
The first thing I did was to plan the processing chain that I would create. I wanted a rather typical guitar rig, with the addition of a looping delay line. I decided on the following functional graph:
I also made a few decisions about general program functionality. Since this will be a stand-alone program/patch, I wouldn’t worry about limiting system usage; therefore I wouldn’t wrap subpatches inside of poly~ objects (to reduce their CPU load when unused). Also, I decided to take full advantage of the Max 5 Presentation Mode layout, allowing me to code in a convenient way and worry about the performance layout in a separate step.
Since I am a dyed-in-the-wool tweaker, I wanted to structure the code in such a way that I could manipulate it easily – even during live performance. This means that I needed to create an easily-to-follow patch that was well-labeled, and I needed to encapsulate at a level conducive to on-the-fly changes.
Finally, in order to build this quickly, I decided to beg, borrow or steal as much of the programming as possible without resorting to the use of VST plug-ins (which would reduce tweak-ability). Throughout these articles, you will see me reuse older code, appropriate from other applications and use third-party objects whenever necessary. I also take full advantage of some design patterns I’ve developed that allow for quick coding of more complex patch routings.
Input and Gain Handling
Since this is a stand-alone patch, we need to control our own input and output. Let’s start by creating an input section. If you open the patch GtrProc-1.maxpat, you will see that I have discrete patch segments that correspond to the individual functions described in my processing graph. Place the patch into Presentation Mode, and you will see that these segments jump into a “rack style” package that is easy to follow during performance.
Move back into Patching Mode, and look at the top-left section of the patch. It is labeled “Input”, and includes several controls connected to an abstraction named “input_handler”. Double-click on the input_handler subpatcher to see its contents.
This subpatch is pretty simple: it contains a number of adstatus objects that allow my controls to display and alter the audio settings used at runtime. The function is similar to using the DSP Status window, but exposes only those items I considered important for realtime performance. Since I don’t use the adstatus object very often, I don’t know the functional arguments by heart. While I could have used the documentation to learn about the object, I decided to steal the functions from the help patch instead!
By viewing the help patch, I can learn about the function of an object. However, all help patches are also functional Max patches. In this case, I was able to unlock the adstatus.maxhelp patch, copy the functional objects that I needed, then paste them into my subpatcher. By connecting a few inlets and outlets, I’d created the small and efficient input setup handler that I needed.
In addition to setting the DSP Settings, I also want to control gain at this point. This is especially important with an instrument-driven patch, since different input hardware will provide vastly different levels. In my case, I may be using an IK Multimedia StealthPlug, a mixer with an audio interface, or even a direct connection into my Mac’s audio input – so I want good control of my front-end gain. I did this by adding a *~ object to the input section, then routed the input into this control. By using a scale control, I’m able to change the normal range of the gain knob (0-127) into a range that goes from 0.0 (silent) to 2.0 (double-volume).
Compression
The next stage of the patch is a compressor. Since compression can have a very significant effect on the sound of the instrument, I wanted detailed control over its parameters. Therefore, I am using number boxes (rather than knobs) to control the values; this gives me precise control over the compressor’s operation without having to guess at a knob’s output value.
Again, this section is mostly a set of controls connected to an abstraction named “comp_handler”. Double-click on this subpatcher to view its contents. This is a very simple subpatch; I’ve just borrowed the komp subpatcher (found in examples/effects/kompressor/lib of the Max folder) without change. I set a one parameter (lookahead) directly with a loadbang, but mostly expose the functions directly to the main patcher. Since this subpatcher is so simple, why didn’t I just use komp directly in my main patch?
In the future, I may want to develop my own compressor system, or I may choose to implement the omx-based compressor included with MSP. I may even break down and use a VST compressor. In any of these cases, I would want to change the compressor “engine” without having to disturb my main patcher. By placing the compressor in a subpatch, I can change the underlying compression engine without ever having to change the top-level patch or the user interface.
This is a “high detail” compressor, giving me the kind of control I want for a flexible rig. If you aren’t that familiar with compression, though, it may seem a little intimidating. Here are a few settings that I’ve found useful with this processor:
(Sidebar) In each of the processing areas, you will see a small grey panel – it doesn’t seem to do anything. However, when you switch into Presentation Mode, you should see these panels expand to become the background of the individual processing sections. This is an example of using the Presentation Mode’s ability to change an object’s geometry to keep my patching view clean, but give me an attractive user interface.
Output Staging
The final processing section for this tutorial controls the output. This section will be familiar to any Max/MSP programmer – it’s the basis for audio output in almost any audio patch. The output of the compressor is sent to the two faders, which provide left/right volume control. The signal is then routed to an EZDAC~, which is also our DSP on/off control. If you have your guitar (or other instrument) plugged into the computer, you can check the settings on the input module, turn on the DSP, turn up the volume and hear your instrument through the patch.
However, it’s not always convenient to test with an actual instrument. For example, some of this patch was done while I was waiting in an airport. The last thing the PSA wants to see is for me to pull out a guitar and start wailing. What I’ve done is to include a “test fixture” – some standard audio generating code – that I can use to test the patch as it is being built. You will notice that this section (labeled “test fixture”) gives me the opportunity to load and play a file or to generate noise. This is a small bit of code I’ve copied from another patch of mine – I use it as a test fixture for almost every audio patch I write.
The output of this fixture is routed to a send~ object labeled “test”. This audio is received in the input section, at the same point that my patch would see an instrument from the audio input. This way, I can test patches without having an instrument plugged in, helping me to be productive when the guitar isn’t easily available.
Conclusion
We’ve created the basic I/O blocks for our performance patch. In the next article, we will expand it by adding a good-sounding distortion/overdrive system and a highly controllable filter/EQ. In the meantime, work with this basic patch, learn to understand the routing between the objects and explore the use of the compressor. Until next time – have fun!
As a Max programmer, I spend quite a lot of time making patches that some people might find a little odd; rather than a large “instrument” that I toil over at great length or “the patch is the piece” outings, I love to make Max patches that don’t make any noise or play any movies or create OpenGL scenes. Instead, I make things that are generative – working from the idea that Max is really just about messages, numbers, and lists and how you move them about; I love creating ways of generating and organizing variety, and then exploring what those generative structures do when hooked up to audio or video or other outputs.
Just as there are always several ways to do any given thing in Max, so there are several different ways to end the sentence, “One of the cool new things in Max 5 is….”
One of those things that piqued my interest is the ability to specify time in Max in ways that make so-called “musical” sense; in addition to creating metronomes that send out a bang every 100 milliseconds, you can now work with lots of new and interesting ways to specify time: ISO 8601-format hh:mm:ss.ms-style time, samples, “ticks” and note values. There’s a long list of objects you can work with (you can find that list here, both older Max objects as well as some new objects that help you take advantage of them (timepoint, when, and combine). While lots of people are interested in using the new Max 5 transport object to arrange and trigger events in time, I’m personally a lot more interested in the ability to synchronize processes in Max using time values that resemble musical note values to create control structures that can be easily time synced.
This may be because I have a deep and abiding love for some of the interesting and esoteric control voltage uses of the LFO (Low Frequency Oscillator) in traditional analog synthesis. I’m thinking of analog modules like this – a cool set of LFOs that can be combined to achieve interesting effects (Anybody want to sell me a beat-up old AKS Sythi on the cheap? <SFX: crickets> No? And you wonder why I patch…). The great thing about Max is that you can produce the Max equivalent of these great general-purpose modules, and use them in any way you want. In fact, I’ll often create patches like this and then spend the next few weeks hooking them up to all the patches I’ve got sitting around to see what they can do.
This tutorial is about making one of those kinds of modules – a quartet of synchronized LFOs whose outputs I can sample individually for several kinds of data (triggers for waveform start, LFO outputs that I can sample at variably synchronized rates, and a nifty summed waveform I can use for more exotic kinds of control).
Note: The downloadable materials that accompany this tutorial contain not only the large patch I’m going to talk about making, but a number of the little steps along the way – feel free to take the pieces and tear them apart and make things that are interesting to you, in addition to following along.
Humble beginnings
Here’s what I usually use to create LFOs in Max.
You might have been expecting to see a cycle~ object here instead of the phasor~ object. While you could certainly use cycle~ objects for your waveform output, I prefer to use the phasor~ – a Max object that outputs a ramp that cycles between the floating point values 0. and 1.0 at a rate you specify.
The cos~ object will take the output of a phasor~ object and produce a nice sinusoidal waveform with the standard -1.0 to 1.0 output range. The nice thing about using a phasor~ is that I can also easily produce a sawtooth LFO output by adding a pair of objects that change the range of the phasor~ object’s output. (For extra credit, you might think about how you could generate a triangle wave using a phasor~ as a source. Yes, I know – you could just slap a triangle~ object below the phasor~ in the patch, but I’m encouraging you to think about signal math. If that seems too easy for you, how would you produce a square wave?
Now that we’ve got two waveforms, what kinds of useful information can we extract for our use? Since the waveform’s output is happening at audio rate, we’ll need to sample its output. Personally, I prefer to use the snapshot~ object, which samples a waveform output at whatever millisecond interval I provide as an argument. I’ll probably need to do some linear mapping of the resulting numerical output into a more useful range of numbers, but I’ll get to that in a minute. I’d also be interested in sending a bang message every time my waveform began its cycle. The example above shows how you can do this using two simple Max objects – a >=~ (greater than/equal to comparison signal object) followed by the handy edge~ object that detects zero to non-zero signal transitions (and vice-versa, too). Beginning Max users might wish to meditate on this pair of objects in the course of their quest for enlightenment.
But there’s another good reason to use the phasor~ object to create LFOs in Max, as shown in the left-hand part of the next example.
The rate~ object takes a phasor~ as an input source, and produces the same kind of output (0. – 1.0) time-scaled by the number specified as an argument or sent to the object’s right inlet. In the previous Max 4.x world, this was the only reliable way to synchronize any two LFOs. While there’s a new way to do that now (shown on the right-hand side of the patch below), the rate~ object will still be really useful, as you’ll soon see.
As I mentioned earlier, Max 5 gives you new ways to specify time in Max – some of which use terminology that is more explicitly “musical”; besides the old school metronomes that send out a bang every 100 milliseconds, you can now work with different “kinds” of time: ISO 8601-format hh:mm:ss.ms-style time, samples, and the time format we’ll be using in this tutorial – note values. Since I’m interested in the ability to synchronize processes in Max using time values that resemble musical note values, I’ll use note values – whole notes, half notes, etc. – to set the rate of my LFO. Here’s a simple example:
This patch contains the familiar phasor~ object, but instead of using an argument to specify a frequency for the ramp, I’ve used different arguments to each phasor~ object (1n and (2n), and I’ve also added an attribute called (lock), set to a value of 1 (on). The arguments 1n) and 2n) refer to note values – in this case, a whole note and a half note (you can find a complete listing of these note numbers here).
Each phasor~ object also has a umenu object connected to it containing a listing of all the possible note values. By selecting a note value from the menu, I can reset the phasor~ on the fly.
But what are the phasor~ objects locked to?
A Transport of Joy
In Max, a central timekeeping object called transport is used to handle metrical timing. The transport object not only keeps track of time, but broadcasts information to any Max objects in your patch that are “listening” or expecting timing information. When we use a note value argument with the phasor~ object, we’re telling the object that it will need to listen for timing instructions.
While you can add transport objects to your patch (and even have different transport objects running at different rates controlling different sets of Max objects in a single patch), Max 5 includes a handy Global Transport – a default source of timing information that is ready any time you launch Max. You can find the global transport by choosing GlobalTransport from the Max Extras menu
The Global Transport has an on/off switch (the button to the right of the word Activate”, a flashing LED indicator, displays bar/beat/unit timing and temp, and a display that shows the current “position” of the transport since you’ve started it.
NOTE: Since these tutorial patches are time-critical, you should have Overdrive turned on when you run them. If Overdrive is off, you may find that playback gets jerky when the density of notes is high or you perform other tasks on your computer (such as opening another patch).
To see the musically time-synced phasor~ objects in action:
Click on the ezdac~ object;
Click on the Global Transport’s activate button.
Once the Global Transport is running, you’ll see a pair of synchronized waveforms, and you can use the umenu objects to change their relative rates on the fly.
Now that we’ve got a great way to product perfectly synchronized LFO waveforms, let’s go back to our earlier LFO examples and add some additional functionality. Of course, since our locked phasor~ objects are producing beautifully choreographed ramp outputs, they’ll also be outputting beautiful sine waves when we use the cos~ object. And while we don’t really need the rate~ object to synchronize the phasor~ objects’ outputs any more, it’s still useful. Suppose we want to produce a nicely synchronized waveform that repeats at a period that’s greater than a dotted whole note (1nd, the largest time increment we can specify using note values)? Easy – we can use our old friend the rate~ object to scale the output of the locked phasor~ objects to longer time periods.
And while we’re at it, it would also be nice to be able to invert the waveform phase outputs, too. That’s an easy one – just multiply the signal by a value of -1.0. The following example patch adds all of these features, and a set of switches that let us configure our output
It’s hard to stare at any number of simultaneously synchronized LFOs and not feel the urge to sum those waveforms to produce lovely new shapes, if only for the pleasure of watching them. It’s easy enough to do – you just scale the output of each waveform by the reciprocal of the number of waveforms you want to sum (1/2 or .5, 1/3 or .33, etc.) and then add them together.
And for one final touch, I think I’d like to be able to sample the LFO waveform output at a rate which is independent of the waveform itself, but still synced to the “global” rate of the Global transport. In this example patch, I’m sampling the waveform output with a snapshot~ object, and using the scale object to map the waveform’s output data range from -1.0 – 1.0 to a more MIDI-friendly range. That data stream is then sent to the right inlet of an int object, whose current value will be output any time a bang is sent to the left inlet of the int object. The source of that bang comes from another Max object that can take advantage of the new timing values – the metro object. I’ve used a note value argument to set the rate at which the metro object sends bangs rather than the more common number argument (which specifies milliseconds), so the metronome is now also outputting its bangs at a rate derived from the Global Transport.
Putting It All Together
All of these little component level solutions are used in the giant example patch called LFOur.maxpat – which, as its title implies, contains FOur different and independent LFOs. Although the patch is pretty large, it contains all the little techniques you’ve seen so far.
You should feel free to open up the LFOur patch and poke around “under the hood.”
Max 5 makes it really easy to create user interfaces using the Presentation Mode. You can find a good tutorial on it here, and Andrew Benson has written a great tutorial on creating interfaces, as well. Working in Max 5, I now find that using the Presentation Layer to prototype interface designs accounts for a lot of my patching time these days; the patch that you see when you launch the LFOur patch is the presentation layer arrangement I decided on. Since I’ve set this patch to open up in Presentation mode and show you the user interface, you’ll need to do two things:
Click on the Lock icon in the patcher toolbar to unlock the patch;
Click on the Presentation Mode icon to display the LFOur patch in patching mode.
(Note: You’ll notice that when you click on the Presentation Mode icon, all the UI objects gracefully glide to their original position in the larger patch. If you’re curious about how to do this in your own patchers, choose Patcher Inspector from the View menu and double click in the Setting column for the Box Animate Time setting. Typing in a large number will create the glide as you toggle between patching and presentation modes).
While you’ll probably be able to recognize parts of the larger patch from the examples above, there are some useful things I’ve added to the patch that may be less obvious:
Each user interface item in the LFOur display in presentation mode includes a hint that describes what the object displays or does, and gives you hints about how to use it. Those hints were added by entering text into the Hint area in each object’s Inspector.
I also thought it’d be useful to be able to create preset combinations of waveforms and rates, so I added support for the pattr family of objects. Although the pattr family of objects are deep and subtle, it was actually quite simple to add this to the LFOur patch in just four steps:
I opened the Inspector for each and every UI object I wanted to save as part of a preset, and entered a unique name for the Inspector’s Scripting Name setting.
I added a pattrstorage object to my patch and gave it a name (in this case, LFOs).
I added an autopattr object to my patch (which has the effect of adding all the objects with scripting names to the pattr registry automatically).
To make storing and recalling presets super easy, I added a preset object to the patch (and added it to the Presentation layer, of course), opened its Inspector, and entered the name of the pattrstorage object (LFOs) to the value column for the pattrstorage setting. Once I did this, I could use the preset object to load and store presets for my patch.
Using the LFOur Patch
Of course, this patch doesn’t make any noise at all, although it is interesting to play with and restfully instructive to watch. LFOur is useful because of a key idea in Max: data spaces are global; any time you have a send object sending data to a destination named “suitcase”, anyreceive object in any Max patch you have open and running with a receive object that specifies “suitcase” as its source will receive and output the message. So I run my LFOur patch alongside whatever Max patch I want to use it with, and as long as I’ve added receive objects with arguments that correspond to the names of the send objects in the LFOur patch, I’ve got data to work with. In addition, I’ve added a few receive objects that will let you control some functions of the LFOur patch from your patch.Here’s a listing of the data that my LFOur patch makes available when it is running:
There are four outputs (named nvA – nvD) that correspond to the note values for LFOs A, B, C, and D. The messages sent correspond to what you see on the control panels for each of the four LFOs (e.g. the output is sent to an r nvA object would be 1n).
An output named wavelist continually outputs the current value of the scaled integer values (in the range 0 – 127) shown in the moving window display.
LFOur has a single send~ object that sends the summed output of the four combined LFOs at audio rate to the destination audiowave. The output corresponds to what you see in the oscilloscope output.
There are four outputs (sampA – sampD) that correspond to the most recently sampled waveform output value (in the range 0-127) driven by the notevalue sampling rate. These values are displayed above the display on the far right.
There are four outputs (named bangA – bangD), each of which triggers at the start of the waveform’s output. Each bang is displayed above the third output display.
The LFOur patch also contains several receive objects you can control from your own Max patch.
Sending a bang with a destination L4sync will automatically synchronize all four LFOs to their starting point (0.) simultaneously. It is the equivalent of clicking the sync button located below the third output display.
Sending a zero or one with a destination L4start will toggle audio processing in the LFOur patch.
Sending a zero or one with a destination L4samp_on will enable or disable sampling the LFO waveform ouputs. When sampling is off, you can use the rightmost display to set the sliders to that the same value will be output every time an LFO starts its waveform and sends a bang message.
Sending a number in the range 0 – 23 with a destination L4samp will change the note value rate at which values are reported (you might want to copy a umenu object from the original LFOur patch and connect its left outlet to the send object instead of trying to remember what numbers to send). Since the rate at which the LFO waveform’s position is sampled is not tied to the rate at which values are reported, setting different note values for the sampling will produce different sets of values = at very high samples rates [e.g. 64n], the outputs will essentially be in sync with the third waveform display.
Next time, we’ll create a simple patch that uses the LFOur patch to make some noise.