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.
-