Logic and Flow Control

The code for many of your Csound instruments will be structured in a fairly straightforward way. During each k-period, Csound will run through the instrument code from top to bottom, one line at a time, cycling through the code over and over for as long as the instrument is active. The sound may change radically while this is going on, but the process of running through the code from top to bottom will always be the same.

With some instruments, however, you’ll undoubtedly want the order in which the lines of code are executed to change depending on some condition or other. This is a very standard requirement for computer programming languages—to test whether a certain condition is true or false, and to do something different when it’s true than when it’s false. In effect, the code branches. If the condition is true, Csound will execute the code in branch A. If the condition is false, it will execute the code in branch B.

Testing whether a condition is true or false requires a special set of symbols, as we’ll see below.

At the beginning of a new score event, the code in an instrument is run through in an initialization pass. Then the instrument starts playing, and the code is executed repeatedly, once in each k-period. As a consequence of this design, you’ll need to consider whether you want the conditional branching to occur during initialization, during each k-period, or both.

Because Csound’s ancestry dates back to an earlier period in computer science, its original branching mechanism would be considered rather primitive by today’s programmers. Yes, Csound lets you use the goto command, which was common in BASIC and other languages of the same era but is generally frowned on today. A slightly more modern form of flow control is also available and allows us to write more concise, readable code without using goto. But Csound is not well equipped with more advanced mechanisms for logic-based branching. In particular, Csound has no switch statement.

Switch statements are used in computer programming languages to set up parallel sets of branches. For instance, the code might say, “If x is 1, do these operations; if x is 2, do these other operations; if x is 3, do a different set of operations…” and so on, ending with, “if x is none of the above, here’s what to do.” A long list of conditions can be set up using very concise syntax. You can create an equivalent structure in Csound, but it will use ordinary logic and flow control statements, not a switch statement per se.

In recent years, a form of the for loop, another type of flow control structure familiar to computer programmers, has been added to Csound using the loop_ge, loop_gt, loop_le, and loop_lt opcodes. These are discussed below. Again, it’s possible to create loops in Csound using basic logic tests and the goto statement, but these opcodes allow us to avoid the embarrassment of having our computer-science-major friends snicker when they look at our code.

Testing Values

For testing whether a condition is true or false, Csound provides the operators ==, >, >=, <, <=, and !=. These operators can be used with both i-time and k-rate values (and with constants), but not with a-rate variables, because a-rate variables are vectors, not single numerical values. More complex logical tests can be constructed using the && and || operators.

The meanings of these symbols are easy to understand:

== “is equal to”
!= “is not equal to”
> “is greater than”
>= “is greater than or equal to”
< “is less than”
<= “is less than or equal to”
&& “and” (if both of the expressions are true, the result is true)
|| “or” (if either of the expressions is true, the result is true)

To use any of these symbols, you’ll need to write a statement using the if opcode. There are two ways to structure this, as indicated in the following sections of this chapter. No matter which form you use, the statement will begin something like this:


  if kfreq > 300

This statement will evaluate as either true (if the current value of kfreq is greater than 300) or false (if kfreq is 300 or less).


image

Note One of the most common mistakes in computer programming is to type the assignment operator (=) when you meant to type the equality operator (==). In Csound, this mistake is harmless, because Csound doesn’t allow assignments to be made within if statements. All the same, it’s wise to be in the habit of typing ==.


When you write a simple logical test, Csound doesn’t care whether you use parentheses. These two lines are exactly equivalent:


  if kfreq > 300 then
  if (kfreq > 300) then

Sometimes, more complex logical tests are needed. In this situation, it will sometimes be necessary to use parentheses. Complex logical tests use the && (and) and || (or) operators. When two expressions are connected by &&, they must both be true for the compound statement to evaluate as true. For example:


  iValA = 2
  iValB = 3
  iValC = 2
  if (iValA < iValB) && (iValA == iValC) then

The compound expression in this if statement will evaluate as true, because both the first part and the second part are true. But what happens if we change the less-than operator to a greater-than operator?


  if (iValA > iValB) && (iValA == iValC) then

In this case, the compound statement evaluates to false, because only the second condition is true. The first condition is false, because iValA is not greater than iValB. The && operator causes the compound expression to evaluate as true only if both parts are true. This next compound statement, however, evaluates as true:


  if (iValA > iValB) || (iValA == iValC) then

Here, we’ve replaced the “and” operator with an “or” operator. A compound expression using “or” evaluates as true if either of the component expressions is true. If this seems confusing, try reading the line out loud: “If iValA is greater than iValB or iValA is equal to iValC, then….”

Using if/goto

Csound provides three forms of the traditional goto instruction: igoto is valid only during the i-time pass, kgoto only during k-rate passes, and goto itself combines the two, operating both at i-time and at k-rate. These can used in an if statement or on a line by themselves. In either case, they direct Csound to jump to a named label. In some cases it may be safer to use if/ igoto or if/kgoto rather than if/then, because with igoto and kgoto you know exactly when the branching will or will not take place. With if/then, Csound will make its own decision about whether to branch. That said, branching with if/then/else, as explained in the section following this one, is usually safe.

A label is any word that isn’t a variable name, a reserved word, or an opcode. A label should be placed on a line by itself. Here’s a simple example:


  if (ifreq > 200) igoto hifreq
   ilforate = 6.0
   igoto done
  hifreq:
   ilforate = 7.0
  done:

The indentation is purely to make the code easier to read; it has no effect on the logic. This block of code, which will run only during the initialization pass because everything in it relates to i-time, tests whether the value of ifreq is greater than 200. If so, Csound jumps past the next two lines without even looking at them. It jumps directly to the label hifreq. At that point, the value of ilforate is set to 7.0. However, if the value of ifreq is 200 or less, the expression (ifreq > 200) evaluates to false, so the first igoto is not obeyed. Instead, Csound proceeds as it normally would, to the next line, where ilforate is set to 6.0. On the third line, Csound is instructed unequivocally to jump to the label done, so it skips the line where ilforate would be set to 7.0. The result, assuming ifreq sets the frequency of the note and ilforate is used to set the rate of an LFO, is that higher-frequency notes will have an LFO rate of 7 Hz, while lower-frequency notes will have an LFO rate of only 6 Hz.

This type of branching code is extremely useful for making an instrument respond more flexibly to the data in p-fields and to other variables. If we know that we want the LFO rate to be directly contingent on the note frequency, we don’t need to create a separate p-field in the score for LFO rate. Instead, we set it on the fly using an if statement and igoto.

Using if/then/else

Because the goto instruction is not much favored these days by computer programmers, Csound was enhanced a number of years ago to enable a more standard type of if/then/else syntax to be used. We can rewrite the code in the preceding section like this:


  if (ifreq > 200) then
   ilforate = 7.0
  else
   ilforate = 6.0
  endif

Again, the indentation is purely cosmetic. It’s ignored by the Csound compiler; it’s strictly there to make the code easier to read. In this code, if the value of ifreq is greater than 200, then the expression evaluates as true, so ilforate is set to 7.0. The keyword else is used to start the block of code that executes only if the expression is false. The entire construction ends with the keyword endif, on a line by itself.

If we need to allow for more than two possibilities, we can use the keyword elseif:


  if (ifreq > 200) then
   ilforate = 7.0
  elseif (ifreq > 150) then
   ilforate = 6.5
  else
   ilforate = 6.0
  endif

This structure is the logical equivalent of a switch statement in other programming languages. In the code above, the value of ilforate will depend on whether ifreq is above 200, between 150 and 200, or at or below 150. The key to understanding this code is that an else is consulted only if the if test returns false. If the if test returns true, any code in the else block is ignored.

If tests can be nested if desired. This type of structure is legal and sometimes useful:


  if ifreq > 200 then
     if ival == 5 then
        iresult = 60
     else
       iresult = 30
     endif
  else
     iresult = 45
  endif

