You are on page 1of 15


GenExpr is the internal language used by gen patchers. It is used to describe
computations in an implementation agnostic manner. To perform actual computations, it
is translated into machine code for the CPU or GPU by the various Gen objects ( gen~ ,
jit.gen , etc.).
The GenExpr language can be used directly in gen patchers with the expr and codebox
objects. These objects analyze the expressions written in them and automatically
construct the the appropriate number of inlets and outlets so that patchcords can be
connected to the computations described within.

Note that there is absolutely no difference in terms of performance between describing
computations with object boxes and the GenExpr language. When a gen patcher is
compiled, it all gets merged into a single representation, so use the approach that is most
convenient for the problem.

The Gen Patcher and the codebox Object
The GenExpr language is designed to complement the Max patching environment within
Gen patchers. It provides a parallel textual mechanism for describing computations to be
used in concert with the graphical patching paradigm of Max. As one example, the
structural elements of user-defined GenExpr functions correspond closely to the
structural elements of Max objects with their notions of inlets, outlets, arguments and
attributes. Furthermore, the GenExpr language has keywords in, in1, in2, … and out,
out1, out2, … that specifically refer to the inlets and outlets of the expr or codebox the
GenExpr code is embedded in.

Adding a codebox to a Patch and Compiling It
Click in a blank space in your unlocked patcher window and type "n" (new) to
create a new object box with a cursor. Type in the word 'codebox,' followed by a


click on the Compile button at the bottom of the codebox window to compile and run your code. When you're done. semicolons are only necessary when there are multiple statements. a new codebox will be created. Enter your GenExpr language code into the codebox . Language Basics The GenExpr language’s syntax resembles that of C and JavaScript for simple expression statements like those in the codebox above. When there is only one statement. When there is a single expression with no assignment like in the far left codebox. The codeboxes below all contain valid expressions in GenExpr. When you hit return. the assignment to out1 is implied. however. Notice that it also doesn’t have a semicolon at the end. return. 2/15 . the semicolon is also implied.

The codebox on the right is correct. in1 and out1 are the left-most inlet and outlet respectively. The codebox on the left doesn’t have them and will generate errors. The expr operator is functionally the same as a codebox but lacks the text editor features such as syntax highlighting and multi-line text display and navigation. An expr or codebox determines its number of inlets and outlets by detecting the inN and outN keywords where N is the inlet/outlet position. semicolons are required. 3/15 . one-line expressions. expr is most useful for short.For multi-line statements however. saving the effort of patching together a sequence of objects together that operate as a unit. the keywords in and out are equivalent to in1 and out1 respectively. For convenience.

