Conditional tests and branches in Csound seem to have caused some misapprehension—and just plain apprehension sometimes(!)—in myself and others, so I’ve been induced to do some investigation and dig into the sources, to really clarify how they function, and where any traps might be. With luck the following might help others too.
My first surprised realization was that there is no actual ‘if’ opcode
in Csound! (Check the output of “csound -z” if you doubt.) Instead there
are a bunch of ‘conditional gotos’, such as “cggoto”, that are immediately
substituted for the “if” form by the orchestra compiler before doing
anything else. (I don’t know if anybody ever uses these directly—
I’ve never seen it—but they’re perfectly good opcodes to include
in your orchestra if you want.) For example, “if (ia > ib) goto alabel”
is translated into “cggoto (ia>ib), alabel”. Similarly, “if ... igoto”
becomes “cigoto ...”, “if ... kgoto” becomes “ckgoto ...”, and so on.
The “if ... then” form is handled similarly, except that it needs an
auto-created label to be inserted at the next corresponding else or
endif, and the jump to that is taken if the condition is false.
(This causes a slight complication that we will return to later.)
The opcode here is “cngoto ...“
As you likely know, two action-lists are built from the opcodes, one
traversed at instrument initialization and the other at performance time.
The difference between the forms of conditional goto (and unconditional
gotos, for that matter) is which lists they appear in. cggoto and
cngoto are inserted in both lists, the others in just one or the other.
Straightforward, except that the condition expression is also effectively
its own opcode, that will get evaluated in one list or the other (apparently
never both) depending on whether it contains any k-variables or not.
If it does, the result will never be available at init-time, and is
therefore set to be false then. Consequently a cggoto, although it appears
in both lists, cannot actually ever jump at i-time if any k-variable is
referenced; all the opcodes always get initialized.
The complication with “if ... then”/cngoto is that the jump would
always be taken at i-time when a k-variable is involved, because it
jumps on false. So cngoto makes a special test for the type
of the expression, and if it is a p-time one will never jump at i-time.
So, if the above makes any sense, you can see that an “if ... goto”
or “if ... then” with an i-variable condition is pretty sure to behave
as you expect, whatever type of opcodes it controls: the condition is
evaluated at i-time and remains available unchanged throughout that
performance. All actions, i-rate, k-rate, and a-rate, will be invoked
or skipped in the same way. Using a k-variable expression instead is
also unlikely to surprise you: everything gets initialized, but only
the selected opcodes get performed. (Be careful if you include any
unconditional gotos in the controlled section, though. See below.)
The “if ... igoto” and “if ... kgoto” variants may need a little more
thought. “if ... kgoto” with a k-variable condition is obviously just
the same as an “if ... goto” with the same condition. With an i-variable
condition it is much the same, except you know that all initialization
will be done, regardless. The igoto form is much more likely to throw
you; it probably should only be used where all the opcodes it controls
are init-time only.
Here’s a little cautionary tale:
if (1 > 0) igoto skipi
prints "this i-time op should be skipped\\n"
printks "this should not be skipped... right?\\n", 1
skipi:
One might think that while the prints, being an i-time statement, would
be skipped, the printks—at p-time—would not. However, if you
run it, you will see nothing! The gotcha is that, like many opcodes,
the printks has an initialization part, which is in fact where the
string to be output is set up. When the igoto skips the initialization
it is left with nothing to display…
You could slip into the same sort of trap if you’re not careful with
gotos inside a conditional section. For instance, you should use
kgoto in a section controlled by a k-variable conditional; a plain
goto would cause some expected initializations to be skipped.
Conversely only igoto is correct inside an “if ... igoto” section.