Home Page

Functions And Keywords Page

Alphabetical Index

Next Topic

Previous Topic

Sample Programs

Updates And Corrections


Looping and Branching

CAL is what's called a "top down" language. This means that execution starts at the top of the code and flows down to the bottom without any chance of being directed back up or skipping down or running sub-routines. This lack of sub-routine and jump functions is annoying at times, but not insurmountable. For the record, there are some limited ways of branching out of the "top down" format, but very limited. Be that as it may, these meager branching options are enough to allow the needed flexibility to generate some very powerful programs. There are two effective looping and two branching functions. The loops are "forEachEvent" and "while", and the branches are "if" and "switch" Along with these functions we will discuss the do-nothing loop function "delay", the nesting control function "do" and the external program spawning function "include", which is the closest thing we have to a user friendly "gosub" function. Finely we will touch on the "DLL" function which is an "include"-type function that operates on the DLL programs that exist to support Windows and Windows applications. It is NOT user friendly.

Loops

Loops are chunks of code that keep being executed over and over until some condition tells them to stop. We have already touched on both "forEachEvent" and "while", but now is the time to get knee-deep into them.
 
(forEachEvent
Do you notice anything odd about this function - like it has no closing bracket? Actually it does, but as you may have noticed in the examples so far, it comes after all of the nested statements that make the loop worth invoking. I will present "while", "if", "switch" and "do" the same way. The closing bracket is implied but not shown. This is how it really looks to CAL:
 
(forEachEvent (function))
 
or more like you are used to seeing:
 
(forEachEvent
    (function)
)
The "forEachEvent" function can have only one function in its nest. In order to get things done, that function is often a compound function made up of nested functions. This compound function is usually a "do" or an "if" function with lots of nested functions like so:
 
(forEachEvent
    (do
         (somefunction)
         (anotherfunction)
         (morefunctions)
    )
)
The "do" tells CAL that even though it would expect only one function for this loop, there will be several instead. This is because the 3 functions are not nested together. Each stands alone. We therefore use "do" to create a nest for them all to share so that the "forEachEvent" loop still sees just one function. The same is done with an "if" function.
 
(forEachEvent
    (if (condition)
         (thenfunction)
         (elsefunction)
    )
)
Even though the "if" may have "thenfunction" statements and even "elsefunction" statements, the loop sees only one function in its nest; the "if". As you may already have gathered by now, this beast is the major workhorse of CAL. It takes the events in the selected part of the sequence (the area between "From" and "Thru" on the tracks you have highlighted) and runs each event through it's nested functions, one event at a time and in sequential order, until all of the events have been scanned. It then aborts and CAL continues with the next function after the loop's closing bracket. You can run as many "forEachEvent" loops as you want in a CAL program so long as you don't try to run one inside of another one. When this loop is running, CAL places the kind of event currently being serviced in the variable "Event.Kind", the event's starting time in "Event.Time" and MIDI channel in "Event.Chan". The event's parameters are placed in the other event variables as applies. Notes will have their parameters available in "Note.Key", "Note.Vel" and "Note.Dur" and so on for each type. Also during the running of this loop, the marker variables "From", "Thru" and "Now" are no longer responsive to any changes you might try to make to them. I'm not sure of just what the mechanics of this loop are as far as memory usage, but while it is running, if you insert a new event at an "Event.Time" higher than the spot the loop is currently at, CAL will not see the new event when it comes to the new event's time until the loop is over. This is likely because such changes are buffered someplace in memory during the running of the loop and don't actually become integrated into the sequence until the loop ends. If you were to run a second "forEachEvent" loop, the events inserted by the last loop would be available to this new loop.
 
(while (condition)
The "while" loop is very useful. It can be made to execute a nested function for as long as "condition" is true. You can run "while" loops within a "forEachEvent" loop, you can run "forEachEvent" loops within a "while" loop, you can even run "while" loops within other "while" loops. The "condition" can be any valid function or group of nested functions so long as it will resolve to a true or false result. There are so many uses for the "while" loop that I hardly know where to begin giving examples! We looked at one example when we demonstrated the "OR" function. Here's another:
 
(while (< looptime stoptime)
    (do
         (= newa (+ ref (/ (* percent change) 100)))
         (insert (+ notetime looptime) chan CONTROL 7 newa))
         (+= looptime gap)
         (= percent (/ (* looptime 100) stoptime))
    )
)
This may get a bit harry, but here goes. This is a simplified fragment from a program I wrote that performs automatic crossfades between two tracks at the start of each note so that part of the note voices from the first track and then switches to the voice on another track part way through the note. It then goes back to the first track as the note ends to be ready to do the whole thing again at the start of the next note. I have removed a lot of the contents of the loop and left just enough to hopefully get across my point. This "while" loop is within a "forEachEvent" loop so that it runs for each note in the selected part of the sequence. First we have the condition for the loop. So long as "looptime" is less than "stoptime", the loop will run. We use "do" to create a nest for the 4 functions that follow so that "while" will see only one function in its nest just like "forEachEvent" as discussed above. The first nested function calculates a value for "newa" by multiplying "percent" and "change", dividing the result by 100 then adding it to the offset "ref". This value will be the value of our volume controller event which we insert in the next line at time "notetime" plus "looptime". We then add "gap" to looptime to point to the place we want to insert the next volume event, and then calculate a new value for percent which is derived by seeing how far along we are in "looptime" relative to "stoptime". Now we go back and test the condition again to see if we can run another "while" loop or if the conditions have been met and we can abort the loop. When "looptime" fails to be smaller than "stoptime", the loop ends. It is important to keep in mind that if the "condition" is false the first time CAL looks at it, the "while" loop will never run at all. This is useful because there may be times when you will want to run the loop only if "condition" exists and bypass the loop if "condition" doesn't exist. By the way, you can use "while" loops to build on-line help systems for your CAL programs. Check out the section called Implementing On-Line Help In A CAL Program on the Tips, Techniques and Work-Arounds page for details.
 
(delay time)
This function doesn't do anything except suspend execution of the CAL program by the number of milliseconds (1000ths of a second) in the value "time". This is a way of making CAL stop what it's doing long enough for a human to read some message or react to a condition or perhaps it can be used as some kind of time keeping function causing some function to run every so often. Personally, I've never had many occasions to use it, but you might. As an example, let's say we wanted to scan a part of a sequence and give the user time to make notes of what's going on like this:
 
(forEachEvent
    (if (== Event.Kind NOTE)
         (do
              (message "Current Note Pitch Is " Note.Key)
              (delay 2000)
         )
    )
)
In this example we call a "forEachEvent" loop and use "if" to make sure we only look at NOTE events. We display on the status bar at the bottom of the screen the pitch number of the note we have found, delay for 2 seconds to give the user time to write this number down, then go on to the next event in the loop.

Branching Functions

Branching functions allow us to skip over some of the code that we don't need on that occasion and go directly to some code that we do need. The rules for using the two branching functions are to be strictly followed or your program can do some strange stuff!
 
(if (condition)
Here is our old friend, the "if" function. Thought we had seen enough of him already? You don't know the half of him yet! Let's look at the entire form of the "if" function:
 
(if (condition) (thenfunction) (elsefunction))
This form is just fine if the "thenfunction" and, if used, the optional "elsefunction" are simple and not made up of nested functions. Otherwise, the more readable form would be
 
(if (condition)
    (thenfunction)
    (elsefunction)
)
Needless to say, the "thenfunction" and "elsefunction" functions can be compound nested functions as well. Look at this monster "if" function:
1   (if (&& (== Event.Kind NOTE) (> Note.Key lownote))
2       (do
3          (if (!= Note.Key lastnote)
4              (do
5                 (if (< Note.Dur length) (= Note.Dur length))
6                 (++ count)
7              )
8              (if (>= count limit) (+ lownote offset))
9          )
10         (insert Event.Time Event.Chan NOTE lownote Note.Vel length)
11      )
12      (= count 0)
13  )
For the sake of this example, I have numbered the lines as well as coloring the brackets. I must also confess that this code fragment doesn't really do anything useful, I just made up a bunch of stuff in order to demonstrate the form of a compound "if" statement. The "if statement on line 1 has a condition made of the Boolean function "AND" and two relational functions, both of which must be true for the "if" function to execute. This "if" has a "then" part that runs from line 2 through line 11, and an "else" part made up of the assignment function on line 12. The "do" function starting on line 2 and closing on line 11 nests the two functions, "if" from line 3 to line 9 and the "insert" function on line 10. As it happens, the "if" on line 3 uses a "not equal" as its conditional, It nests a "then" part in the form of the "do" from line 4 to line 7 and an "else" part, the "if" function on line 8. The "do" on line 4 nests the "if" function on line 5 and the "increment" function on line 6. Both "if" functions on lines 5 and 8 are on one line in that they have only a short conditional statement, a single "then" function and no "else" part. Do you notice how the functions at the same indention point within an "if" function make up the "then" and "else" parts? Notice also that the functions at the same indention point within a "do" function are all separate functions that share the same nest. Can you see how the closing brackets of a compound function is always at the same indention point as the corresponding opening bracket that resides a few lines above? These are important conventions that make good programming habits and save hours of frustration if things need to be changed later on. Remember, the "then" part is mandatory, the "else" part is optional. You cannot technically have an "if" function with an "else" part and no "then" part. This is of little consequence in that an "if" statement's conditions can usually be rewritten to make it unnecessary. If for some reason you have built a condition so complex or wish to keep it readable from some particular point of view, and the action of the "if" function as written need only act upon events other than those filtered by the condition (in other words, the condition selects events that must be bypassed and everything else needs to have something done to it), then you can make a do-nothing "then" part using the relatively undocumented "NIL" keyword and have the "else" part do all of the work. The form might look something like this:
 
(if (&& (== Event.Kind NOTE) (|| (> Note.Vel 40) (>= Note.Dur 10)))
    NIL
    (delete)
)
In this example we set up a rather complicated condition that an event must be a note event and have either a velocity above 40 or a duration as long or longer than 10 ticks. If these conditions are true, we do nothing. For all other events and for notes that do not meet these conditions, we delete them. Notice that "NIL" is not in brackets. I don't know why this is so, it just works that way. You never need to put "NIL" in brackets, I guess because as far as CAL is concerned, it doesn't exist! Technically, we could rewrite the above condition so that the "then" part could hold the "delete" function like this:
 
(if (|| (!= Event.Kind NOTE) (&& (<= Note.Vel 40) (< Note.Dur 10))) (delete))
 
This condition says that if an event is either not a note or is a note that has both a velocity less than or equal to 40 and a duration less than 10, it will be deleted. Both statements do the same job, but the second one is simply better form. However, if for continuity reasons it behooves you to create the "if" condition in the first form, the "NIL" keyword is available to you.
 
(switch
The "switch" function is a very useful device so long as you understand how and when to use it. This is the full form as you would use it in a program:
 
(switch value
    1st_case (function)
    2nd_case (function)
    .
    .
    .
    last_case (function)
)
What "switch" does is allow you to select a chunk of code to run based on the contents of "value". The "case" parts are simply the various possible values that "value" can have, and the "function" part is what ever you wish to happen if "value" equals "case". CAL evaluates the "value" part, which may be a variable or a function or nested functions or anything you want to use. Whatever the outcome is, even if it's a TRUE or FALSE outcome, CAL will scan the list of "case" values until it finds a match and then run the "function" and only that function associated with it. Having done this, CAL exits the "switch" function (often referred to as a "switch tree") and continues with whatever follows the "switch" closing bracket. Of course, using a "switch" tree for a TRUE/FALSE "value" wouldn't make much sense because there are only two possible outcomes, and an "if" function is all you need to select a course of action based on two outcomes. Indeed, it makes no sense to use "switch" at all unless you wish to select from three or more possible outcomes of "value". As you might suspect, "function" can be a simple function or a compound function of any size, just so long as CAL sees only one function in each "case". All you need is a "do" to nest together everything you want to have run and you can place entire programs in each "case". In fact, that's one of the best purposes of a switch tree. See the section called Tips, Techniques And Work-Arounds under the sub-heading Switch Tree Menus for an example of a menu switch function. Another use for a "switch" tree is as an access matrix to an array. I suggest you examine the code of a CAL program I wrote called ANTILAP.CAL to take a look at an example of building and accessing an array using "switch".
 
(do
I know that we have seen this little fellow allot so far, but I think it deserves a formal introduction. The "do" function is basically a nest builder. (no, it doesn't chirp and no, it won't poop on your disk drive). What it will do is take multiple functions and nest them so they will fit into places where CAL expects to see only one function. This is why we start CAL programs with a "do". A program is simply one big function! The same is true for the functions that go within "if" functions, "while" and "forEachEvent" loops, indeed anywhere that has requirements for a single function but more needs to get done than will fit in one function. Here is a specific example:
 
(forEachEvent
    (do
         (somefunction)
         (somefunction)
         (somefunction)
         (anotherfunction)
         (soforth)
         (andsoforth)
    )
)
See how all of the functions that come after the "do" are grouped into one nest because the "forEachEvent" function can have but one function as an action. That one function is "do". Needless to say, any of the functions nested within the "do" can likewise be "do" functions with their own nested functions. Here is a place where you might think a "do" is needed but isn't:
 
(forEachEvent
    (if (== Event.Kind NOTE)
        (while (< Note.Key 64)
               (if (> Note.Vel 40)
                   (+ Note.Key 10)
                   (+ Note.Key 20)
               )
        )
        (delete)
    )
)
This is another useless code fragment, but proves a point. We don't need any "do" functions because the "forEachEvent" sees only one function, the "if". The "if" sees only one function, the "while" loop for its "then" part. The "while" loop sees only one function, the last "if". This "if" sees only one function for its "then" part and only one function for its "else" part. Afterwards, the first "if" has only one function, the "delete" for its "else" part. This is an extreme example, but illustrates that you must think about your "do" placements as each nested function takes up room in CAL and leaves less for calculations. If the nesting levels become too encumbered, it has been my experience that you can elicit an "evaluation stack overflow" error at some point from CAL. There is one qualifier to the above statements. I have had times when building multiple "if" functions, that on some rare occasions one of the nested "if" statements will not execute. I solved the problem just by adding an otherwise useless "do" and nested the problem "if" statement under it. I really didn't need it because there was technically only one function there. It seems that the "do" somehow sharpened CAL's view of the whole compound statement. Otherwise I can't explain why the "do" was necessary, it just worked! For a more detailed discussion of this problem, see the section Tips, Techniques And Work-Arounds under the sub-heading Hidden Traps In The CAL Editor.
 
(include "filename.cal")
The "include" function causes the CAL program named in quotes to be loaded and run at that point in the program containing the "include". After this spawned program ends, execution continues with the code that follows the "include" statement. It is as if the code in the "included" program had been pasted into the calling program in place of the "include" statement. A good example is:
 
(do
    (include "NEED20.CAL")
    (declare your variables)
    (and continue as usual)
At the very start of a CAL program, it's a good idea to "include" the small NEED20.CAL program supplied with Cakewalk that tests the version level of the CAL system the user is running and makes sure that it is sufficient for running the program. If the test in NEED20.CAL fails, the entire CAL system aborts and all execution stops. Therefore, NEED20.CAL doesn't need to return any information back to the calling program. Make sure you move NEED20.CAL from the Cakewalk directory to whatever directory you keep your CAL programs in so CAL can find it. Otherwise you will have to put the entire path in the quotes along with the file name and this makes your programs less portable. As a quick aside, some functions such as the new, advanced EDIT MENU functions cannot run on a Cakewalk version 3 system, and some of them may not run on versions 4 or 5 if they contain version 6 arguments. Keep this in mind, and if your program requires a more advanced CAL system than 2.0, you will have to call similar programs to test for "VERSION" being less than 31, 40 or 60 depending on the point where you wish the test to fail. Examples of these version testing programs are part of my CAL programs package (see the link to download the entire package of my CAL programs). You can also use "include" to add in to all of certain types of programs some feature they all should have in common such as correcting note overlaps or removing slurred notes or whatever. See the section Tips, Techniques And Work-Arounds under the sub-heading Building An Include Library for insight into using included programs.
 
(DLL "file_name" "service_name" first_argument second_argument)
The best thing I can tell you about this function is to stay away from it! Pretend it doesn't exist. Unless you are an experienced Windows programmer with all of the documentation you need to use DLL programs, this function is poison. In a nutshell, this function calls the DLL, or Dynamic Linked Library program "file_name" listed in quotes and requests execution of the service "service_name" within that DLL file. Both of these names must be in quotes. After the "service_name", you supply whatever arguments the service requires in order to carry out its function. The (DLL) function will only accept 2 arguments. If you leave out an argument you are likely to corrupt the system stack and thus likely crash your session of Windows, or at the very least make CAL and/or Cakewalk go south. If you supply more than 2, those extra arguments will pass garbage instead of the data in those extra arguments to the DLL. There is an example listed in the Help files, butupdate.gif (5157 bytes) I won't bother to show it or any other because if you need an example, you have no business fooling with it and if you are experienced enough to use it, I won't insult you with some piddly example. Enough said about the "DLL" function.
Updated Information: See the Updates page Helpful Tips for Using the DLL Function for a bit more scoop on using this function.
 
Next Topic Top Of Page