Tutorials

gen~ for Beginners, Part 7: Creating Reusable Tools

As a Max programmer, you’ve already learned about the practice of creating abstractions – bits of patching that you name and save in your search path that allow you to build up a collection of tools you can use over and over again.

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

In the Gen world, you can do the same thing by making use of the gen operator. It’s the same approach: name and save the contents of the inside of a gen~ patcher somewhere in your search path, and you can use a gen operator with the @gen attribute to add that patching to your work (You can also just use the name, as you do with Max abstractions - using the abstraction name is really shorthand for gen @gen abstraction-name).

By the way - here’s a useful “secret Gen trick:” - When you’re working in a gen~ patcher, Control-Shift-S will let you save the current contents as a .gendsp file.

The codebox operator lets you do the same thing – create functions for use in a codebox that can be saved and reused. Let’s take a simple example, and trace it from start to finish…..

One of the very first bits of codeboxing that I ever did involved my wish to add some traditional easing functions for use in my Max patching. Back when I started, we didn’t have access to something like the awesome Easing tools you can download using the Package manager, so I was kind of on my own.

I started by downloading the set of easing functions from here.

There it was – a whole library of cool functions I could use. My original interest was in a “bounce” function, so I started there. Here’s what was in the source code:

I had three bits of C code:

  1. One that calculated the ease out function - AHFloat BounceEaseOut(AHFloat p)

  2. One that calculated the same function for easing in - AHFloat BounceEaseOut(AHFloat p) – which was merely a case of doing the same calculation as the ease out function and then doing a !- 1. on the result

  3. A function that took both of these calculations and used one or the other, depending on the current position of the input being more or less than halfway.

The main bit of code I needed to work with was the calculation part of the ease out function, so I thought I’d start with that:

I honestly wasn’t sure that it could really be that simple to port something to the codebox, but I gave it a try – nothing ventured, nothing gained.

It looked as though all I really needed to do was to create a function in my codebox operator and then specify inputs and outputs in GenExpr. Looking at the calculations, p was the input value, which I got from my input (in1). The if… else part of my calculations was using return everywhere that an output was calculated, so I created a variable called output and substituted it for the return stuff.

Since the output variable is something I wanted to be able to make use of outside of the if… else calculation stuff, I’d need to declare the variable up at the very top. After that, it was just a case of sending the result out out1.

So that’s what I did. I made the changes, crossed my fingers and closed my gen~ patcher object and saw no errors appear in the Max console – so far, so good. I added a few MSP objects to give my gen~ patcher some phasor~ input, added a scope~ object to watch the results, held my breath, and turned on the audio. The result?

Note: I will omit exactly how long I hollered and danced around and just sat and watched the output in awe and wonder – I’m sure it’s happened to you before, too.

Drunk on victory, I did the same thing for the ease in Bounce function. Based on the code, it was really simple – take what I’d just made, subtract the result from 1.0, and send it out. (Insert more hollering and dancing and staring at the UI here).

Okay – what’s with that return stuff in the original C code, anyway?

The part of my original C code set up a function, which people who write code all the time use.

GenExpr also lets you define functions in much the same way as with other programming languages. The biggest difference if you’re familiar with other programming languages is that most of the time you don't explicitly write data types in GenExpr. Since everything is done with 64-bit data, you specify function arguments using names only. The only thing you really need to keep in mind is that functions need to be declared before any other statements.

It’s not a lot different from what I’d originally had – just a few lines of code to specify that the body of the calculation was a function with a name that returned a value instead of assigned a variable. Here’s what the contents of my codebox operators looked like when I turned the contents into functions:

Here’s something I learned in the process of putting this together (I am putting it in boldface type to suggest that this is important) :

Put the functions before declarations and calculations

So I had a nice bit of codeboxing that calculated all three easing functions for me. Now, it was time to bring it all home - to convert my patch to something I could reuse, and to add some of that stuff you can only do using the codebox operator - a little if/else programming to let me select the Bounce easing function I wanted to use. To do that, I removed the four lines from the bottom of the codebox and saved the results to a file I named using the file extension usually used for GenExpr code: BounceEaseFunctions.genexpr.

Next, I used the Gen require operator to reference the file full of functions I’d saved at the very beginning of a codebox in a new patch. After that, it was merely a matter of defining the variable I wanted to use to select which Bounce easing function I wanted, and adding some very simple if… else code to my codebox that did my calculations based on my selection (easefunc). Here’s the result:

After my initial success, I went through all of the easing functions described by Warren Moore, and saved them as you see here. I’m including a folder in the examples that includes this patching. Please enjoy them responsibly.

As you might expect, you can use this technique to include large amounts of complex GenExpr code to make new tools for your gen~ patching life.

So get out there and find yourself some code to port. Happy patching!

gen7_tutorial_patches.zip
application/zip 4.51 KB
Download the patches used in this tutorial

Gregory

by Gregory Taylor on June 19, 2018

Creative Commons License
larme's icon

Can require operator import constants defined in other genexpr files? In my test I can only import function but not constants.

teeshirtguy5's icon

thanks so much for putting this concise yet thorough series together gregory ! been very useful to come back to

peternortoft's icon

thanks for this
i tried to replicate what you did
fine: codebox found my .genexpr file
problem: "operator interpBounceEaseIn not found"
regs Peter (Max7)

Graham Wakefield's icon

You would need the BounceEaseFunctions.genexpr file from the tutorial downloaded to somewhere in your Max search path.

peternortoft's icon

Hi Graham,
Thanks for your reply. Yes that works. But I would like to save my own file, eg:

wrapover1(a,b)
{
x = abs (b);
while ( x > 1 ) {
if ( b > 0 ) {
b = 2*1 - b;
x = abs(b);
}
else { b = -2*1 - b;
     x = abs(b);
}
    }
return b;    
}

If I save in codebox as file/save as/ all files/wrapover.genexpr I get a file that the require operator can find, but the compiler can not find the wrapover1 . So the question is how does Gregory save the BounceEaseFunctions.genexpr file the first time.