Tutorial 19: Timing
In this tutorial, we will be working with time – objects that deal with time, and a set of objects that work with a metrical timing system built into Max. Additionally, we will look at some decision-making objects that use logic operations to determine program flow.
As we’ve seen in earlier tutorials, the use of time is an important part of working with Max programs; time-based objects like metro can help create generative music and drawing actions, and can be used in conjunction with user actions to create playable programs. The transport objects in Max provide a centralized musical timing system that can provide play/stop controls, bar/beat feedback and can even cause events to fire at specific points in time. All of this is built on the ability to define specific times and time durations in metrically-significant ways.
In addition to the time functions, we also cover some of the Boolean comparison objects – objects that create logical results based on comparing and testing input values. These are especially useful when working with time-based objects, since you will often want to make operational decisions based on comparisons between the current time and some desired time or sub-time function.
To open the tutorial patch, click on the green Open Tutorial button in the upper right-hand corner of the documentation window.
Some new objects...
The left-hand side of the tutorial has five small patches that show the operation of a handful of new objects that deal with time in Max. Patch 1 provides an example of the pipe object, which can be used to delay the passage of any message or list for a certain amount of time. The argument provides the default delay time – in this case, milliseconds (1 second). If you change the value of the left number box, you will see your changes reflected in the output 1 second later. The delay time can be changed with a value sent to the right inlet – an integer value is used to set the number of milliseconds of delay time. Change the right-hand number box to , then change the left input – you will now see the changes occur after a 2 second delay.
The next small patch, labeled 2, is an example of the delay object. This is very similar to the pipe object, except that it is specifically tailored to delay messages. Like pipe, the delay time can be changed by sending an integer value into the right inlet. Both the delay and pipe objects will respond to a message to abort pending output.
Patch 3 shows the clocker object, which is closely related to the metro object. The primary difference between clocker and metro is the output: while the metro object produces messages at regular intervals, clocker outputs the overall elapsed time (in milliseconds) since it was started with a . Clicking on the message will stop the clocker, while sending a new message to clocker will restart the timer and the elapsed time measurement.
The patch labeled 4 demonstrates another object that counts elapsed time: the timer object. This object takes messages in both inlets; the left inlet starts the interval to be timed, and the right inlet stops the interval. The timer object is therefore equivalent to a stopwatch; click the left-hand button to start the clock, wait a few seconds, and then click the right-hand button to see how much time has elapsed. Note that timer is a slightly unusual Max object in that its right inlet is hot (produces output), not its left inlet.
The fifth patch shows some of the most common comparison objects in action. The > (greater-than) object outputs a if the incoming number is greater than the number used as an argument, or a if it is not; the object therefore performs a test, reporting TRUE ( ) or FALSE ( ). The < (less-than) object is the inverse, reporting true or false ( or ) depending on whether the input is lower than the object's argument. The use of or to indicate true or false are in common use across programming languages, providing the foundation of Boolean operations. The remaining logical objects will probably be more familiar to programmers. The == (equals) object reports a only when the incoming number is equal to the argument, while the != (not equals) is its inverse – it reports a when the incoming number is not equal to the argument. As with many Max objects, a new comparison operator can be supplied by a number sent into the right inlet of the object.
The final two logical operators are && (logical AND) and || (logical OR). In the first case, the && object will output a only if both numbers sent into the left and right inlets are non-zero; otherwise, it reports a . In the second case, the || object will output a when either the left or the right inlets are sent non-zero values. If both are , this object will output a .
Change the number box at the top of the patch containing these objects and you'll see how they behave. Notice how the only time all six operators yield a is when the input value is .
Metrical Timing, Part 1
The large patch on the right (labeled 6) is a performance patch that uses all of these new objects (and some old ones) within the context of the metrical timing system. Start the patch (using the large yellow toggle at the top); you will hear a steady beat out of your default MIDI synthesizer, with variations that occur at different times throughout the performance. The performance is looped, rerunning itself every 16 measures. In order to understand this rather complex patch, we first need to understand the metrical time system.
Metrical timing is based on a central time-keeping object – the transport – and its ability to broadcast time information to other objects that are expecting timing information. Many of these “listening” objects are already familiar to us (metro) or have been introduced in this tutorial (clocker). We have used these objects in self-clocking mode, where they use a timebase expressed in milliseconds to produce output at regular times. When used in conjunction with the transport object, however, time is expressed using metrical notation: values such as (for a quarter-note) can be used as an alternative to millisecond timing notation.
The transport object is the central time keeper, and provides time in bar/beat/tick format – all driven by a tempo and a time signature. Hence, if a transport is set to run with a time signature of and at a tempo of beats-per-minute, each beat will take 500 milliseconds, and a measure will take 2 seconds (2000 milliseconds). In order to see all the ways the transport controls this patch, we need to break it down into sections.
Our “big checkbox” starts the process – mainly because it starts the transport. The transport accepts a / message for start and stop, which is perfect for control by a toggle. The toggle also starts the metro to the left (part of the “red” section of the patch), which uses as the timing argument. In the past, we’ve used milliseconds for the metro object's timing, but in this case, the notation means that it will send out a every quarter note, based on the timing broadcast by the transport. The output of this metro goes to two places: to a message box that creates a MIDI note (via makenote), and back to the transport. Why does it get routed to the transport? Because a message received by the transport will cause it to output the current time, which we display in three number boxes connected to the first three outlets of the object. This time is displayed in bars, beats and ticks, respectively. There are ticks per beat (480 pulses-per-quarter-note, or ppq, by default).
Next, let’s look at the “green” section of the patch. This uses the bar count (as output by the transport), and uses the > and < objects to determine if we are between bars and . If we are in that range, it turns on another metro that is clocked at (16th notes). This generates a random number used to create one of four values (using the random and select objects); these numbers are sent to the makenote object to generate MIDI notes. That value is also sent to a pipe object with an argument of , which delays the values by a dotted-quarter note. This output has added to it, causing output that, when sent to the MIDI synthesizer, sounds a perfect fifth above the original. The result is a rapid-fire set of notes for a four-bar segment of the loop.
Let’s now dig into the “blue” section, which is the part of the patch that forces the sequence to loop. The head of this section of the patch is a new object: timepoint. The timepoint object takes a metrical time as an argument; its sole purpose is to generate a message when that metrical time is reached by the transport. In this case, when we reach the beginning of the measure (i.e. after sixteen measures), a four-note chord of numbers is sent to the makenote, then a message is sent all the way back to a message connected to the right inlet of the transport. This message ( ) tells the transport to immediately jump to tick of the sequence, i.e. bar 1, beat 1 and tick 0. This is how we force the sequence to loop: every time we hit the 17th bar, we are immediately sent back to the beginning.
Metrical Timing, Part 2
Next, we should decode the “brown” section of the patch. It uses key and select objects to watch for the space bar (ASCII ) being depressed. When this happens, a timer object receives a on both inlets. Because Max objects transmit messages in right-to-left order, hitting the space bar simultaneously stops the timer with a to the right (and outputs the elapsed time) and restarts it for another measurement with a to the left. The object outputs the elapsed time in milliseconds; we divide (the number of milliseconds in a minute) by the time to get a bpm (beats-per-minute) value.
However, we want this bpm value to fit in a sensible range, so we use the split object, and only use values that are between And . If we are within this range, split will output the number from its left outlet, and the value gets used to create a message that is sent to the transport. This will immediately change the playback tempo of the sequence, and will change the relative speeds of many (but not all) of the objects used to create our sequence. If the timer outputs a value outside of the split object's range (for example, if we haven't hit the space bar in a while), it will be sent out the right outlet of the object and discarded.
One of the reasons that not all of the sequence is changed is because some of the patch functions use standard (millisecond) timing rather than metrical time. The right-most (“blue-and-purple”) section of the patch is a good example. A timepoint object is fired when the transport time is at bar , beat . This starts a clocker that runs for seconds (10,000 milliseconds), producing a two-note chord every quarter-note ( ). The timepoint object also sends a to a delay object, which produces a two-note chord milliseconds after it receives the message. At a very slow tempo, this may occur only a few beats after the timepoint is fired, while at rapid tempo, if may take many bars. This is a good example of how metrical and standard time can be mixed and matched for interesting generative results. Note that the clocker object's running time is compared against a > object to switch itself off after 10 seconds... a timer object tracking the interval between the start and stop of the clocker object confirms this.
The Importance of Overdrive
One factor that could cause variant behavior in this patch is the Overdrive setting, found in the Options menu. If Overdrive is checked (on), event processing and calculations are given priority over screen-drawing and graphical processes. When Overdrive is unchecked (off), drawing processes get the same priority as event handling. Depending on the nature of your patch, Overdrive may provide better performance for time-critical event handling tasks.
Since our tutorial patch is, in fact, time-critical, it will perform best with Overdrive turned on. If Overdrive is off, you may see that the transport output display doesn’t always occur on thetick, and you may find that playback gets jerky when the density of notes is high or you perform other tasks on your computer (such as opening another patcher in Max). Modern computers are incredibly fast, but our ears are very sensitive to timing discontinuities. Therefore, when working on patches that sequence events in a time-critical way, it is normally advisable to turn Overdrive on.
In this tutorial, we covered an extensive new system within Max: the metrical timing system. This provides a centralized timing control for your Max patch, coordinating the generation of events and easing the creation of musical sequences. We also learned about several new time-based object, including pipe, delay, clocker, and timer, which can be used (either with or without metrical timing) to create sequenced actions. Finally, we saw the use of boolean comparison and logical operator objects to aid in decision making within a complex patch.
|Timing and Sequencing Events||Timing and Sequencing Events|
|pipe||Delay numbers, lists or symbols|
|delay||Delay a bang|
|clocker||Report elapsed time, at regular intervals|
|timer||Report elapsed time between two events|
|>||Compare numbers for greater than condition|
|<||Compare numbers for less than condition|
|==||Compare numbers for equal-to condition|
|!=||Compare numbers for not-equal-to condition|
|&&||Perform a logical AND|
|||||Perform a logical OR|
|transport||Control a master clock|
|timepoint||Bang at a specific time|
|split||Look for a range of numbers|