Tutorial 6: Simple Math in Max

Performing calculations

This tutorial covers the basic mathematical operations that are available with Max objects, and the process of connecting up several objects into a calculation chain. We will also cover the common operations of objects with multiple inlets, and some issues with numeric conversion.

Common math operations, such as addition and multiplication, are important for manipulating incoming data into a range and form that is useful for our patches. Additionally, dealing with the difference between integer and floating-point numbers is critical when working with many types of data such as MIDI, audio and serial information.

Everybody loves Math

Look at the tutorial file 06mSimpleMath.maxpat. The top row of patches shows examples of the various simple mathematical operations that are available in Max. The left-most patch performs addition using the + (*plus*) object. The + object, like most math objects in Max, has two inlets – one for each operand. Numbers coming into the right inlet are stored, and added to numbers coming into the left inlet. If no number has yet come into the right inlet, the right operand is set to the object's *argument*; if no argument is supplied, the right operand defaults to .

When we click on the message box containing the number (on the left), the result (which is displayed in the connected number box) is revealed to be as well; since no number has come into the right inlet, the current stored operand remains . Clicking on the message box with the number produces no output, but *stores* the number as the new right-hand operand. When we click on the again, we see the additive result – – displayed in the number box.

The next patch uses an *argument* to the + object that sets an initial default value for the right-hand operand. Now, when we put a number into the + object (using the top number box), we see a result without having to send any number into the right inlet. Using default arguments for math operations is a common setup in many situations where you need to perform a constant mathematical operation (e.g. offsetting or scaling a number) on a stream of input values.

Next are the - (*minus*) and * (*multiply*) objects, which perform subtraction and multiplication, respectively. Like the + object, they have default operands of , and accept stored values in their right inlet and produces results based on values coming in the left inlet. The *divide* (/) and *modulo* (%) operations are also similar, but with one difference – in order to prevent unwanted "divide by zero" errors, their default right-hand operands are one ( ) rather than zero.

Inlets, Hot and Cold

As we’ve seen in the first set of math objects, some inlets cause an object to output a message, while others cause the object to simply store values. In fact, messages sent to *any* of the inlets cause these objects to store values; the difference is that values coming in the left-most inlet have an implicit message attached to them - this forces the math object to output the result of the calculation once the value is received and stored. We can see this in action by attaching a object to a math object, and using it to explicitly force output.

The first patch in the second row is a simple addition patch, but with a button connected to the left inlet. Sending numbers into the + object performs the expected result: numbers in the right inlet are stored and used as a right-hand operand, while numbers in the left inlet are used as left-hand operands *and* cause the object to output results. Clicking on the button also produces a result: the addition is performed with the current set of operands, and the result is sent out of the outlet. *This is useful when you want to change the right-hand operand, but want output without having to resend the left-hand operand*. It is also a good example of the message performing its "do it!" function.

Inlets that cause an object to output messages are often called *hot* inlets while inlets that only store information are called *cold* inlets. It is a common practice, when a cold inlet needs to produce output, to route a message (via a button or trigger object) into the left-most inlet to force output.

Type conversions through arguments

The next two parts of the patcher show what appear to be equivalent operations using the + object. Both of them purport to add together the numbers (in the right inlet) and (in the left). Try them both out (remember we need to click the right-hand message first to set the right operand).

The result of adding + objects, only the right-hand one produces the correct result. The left-hand + outputs . Why?

and should be . But between the two Like many programming languages, Max has different *types* of numeric data, and operations on that data can be done using integer or floating-point mathematics. By default, all of the objects that perform math operations in Max use integer-based arithmetic. When floating-point numbers are received by a math object in "integer" mode, they are *truncated* to integers before being used. Thus, becomes , and is converted to – which adds up to . In order to set a math object to use to floating-point arithmetic, *we need to provide an argument that is floating-point*. The right-hand patch does just that – it uses a argument to inform the + object that we want to perform floating-point arithmetic, and the addition results in the value that we were expecting.

Forcing a calculation

The final two patches in the second row show a simple mechanism for triggering output from the + object, regardless of which inlet receives messages. The left inlet is fed directly by a number box; since left inlets are already hot, they need no further attention. However, in order for the right inlet to produce output, we have to send a into the left inlet *after* the right inlet has received input. This is a perfect application for the trigger object, which is used to manage this function.

In both cases, the trigger object (abbreviated to t) has two outlets (defined by its arguments): the right outlet is type-specific ( for integer, for float), while the left ( ) sends out a . When a number is entered into the object, trigger processes the arguments in a *right-to-left* order, first sending the numeric value (integer or floating-point) into the right inlet of the + object. Then, a is sent to the left inlet of +, forcing calculation and output to the number box below. In this way, we can force both inlets of the + object to behave as though they are "hot", and receive output regardless of which inlet is sent messages.

Using cold inlets for recursion

At the bottom of the tutorial file is a curious little patch that has the result of a + object *feeding back into itself*. You can see what it happening by clicking on the top number box, entering a number, and then entering more numbers after that (you can also drag the number box to scroll values). As you enter numbers, the result keeps accumulating.

This is a case where we are taking advantage of the "cold" (right-hand) inlet of the + to change its right operand *based on the previous operation's output*. In this case, the result of the addition is fed back into the right-hand inlet, where it is stored as the right operand for the next addition operation. This results in an accumulation of the output, which can be handy for tracking a running total of incoming values. These types of recursive operations are useful for making counters, smoothing algorithms, and pieces of code where the system's behavior is dependent on previous outputs of the system.

Cascading math objects

All of the above examples have been pretty simple – they’ve been simple math operations with a simple result. However, you can chain several of these operations together to create more complex equation.

For example, let's say we wanted to make a Max patcher for converting temperatures: specifically, converting Fahrenheit to Celsius. The formula for this conversion is:

°C = (°F – 32) * (5/9)

From an object standpoint, it might be easier to simplify this to:

°C = (((°F – 32) * 5) / 9)

We can produce this output by using the -, *, and / objects chained together. Unlock the patch, and begin by using a number box for the Fahrenheit (°F) input. Connect this to a - object with an argument of as the default right-hand operand. Connect the output of the - object to a * object with as an argument, and connect it to a / object with as the argument. Finally, connect the output of the / object to another number box to see the result of the Fahrenheit to Celsius conversion.

Conclusion

The mathematical objects, used either singly or in a sequence, provide the tools necessary for manipulating numeric input in many ways. The math objects provide both hot and cold inlets, but simple patching operations can be used to force output on cold inlets. Math objects, by default, perform their computations using integer arithmetic; a floating-point argument can be provided to tell the object to perform arithmetic using real numbers.