Debugging is...
“the process of finding and resolving bugs (defects or problems that prevent correct operation) within computer programs, software, or systems.”
But it can also be thought of as...
a process of identifying
—Rebecca Hill,
Debugging Specialist
JavaScript Developer
WeTransfer.com
In MaxMSP, this can also be extended to...
a process of identifying
which object boxes and patch cables
In MaxMSP, this can also be extended to...
a process of identifying
which object boxes and patch cables
are carrying specific signal and message values
In MaxMSP, this can also be extended to...
a process of identifying
which object boxes and patch cables
are carrying specific signal and message values
at any point in time.
To understand this process better,
Let's first discuss the order of messages in Max.
By now, you probably realize that data flows top-to-bottom in your patch:
But for object boxes that have more than one inlet or outlet,
data also flows right-to-left.
When we design our own patches,
most mistakes (“bugs”) happen
because we forget the message order!
To help us remember messaging order,
look closely at the colors of an object’s inlets...
Hover your mouse over the right inlet.
Notice it is blue.
The blue inlet indicates
that this is a cold inlet
because it is on the right.
Cold inlets do not trigger output from an object box.
Instead, they replace the argument(s) of the function typed inside the box.
For example, if we type a new number in the integer box, it will replace the number 4,
which is the argument to the + function.
...But, curiously, it will not trigger output from the + function...
If we set up the patch above,
the metro object will generate a new random number between 0-100 every 200 miliseconds.
But no matter how many numbers we generate,
we will never get output from the + function...
because the numbers are only feeding its cold inlet.
Next, hover your mouse over the left inlet.
Notice it is red.
The red inlet indicates
that this is a hot inlet
because it is on the left.
In this case, the hot inlet sets the left operand to the + function,
and most importantly, it triggers output from the object box.
In MaxMSP, the left inlet always triggers output.
In other words, output is triggered any time an input is delivered to a left inlet.
If we set up the patch above, with a new random number process feeding the hot inlet,
the metro on the left will generate a random number every 50 miliseconds.
That’s much faster than the cold inlet, which only receives a new number every 200 miliseconds.
The numbers received in the hot inlet will always trigger output,
in other words, every 50 miliseconds,
While the numbers on the right update much more slowly.
This snapshot of the patch above represents a discrete moment in time.
At this moment, the cold cable is carrying a value of 23,
while the hot cable is carrying a value of 77.
At this very moment, this produces an output of 100.
Because these streams of numbers move at different speeds,
we know that each number feeding the hot inlet
will be added to the same number (in the cold inlet) many times over,
before a new number replaces the argument in the cold inlet.
We can prove this by using the Max console to monitor our stream of numbers.
The Max console allows us to view many successive snapshots,
that is, many individual moments...
in order to monitor what changes over time.
First, if you don't see it, open your Max console by choosing Window > Max Console.
In our patch, we can use a print object to monitor the values
flowing through our patch cables
each time a hot value triggers output.
Notice that the pack object also has hot and cold inlets.
We created 2 arguments for the pack function by typing f f
which resulted in 2 inlets.
Each f represents a floating-point value to be received in an inlet.
Now, take a look at your Max console.
We see that the values on the left are indeed changing faster than the values on the right.
Using the Max console with print statements, like this,
allows us to see things we cannot easily see if we are only looking at our patch.
For example, remember this simple algorithm we created?
Here, we can only see the first number, at the top, and the last number, at the bottom.
But, what can’t we see in this view?
Answer: We cannot see the inputs and outputs for the objects in the middle...
that is, the [* 5.] object, and the [- 8] object.
But what if we needed to monitor each of these steps?
Let’s create some example data to test this algorithm...
Here, we enter the number 13 in the topmost flonum object and press enter.
If we check using a calculator, we can be sure 57 is the expected result.
Now, let’s test a decimal input value. If we run 13.01 at the top...
We would expect a floating-point result...
And yet, we still get a 57!
If we use our calculator, we know to expect 57.05 instead.
What’s wrong?!
This is pure chaos...
What is this world we’re living in??
I feel sick...
Answer: We found a bug in our code.
So... Let’s fix it...
Are you ready to debug this bug?
Our procedure is really simple, in fact...
Just set up 2 print statements
and label them 1st: and 2nd:
so we can easily see what is happening at each step...
Now, look at your Max console...
Here, we can see the problem is clearly at the 2nd step:
At the 2nd step, our value changes from a floating-point to an integer.
But again, we expect these to be floating-point values.
So, what is going at our 2nd stage?
Upon closer inspection of the [- 8] object...
We have localized our bug!
Answer: We forgot a dot (.) after the argument.
Without this dot (.), the [- 8] function will only return integer values instead of floats.
So, we just need to change [- 8] ... to [- 8.].
And when we update our patch,
and rerun the input value of 13.01, voilà! —
We have the correct and expected result: an output of 57.05!
Using multiple print statements in combination with our Max console
teaches us to be systematic and logical about our coding practice
in order to obtain the results we need...
and in order to remain in control of our programs.
We can further use print statements
to verify MaxMSP’s sophisticated messaging order structure:
Data flows right-to-left,
But also from top-to-bottom...
Notice how it works in columns first, followed by rows...
First, print statements 2nd, 3rd, and 4th are triggered in one column on the far right.
Then, statements 5th, 6th, and 7th are triggered in the middle.
Finally, statements 8th, 9th, and 10th are triggered on the left.
So, the messaging order is:
Starting on the right, move top-to-bottom, then one move to the left,
then top-to-bottom again, followed by another move to the left,
and finally, top-to-bottom one last time.
Keeping track of this messaging order may seem redundant, basic, or even boring,
and to be sure, you do not need to do this all the time!
But, knowing how to do it will save you many hours...
...searching for bugs!
Signal cables carry extremely fast streams of values.
And each MSP object can be imagined as its own “island” of C code.
Think of signal cables like a garden hose
carrying a continuous flow, or stream of values.
Unlike Max’s controle rate message cables,
which may only occasionally carry values,
MSP’s signal cables always have signal values moving quickly through them.
Like the garden hose, these cables are either on or off;
When they are off, nothing is happening,
but when they are on, sample values are moving in a quick flow.
The short answer is:
You don’t have to think about the messaging order of signal cables.
Message order is related to the slower control rate:
the “Max” side of MaxMSP.
This means: only the values traveling through the gray control rate cables.
What holds the signal cables and MSP objects together in synchronization is different...
This is controlled by the Max scheduler and a number of parameters set in the Audio Status menu.
We will look at these items together another time.
MaxMSP also includes more sophisticated tools to monitor message and signal values...
For example, find the Debug menu,
select Event Probe
and Signal Probe
so that a check mark appears to the left of each.
Now, when you hover your cursor above a gray patch cable,
you can monitor rapdily-changing values traveling at message rate.
Move your cursor above other patch cables to observe different parts of your process.
This way, you can be sure each step is doing what you think it is...
You can do the same thing with signal cables!
Even more sophisticated is the concept of Watchpoints.
Watchpoints allow you to monitor many cables at the same time...
They also allow you to create breakpoints in the messaging flow.
This allows you to stop time and monitor each step at any instant.
To create a Watchpoint, first select a patch cable.
Then select Debug > Add Watchpoint — Print.
Your watchoint has a blue number and each value prints to the Max Console.
That's a fairly basic use of Watchpoints, though...
If, instead, we choose Debug > Add Watchpoint — Monitor,
And choose Debug > Enable Debugging...
we can create easily-identifiable Watchpoints
for many cables at the same time,
allowing us to monitor the flow of data from one object box to the next.
Here, we have created Watchpoints on each of the 2 patch cables.
Now, select Debug > Watchpoints...
The Watchpoints window pops up,
displaying the monitoring status of each of our numbered watchpoints, 1 and 2.
Now, suppose we want to stop the flow of time and monitor
a single transaction of values from one object box to the next.
Choose one of the cables, and select break or break/watch from the list (red arrow above)...
Now, we see both the Debug Window and the Watchpoints window.
And we can monitor the current value being passed along this patch cable.
We can even see the values current being held in the other cables as well.
If everything looks good,
Press the Continue button,
which advances to the next value, and see if the problem is there...
If something is not working in your patch,
use this method to see if the bug exists between your patch cables.
This will help you isolate your bug!
By understanding how data flows in MaxMSP, we can more rapidly debug our work.
Today, we reviewed:
MaxMSP includes tools to help us debug our work:
These tools help us answer some essential questions:
They help us identify...
In other words, they help us identify...
And this is the core of debugging.
Happy debugging!