Professional Documents
Culture Documents
docs.cycling74.com/max6/dynamic/c74_docs.html
1/15
return. When you hit return, a new codebox will be created.
Enter your GenExpr language code into the codebox . When you're done, click on
the Compile button at the bottom of the codebox window to compile and run your
code.
Language Basics
The GenExpr language’s syntax resembles that of C and JavaScript for simple
expression statements like those in the codebox above, however, semicolons are only
necessary when there are multiple statements. The codeboxes below all contain valid
expressions in GenExpr. When there is a single expression with no assignment like in
the far left codebox, the assignment to out1 is implied. Notice that it also doesn’t have a
semicolon at the end. When there is only one statement, the semicolon is also implied.
2/15
For multi-line statements however, semicolons are required. The codebox on the left
doesn’t have them and will generate errors. The codebox on the right is correct.
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. expr is most useful
for short, one-line expressions, saving the effort of patching together a sequence of
objects together that operate as a unit.
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. in1 and out1 are the left-most inlet and
outlet respectively. For convenience, the keywords in and out are equivalent to in1 and
out1 respectively.
3/15
Almost every object that can be created in a Gen patcher is also available from within
GenExpr as either a function, a global variable, a declaration, or a constant. The number
of inlets an object has corresponds to the number of arguments a function takes. For
example, the object atan2 has two inlets and takes two arguments as follows:
Parameters
Parameters declared in GenExpr behave just like param operators in a patch. 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.
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 */.
4/15
Multiple Return Values
Just as object boxes can have multiple inlets and outlets, function in GenExpr can take
multiple arguments and can return multiple values. The object cartopol has two inlets and
two outlets. Similarly, in GenExpr the cartopol function takes two arguments and returns
two values. In code, this looks like r, theta = cartopol(x, y). Functions that return mutiple
values can assign to a list of variables. The syntax follows the pattern: var1, var2,
var3, … =
When a function returns multiple values but assigns to only one value, the unused return
values are simply ignored. When a return value is ignored, the GenExpr compiler
eliminates any unnecessary calculations. The function cartopol could be expanded out to
r = sqrt(x*x+y*y), atan2(y, x)
r = sqrt(x*x+y*y)
In the reverse case where we only use theta, the Gen compiler will strip out the
calculations for r
effectively becomes
Even for more complex examples where the outputs share intermediate calculations, the
compiler eliminates unnecessary operations, so there is no performance penalty for not
using all of a function’s return values.
Just as the left-hand side list of variable names being assigned to are separated by
commas, the right-hand side list of expressions can also be separated by commas:
5/15
sum, diff = in1+in2, in1-in2
out1, out2 = diff, 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.
For example,
becomes
If any of the expressions in the right-hand side return more than one value, these
additional values will be ignored unless the expression is the last item in the right-hand
side list. This is complex to describe, but should be clear from these examples:
r = cartopol(x, y)
x, y = in1
becomes
x, y = in1, 0
Only the last expression can return multiple values. cartopol’s second return value
discarded, as it is not the last expression in the right-hand side:
6/15
Here cartopol returns both values, since it is in the last position:
The same principle applies when expressions are used as arguments to a function call.
In this example, the two output values of poltocar connect to the two input values of min:
7/15
The cylinder operator in Jitter Gen objects is defined as:
8/15
While simple functions in GenExpr can be easily patched together, more involved
functions like the above cylinder definition start to become unwieldy, especially if the
function is used several times within the GenExpr code. This is the advantage of textual
representations.
Operator Attributes
In Gen patchers, some objects have attributes like the Jitter Gen operator sample, which
as a boundmode attribute. In GenExpr, function arguments correspond to operator inlets
and function return values correspond to outlets. Attributes are set using a key/value style
argument. For example:
will use a version of the sample operator with a mirroring boundary behavior. The
attribute is set with boundmode as the key and "mirror" as its value. Since the concept of
Max messages doesn't exist within Gen patchers, attributes for built-in operators are not
dynamically modifiable. This holds equally in GenExpr. Such attribute values must be
constants. If the attribute takes a numerical value, it cannot be assigned to from a
variable.
Attributes can also be defined for function definitions. Here, attributes can be dynamic,
behaving in a similar manner to how setparam interacts with subpatcher parameters.
Attributes can be defined in one of two ways. With one syntax, the attribute is defined and
given a default in the function signature. With the other, a Param object is declared in the
function, adding the name of the parameter as an attribute to the function.
9/15
With the first method, only the default value of the attribute can be defined. With the
second method, other properties such as minimum and maximum values for the attribute
can be set. By declaring a Param object, you get more control over how the attribute
operates.
As with built-in operators, attributes of function definitions can be set with key-value
syntax. In the above example, the amp attribute is given a value of 0.5 while the offset
attribute is given a value of in1*2, which is an expression that is not constant but valid
because func is a function definition. Note, however, that the offset attribute has
minimum and maximum values defined, so any expression assigned to it will be clamped
to that range.
10/15
encounters a function it can't find the definition of, it will use the current Max search paths
to look for a Gen abstraction with the name of the function.
out1 = myAbstraction(in1)
There is no definition of the function myAbstraction in the above code and it isn't a built-
in operator. As a final attempt to define myAbstraction, the GenExpr interpreter will look
for myAbstraction.gendsp in the case of gen~ or myAbstraction.genjit in the case of the
Jitter Gen objects.
There are some caveats with using abstrasctions as GenExpr functions. GenExpr
function names must be valid identifiers. An identifier in GenExpr is a sequence of
characters starting with a letter or an underscore ([a-z], [A-Z], _) followed by any number
of letters, numbers or underscores ([a-z], [A-Z], [0-9], _). It is not uncommon for Max
subpatchers to have other chartacters such as '~' or '.' in them. These are invalid
characters when it comes to GenExpr function names, so if they're used in the name of a
Gen abstraction, they cannot be used as GenExpr functions.
require("mylib")
require("mylib");
require"mylib"
require"mylib";
In the above code, calling require triggers the codebox to look for the file 'mylib.genexpr'
using the Max search paths. Required .genexpr files can also require other files. If a file is
required more than once, it will only be loaded once.
GenExpr files can be created and edited using the built-in Max text editor. If a GenExpr
file is required in a gen object and the file is edited and saved, the gen object will detect
that a file it depends on has changed through filewatching and recompile itself to reflect
the new changes.
11/15
Branching and looping is supported in GenExpr with if/else, for and while constructs. 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. For example:
Note that in the Jitter gen objects, 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.
i = 0;
val = 0;
while(i < 10) {
i += 1;
// accumulate
val += i;
}
out = val;
for loops are also similar to this in many other languages although there is no ++
operator in GenExpr to increment a loop counter. Instead, += can be used:
val = 0;
for(i=0; i < 10; i += 1) {
// accumulate
val += i;
}
out = val;
Also, note that in many cases values in GenExpr are floating point, even loop counters.
Floating point numbers can't exactly represent every number, sometimes a little fudge
factor to account for this might be necessary. For example, this loop:
12/15
val = 0;
for(i=0; i <= 1; i += 0.05) {
// accumulate
val += i;
}
out = val;
will likely not reach 1.0 despite the <= operator because of floating point precision.
Instead, write something like:
val = 0;
for(i=0; i <= 1.04; i += 0.05) {
// accumulate
val += i;
}
out = val;
val = 0;
for(i=0; i < 10; i += 1) {
if(val > 20) {
// bail early
break;
}
val += i;
}
out = val;
val = 0;
for(i=0; i < 10; i += 1) {
if(val == 6) {
// skip an iteration
continue;
}
val += i;
}
out = val;
When GenExpr code is compiled, 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. The validation process can track inputs even through function calls so
sample and nearest can be used within functions without issue.
In the first instance above, norm is syntactically a global variable while dim is syntactically
a function call. All of the coordinate operators follow this convention.
14/15
Technical Notes
GenExpr is a type-less language. Variables are given types automatically by the compiler
depending on the Gen domain and the Gen object’s inputs. 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. Note that GenExpr has no array notation [index] as there is currently no
notion of an array structure.
15/15