Monday, August 18, 2008

Timed Counting

Counting is a crucial part of music composition and performance. Music is full of instructions like "play this four times, then play that two times", or "play this chord for four measures, then play that chord for two measures", or "play seven notes in C major as fast as you can", or "the third time this happens, proceed to the next section". Counting is such a well-learned procedure, used dozens of times every day in all sorts of situations, that we generally don't think about how we do it. Well, let's think now about how we do it, or at least how we might get a computer to do it.

We usually start by saying the number 1, then go to the next number (i.e., the number that is 1 greater than the number we just said) and say it, and keep doing that till we've said the desired number.

Before we start counting, we already know a few things. We know the number we hope to reach eventually (let's call it the destination), we know the number we plan to count by (let's call it the increment; usually we count by ones, but sometimes we count by twos, or tens, or twenties, etc.), and we know the number we plan to start on (let's call it the startpoint; usually it's 1, or is the same as the increment, such as 2 if we're counting by twos). We'll call the number we're currently on (the number that we're saying) the count. So, using those terms we've just defined, the process of counting is:
1) Start with the count at the startpoint.
2) State the count.
3) Check to see if the count has equalled or exceeded the destination. If not, add increment to the count and go back to step 2; otherwise,
4) Report that we're done, and stop.

Here's an example. Let's say the starting point is 1, the increment is 1, and the destination is 4. We start with the count equal to 1 (startingpoint). We say the count (1). We check mentally to see if we've reached the destination (4) yet. Nope. So we add the increment (1) to the count to get 2, and say the count (2). Are we at 4 yet? No. Add increment again to get 3, and say it. Are we at 4? No. Add increment again to get 4 and say it. Are we at 4? Yes. So we report that we're done, and stop.

That's a pretty clearly defined process, so if we provide a computer with those three values (startingpoint, increment, and destination) we should be able to write a program that counts. We're using a very simple case in which we start at 1 and count upwards by ones until we get to some number greater than 1. We'll eventually want to handle more complicated cases, in which startpoint, increment, and destination can be any number at all, but we'll leave that for another lesson. For now we'll assume that startpoint is always less than or equal to destination, and that our increment is always 1; in other words, we're always counting upward by ones to reach the destination.

The process for programming a computer to count from startpoint to destination by increment is essentially this:
1) Set the startpoint, increment, and destination values.
2) Set one other value, the count, to be equal to startpoint.
3) Report the current value of the count.
4) Check "Is the count is equal to or greater than the destination?"
5) If not, add increment to the count, and go back to step 3.
6) If so, report completion, and stop.

In musical situations, we usually want to count at a specific rate. That is, we want to wait a certain period of time between each statement of the count. So, to include this idea of rate in our program, we should modify step 5 to read, "If not, add increment to the count, wait a certain period of time, and go back to step 3." In other words, each time we state the count, if we decide that further counting is needed we plan (schedule) another count for some time in the future. (As you recall, that's what the Max metro object actually does. It reports a bang, and schedules the next report to occur a certain interval in the future.)

This program shows three ways to implement timed counting in Max. (There is almost always more than one reasonable way to do a task in Max.)

The first way is designed to correspond to the above description of how to write a counting program. The second way is the same, but uses the metro object's scheduling capability instead of explicitly scheduling a future event with the delay object. The third way also uses metro, but uses the counter object to keep track of the minimum and maximum values and the incrementing of the count.

In all three cases, the starting value has been set to 0, the ending value has been set to 10, the increment is 1, and the time interval is 1000. Why has the starting value been set to 0 instead of 1? This might be a good time to discuss a couple of details of counting.

By convention, most computer counting starts with 0 rather than 1. There are some good reasons why this is so, but it can lead to some confusion between human counting and computer counting. For example, almost all programming languages number a collection of items starting with 0, such that the 1st item is called item 0, the 2nd item is item 1, and so on. Whether we particularly like it or not, or think it's sensible or not, that's just the way programmers count.

An instance of this in non-digital life is the difference between the way that people in China and the U.S. count age. In the U.S., you say your age is 10 after you have completed 10 full years of life (even though at that point you will have entered your 11th year). In China you say your age is 10 when you enter your 10th year (even though at the beginning of your 10th year you have only lived for 9 years). Because the thing we're counting (years) takes a nonzero amount of time (1 year), it matters whether we count the thing at the beginning of that period of time or at the end of it.

You might also think of it as the difference between measuring an amount of something or counting a number of somethings. If we're measuring the amount of time lived, it's clear that a person has lived 0 years at birth and has not reached the number 1 until s/he has lived 1 full year. If we're counting which year of life the person is in, then s/he is in year 1 from birth, and is in year 2 as soon as the 1st year has been completed.

A ruler (measuring stick) starts with the number 0, even though it's not usually written on the ruler. The number 1 appears 1 unit away from the end, not at the end.

If we count one number per second, from 1 to 10, we will have said ten numbers by the time only 9 seconds have elapsed because we said the number 1 at time 0. That is, the difference between where we started (1) and where we end (10) is only 9 (10-1=9).

It's up to you to decide whether it's more practical for your purposes to write your program so that it starts counting at 0 or 1 (or anything else, for that matter). In the case of timed counting, it probably depends on whether you're counting at the beginning of the time interval or the end of it. It might also depend on whether you're counting how many of something have occurred, or whether you're measuring how much of something has elapsed. This discussion has just been to point out that you should be aware of what numbering system is most practical for what you're counting, and whether you're counting at the beginning of a unit of time or at the end of it.

In these examples, we start counting from 0 because that is "time 0" as far as the start of the program is concerned. The number 10 is thus reached after 10 seconds have elapsed, even though it is the 11th digit counted. In timed counting, counting from 0 makes sense because it reports the elapsed number of units of time--beats, seconds, or whatever units we're counting. Starting from 0 is also practical because it's the way that Max identifies items in an array or a menu. The bang at the end (when 10 seconds have elapsed) can be useful to trigger some other process.

In the first program, the 0 in the message box sets the count to the desired starting point. The i object is the counter. The select 10 object sets the maximum and reports when it has been reached. The + 1 object is the increment, adding 1 to the counter after each report and storing it back in the right inlet of the i object. The delay 1000 object schedules the next count for the future. When the maximum is reached, the select object triggers the led to flash, and also triggers a stop message to the delay object, which cancels its scheduled output, thus stopping the program.

The second program is almost exactly the same, except that the scheduling of each next event is done within the metro object. When the maximum is reached, select triggers a 0 to turn off the toggle which turns off the metro (which cancels its next scheduled output).

The third program is similar to the second, but the counter 0 10 object takes care of setting the minimum and maximum values of the count, performs the incrementing process internally each time it gets a bang at its left inlet, and reports a 1 out its third outlet when it reaches its maximum. We just need to look for that 1 (the maximum-has-been-reached indicator), and use it to trigger a bang and stop the metro.

No comments: