Home Page Functions And Keywords Page Alphabetical Index Next Topic Previous Topic Sample Programs Updates And Corrections

Relational Functions

Relational functions have nothing to do with the mating habits of CAL programs (disappointed? Sorry). What these functions do is test arguments for there relative values by making comparisons. I'm sure once you've seen them, they'll make sense. Using these functions doesn't change the values of any variables and doesn't result in a numerical result (well, it really does, but we don't care what that number result is). The outcome of running relational functions is either TRUE or FALSE, and as far as we care, this is all we need to know about their output. Just for the record, the numerical result for a condition being FALSE is zero and for being TRUE is any other number, usually 1. Like the math functions, these functions do not stand alone but make up the condition statements of "if" and "while" functions. In order for CAL to make a decision, it needs to test some condition and plan its course of action based on the outcome. We'll see lots of examples of these statements, as conditional tests make up the heart of many CAL programs.

Comparison Functions

This first group make comparisons between two arguments and offer a result. One argument can be compared as being equal to the other, not equal to the other, larger than the other, smaller than the other or some combination.

(== variableA variableB)
This function tests for equality between two variables or a variable and a constant. Either or both variables could also be functions instead. The point is that the result will be true if the two arguments are equal to each other and false if they are not. Notice that the keyword is made up of two equal signs, and even though you may not be able to tell by viewing this statement on your browser, there is to be no space between them. If you remember from an earlier part of this document, there can be some confusion between the use of a single equal sign that means "set variableA equal to the value of variableB" and these double equal signs that mean "test to see if variableA is equal to variableB". In the first case, "variableA" has its value reassigned and in the second case nothing changes and only a condition is tested for. Using such a statement with an "if" function is the most common form. Here is an example:

(forEachEvent
(if (== Event.Kind NOTE)
(+= Note.Vel 10)
)
)
In this example we compare the value CAL has provided us in "Event.Kind" with the constant "NOTE" to see if the current event in our "forEachEvent" loop is a note. If it is, then the nested function that makes up the "THEN" part of our "if" function is carried out. In this case, the note velocity is increased by 10. If the event had not been a note, the "if" condition would have failed and no action would have been taken on the event.

(!= variableA variableB)
This is the opposite of the above function. This tests for the two variables being unequal. The result is true if the values of the variables are different and false if they are equal. As always, we could use a constant for one of the variables and/or nested functions for one or both of them. Here is an example of using "!=".

(forEachEvent
(if (== Event.Kind CONTROL)
(if (!= Control.Num 7)
(delete)
)
)
)
Here we test each event during a "forEachEvent" loop to find only controller events. Having found one, we test to see if the event is not a volume controller (controller number 7) and if it isn't, we delete it. When we look at the Boolean functions, I'll show you how to make this same program fragment more efficient by combining the test for controller events and test for the controller not being number 7 into a single "if" statement.

(< variableA variableB)
As you might guess, this statement tests for "variableA" being less than "variableB". We might use this function to segregate items or variables as in this example:

(if (< Note.Key 64)
(+= Note.Vel 10)
)
This little fragment tests to see if the current note event has a pitch less than 64, or E3 in MIDI note form, and if so raises the note's velocity by 10. The usual rules for using constants and functions in place of variables apply.

(<= variableA variableB)
A variation on the above function, this one results true if "variableA" is less than or equal to "variableB". If we had to do this in long hand as part of an "if" function, it would look like this:

(if (|| (< variableA variableB) (== variableA variableB))
(somefunction)
)
I have used the Boolean "or" function to create the long hand version. This statement reads "if either variableA is less than variableB or variableA equals variableB then return a result of TRUE and run (somefunction). As you can see, the "<=" shorthand is superior. The same "if" statement above now reads:

(if (<= variableA variableB)
(somefunction)
)
which is much easier to read and work with.

(> variableA variableB)
As the obvious complement to the above "<" function, this one tests for "variableA" being greater than "variableB".

(>= variableA variableB)
We finish this group with the "greater than or equal" function. It works like the others displayed above.

Boolean Functions

