gen~ for Beginners, Part 2: Similarities and Differences
In Part 2 or our gen~ for beginners tutorial, we're going to start our program of anxiety reduction by running through the gen~ basics that beginning beginners may find uncomfortable.
But first, let's start on a positive note by taking a minute to talk about some of the places where your Max experience translates easily to working in gen~.
If you’re used to working with MSP, one of the comforting similarities is that you can transfer your understanding of the audio-rate part of Max to working with the gen~ object: All of the processing inside of a gen~ object resembles MSP because processing in gen~ is also synchronous. The basic concepts you learned when you first learned MSP will serve you well here:
- There are no hot or cold inlets.
- There is none of the left-inlet triggering you know from the non-audio-rate Max world.
- There is no right-to-left ordering when you think of output from gen~ operators.
However, you might be wondering why you don't see any tilde (~)objects in the names of gen~ operators. MSP objects are all identified by having a tilde in their names as a way of indicating that they are working in the synchronous audio domain and doing their work by processing batches of samples (which we call signal vectors in Max) at every tick of Max's audio-rate scheduler.
All operators in your gen~ patcher window are performing their calculations on one sample at a time, whether it's processing audio, comparing two numbers, or opening or closing a switch. Since everything is running at sample-rate, you don't need the tilde to remind you. Many gen~ operators operate so much like their MSP cousins that doing some things in Gen can be really simple. As an example, I wrote a tutorial about implementing LFOs using Gen a while back that you might want to check out. I think you'll be surprised at just how similar the original MSP patch and its gen~ counterpart are.
Whenever possible, we have tried to name a gen~ operator whose function is similar to its MSP counterpart with the same name (minus the tilde, of course). Here are some examples - phasor~/phasor, rate~/rate, selector~/selector, sah~/sah, and all of the math operators. When you're not sure of whether or not there's a gen~ operator that does what you want, just type in the name of a Max or MSP object and keep an eye on the gen~ patcher window's autocorrect feature. If you don't see an operator with that name, check the examples folder.
All operations inside a gen~ patcher are performed on a single sample instead of working on a block of samples and then shipping the results for the calculations of each sample in that block along with the next tick of Max’s audio-rate scheduler. While that introduces some interesting differences we’ll talk about in a minute, some things about working at signal-rate in MSP and sample-rate inside a gen~ object are similar. For example, multiple input connections are summed, as is the case in MSP.
And, like signals in an MSP patch, everything inside of your gen~ patcher is “on” all the time. When you first learned MSP, you learned learned to use the sig~ object to send a floating-point value at signal rate - the sig~ object takes an input value and “broadcasts” that value for every signal vector rather than providing a one-time message in the Max world. All gen~ operators -- even operators that function as numerical constants (like this humble 0.5 operator in the following example) -- are broadcasting their value to the world as long as audio processing is turned on.
These two features, taken together, let us easily do some routing and mixing of a sort you’ll recognize as an MSP user. When you think of it, multiplying a constantly running signal value by 0 “turns it off,” and we can use summing to let us turn multiple inputs on and off, as the following example demonstrates:
As we go through this sequence of tutorials for beginners, I think you’ll find lots of examples of the comforting similarities between working in MSP and working in the Gen environment. But some things are a different, so let's start talking about them.
The Ins and Outs of the gen~ object
There are three ways to connect what’s inside your gen~ patcher to the outside world:
1. The param operator
First, you can use the param operator to add parameters used inside your gen~ patcher. Those param operators are named, and the name corresponds to the message you send to the left inlet of your gen~ object to change that parameter. Parameters are updated at MSP signal-vector rates.
2. Inlets and outlets
You can add inlets and outlets to your gen~ patcher using numbered in and out operators, just as it works with inlet/outlet objects in the Max patching environment. The output from a gen~ object is always a signal – it’ll be a 32-bit or 64-bit signal, depending on which version of Max you’re running.
Everything inside of the gen~ patching environment is done using 64-bit floating point numbers – in fact, you don’t need to distinguish between integer values and floating point numbers at all inside of your gen~ patcher (you’ll probably notice that in the example patches and in the work posted to the Forum).
When it comes to inputs to an in operator in your gen~ patcher, gen~ does something clever – it will recompile to adapt to the type of input you give it. The following example shows that adaptation in action:
The three gen~ objects are using an MSP cycle~ object’s output to modulate the slope of a triangle operator inside the gen~ patchers. The first example is calculating the result at Gen’s single-sample rate, and shows a nice smooth curve. The second example is setting the modulation input using a param operator, and you can see that the output is stepped. That’s because it’s calculating the value at every tick of MSP’s audio scheduler.
The third case is the interesting one, though – instead of sending the gen~ object’s second inlet an audio signal, we’re sending it a floating-point number we sampled using a snapshot~ object in MSP. The output looks just like the param operator example because the Gen environment is automatically adapting its input and updating it at signal vector rates.
As you create gen~ patches, it’s always good to keep in mind how often you’re going to want to update inputs as you calculate; Use parameters or the number-to-audio-input trick when you need less time-critical output.
3. The gen~ buffer operator
The Gen buffer operator lets you import audio files into your patcher by using a reference to a buffer~ object in your main patcher, and to reload the contents of the buffer operator inside your gen~ patcher by changing the reference, in a similar way to how you work with buffer~ references in MSP.
The example below demonstrates using a really simple method for doing audio buffer playback - counting one sample at a time (using a Gen counter operator), fetching a sample from a buffer operator inside the gen~ patcher (using the peek operator), and then outputting that sample.
You’ll notice that the buffer operator inside the gen~ patcher on the right has a name that we set using an argument (myfile). In the main body of the example patch on the left, we have three named buffer~ objects. Sending a message in the form <buffer-operator-name MSP-buffer~-name > to the gen~ object loads the MSP buffer, ready for playback.
Note: The gen~ object also supports a second buffer-like operator used for data storage - the data operator. There are some differences between the two operators that you will want to keep in mind:
- You cannot associate the contents of a data operator with an MSP buffer~ object outside of your gen~ object.
- The contents of a data operator is 64-bit data, while the content of a buffer operator is 32-bit data.
- The data operator lets you specify the size and number of channels from within the gen~ object - the size and number of channels for a buffer operator can only be defined outside in the parent Max patcher.
Setting State: Inlets, Arguments, and Attributes
Although the operators you use in the gen~ patching environments resemble MSP objects in that they take arguments and/or attributes to set their initial state, The use of arguments in gen~ patching result in a little different behavior—a difference that can be confusing because you’ll occasionally find the same operator used twice in the same patch that has a completely different number of inlets.
In the Max/MSP world, you have several ways to set the initial values of any parameter. Here are three popular approaches:
- Using a loadbang object/message box pair
- Using a loadmess object
- Using an object's Inspector for an object to turn on Parameter Enable and then setting an initial value
The gen~ environment is different – the only way to get your single-sample data into the gen~ patcher would use the param operator, or sending a value into an inlet for the gen~ patcher using a number box or a signal as a source. That means that you have to approach setting initial values differently.
The param operator lets use arguments to set the initial value of a parameter, and provides a nice additional feature - you can set a range for your input values using the @min and @max attributes, as well (say goodbye to your all those clip -1. 1. externals on your patch inputs or clip -1 1 operators in your gen~ patcher's inlets!
If you’d like to set a default input value for an in operator - to set the value output if the inlet to the gen~ object in your MSP patch is unconnected - use the @default attribute. You can use them alongside the @min and @max attributes.
When you create any gen~ operator, the operator has its own internal init values. Those initial values are ones that aren’t likely to cause any problems (for example, the divide operator defaults to dividing by one).
If you're not sure what the default values for an operator are, you can add an operator to your patch, connect it up, and then click on the C (Code) icon on the right-hand side of the Gen patcher window. The Code window will open, and you can see the defaults for the object listed in the GenExpr version of your patch. In this example, a vanilla scale operator is equivalent to a scale~ 0. 1. 0. 1. 1 object in Max.
One feature of the Gen environment that beginning users sometimes find confusing is that attributes and arguments in Gen operators are static: once you set them, they’re not subject to change. The main way in which that’s apparent has to do with the look of a Gen operator itself.
In the Max/MSP world, arguments are used to set the default state of an external object, but you can change that state by sending messages to the input associated with the argument. For example all scale objects in all Max patches always have 6 inlets. If you want to set the default state of a scale object, you type in as many arguments as you want to set (e.g. scale 0. 1. 0 127). To change the behavior of the object so that the output range values for the object are different than the original arguments, you send new number values into the fourth and fifth inlets of the scale object. The Gen world is different. When you use an argument to set a default value, that value set by the argument is fixed and cannot be changed.
Gen operator operators helpfully indicates that an initial value is fixed by “removing” the inlet of the displayed operator associated with the value you set. It’s not a hard thing to understand, but you need to get used to seeing the same Gen operator look different in different situations. Here are four examples:
In Gen you can name parameter operators using an argument, and then use that parameter name when performing calculations. Using them can make your Gen patching a lot nicer looking.
Gen also includes a number of constants - useful numbers that you type in as operators and use as part of your patching. You'll find them all listed in one place in the Gen object list.
I hope you're feeling a little less anxious about working with the gen~ object. In Part 3 of our tutorial series, we're going to look at how working one sample at a time changes some of the ways that we approach patching, and some of the ways that Gen users work in a world without bang messages or lists.
by Gregory Taylor on
Mar 7, 2018 3:12 PM