They can only be declared in the main body of GenExpr code where inlets and outlets (inN and outN) exist because they operate at the same level as object box Gen operators in the patcher. a global variable. norm). the GenExpr Param will simply alias the object box param. below we sample an input pix = sample(in1. If there isn't a param object box with the same name.Almost every object that can be created in a Gen patcher is also available from within GenExpr as either a function. the object atan2 has two inlets and takes two arguments as follows: out = atan2(in1. In the code above. For example. The number of inlets an object has corresponds to the number of arguments a function takes. offset aliases the object box param offset. while amp creates a new global param. a declaration. // this is a comment. 4/15 . Comments Comments in GenExpr follow the C style syntax for both single-line and multi-line comments. Single-line comments start with // and multi-line comments are defined by /* until the next */. A Param declaration in GenExpr names a parameter and creates it if necessary. in2) Parameters Parameters declared in GenExpr behave just like param operators in a patch. one will be implicitly created. or a constant. If there is a param object box with the same name as a Param declared in GenExpr.

the GenExpr compiler eliminates any unnecessary calculations. x). theta = sqrt(x*x+y*y). … = When a function returns multiple values but assigns to only one value. this looks like r. y). x) If we remove theta and have instead r = sqrt(x*x+y*y). atan2(y. the unused return values are simply ignored. theta = sqrt(x*x+y*y). so there is no performance penalty for not using all of a function’s return values. x) the compiler simplifies it to r = sqrt(x*x+y*y) In the reverse case where we only use theta. var3. In code. function in GenExpr can take multiple arguments and can return multiple values. The syntax follows the pattern: var1. out = theta. atan2(y. the Gen compiler will strip out the calculations for r notused. Just as the left-hand side list of variable names being assigned to are separated by commas. effectively becomes theta = atan2(y. Similarly. The function cartopol could be expanded out to r. The object cartopol has two inlets and two outlets. Functions that return mutiple values can assign to a list of variables. x). out = theta. When a return value is ignored.Multiple Return Values Just as object boxes can have multiple inlets and outlets. Even for more complex examples where the outputs share intermediate calculations. theta = cartopol(x. the right-hand side list of expressions can also be separated by commas: 5/15 . var2. the compiler eliminates unnecessary operations. in GenExpr the cartopol function takes two arguments and returns two values. atan2(y.

diff = in1+in2. but should be clear from these examples: Unused Return Values The second return value gets discarded and cartopol is optimized: r = cartopol(x. these additional values will be ignored unless the expression is the last item in the right-hand side list. in1 6/15 . out1 = cartopol(x. 0 Multiple Return Values in an Expression List Only the last expression can return multiple values. This is complex to describe. y). cartopol’s second return value discarded. out2 = diff. 0 If any of the expressions in the right-hand side return more than one value. y = in1.sum. For example. out2 = in1 becomes out1. sum If there are more values on the left-hand side than on the right-hand side. the extra variable names are given a value of zero. y = in1 becomes x. out2 = in1. y) Extra Assignment Values Zeros are assigned to extra assignment values: x. as it is not the last expression in the right-hand side: r. out1. in1-in2 out1.

r. the two output values of poltocar connect to the two input values of min: out = min(poltocar(in1. Since there are no types in GenExpr function arguments are specified simply with a name. A basic function definition with an equivalent patcher representation looks like: A function returning multiple values looks like: 7/15 . in2)) Defining GenExpr Functions Defining new functions in GenExpr happens in much the same way as other familiar programming languages.Here cartopol returns both values. In this example. since it is in the last position: out1. theta = in1. y) The same principle applies when expressions are used as arguments to a function call. cartopol(x.

The cylinder operator in Jitter Gen objects is defined as: 8/15 .

For example: out = sample(in. This holds equally in GenExpr. more involved functions like the above cylinder definition start to become unwieldy. a Param object is declared in the function. With one syntax. especially if the function is used several times within the GenExpr code. some objects have attributes like the Jitter Gen operator sample. The attribute is set with boundmode as the key and "mirror" as its value. Attributes in Function Definitions Attributes can also be defined for function definitions. will use a version of the sample operator with a mirroring boundary behavior. Operator Attributes In Gen patchers. which as a boundmode attribute. Here. This is the advantage of textual representations. If the attribute takes a numerical value. norm. Since the concept of Max messages doesn't exist within Gen patchers. In GenExpr. the attribute is defined and given a default in the function signature. 9/15 . attributes can be dynamic. Attributes are set using a key/value style argument. boundmode="mirror"). adding the name of the parameter as an attribute to the function. behaving in a similar manner to how setparam interacts with subpatcher parameters. Attributes can be defined in one of two ways. Such attribute values must be constants. function arguments correspond to operator inlets and function return values correspond to outlets.While simple functions in GenExpr can be easily patched together. it cannot be assigned to from a variable. With the other. attributes for built-in operators are not dynamically modifiable.

By declaring a Param object. which is an expression that is not constant but valid because func is a function definition. attributes of function definitions can be set with key-value syntax. Gen patchers save as abstractions (.5 while the offset attribute is given a value of in1*2. In the above example. only the default value of the attribute can be defined. outputs and named parameters. other properties such as minimum and maximum values for the attribute can be set. With the second method. you get more control over how the attribute operates. Both can have inputs. Note. the amp attribute is given a value of 0. however. As with built-in operators.gendsp or . that the offset attribute has minimum and maximum values defined.genjit files) can be used as functions. so any expression assigned to it will be clamped to that range. In GenExpr.With the first method. When the GenExpr interpreter 10/15 . Abstractions as GenExpr Functions Structurally. GenExpr functions are equivalent to Gen patchers.

the GenExpr interpreter will look for myAbstraction. The following are all valid ways to require a . These are invalid characters when it comes to GenExpr function names. For example. In the above code. they cannot be used as GenExpr functions. _) followed by any number of letters. so if they're used in the name of a Gen abstraction. _). Requiring GenExpr Files When defining operators in GenExpr. use the require operator.genexpr file and loads its definitions. GenExpr function names must be valid identifiers. it will only be loaded once. [A-Z]. if I have the code: out1 = myAbstraction(in1) There is no definition of the function myAbstraction in the above code and it isn't a built- in operator. An identifier in GenExpr is a sequence of characters starting with a letter or an underscore ([a-z]. the gen object will detect that a file it depends on has changed through filewatching and recompile itself to reflect the new changes.genexpr files can also require other files. There are some caveats with using abstrasctions as GenExpr functions. GenExpr files can be created and edited using the built-in Max text editor. Required .encounters a function it can't find the definition of. numbers or underscores ([a-z].genjit in the case of the Jitter Gen objects.genexpr' using the Max search paths. it can be handy to keep them in a separate file so that they can be reused frequently without having to constantly re-type the operator definition in a codebox.' in them.genexpr file: require("mylib") require("mylib"). it will use the current Max search paths to look for a Gen abstraction with the name of the function. [A-Z]. If a GenExpr file is required in a gen object and the file is edited and saved. If a file is required more than once.gendsp in the case of gen~ or myAbstraction. As a final attempt to define myAbstraction. To include GenExpr operators defined in a separate file. The require operator takes the name of a . It is not uncommon for Max subpatchers to have other chartacters such as '~' or '. Branching and Looping 11/15 . require"mylib" require"mylib". [0-9]. calling require triggers the codebox to look for the file 'mylib.

Floating point numbers can't exactly represent every number. For example: if(in > 0. it can be easy to make a programming mistake and create an infinite loop.25) { out = sin(in*pi). for and while constructs. } Note that in the Jitter gen objects. even loop counters. val = 0. Instead. i < 10. += can be used: val = 0. Also. } out = val. note that in many cases values in GenExpr are floating point. if statements with vector-valued conditions will only use the first element of the vector to determine whether a block of code should be tested or not. } else { out = cos(in*pi)*sin(in*pi). it won't cause Max to get stuck and freeze. for loops are also similar to this in many other languages although there is no ++ operator in GenExpr to increment a loop counter. sometimes a little fudge factor to account for this might be necessary. this loop: 12/15 . while loops in GenExpr are similar to those in many other languages: i = 0. for(i=0. } out = val. i += 1) { // accumulate val += i. if statements can be chained together using else if an arbitrary number of times such that different blocks of code will be executed depending on different conditions. while(i < 10) { i += 1. // accumulate val += i. } else if(in > 0. For example.Branching and looping is supported in GenExpr with if/else. All of the gen objects have protections against infinite loops. Since GenExpr is compiled on-the-fly. so while an infinite loop might slow things down.5) { out = cos(in*pi).

05) { // accumulate val += i. write something like: val = 0. to ensure that the i variable goes all the way to 1.04.val = 0. for(i=0.texture) depending on the object. } out = val. } val += i.matrix) and/or textures (jit. i <= 1.05) { // accumulate val += i. } out = val. i += 0. i < 10. } out = val. i += 1) { if(val > 20) { // bail early break. Continue and Break With looping constructs. GenExpr supports break and continue statements. will likely not reach 1. } val += i. for( for(i=0. val = 0. for(i=0. GenExpr and Jitter Inputs Jitter Gen objects take both Jitter matrices (jit. Within the Gen patcher the operator in represents both the 13/15 . break causes an early exit from a loop while continue causes the loop to start the next iteration without finishing the current one. } out = val. val = 0. i += 0. i < 10. Instead. i <= 1.0 despite the <= operator because of floating point precision. i += 1) { if(val == 6) { // skip an iteration continue.

and dim) are special-case operators that are a hybrid betwen operator and global variable. In the first instance above. norm is syntactically a global variable while dim is syntactically a function call. GenExpr and Jitter Coordinate Operations The coordinate operations in Jitter Gen patchers (norm. which are used to grab data from arbitrary locations within the input. 14/15 . All of the coordinate operators follow this convention. When GenExpr code is compiled. Only an in operator can be connected to the left input of sample and nearest. In most cases.input matrix or texture in its entirety and the current cell of that input being processed. The validation process can track inputs even through function calls so sample and nearest can be used within functions without issue. snorm. the in operator represents the current cell being processed. The only time where it represents the entire input is with the sample and nearest operators. the inputs to sample and nearest are validated to ensure that their first arguments are actually Gen patcher inputs and an error thrown if this isn't the case. cell. The same holds true when sample and nearest are used in GenExpr. There are two equally valid syntaxes for using these operators: out1 = norm * dim().

Note that GenExpr has no array notation [index] as there is currently no notion of an array structure. Gen variables are also local- to-scope by default so they don’t have to be declared with a keyword like var as in JavaScript.Technical Notes GenExpr is a type-less language. 15/15 . Variables are given types automatically by the compiler depending on the Gen domain and the Gen object’s inputs.