The two Boolean functions derive from classical logic. The two operator keywords CAL uses are logical AND and logical OR. In the academic world, these operators are associated with a "truth table" which describes the outcome of applying Boolean logic to a set of conditions. I'll save us the trouble by saying it in simple English.

(&& condition1 condition2)
This is the logical AND function. As with the other relational functions, the Boolean functions are not meant to stand alone but are to be used as conditions for "if" and "while" functions. Here, "condition1" and "condition2" apply to any statement that produces an outcome. Although it is possible to use a variable alone as a condition, in that its very existence defines an outcome, the most practical application is to make the "conditions" relational functions. First, let me explain what logical AND means. Simply put, the outcome of the "AND" function will be true if and only if BOTH "condition1" and "condition2" are true. Here is an example.
(if (&& (== Note.Key basenote) ( > Note.Vel minlevel))
(insert Event.Time Event.Chan NOTE (+ Note.Key 12) Note.Vel Note.Dur)
)
With this example, we test to see if BOTH the current note pitch is equal to a pitch number stored in "basenote" AND the note has a velocity greater than the value stored in "minlevel". If BOTH of these conditions are met, then we insert a new note at the current note's event time and on the current note's channel with the current note's velocity and duration, but one octave higher in pitch. If either of the conditions had been false, the entire "if" statement would have been false and thus, no new note would be inserted. As promised, I will now show how to make more efficient conditional statements using Boolean functions. If you recall earlier we had an example that went like this:

(forEachEvent
(if (== Event.Kind CONTROL)
(if (!= Control.Num 7)
(delete)
)
)
)
We can put both conditions in one statement using "AND" like so:

(forEachEvent
(if (&& (== Event.Kind CONTROL) (!= Control.Num 7))
(delete)
)
)
This makes the code a bit more compact and probably speeds up the execution process slightly. In an earlier section, I alluded to the idea of stacking Boolean statements. I will repeat the example here for context, including color-coding the brackets.

(if (&& (&& (&& (== Event.Kind NOTE) (> Note.Vel 80)) (!= Note.Dur 0)) (> Note.Key 64))
(somefunction)
)
Take a close look at how the brackets are paired up. To make it more clear, you can resolve the statement down into letter symbols to examine the syntax:

(if (&1(&2(&3 (e) (f)) (g)) (h))

The most "nested" function, the "&3" function, has (e) and (f) for arguments and so there are brackets (&3 (e) (f)) enclosing the &3 function. The next "&2" nested function has the entire "&3" function as one of its components but ends with (g) and so it has brackets (&2 (the &3 function) (g)). The most outside conditional function goes from "&1" to (h) and so is enclosed (&1 (the &2 function) (h)). As long as you remember that each complete function needs its own set of brackets and that no one set offers an excuse to leave out any other set, you'll be OK. There is a limit as to how many individual functions you can stack in a single statement like this. At some point, CAL will give you an "evaluation stack overflow" error if you get too carried away.
(|| condition1 condition2)
The other Boolean function is the logical OR. For this statement to be true, EITHER of the conditions or both can be true. The OR functions resolves to FALSE only if both conditions are false. You can think of OR as the inverse of AND. Here is an example of an OR function:

(while (|| (< count 64) (< time Event.Time))
(do
(insert time Event.Chan CONTROL 7 count)
(++ time)
(++ count)
)
)
Ah, the "while" loop function! I will go into how the "while" loop works in the next section, but for now understand that a "while" loop executes it's nested functions until the conditional statement becomes false. At that time, the loop becomes unstuck and the program continues with whatever comes after the "while" loop's closing bracket. In this example, the while loop has a Boolean OR function as its conditional statement. It says that for as long as either the value of "count" is less than 64 OR "time" is less than Event.Time, keep inserting volume events and incrementing both "count" and "time". At some point, "count" will become greater than 64 and "time" will exceed Event.Time, then the OR condition will be false and the loop will abort. This program fragment doesn't have much particle use, but serves as an example only. For the record, you can stack "OR" functions just like we did with the "AND" functions. In fact, you can mix "AND" and "OR" functions in the same stack.

 Next Topic Top Of Page