Showing posts with label function. Show all posts
Showing posts with label function. Show all posts

Sunday, June 06, 2010

Pfunc

Let's say you want to control some part of your Pattern from, say, a task. For example, we want to execute some block of code, then wait 5 seconds and then casue the amplitude of a Pbind to fade towards zero. We can do this by using some variables:

(
 var db, pbind, task;
 
 db = -12;
 
 task = Task({
 
  // ...
  
  5.wait;
  
  60.do({
  
   db = db - 1;
   0.1.wait;
  });
  
  "done fading".postln;
 });
 
 // ...
)

db will hold our amplitude in decibels. We're using db because it's logarithmic and will sound right if we just use subtraction to change the value.

task will hold our task. That "..." near the top represents whatever we wanted to do before starting the countdown to the fade-out.

After that, we wait for 5 seconds, then we slowly drop 60 db.

Now for the pbind:

 // ...

 pbind = Pbind(
  
  \dur,   Pseq([0.2, 0.4, 0.6], inf),
  \degree,   Pseq([0, 3, 5, 7, 9, 8, 8, 2].pyramid(1), inf),
  \steapsPerOctave, 10,
  \db,    db
 );
 
 // ...
  

In the line for degree, we see that we're sending the message pyramid to the array. It reorders the array in an interesting way. See the helpfile for Array for more information about it.

Pbinds understand the symbol \db and will convert to amp automatically, as needed. There's a message you can pass to numbers called dbamp. It converts from db to amps. -12.dbamp is 0.25118864315096. Or if you want to go the other way, there's a message ampdb. 0.25.ampdb is -12.041199826559. However, the Pbind will do this for you, so you don't need to convert ahead of time.

Now we can try running the task and the pbind simultaneously:

 // ...
 
 pbind.play;
 task.play;

And when you put this all together and try it, "done fading" prints out, without there having been any fade.

This is because the interpretter evaluates the expression db and then passes the result of that expression as an argument to the Pbind contructor. It is only evaluated that one time. In order to get the current value of db, we need a way to tell the pbind to re-evaluate it every time it comes up. We can do that with Pfunc

Pfunc is a wrapper for functions, so they can be used in patterns. It's constructor takes a function as an argument and then it evaluates that function every time it's asked for a value. Let's re-write our pbind:

 // ...

 pbind = Pbind(
  
  \dur,   Pseq([0.2, 0.4, 0.6], inf),
  \degree,   Pseq([0, 3, 5, 7, 9, 8, 8, 2].pyramid(1), inf),
  \steapsPerOctave, 10,
  \db,    Pfunc({db});
 );
 
 // ...

Remember that functions return the value of their last line. Since we only have one line, it returns that. And, indeed, the amplitude fades away to quiet. However, the Pbind keeps running even after we can't hear it. We can use an if statement to tell it to stop:

 pbind = Pbind(
  
  \dur,   Pseq([0.2, 0.4, 0.6], inf),
  \degree,   Pseq([0, 3, 5, 7, 9, 8, 8, 2].pyramid(1), inf),
  \steapsPerOctave, 10,
  \db,    Pfunc({
       if(db > -72, {
        db
       }, {
        nil
       })
      })
 );

Pbinds stop running as soon as any symbol is paired with nil. Therefore, when db gets to -72 and the Pfunc returns nil, the whole Pbind stops. If the Pbind were in a Pseq, it would go on to the next item.

You may have also noticed that we don't have semicolons everywhere they could be. This is because they separate lines of code, so it's ok to skip them on the last (or only) line in a block of code. It makes no difference to the interpretter if there is a semicolon after nil or not. If you find it easier to use them everywhere, then you should keep doing that.

We could also skip the false function for the if, since it will still return nil if we do:

Pfunc({ if(db > -72, { db }) })

The final version of this example, all together is:

