Saturday, May 29, 2010

Boolean Expressions

Last time, we learned to randomly pick true or false values, however, it would be more useful if we could test to see what's going on and make decisions according to those tests. Fortunately, we can do that with boolean expressions.

An expression is a bit of code that can be evaluated. 2+3 is an expression, who's value is 5. A boolean expression is an expression that results in a boolean. Boolean expressions are usually tests. For example, we can test for equivalency with ==

(
 a = 3;
 if (a == 3, {
  "true".postln;
 }, {
  "false".postln;
 });
)

Note that is two equal signs next to each other when we're testing for equivalency. If you just use one equal sign, it means assignment. I often accidentally type one equals sign when I mean to type two.

We can test for greater than or less than:

(
 a = 3;
 if (a > 4, {
  "true".postln;
 }, {
  "false".postln;
 });
)
(
 a = 3;
 if (a < 4, {
  "true".postln;
 }, {
  "false".postln;
 });
)

We can also test for greater than or equals to and less than or equals to:

(
 a = 4;
 if (a >= 4, {
  "true".postln;
 }, {
  "false".postln;
 });
)
(
 a = 3;
 if (a <= 4, {
  "true".postln;
 }, {
  "false".postln;
 });
)

We can also do boolean operations. Some of the most important ones are not, and, and or.

The easiest way to illustrate these is with truth tables. A truth table shows you all possible combinations of true and false variables and what the results would be. Any Boolean variable can be either true or false. This is a truth table for not:

not
truefalse
falsetrue

Not is a unary operator. That means it only involves one object. The top of the table shows a true input and a false input. The bottom of the table shows the result. true.not returns false and false.not returns true.

And is a binary operator. Like +, -, *, / and %, it operates on two objects. Lets’ say we have two variables, a and b, and either of them can be true or false. We can put a along the top of the table and b down the left side. In the middle we put the possible results of a and b.

and
truefalse
truetruefalse
falsefalsefalse

Or is also binary:

or
truefalse
truetruetrue
falsetruefalse

So how do we code these? Let’s look again at not. Not can be a message sent to a boolean or boolean expression:

(
 if ((a==4).not , {
  "true".postln;
 }, {
  "false".postln;
 });
)

You can combine not and equivalency with not equals: !=

(
 a = 2;
 
 if (a != 4 , {
  "true".postln;
 }, {
  "false".postln;
 });
)

The last two examples are the same.

And is represented by &&

(
 a = 3;
 b = 4;
 
 if ( (a > 2) && (b < 5), {
  "true".postln;
 }, {
  "false".postln;
 });
)

Both (a > 2) and (b < 5) must be true for this expression to evaluate as true. If one of them is false, the whole thing is false.

Or is represented by || (Those are vertical lines. If you have a Macintosh with an American keyboard, they're over the slash \)

(
 a = 3;
 b = 4;
 
 if ( (a > 2) || (b < 5), {
  "true".postln;
 }, {
  "false".postln;
 });
)

(
 a = 3;
 b = 4;
 
 if ( (a < 2) || (b < 5), {
  "true".postln;
 }, {
  "false".postln;
 });
)

(
 a = 3;
 b = 4;
 
 if ( (a > 2) || (b == 5), {
  "true".postln;
 }, {
  "false".postln;
 });
)

For these expressions to evaluate to true, only one part of it needs to be true. If neither part of an or is true, then the whole thing is false.

Musical Example

Let's have a musical example. We'll again generate random pitches, but low pitches will have long durations and high ones will be short. First, we need a SynthDef:

(
  SynthDef("example6", {arg out = 0, freq = 440, amp = 0.1,
       dur = 1;

     var blip, env_gen, env;

     env = Env.triangle(dur, 1);
     env_gen = EnvGen.kr(env, doneAction:2);
     blip = Blip.ar(freq, env_gen * 3, env_gen * amp);
     Out.ar(out, blip);
  }).load(s);
)

We're trying out a new oscillator called Blip. The helpfile for it says:

Blip.ar(freq, numharm, mul, add)

Band Limited ImPulse generator. All harmonics have equal amplitude. This is the equivalent of 'buzz' in MusicN languages.

WARNING: This waveform in its raw form could be damaging to your ears at high amplitudes or for long periods.

The first argument is the frequency. The second is the number of harmonics, which is to say the number of overtones and the third is amplitude. Loud tones with lots of harmonics can hurt our ears! So let's watch out. I suspect anyhting that's bad for ears is probably also bad for speakers.

We're going to control the number of harmonics with the envelope, so they change over time. We're using the same envelope for amplitude as we are for harmonics, so env_gen * 3 gives us a maximum of 3 harmoics at the peak. If you want more or less, you can change that number. And, of course, env_gen * amp gives us an envelope that goes from 0 to the amp passed in as an argument.

Ok, now for the Task that will play this


(
  Task({
    var freq, synth_dur;
    50.do({
      freq = 200 + 800.rand;
      if((freq >= 600), {
       synth_dur = 0.1 + 1.0.rand;
      } , {
       if ((freq <= 400), {
        synth_dur = 1 + 5.0.rand;
       },{
        synth_dur = 0.1+ 2.0.rand;
       });
      });
      Synth(\example6, [\out, 0, \amp, 0.05, \freq, freq, 
          \dur, synth_dur]);
      (0.01 + 0.4.rand).wait;
    });
  }).play;
)

Ok, in our first if statement, we test for frequencies that are greater than or equal to 600 Hz. If they are that high, then we set our variable synth_dur to 0.1 + 1.0.rand. If the freq is not that high, we go to the false function. In the false function, we find another if statement. In this if statement, we test for frequencies that are less than or equal to 400 Hz. If they are that low, then we set our variable synth_dur to 1 + 5.0.rand. the the freq is not that low, we go to the false condition and set our variable synth_dur to 0.1+ 2.0.rand. We only get to that second false function if both true functions are false. We could re-write those test conditions:

    if((freq >= 600), {
     synth_dur = 0.1 + 1.0.rand;
    });
    if ((freq <= 400), {
     synth_dur = 1 + 5.0.rand;
    });
    if ((freq < 600) && (freq > 400), {
     synth_dur = 0.1+ 2.0.rand;
    });

The result would be the same. But if we do it that way, we have to go through three if statements every time through the loop. If we do it with nested if statements as above, we only have to go through two of them at most. And, if the frequency is high, we only need to go through the first one. So the nested if statements are slightly more efficient, however, it's good to figure out exactly what conditions would have to be to get to a particular code block.

Summary

  • An expression is a snippet of code that has a value or can return a value
  • You can test for equivalency with ==
  • Greater than is >
  • Less than is <
  • Greater than or equal to is >=
  • Less than or equal to is <=
  • Not is boolean.not and causes things to invert
  • Not equals is !=
  • And is &&. Both conditions must be true for and to be true.
  • Or is ||. One or both conditions must be true for or to be true.
  • If statements can be nested

Problems

  • Write if statements using and, or, and not using Boolean values of true and false to illustrate the truth tables, using all possible combinations of true and false. For example:
    (
     if (true.not, {
      "true".postln;
     }, {
      "false".postln;
     });
    )
    
    What do you expect each of them to print? Did the results you got from running them match your expectations?
  • No comments: