Ifs, buts, and gotos—a short essay on conditionals

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.