(
 var db, pbind, task;
 
 db = -12;
 
 task = Task({
 
  // ...
  
  5.wait;
  
  60.do({
   db = db - 1;
   0.1.wait;
  });
  
  "done fading".postln;
 });
 
 
 pbind = Pbind(
  
  \dur,   Pseq([0.2, 0.4, 0.6], inf),
  \degree,   Pseq([0, 3, 5, 7, 9, 8, 8, 2].pyramid(1), inf),
  \steapsPerOctave, 10,
  \db,    Pfunc({ if(db > -72, { db }) })
 );
 
 
 pbind.play;
 task.play;
 
)

Summary

  • Decibels can be an easier system to use when computing amplitudes
  • Pbinds will convert between db and amp
  • The messages ampdb and dbamp can convert number values from amps to db or db to amps
  • pyramid is an interesting way of shuffling an array
  • You can put a function in a pattern with Pfunc
  • A Pfunc with get re-evaluated for every event
  • A Pbind stops when it gets a nil value

Problem

  1. Write a short piece of music where two separate Pbinds both access the same variable. They can be in the same Ppar or not.
  2. Write a programme such that one Pbind sets a flag that causes another Pbind to change in some way.

Thursday, May 27, 2010

Tasks

Last time, we learned how to use an envelope to cause synths to stop playing after a set time. Now we're going to look at one way we can control when they start. As with any programming language, there are multiple ways to do this in SuperCollider, each with particular strengths and weaknesses.

We're going to look at something called a Task:

(
 r  = Task.new ({
 
  5.do({ arg index;
   index.postln;
   1.wait;
  });
 });
 
 r.play;
)

That example prints out:

0
1
2
3
4

With a one second pause between each number. Try running it.

Remember that the letters a-z are global variables in SuperCollider. Also, remember that the class SimpleNumber is a superclass of both Float and Integer. It can take a message called wait. That message only works in the context of a task (or it's superclasses, like Routine). If we try wait in a normal function:

(
 f  = {
 
  5.do({ arg index;
   index.postln;
   1.wait;
  });
 };
 
 f.value;
)

We get errors:

• ERROR: yield was called outside of a Routine.
ERROR: Primitive '_RoutineYield' failed.
Failed.

Tasks (which are a subclass of Routine) are one powerful way to add timing to your program. Task.new takes a function as an argument. It runs that function when we send it the play message. Hence, r.play; in the Task example. When we play a Task, it will pause for a duration of SimpleNumber.wait. If we code 5.wait, it will pause for five seconds. If we have 0.5.wait, it will pause for half a second.

Ok, now we know how to control when things happen! Let's try this to play some notes! First we need a SynthDef:

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

  var square, env_gen, env;

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

This is just like our last SynthDef, except that it uses a square wave instead of a sine wave. To find out more about the Pulse UGen, read the help file on it.

Ok, let's play some pitches. the range of human hearing is 20Hz - 20,000 Hz, but the range of my laptop speakers is somewhat less than that, so let's pick between 200 - 800 Hz.

(
 Task({
  var freq;
  10.do({
   freq = 200 + 600.rand;
   Synth(\example4, [\out, 0, \freq, freq, \dur, 0.5]);
   0.5.wait;
  });
 }).play;
)

When you we that, we get random pitches.

In the first line, you'll notice that we just have Task({ instead of Task.new({. This is a shortcut that SuperCollider allows. You can just have Class(argument1 , argument2 . . .) in place of Class.new(argument1, argument2 . . .). The .new is implied.

The next line with something new is freq = 200 + 600.rand;. You can pass the message rand so a SimpleNumber. It will return a random number between 0 and the number that it's passed to. So 600.rand will return a random number between 0 - 600. If we add 200 to that number, then we have a random number between 200 - 800, which is the range we want.

In the past, we talked a bit about precedence. In SuperCollider, passing messages will be evaluated before math operations like addition, subtraction, etc. So the interpreter will evaluate 600.rand first and then add 200. Therefore, we will get the same results if we type freq = 200 + 600.rand; or if we type freq = 600.rand + 200;

In the next line, we've got Synth(\example4 instead of Synth.new(\example4. This is the same shortcut as in the first line.

In that example, we pass 0.5 to the synth for it's duration and wait 0.5 seconds, so there's no overlap between the notes. We don't have to do it that way. We could only wait for 0.25 seconds and let the notes overlap, or we could extend the wait and leave spaces between the notes. Or we could change the Synth duration. We could use random numbers for waits and durations:

(
 Task({
  var freq;
  10.do({
   freq = 200 + 600.rand;
   Synth(\example4, [\out, 0, \freq, freq, \dur, (0.1+ 2.0.rand)]);
   (0.01 + 0.4.rand).wait;
  });
 }).play;
)

The two parts that have changed are \dur, (0.1+ 2.0.rand) and (0.01 + 0.4.rand).wait;. The reason that we have 2.0.rand is because Integer.rand returns an Integer and Float.rand returns a float. 2 is an integer, so 2.rand will return either 1 or 0 and no other numbers. 2.0.rand, however, will return real numbers and thus can return things like 0.72889876365662 or 1.6925632953644. Try evaluating 2.rand and 2.0.rand several times and look at the results you get.

We use parenthesis in (0.01 + 0.4.rand).wait; so that we sent the wait message to the results of 0.01 + 0.4.rand. If we were to skip the parenthesis, we would pick a random number between 0 and 0.4 and then wait that long and then add 0.01 to the results of the wait message, which is not what what we want. We add in that 0.01 to make sure that we never have a wait of 0. This is an aesthetic decision. 0.wait is perfectly acceptable.

What if we want to modify the number of times we run through our main loop? We can't pass arguments to a Task, but fortunately, because of scope and functions returning things, there is a clever work around:

(
var func, task;

func = { arg repeats = 10;

 Task({  
  var freq;
  repeats.do({
   freq = 200 + 600.rand;
   Synth(\example4, [\out, 0, \freq, freq, \dur, (0.1+ 2.0.rand)]);
   0.01 + 0.4.rand.wait;
  });
 });
 
};

task = func.value(15);
task.play;
)

First, we write a function that takes an argument. Then, inside the function, we have a Task. Because the Task is inside the function's code block, the Task is within the scope of the argument. And because the Task is the last (and only, aside from the args) thing inside the function, it gets returned when the function gets evaluated. So task gets the Task that is returned by the function. Then we call task.play; to run the Task. It's ok to have a variable called task because SuperCollider is case-sensitive and therefore Task, task, tASK, taSk, etc are all different.

Remember that functions return their last value. And remember that scope means that inner code blocks can see variables from outer code blocks, but not vice versa. So the Task can see the args. And there's nothing after the Task in the function, so it's the last value, so it gets returned when we send a value message to the function. Then we send a play message to the returned Task. We could abbreviate the last two lines of our program by just having one line:

func.value(15).play;

The interpreter, reading from right to left, will pass a value message to the func, with an argument of 15. That returns a Task, which is then passed a message of play.

Summary

  • Tasks are functions that can pause.
  • In a task, number.wait will pause for that number of seconds.
  • You cannot ask a code block to wait unless it is within a a Routine, which is a superclass of Task.
  • n.rand returns a number between 0 and n. If n is an integer, it returns an integer. If n is a float, it returns a float.
  • You cannot pass arguments to Tasks, but you can put them in functions that take arguments and then return the Task from the function.

Problems

  1. Write a a task that plays an arpeggio of the first n overtones over a base frequency. Use a wrapper function to pass in both the frequency and the number of overtones. Give the arguments descriptive names. (There is an explanation of overtones in the post on numbers.)
  2. Write a task that produces n separate sound clusters. Pass that number as an argument (using functions). Give the argument a descriptive name.

Project

  1. Write a short piece that uses tone clusters that are in different frequency ranges. For example, one section might be 100-300 Hz and another might be 600-1200 Hz. You may wish to vary the timbres by using multiple synthdefs. You may wish different sections to have different densities, also.

    You can make a recording of your piece by pressing the "Prepare Record button" on the server window, after you boot it. It will turn red and say "record >". Press it again to start recording. The button will change color again and say "stop []". this means it's now recording. Run your piece. Click the button again when your piece has finished to stop recording. The Recording will go in to your home directory under Music/SuperCollider Recordings. It will be a 32 bit aiff, but you can convert it to a 16bit aiff or to mp3 with Audacity.

    If you do this project, please feel free to share your results in the comments!

Sunday, May 23, 2010

Functions

After reading the last chapter on SynthDefs and the one before that on Variables, Nicole feels like she's got the hang of SuperCollider so she's dropped out of grad school to work for the new SuperCollider start-up company SuperSounds.com. On her first day, her boss tells her to write a function that prints "hello world" four times. "No problem," she thinks and goes to look at previous posts. Functions are code blocks encased by curly brackets, { } and hello world is easy enough. So she writes:

(
{

var greet;
greet = "hello world";

greet.postln;
greet.postln;
greet.postln;
greet.postln;
}
)

Then she thinks, "I should have asked for a signing bonus." She tries running it, by double clicking to the right of the top parenthesis and hitting enter. In the Untitled output window it says, "a Function"

What's the problem? She declared a variable called greet. greet gets "hello world". Then she sends a postln message to greet four times. Every line ends with a semicolon . . .

Then she realizes that she defined a function, but never told the interpreter to run it. The interpreter saw a code block surrounded by curly brackets and thought "a function!" Then it thought, "What do you want me to do with this? Nothing? Ok, I'll throw it away." So Nicole modifies her code:

(
var func;

func = {

var greet;
greet = "hello world";

greet.postln;
greet.postln;
greet.postln;
greet.postln;
};

func.value;
)

And then it works great. value is a message you can send to functions. It means, "Run yourself!"

But then Nicole gets a message from her boss saying, "Sometimes, we need to print out hello world five times and once in a while, three times, and rarely, it needs to print out infinite times." Nicole considers writing a few different versions of the function and calling them func4, func3, etc, but then remembers about arguments to functions.

(
var func;
func = { arg repeats;

var greet;
greet = "hello world";

repeats.do ({
greet.postln;
});
};

func.value(4);
)

When she writes her function, she declares to the interpreter that the function takes one argument. An argument is a special type of variable that gets set when the function is called. When she calls the function withfunc.value(4);, she's assigning a value to the argument repeats.

Then, inside the function, she's written, repeats.do. What is 'do'? It's a message. It takes a function as an argument. 'do' is a message you can send to integers that runs the function passed as an argument the number of times as the integer that it was called on. In this example, repeats is 4, so it runs four times.

What is an integer? An integer is a whole number. -2, -1, 0, 1, 2, 3, 4, etc. There is a special integer in SuperCollider called inf. It means infinity. If we try calling our above function, with

func.value(inf);

hello world will print out forever, or until we stop the program by hitting apple-. .

Then Nicole's boss sends another email saying that marketing has some changes. Every line needs to start out with the line number, starting with zero. So she makes another change:

(
var func;
func = { arg repeats;

var greet;
greet = "hello world";

repeats.do ({ arg index;
index.post;
" ".post;
greet.postln;
});
};

func.value(4);
)

The function called by do takes an argument. And that argument is the number of times the loop has been run, starting with 0. So her output from this program is:

0 hello world
1 hello world
2 hello world
3 hello world

post just means print without a new line at the end.

Almost every time, she runs this function, the argument is going to be 4. So she can declare a default argument for the function.

(
var func;
func = { arg repeats = 4;

var greet;
greet = "hello world";

repeats.do ({ arg index;
index.post;
" ".post;
greet.postln;
});
};

func.value;
)

When she calls func.value, if there's no argument, then the interpreter will assign 4 to repeats by default. If she calls it with, func.value(6); then repeats gets 6 instead. What if she accidentally passes in something besides an Integer into her function? That depends. If the object she passes in also understands a do message, then it will use that instead, although the result may differ. Otherwise, she will get an error.

What if the function took a lot of arguments with default values?

(
var func;
func = { arg foo = 0, bar = 0, baz = 1, repeats = 4;

var greet;
greet = "hello world";

repeats.do ({ arg index;
index.post;
" ".post;
greet.postln;
});
};

func.value;
)

If she wants to pass in arguments, she does it in the order they're declared in.

func.value(0 /* foo */, 0 /* bar */, 1 /* baz */, 3 /* repeats */);

However, if we're happy with all the default values, there's a way to tell the function just to assign to a particular variable, out of order:

func.value(repeats: 3);

You can also just pass in the first N arguments and leave the remainder to the default values:

func.value(3, 1);

And you can combine approaches:

func.value(2, repeats: 6);

Those slash-stars up there are comments. The interpreter ignores everything between a forward slash star and a backslash star. They can span multiple lines. You can also create single line comment by using forward slash forward slash:

// This is a comment

Some times it's useful to comment out a line of code while debugging. So you can skip a particular line in order to figure out where your error is.

The philosophical point of a function is to return a value. Doing things within functions, like printing are technically called side effects. What the function returns is the value of its last line. Let's change that function so it returns the number of times that it printed.

(
 var func;
 func = { arg repeats = 4;
 
  var greet;
  greet = "hello world";
  
  repeats.do ({ arg index;
   index.post;
   " ".post;
   greet.postln;
  });
  repeats;
 };
 
 func.value;
)

Now, if we create a new variable called times, we can assign the output of the function to it.

(
 var func, times;
 func = { arg repeats = 4;
 
  var greet;
  greet = "hello world";
  
  repeats.do ({ arg index;
   index.post;
   " ".post;
   greet.postln;
  });
  repeats;
 };
 
 times = func.value;
 times.postln;
)

Prints out hello world with the line number like before, and then at the bottom, prints out a 4. Or we could change those last two lines from

 times = func.value;
 times.postln;

to

func.value.postln;

and the interpreter will read that statement left to right, first finding the value of the function and then sending that value a postln message.

Ok, what happens if we take the above function and write some code below it that looks like this:

(
 var func, times;
 func = { arg repeats = 4;
 
  var greet;
  greet = "hello world";
  
  repeats.do ({ arg index;
   index.post;
   " ".post;
   greet.postln;
  });
  repeats;
 };
 greet.postln;
)

We get errors.

• ERROR: Variable 'greet' not defined.
 in file 'selected text'
 line 14 char 6 :
 greet•.postln;

This is because of something called scope. Variables only exist in the code block in which they are declared. Code blocks are zero or more lines of code surrounded by curly braces or highlighted by the user. This means that variables and arguments declared inside a function only exist inside that function. index does not exist outside of its function, which is the function passed as an argument to repeats.do. greet does not exist outside of it's function. None of these variables exist of the text we highlight.

Variables in outer blocks are accessible within inner blocks. For example,

(
 var func, times;
 times = 0;
 func = { arg repeats = 4;
 
  var greet;
  greet = "hello world";
  times.postln;
  
  repeats.do ({ arg index;
   index.post;
   " ".post;
   greet.postln;
  });
  repeats;
 };
)

Is fine because times exists in the outer most block. In the same way, we can use greet inside our repeats.do function.

There are, however, a few variables that can be used everywhere. The interpreter gives us 26 global variables. Their scope is all of SuperCollider. They are single letter variable names, a, b, c, d, e, f, etc. You don't need to declare them and they keep their value until you change it again, even in different code blocks. This is why the variable 's' refers to the Server. You can change that, but you might not want to.

If you have any questions about functions, you can look at the Function help file for more information. The most useful message you can send to a function is value, but there are a few others, which you can read about. You may not understand everything you see in the helpfiles, but it’s good to keep reading them.

Summary

  • Blocks of code surrounded by curly brackets are functions.
  • Functions do not run unless you tell the interpreter to run them, which you can do with the message "value".
  • You can declare arguments to function. That argument can have a default value.
  • An integer is a whole number
  • If you pass the message "do" to an integer with a function as an argument, that function will run as a loop.
  • Functions can have many arguments. You must pass arguments in the correct order or list them by name.
  • Comments are either surrounded by /* and */ or are on a single line, preceded by //
  • All functions return a value, which is the result of the last line of code in the function
  • Variable names are only valid in the block of code in which they were declared, and within blocks of code contained within that block.
  • There are 26 global variables, each a single lowercase letter a-z.

Problems

  1. Write a function with nested do-loops, so that there is one loop inside the other.
  2. If x and y are intergers and the inner loop is x.do and the outer loop is y.do, how many total times with the inner loop be run?