Sunday, May 30, 2010

While

We’ve used Boolean expressions to control the flow of execution of a program with if. Another control structure is while. While is a message passed to a function. The function must return either true or false. It is a test function. There is another function passed in as an argument. test_function.while(loop_function); If the test function is true, the loop function gets run. Then the test function is run again. If it returns true again, the loop function is run again. This continues until the test function returns false. While the condition is true, the loop is executed.

(
 var counter;
 
 counter = 0;
 
 {counter < 6}.while ({
  
  counter = counter + 3.rand;
  counter.postln;
  
 })
)

{counter < 6} is the test function. If it is true, we run the next two lines inside the while loop.

counter = counter + 3.rand; adds the result of 3.rand to the variable counter and then stores the result of that in the variable counter. Recall that in assignment statements (statements where variables get a value), there can only be one thing on the left of the equals sign. Everything to the right is evaluated and then result of that evaluation is assigned to the variable. It's ok to have the same variable on both sides of the equal sign. The value of the variable does not change until everything has already been evaluated.

If instead of counter = 0; we had counter = 7; the test function would have run once, but the loop function would not have run at all.

As with if, functional notation is also common with while. Remember that both notations are entirely equivalent. The following two lines are the same:

test_func.while(loop_func);
while(test_func, loop_func);

Let's have a musical example, where we track elapsed time rather than the number of notes played. First, we need a synthdef:

(
 SynthDef(\example7, {arg out = 0, freq = 440, dur = 1, amp = 0.2, pan = 0;
  
  var env, form, panner;
  
  env = EnvGen.kr(Env.perc(0.01, dur, amp), doneAction:2);
  form = Formant.ar(freq, freq + 100, freq + 245);
  panner = Pan2.ar(form, pan, env);
  
  Out.ar(out, panner);
 }).load(s);
)

Formant is an oscillator that creates formants. You can find out more about it in it's helpfile.

Pan2 is a two channel panner which takes three arguments: the signal, the position and the amplitude. The position can vary from -1 to 1 and 0 is in the center.

Now, our Task:


(
 Task({
 
  var freq, dur, total_dur, count; 
  
  dur = 0.17;
  total_dur = 0;
  count = 0;
  
  {total_dur < 20}.while({
  
   freq = 400;
   
   (count % 3 == 0).if({   
    freq = freq + 200;
   });
   (count % 4 == 0).if({   
    freq = freq - 100;
   });
   (count % 5 == 0).if({   
    freq = freq + 400;
   });
   
   Synth(\example7, [\out, 0, \freq, freq, \dur, dur, \amp, 0.2, \pan, 0.7.rand2]);
   
   count = count + 1;
   total_dur = total_dur + dur;
   dur.wait;
  });
 }).play
)

(This code is translated to SuperCollider from a tutorial for making music with Perl.)

At the start, we declare our variables and then we initialise them. This is an important step, because before we assign them a value, they start out with the value nil. Nil is not meaningful with a <, nor with any other mathematical or boolean operation.

In our test function, we see if the elapsed duration is less than 20 seconds.

Then in our loop, we add and subtract from a given frequency based on the divisors of a counter.

rand2 is a message that can be passed to numbers. It returns a random result between (-1 * this) and this. So 0.7.rand returns a number between -0.7 and 0.7. This will cause our sound to random pan from left of center to right of center.

After we play the synth, we update all our variables. If we forget to add the dur to the total_dur, the test function will always return true and we will never leave the loop. I sometimes forget this, as it's a common bug.

The next example is very similar to one from the previous post.

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

     var blip, env, panner;

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


(
  Task({
     var freq, synth_dur, high_freq;
  
     high_freq = 800;
  
     while({high_freq > 0}, {
  
        freq = 200 + high_freq.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(\example7b, [\out, 0, \amp, 0.1, \freq, freq,
            \dur, synth_dur, \pan, 0.8.rand2]);
        
        high_freq = high_freq - 50.rand;
        (0.01 + 0.4.rand).wait;
     });
  }).play;
)

Because we keep subtracting from high_freq, the highest possible frequency is tending downwards. We keep subtracting until it gets to zero. Incidentally, changing allowable ranges of parameters over time is called tendency masking.

There are other Control Structures detailed in a help file called Control-Structures. Highlight "Control-Structures" and press shift-d

Summary

  • While is a control structure that controls a loop
  • The test condition for while is a function which returns a boolean. While is a message passed to that function. It takes a function as an argument.

Problems

  • It is allowable to have two or more tasks going at the same time. Write a programme that has two tasks, one playing notes tending upwards ans the other downwards.
    • Make it such that when one while stops, the other does also. You may find it helpful if there is a variable that they both can access.

No comments: