Friday, May 28, 2010

If

In our programmes, we need control structures, that is, ways to make decisions. One control structure is if. if has it's own helpfile. Highlight if and press apple-d. If is also explained briefly in the helpfile for Boolean.

A Boolean is a value that is either true or false. true and false are reserved words in SuperCollider. We can send an if message to Booleans.

(
 ([true, false].choose).if(
  {
   "true".postln;   
  }, {
   "false".postln;
  }
 );
)

Remember that a list surrounded by square brackets is an Array. Arrays understand a message called choose. It randomly picks an item in the array and returns it.

If you run the above code several times, “true” and “false” should print out about the same number of times in a random order, because [true, false].choose ought to be true half the time and false the other half. The result of that expression is a Boolean. We send an if message to the Boolean, which has two arguments, both functions. The first function is evaluated if the Boolean is true. The second function is evaluated if the Boolean is false.

boolean.if(trueFunction, falseFunction);

You can omit the false function if you want.

This syntax that we've been using, object.message(argument1, argument2, . . . argumentN);, is the most commonly used syntax in SuperCollider programs. It's called receiver notation. However, there is more than one correct syntax in SuperCollider. There also exists a syntax called functional notation. It is more commonly used with if messages than receiver notation. When you see if in the helpfiles, the examples almost always use functional notation. Functional notation is:

message(object, argument1, argument2,  . . . argumentN);

The two notations are equivalent. You can replace one with the other at any place in any program and it will not change the program. The reason that I bring this up is that people very commonly use functional notation for if:

if(boolean, trueFunc, falseFunc);

So our example would change to:

(
 if([true, false].choose, {
  "true".postln;   
 }, {
  "false".postln;
 });
)

It works in exactly the same way.

Why are there multiple correct notations? It's confusing!

SuperCollider is based on many other programming languages, but the language that it borrows most heavily on is one called Smalltalk. Smalltalk, like SuperCollider, is an object-oriented language. When I took Programming Languages at uni, my teacher said that Smalltalk was the best object oriented language and the only reason it wasn't the most popular was that the syntax was insane.

So rather than force us to use Smalltalk syntax, James McCartney, the author of SuperCollider, allows us multiple legal syntaxes. Receiver notation is common in several object-oriented languages. Functional notation, however, persists in if, probably because other languages have different ways of thinking about if.

Let's code an overtone player that has a 50% chance of resting. First, we need a synthdef:

(
 SynthDef.new("example5", {arg out = 0, freq = 440, amp = 0.2,
     dur = 1;

   var saw, env_gen, env;

   env = Env.triangle(dur, amp);
   env_gen = EnvGen.kr(env, doneAction:2);
   saw = Saw.ar(freq, env_gen);
   Out.ar(out, saw);
 }).load(s);
)

This one uses a sawtooth wave oscillator. Check out the helpfile for Saw for more information.

Then, our player:

(
 var player;
 
 player = { arg baseFreq, numOvertones, dur = 0.2;
  var freq;
  Task({
    (numOvertones + 1).do({ arg index;
    freq = index * baseFreq;
    if ([true, false].choose, {
     Synth(\example5, [\out, 0, \freq, freq, \dur, dur]);
    });
      dur.wait;
     });
   });
  };
  
  player.value(220, 12).play;
)

If you run it several times, it should rest in different spots, about half the time.

If we want to give it a 33.3% chance of resting (a 66% chance of playing), we could change out if to look like [true, true, false].choose and expand our array every time we want to change the probability. But what if we want something to play 99% of the time? We would have to have 99 trues and one false. Fortunately, there is a message you can use that returns a Boolean based on percentage. To play 99% of the time, we would use 0.99.coin

If you look at the helpfile for float, you learn that coin, "answers a Boolean which is the result of a random test whose probability of success in a range from zero to one is this." Which means that 0.66.coin has a 66% chance of being true and 0.01.coin has a 1% chance of being true.

The word "this" in that description refers to the number which received the message. If you type 0.66.coin, "this" is 0.66.

Let's give our an example 75% chance of playing a given note:

(
 var player;
 
 player = { arg baseFreq, numOvertones, dur = 0.2;
  var freq;
  Task({
    (numOvertones + 1).do({ arg index;
    freq = index * baseFreq;
    if (0.75.coin, {
     Synth(\example5, [\out, 0, \freq, freq, \dur, dur]);
    });
      dur.wait;
     });
   });
  };
  
  player.value(220, 12).play;
)

Summary

  • Booleans are either true or false
  • if is a way of controlling programme flow and making decisions
  • receiver notation is object.message(argument)
  • functional notation is message(object, argument)
  • receiver notation and functional notation are only stylistic differences and both do exactly the same thing
  • if is often typed as if(boolean, true_function, false_function)
  • The fase function is optional
  • The choose message causes an array to pick a single element at random
  • n.coin returns a boolean value with n probability of being true
  • In helpfiles and other places, "this" refers to the object that received the message

Problems

  1. Re-write "Hello World".postln in functional notation.
  2. Write a programme that has 66% chance of playing the any given note in an arpeggio and a 33% chance of playing a random note instead. You will need to use a false function.
  3. The functions of ifs can contain other ifs. Write a programme that has a 50% chance of playing any given note in arpeggio, a 25% chance of playing a random note and a 25% chance of resting. Remember that if you write if(0.25.coin . . .), it has a 75% chance of picking false and evaluating the false function (if one is present).

1 comment:

Anonymous said...

Just a silly comment, but there's a typo in the summary: "The fase function is optional" should read "the false (...)" function. Great tutorial, thank you very much for dedicating your time to us!!