Note that the interior if block (beginning with the test whether ival == 5) terminates with its own endif. This is why indenting is so useful: You can easily see the structure of an interior block of statements. In extreme cases, a complex test of this sort might end with three or more endif statements in a row, each on its own line.

The Ternary Operator

Like many other programming languages, Csound provides a handy shorthand syntax that can be used when the if/then/else construction is simple enough. The shorthand syntax is called the ternary (three-part) operator. Consider this code:


  if ival < 0.2 then
    iatk = 0.005
  else
    iatk = 0.01
  endif

This works perfectly well. But because both branches of the if/then/else tree do nothing more complex than set the value of iatk, we can get the same result with one line of code:


  iatk = (ival < 0.2 ? 0.005: 0.01)

This shows the ternary operator in action. The ternary operator uses two symbols—the question mark (?) and colon (:). The expression as a whole must be placed in parentheses, as shown. The compiler first evaluates the expression to the left of the question mark, in this case the test of whether ival is less than 0.2. This expression evaluates as either true or false. If it’s true, then the output of the ternary operator is the item before the colon; if it’s false, the output is the item after the colon. The output (which in this case is either 0.005 or 0.01) is given to the assignment operator (the equals sign), which assigns it to the variable on the left side, iatk.

Here’s a nice example of a useful way to use the ternary operator. I spotted it in Steven Yi’s “On the Sensations of Tone,” which is distributed as an example with blue (Yi’s front end for Csound).


  ipch = p4
  ipch = (ipch < 15 ? cpspch(ipch): ipch)

This code lets you use either absolute frequencies or octave/pitch-class notation interchangeably in the score. If the value is less than 15.0 (which would be a very high octave but an extremely low frequency), p4 is interpreted as octave/pitch-class. If the value is 15 or higher, it will be interpreted as an absolute frequency value.

Looping Opcodes

Most computer programming languages provide looping constructs—syntax that can be used for running through one or more lines of code over and over in a loop, until some condition is met. When the condition, whatever it is, becomes true, the program exits the loop. If the condition never becomes true, then the loop keeps running forever, or at least until you shut the program down manually. (Such an infinite loop is a bug.)

Csound almost always runs in looping mode. During performance, the instrument code for each active event is processed once in each k-period. So we can take care of certain basic tasks without employing any special looping syntax. Consider this code, for instance:


  kindex init -1
  kindex = kindex + 1
  if kindex < 16 then
     tablew kindex, kindex, giData
  endif

During the first 16 k-periods, this code will write the values 0 through 15 into the table called giData. Thereafter, the value of kindex will keep increasing, but it doesn’t matter, because the if test will return false, so no more table writing will take place.

Looping constructs usually use a value called an index or a counter to keep track of how often the loop has cycled through. On each pass through the loop, the counter is incremented: Its value is increased by 1. When the counter reaches a pre-defined limit, the condition being tested by the loop code returns true, and the looping ceases. That’s more or less what’s happening in the example above. The only detail that differs from the more usual practice in computer programming is that the loop doesn’t cycle directly, on its own. Instead, Csound’s rendering engine as a whole is looping, and the code simply takes advantage of that fact.


image

Note To simplify the discussion in this section, I’ve assumed that the loop counter always increases, and that it increases by 1 on each iteration of the loop. It’s sometimes more convenient to start with a large value for the counter and decrement (decrease) it instead, or to increment or decrement by a larger or smaller amount than 1. It’s also possible to use some other condition to test whether it’s time to exit the loop, in which case a counter may not be needed. All that’s necessary is that the condition that causes the loop to exit become true at some point.


On occasion, however, we may want to write code that actually loops. This can be done using the syntax shown earlier in this chapter, in the section “Using if/goto.” Here is how the loop above would be rewritten to take advantage of this possibility:


  kindex init 0
  backto:
  if kindex > 15 kgoto done
  tablew kindex, kindex, giData
  kindex = kindex + 1
  if kindex < 16 kgoto backto
  done:

This code does exactly the same thing as before: It writes the numbers 0 through 15 into the giData table and then stops. The main difference, aside from the fact that it looks rather messy because it needs two goto statements and two labels in order to operate correctly, is that this loop executes 16 times within a single k-period. The loop in this example is simple, and 16 is not a large number, so the fact that the loop runs 16 times in quick succession is unlikely to have any audible effect on your instrument. If the code within such a loop is complex and has to be executed hundreds or thousands of times in a row, you should expect to hear a glitch in the audio output.

Csound provides four opcodes that we can use to simplify this loop just a bit: loop_ge, loop_gt, loop_le, and loop_lt. These opcodes can operate either at i-time or at k-rate, and their syntax is pretty much identical. The main difference is how the counter (index) is compared to the boundary value (the value that, when the index reaches it, will cause the loop to exit). The syntax, in pseudo-code, looks like this:


  loop_opcode index, increment_amount, boundary_value, label

image loop_ge tests whether the index is greater than or equal to (ge) the boundary value.

image loop_gt tests whether the index is greater than (gt) the boundary value.

image loop_le tests whether the index is less than or equal to (le) the boundary value.

image loop_lt tests whether the index is less than the boundary value.

To see how this works, let’s rewrite the example above yet again, using loop_lt.


  kindex init 0
  backto:
  if kindex > 15 kgoto done
  tablew kindex, kindex, giData
  loop_lt kindex, 1, 16, backto
  done:

We’ve saved only one line of code. Instead of incrementing the index manually (with the line kindex = kindex + 1) and then testing whether to go back to the label, we can use loop_lt, which does the same thing in a single line. In some circumstances (depending on what’s going on within the loop and elsewhere in the instrument), we may still need to bypass the code within the loop manually by testing the value of kindex and then using kgoto done. This instruction may appear redundant, but if we don’t use it, the tablew line in the loop above will continue to be executed at every k-period until the instrument stops, and kindex will continue to increment during each k-period when the loop_lt opcode is reached, even if it doesn’t cause a jump back to the label.

An important thing to note about loop_lt and its brethren is that each of them increments (or, in the case of loop_ge and loop_gt, decrements) the index variable before testing it against the maximum or minimum value.

As a segue to the next section, on print statements, let’s use loop_lt to write some values to a table, this time using the i-time version of the opcode, read the values during the first k-period, and print them out to the console (or to the terminal, if you’re running Csound from the command line) to make sure the table contains what we think it does.

Two things may be worth noting about this code. First, we don’t have to exit the first loop (the one that writes the data to the table) explicitly, because it runs at i-time, so it won’t be used during k-cycles. Second, the labels we use have to be unique, hence backto1 and so on.


  ; in the orchestra header, create a table with 16 zeroes in it:
  giData ftgen 0, 0, 16, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  instr 1

  ; write integers 0-15 to the giData table:
  index = 0
  backto1:
  tableiw index, index, giData
  loop_lt index, 1, 16, backto1

  ; read the data from the table and print it out:
  kndex init 0
  kdata init -1
  backto2:
  if kndex > 15 kgoto done2
  kdata table kndex, giData
  printk2 kdata
  loop_lt kndex, 1, 16, backto2

  done2:

  endin

  </CsInstruments>
  <CsScore>

  i1 0 0.1

When you copy this code into a .csd file and run it, you should see the following output:


  SECTION 1:
  new alloc for instr 1:
  i11 0.00000
  i11 1.00000
  i11 2.00000
  i11 3.00000
  i11 4.00000
  i11 5.00000
  i11 6.00000
  i11 7.00000
  i11 8.00000
  i11 9.00000
  i11 10.00000
  i11 11.00000
  i11 12.00000
  i11 13.00000
  i11 14.00000
  i11 15.00000

The printk2 opcode has run 16 times, once on each pass through the loop. Each time, it has printed out the current value of kdata, which was read from the giData table.

Incidentally, this is an unusual usage in which the value of an i-time variable can be changed by subsequent code (namely, the loop_lt line, which increments the value of index), because the loop does all its work during the initialization pass.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.34.197