I’m sure that it comes as no surprise to you that I’d say that the community of Max users isn’t a monolithic batch of patching fanatics – the community can be divided in any number of ways. We can think of audio programming versus visual programming, performance versus installation, Max versus Max for Live users, and even further segment user communities from there. For this gen~ tutorial, I’d like to talk about another way to segment the Max community in ways that we don’t often talk about: graphic versus text-based coding.
All the tutorials in this series: Part 1, Part 2, Part 3, Part 4, Part 5, Part 6, Part 7 Follow-up video tutorials: Working with Abstractions, Debugging and Signal-Rate Processing
One of the truly interesting things about the Gen universe in Max is what it offers both groups of users the ability to create efficient and idiosyncratic Max tools by patching with operators, writing code, or combining both approaches and choosing the one that makes the most intuitive or practical sense.
That’s what the codebox operator in gen~ is all about. In this installment of our gen~ for beginners series, I’d like to introduce you to the codebox operator. As before, one of my goals is the reduction of anxiety – and, perhaps more than any other part of the gen~ patching world - the codebox operator tends to be a source of discomfort for some beginners. I think that discomfort takes two forms:
- Some Max users who are uncomfortable or unfamiliar with text-based coding may worry that using the codebox operator is more efficient than the operator-based approach that they’re becoming more comfortable with (now that we’re 5 tutorials into the series). I think that’s because we find ourselves at this time in a culture that privileges text-based coding, and perhaps because beginning users see others opting for codeboxes from the git-go and assume that it must be somehow always be more efficient.
- Max users who are comfortable with writing command-line code soon realize that working with the codebox operator means learning a slightly different bit of programming than they may be accustomed to – GenExpr. For them, the questions involve figuring out what’s different about syntax inside the codebox operator.
To codebox Or Not To codebox, That Is The Question…
Let’s start with the first – and probably most important bit of anxiety reduction for some beginning gen~ users:
There is absolutely no difference in terms of performance between using operators inside your gen~ patch as compared to using a codebox operator or using the GenExpr language in an expr operator to do the same thing.
A Gen patcher contains and describes the calculations a Gen object performs. The gen~ object compiles the contents of patcher into a language called GenExpr, which the object turns into target code necessary to perform its calculations (that GenExpr target code is what you see in any gen~ patcher’s Code tab). From this, the gen~ object then generates and compiles native CPU machine code on-the-fly. Everything – whether it be operator-based patching or codebox operators - gets merged into a single representation. The bottom line? Use what you’re comfortable with, or the approach that is most convenient for the problem you want to solve.
Apart from your own codeophilia or codeophobia, when might you want to (or need) to use a codebox operator in your gen~ patching? The reasons for using regular operator-based patching over the use of the codebox operator has to do with the editing environment itself, as much as anything else.
- Working with operators lets you take advantage of autocomplete features when patching.
- You can easily add assistance on operator inlets and outlets. (You’ll really notice this once you try going back to that bit of codeboxing you forgot to comment well and try to figure out what the third inlet did – believe me).
- You can call up the Max reference resources by option-clicking (Macintosh) or alt-clicking (Windows).
- Although this won’t be true for all of your friends, operator-based patching may be more accessible to most Max users when it comes to sharing your patches.
That said, there are some things for which the codebox operator is incredibly useful – in fact, there are things which you can’t do without a codebox object:
- The codebox operator is useful if you want to perform certain kinds of programming operations – things like for( ), or while( ), or if( ). It’s not just useful – it’s almost always the only way to include those kinds of procedural operations.
- The codebox operator gives you specific and explicit control of the order or reads and writes for things like delays and operations on data and buffer operators. While you can sometimes achieve this with standard gen~ patching, there will be some cases where you want to guarantee a specific order to operations. The codebox operator is your friend here.
- The codebox operator lets you create libraries of functions you can re-use. In the Max world, this is a little bit like having your own folder full of abstractions.
In the course of this series, I've been corresponding with my friend and colleague Graham Wakefield - the gen~ object's father. The subject of working with the codebox operator came up, and his comments about it surprised me a little - I guess I'd assumed that someone like Graham - who codes with the same ease I bring to making errors in my Max patching - would just naturally use the codebox operator for everything. Not really, as it turns out:
I have to say, every time I start a new work in gen~ I start with patching, not with a codebox; and I dive into codebox only where I need to. Partly this is because I’m expecting I might add this to the Gen examples folder, but it's also the case that I just find it easier to think some things as dataflow first. Also, honestly I can never remember the inlets and attributes of buffer and delay operators, so the UI help is really appreciated. Sometimes, my patcher will end up being mostly patching with a few codebox operators here and there as needed. But sometimes there’s no way to avoid doing the whole thing in a giant codebox. That's nearly always because I need to do some parallel operations (loops) or non-samplerate conditionals (via if()).
Let's take a first foray into some codeboxing.
Your First Codebox
To add a codebox operator to your gen~ patcher, click in a blank space in your unlocked patcher window, type "n" (new) to create a new object box with a cursor, and type in the word 'codebox,' followed by a return. When you hit return, a new codebox operator will be created.
As is the case when creating a new gen~ object, the new codebox operator contains an example of GenExpr code by default. The contents of the generic codebox operator bear a very strong resemblance to what appears inside of a gen~ object when you first create it: an example of adding two input values and outputting the sum (of course, you'd need to add two in operators and an out operator to create a precise copy of the generic gen~ starter patch - the codebox isn't connected to the outside world.
We've spent previous installments of our gen~ for Beginners tutorials looking at similarities and differences, so let's turn out attention to figuring out what it is you actually type into the text window of a codebox operator. The contents of a codebox operator are in GenExpr - the internal language used by all Gen patchers. When you type GenExpr text into a codebox operator, the object analyzes the expressions you type and automatically constructs the the appropriate number of inlets and outlets so that patchcords can be connected to the computations described inside.
If you'd like a complete guide to the GenExpr (along with an overview of its use in a codebox operator), type GenExpr into the text box in the upper right-hand corner of your patcher window and hit return. The Documentation Browser will open, and you can click on the Guides tab to the left to be routed to the GenExpr overview. It's full of useful information.
So, what are the equivalent GenExpr commands to the Gen operators you use when doing visual programming? Since you've been following this tutorial series, you already know the answer to this question!
Way back in our first gen~ for Beginners tutorial, we showed you the Code icon in the gen~ patcher window. We explained that clicking on the Code tab’s icon displayed the actual code that Gen operators use to produce the machine code compiled as you work.
This is a feature you can use when you start codeboxing. If you have a question about what the syntax of any Gen operator looks like in GenExpr, here's a quick and dirty technique to get your answer:
- Create an instance of the operator you want to check on (in this example, we'll use the tan operator).
- If you've got an out operator in your patch, connect the operator up. If not, add one and connect it.
- Select the operator you want to check on.
- Click on the Code icon at the right-hand side of your patcher window to open the Code View tab. The operator you've selected will be highlighted.
A Simple Codebox Example
Let's look again at the contents of the vanilla codebox operator:
In this simple example, we can see how the codebox object's inlets (in1 and in2) and outlets are identified and created, how its outlets (out1) are identified and created (Note that we're talking about the inlets and outlets to the codebox object itself - to connect it to the outside world, we'll still need to add standard in and out Gen operators in our patch).
We can also see that the line of code has a semicolon at its end (every line in a multiline codebox that isn't a comment needs to end with that semicolon).
With that in mind, let's create a codebox-based piece of gen~ patching.
This looks pretty straightforward, but what's the range for the "output" supposed to be? Oh, right - it's listed as between -1.0 and 1.0 in the notes. We know that what they're calling x is the input signal, and that f(x) is a description of how you calculate the output. With that in mind, we can make a few minor modifications to the code to "port" it to our codebox. Here's the result:
You'll notice a slight change from the the original code here - that line where the amount is declared. When the original code was put in, it turned out that this bit of code tended to blow up loudly and nastily with values outside of the -1.0 - 1.0 range, so I added the that clamp (yes, it does the same thing as the Gen clamp operator) to constrain the input range save your ears. You're welcome.
When we type the code into our codebox operator, we don't see any errors in the Max window. That ought to mean we're good to go. Let's add a little patching to our gen~ patcher and listen to our waveshaper.
Note: This patch is included as part of the download for this tutorial that you can find at the end of the tutorial, but you'll also find a copy of it as part of the gen~.overdrive.maxpat patch in your Gen examples folder, along with another codebox-based gen~ patcher ported from www.musicdsp.org.
Working With Parameters
You can make use of the Gen param operator in your codebox patching, too - parameters declared in GenExpr behave just like param operators in a patch - you add a param operator to your gen~ patcher, and then reference it by name as part of the code you type into the codebox operator. You'll remember that we do want to limit the range of inputs for our parameter for reasons of your hearing, as we did last time. How can we do this using a codebox operator? If we were working the a standard Gen param operator, we'd use the @min and @max attributes.
In this example, we've changed our patch so that the amount in our waveshaper is a standard Gen parameter. Here's what the result looks like:
One thing you'll need to remember - and you can see it in the example above - is that you need to add those parameters in at the top of your codebox text (i.e. before any declarations) - they need to be there because they're operating at the same level as object box Gen operators in your patcher.
You'll also notice that the inside of our gen~ patcher doesn't include a param operator, as you might expect. As long as the Param line is added to the codebox, it's just as though you'd added a param operator. The line Param amount(0., min=-1,max=1); also lets us set a default value of zero for the parameter, and specify those min and max values to save your hearing - just as though we'd added a param amount @default 0. @min -1 @max 1 operator to our gen~ patcher.
To finish up this edition of our beginner's tutorial, here's another parameter-based example of one of the things you'll need a codebox operator to achieve: wrapping or folding an input waveform based upon its threshold value. It's one of those places where an if... else... construction is just the thing.
This example shows another approach to working with parameters in a gen~ patcher. Instead of adding a Param-style line in the codebox operator, we've added named param operators to the gen~ patcher window as we might normally do in non-codeboxed patch and then made use of those parameters in our codebox code by name (mode and threshold).
Note, too, that we've set a variable (wrapfold_out) at the beginning of our codebox code, which lets us use that variable in the if... else portion of the code and output it when we're done.
In Part 6, we'll look at working with buffers and data operators, history, and functions. See you then!