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!

Wednesday, May 26, 2010

Envelopes

In the last post, we wrote a function to play overtones, but it played them all at the same time. In order to write a piece of music, we need both to be able to tell notes when to start and for how long they should last. This section will be on the latter part of the problem.

"Just like human beings, sounds are born, reach their prime and die; but the life of a sound, from creation to evanescence last only a few seconds" Yamaha GX-1 Guide

We control this process with a type of UGen called an envelope.

(
 SynthDef.new("example3", {arg out = 0, freq = 440, amp = 0.2,
       dur = 1;
  var sin, env_gen, env;
  
  env = Env.triangle(dur, amp);
  env_gen = EnvGen.kr(env);
  sin = SinOsc.ar(freq, mul: env_gen);
  Out.ar(out, sin);
 }).load(s);
)

Synth.new("example3");

There are a few things that make that SynthDef different. One is that it uses variables. Variables are a good way to keep SynthDefs more readable. The easier it is to read, the easier it is to understand, to fix and to change.

The other obvious change is the addition of an envelope. Envelopes have two parts. One is Env. The Env class lets you describe what shape the envelope should have. In this case, we're using a fixed duration envelope shaped like a triangle. When we use a fixed duration envelope, we know the length of the envelope when we create an instance of the Synth. There are other envelopes that we will not know the envelope of until we decide to end the note, for instance, in response to a key up on a keyboard.

triangle is a constructor. It creates a new instance of an Env. The arguments it takes are the duration and the maximum amplitude.

The other part of the envelope is the EnvGen. Since EnvGen is a class, kr must be a constructor. The two most common UGen constructors are ar and kr. You may recall that ar stands for “audio rate.” kr stands for “control rate.” Because the EnvGen is not actually creating sound, but is controlling the amplitude of a sound, we want the control rate. Control rate signals change less often than audio rate signals, and so using kr reduces the load on our computer and makes our SynthDef more efficient.

EnvGen.kr generates the envelope based on the specifications of its first argument, an instance of Env. So, with Env, we define an Envelope. We pass that definition to an Envelop Generator, which plays back the envelope that we defined. We use that envelope to control the amplitude of a SinOsc. We send the output of the SinOsc to an output bus. When the envelope is done, the amplitude of it has returned to zero, so the SinOsc is no longer audible.

With our previous examples, the Synth never ended. The way to stop sound was to stop execution with apple-. . With our new SynthDef, the sound goes on for exactly the length of dur and then ends with no way to restart it. After we create a Synth and play it, there's nothing that can be done with it.

Try running Synth.new("example3"); several times. If you look at the localhost server window, you will see the number of UGens and Synths grow every time you run Synth.new. The Avg and Peak CPU may also increase.

Every time we create a new instance of Synth, we use up more system resources. Eventually we will either run out of memory or the CPU will climb over 100%. We can clear the Synths by hitting apple-., but this still would limit the number of sounds we could play in a row without stopping execution.

We need an automatic way to remove our synths from the server and deallocate their resources. That means that some new Synths would be able to use the resources the old ones were taking up.

Fortunately, EnvGens can both silence a Synth forever and lay it to rest so iti s deallocated. EnvGen takes an argument called “doneAction”. Adding a doneAction to your EnvGen would look like:

env_gen = EnvGen.kr(env, doneAction: 2);

Try changing adding a doneAction to the SynthDef and running Synth.new("example3"); a few times. Notice that now the Synths don’t accumulate. Now, after a Synth stops making sound, it has ended its useful life and so it gets removed from the server because of the doneAction: 2. However, note that the doneAction removes only instances of the Synth. The SynthDef persists and can be used as many times as we'd like.

Envelopes don’t have to only control amplitude, but can control (almost) any aspect of any UGen. You can’t set the bus on Out.ar with an Envelope, but you can control almost anything else. For example, we can cause the frequency to warble


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

  var sin, env_gen, env, freq_env;
  
  env = Env.triangle(dur, amp);
  env_gen = EnvGen.kr(env, doneAction: 2);
  freq_env = EnvGen.kr(Env.sine(dur, 50));

  sin = SinOsc.ar(freq + freq_env, mul: env_gen);
  Out.ar(out, sin);
 }).load(s);
)

Synth.new("example3a");

Note that we only need one of the envelopes to have a doneAction.

Summary

  • Envelopes can be used to control how the amplitude of a Synth changes over time.
  • Env is used to describe the shape of an enevelope.
  • EnvGen.kr is what actually plays the envelope.
  • Synths can be deallocated by using the doneAction argument of EnvGen.
  • Envelopes can also be used to control frequency and many other parameters of SynthDefs.

Problems

  1. Try out the different fixed duration envelopes and try them out with different Oscillators. Especially try out the Env.perc envelope with different noise generators.

Monday, May 24, 2010

Numbers and Math

In the previous post, we talked a bit about Integers, which, remember are whole numbers, like -1, 0, 1, 2. And we learned about the do message.

What are some things you might want to do to a number? Add, subtract, multiply, divide, modulus.

Remember algebra where a*b + c*d = (a * b) + (c * d). And remember how that one cheap calculator just did what you punched in, in the order you punched it in, without respecting order of operations? SuperCollider is like that cheap calculator. Mathematical expressions are evaluated left to right. Addition and subtraction have the same precedence as multiplication and division. That means that if you want to do things in some order, you've got to use parenthesis.

Parentheses are evaluated innermost to outermost:
(a + (b * (c + d)))
( 2 + (4 * (3 + 2))) = (2 + (4 *(5))) = (2 + (20)) = 22

What about order of operations within longer lines of code? If we have func.value(3+4), it evaluates 3+ 4 to 7 before calling the function. We can put anything we want inside those parentheses and it will evaluate what's inside until it gets to the outermost parenthesis and then it will call the function.

One math operation that you might not have seen before is modulus. It means "remainder" and it's represented by a '%'.
10 % 3 = 1
26 % 9 = 8

Ok, so the output of a modulus between two integers is an integer, by definition. And if you add or subtract two integers, you get an integer. As you do if you multiply two integers. But what is the result of division?
3 / 2 = 1.5

1.5 is not an integer. It's a type of number called a Floating point, or in SuperCollider, a Float. A float is a fraction. 1.1, 2.5, -0.2, 5.0 are all Floats. They also can add subtract, etc.

You can do more with numbers than simple math and do loops. There are many interesting and useful messages one can pass to Integer. It has a help file worth reading. So type in the word Integer, starting with a capital-I and press apple-d to look at the help file. The top of that help file says:

superclass: SimpleNumber

superclass is a vocabulary word. It refers to a concept called inheritance. What this superclass designation means is that Integer is a SimpleNumber. When you define classes (recall that a class is the definition for an object), you can define subclasses of any class. A subclass is a special type of the original class. It inherits all the properties of its superclass. So the subclass is the child and the superclass is the parent. We'll come back to this. But what it means for us is that Integer is a SimpleNumber. Which means it understands all the messages that you can pass to a SimpleNumber. So to find out about what these inherited messages are, we should highlight "SimpleNumber" and hit apple-d Float and Integer both inherit from SimpleNumber, so take a look at that help file. Looking at help files and trying stuff out will get you learning the language faster than anything else. They might not make a lot of sense right now, but if you keep looking, you'll get context and be able to figure them out in the future.

We have one more example that hopefully ties all of this together. Remember our SynthDef from posts past? Here's an updated version of it.

(

 SynthDef.new("example2", {arg freq = 440, amp = 0.2;
  Out.ar(0, SinOsc.ar(freq, mul: amp));
 }).load(s);
 
)

We’ve added a field called amp, which indicates the amplitude or volume at which the sound should play. In SuperCollider, amplitudes louder than 1 will peak when you listen to them. Therefore output amplitudes should be between 0, which is silent, and 1, which is as loud as possible.

Now, lets’ write some code to play N overtones of 100Hz. We need to make sure that our overall amplitude doesn’t exceed 1, or else we’ll get peaking. So, we will divide 1 by the number of overtones into 1 to get the amp value to send to each synth.


(
 var func;
 
 func = { arg repeats = 4;
  repeats.do({ arg index;

    Synth.new("example2", [\freq, (index + 1) * 100,
        \amp, 1 / repeats]);
   });
 };
 func.value;
)

Let's hear what happens when we play this. All the sounds play at once! This is because the loop goes through as fast as it can. We'll cover timing issues very soon.

Summary

  • Whole numbers are Integers
  • Real numbers are Floats
  • You can indicate order of operations with parenthesis
  • Math operations are evaluated innermost to outermost parenthesis and left to right
  • Modulus (%) means remainder
  • Subclasses inherit the properties of their superclasses - an Integer is a SimpleNumber
  • Output amplitudes should be between 0 and 1.

Problems

  1. Translate the following algebraic expressions into proper supercollider syntax, using parenthesis where needed. Try to make your answers human-readable.
    1. 3 + 5 * 4
    2. 7 * 2 + 1
    3. 6 * (4 + 2)
    4. 2 * 3 + 4 * 5 + 7 / 2 + 1
  2. Get yourself familiar with modulus by working out these problems by hand.
    1. 5 % 4
    2. 163 % 9
    3. 20 % 5
    4. 17 % 6
    5. 23 % 2
    6. 3 % 5
  3. Write a function to print out the first n multiples of 10, starting from 0. Pass the number of multiples into the function as an argument. Set the default number to 7.

  4. Write a function that takes three arguments, numberOfOvertones, pitch and amp. It should play the number of overtones above pitch specified by the argument numberOfOvertones. The amplitude of the overtones should be half the amplitude of the fundamentals and the total amplitude for the fundamental plus the overtones should be amp.

  5. rand is a message that you can send to numbers. y.rand returns a number between 0 and y. Humans are generally able to hear frequencies between 20Hz and 20000Hz (the freq argument for oscillator UGens is in Hz). Write a function that calls your previous function. You want to play n random pitches and the first m overtones of those pitches. Make sure that the random pitches are all in the audible range. Pass the number of random pitches and the number of overtones into your new function as arguments. Set the default number of random pitches to 2 and the default number of overtones to 3. Make certain that your total amplitude will not peak. Finally, give the arguments descriptive names.

    1. Can you figure out a way to make sure that all your pitches, including overtones, are in the audible range?

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?

Saturday, May 22, 2010

SynthDefs

Last time, I promised we would cover sound-making. First, make sure to boot the localhost server. The localhost server is one of the two small gray boxes at the bottom left of your screen. Press the “Boot” button. When it's booted, the window will say "running" in it and the "Boot" button will turn green and change to say "Quit."

Then, select this code and press enter (not return!). You can select it by double-clicking to the right of the open parenthesis.

 (

  var syn, sound;

  syn = SynthDef.new("example1", {
   Out.ar(0, SinOsc.ar(440));
  });
 
  syn.load(s);

  sound = Synth.new("example1");
 )

When you want to stop the program, press apple-period.

What's going on here? Our program first declares a variable, and then it defines a SynthDef and loads it on the server, then creates a Synth object that plays the sound. This is a complicated example, but I wanted to get you making sounds as soon as possible. Let's look at the example line by line.

The first line is var syn, sound; We are declaring two variables. One is called syn and the other is called sound.

The next line, translated into English, means "I want to create a new SynthDef called 'example1' and store it in the variable syn." Whoa, what's a SynthDef? The SynthDef help file told me that a SynthDef is a "definition of a synth architecture." I got to the help file by highlighting the word SynthDef and pressing apple-d. When I see a term like that, which I don't understand, I can always get help by highlighting it and typing apple-d.

Recall that SuperCollider is not just one program; it is two. One part of the program is the interpreter, which is what we've been talking about so far. The other part of the program is audio server, which is what actually makes the sound. It does not know anything but what it needs to know. This makes it run much faster and more efficiently. You can tell it to make certain kinds of sounds, but in order to run quickly, it wants to know what kinds of sounds you want before you actually make them. You write a description of what you want the server to do. This description, or definition, is called a SynthDef.

When we run SynthDef.new, we get back a new SynthDef, which we're stroing in the variable named syn. Inside the parenthesis are some arguments. SynthDef is the class. new is the message. And "example1" and the stuff in between the curly brackets are the two arguments.

The stuff in the curly brackets is telling the SynthDef what to play. Things between curly brackets {} are called functions. A function is a special type of object that is made up of a code block that you can run. We'll come back to this later.

Out is a UGen. The help file for UGens says, "A UGen or unit generator is an object for generating or processing audio or control signals." UGens exist in SynthDefs. They are what make up a SynthDef. Out is a UGen that writes a signal to a bus, which, in this case, sends it's output to the left channel. .ar is a type of constructor. So Out.ar creates a new instance of a an Out UGen running at the audio rate. ar stands for "audio rate" and is a common constructor name for UGens.

Looking at the arguments to Out.ar, 0 means left channel. If that were a 1, it would mean right channel. And the next thing is what gets sent out, which is a sine tone generator. The argument to SinOsc.ar, 440, is the frequency to play. So SinOsc.ar(440) creates a sine wave at 440 Hz. Out.ar takes that sine wave and sends it out to the left channel, which is channel 0.

The next line, is a closing curly bracket, a close paren and a semicolon. It's the end of the function, the end of the SynthDef and the end of a statement. We've created a new SynthDef that includes in it a function describing what the SynthDef should do when it gets instantiated.

The next line says, take that SynthDef and load it on the server. We're sending a message to syn, saying load. The argument, s, is the server to send it to. In SuperCollider, a lowercase s by itself refers to the audio server.

The next line asks to create a new Synth. The interpreter sends a message to the server, saying, "Hey, could you make a synth that plays the SynthDef called 'example1'?" The server looks and says, "oh yeah, I have a SynthDef called that" and makes a new instance of a Synth, which is running the function that we defined.

We get a new Synth object back from this and store it in "sound."

Let's say we don't want to play an A. Let's say we want to play an E. We can change 440 to 660.


(
 var sound;
 SynthDef.new("example1a", {
 
  Out.ar(0, SinOsc.ar(660));
 }).load(s);
 
 sound = Synth.new("example1a");
)

You'll note that we have one fewer variable in that example. Messages are evaluated left to right. So the interpreter first figures out the result of SynthDef.new("example1a" ... ). Then, it takes that new SynthDef and sends it the message load. We can keep adding on messages at the end as long as we'd like:

object.message1.message2(arg1).message3.message4(arg2)

Of course, this gets silly and difficult to read after a while, but it's legal.

When we're writing a piece, we don't want to have to write a new SynthDef for every note that we're going to play. We can create our own argument, which will tell the SynthDef what frequency to play.


(

 var sound;

 SynthDef.new("example1", { arg freq;
   Out.ar(0, SinOsc.ar(freq));
  }).load(s);

  sound = Synth.new("example1", [\freq, 440]);
)

We call that argument “freq.” An argument is a special kind of variable. You declare then at the top of a code block by using the reserved word “arg.” So the arg freq; part of syn = SynthDef.new("example1", { arg freq;tells SuperCollider that our SynthDef function takes a single argument called freq. We can then use freq like we would any other variable. Here, we’re passing to SinOsc.ar, to use for the frequency.

Passing variables to Synths is a bit different than normal variable passing. The interpreter has to communicate with the audio server. They use a protocol called OSC. The Synth object will handle the OSC for you, but the server does not know which argument you’re trying to pass a value to unless you specifically tell it. So Synth.new takes an optional second argument, which is an Array. An array is a list separated by commas and surrounded by square brackets. When you are passing arguments to the server via a synth, the array must be made up of pairs. The first item in a pair is a symbol or a string and the second is a value. The symbol or string must match the name of the argument. So for an argument called freq, we use the symbol \freq or the string “freq”. If we had an argument called amp, we would use the symbol \amp or the string "amp". A symbol starts with a forward slash: ‘\’. So to pass a value of 440 to the argument freq, our array contains [\freq, 440]. If we had two arguments, one freq and the other amp, we could pass values to freq and amp with an array that looks like [\freq, 440, \amp,0.2]. The array is made up of pairs, to the 440 is paired with freq and 0.2 is paired with amp. The symbols or strings must match the names of the arguments taken by the SynthDef function.

Summary

  • You must boot the server in order to play sounds.
  • There is a variable called "s" which always refers to the server
  • The sounds you want to play must be defined on the server before you play them. They are defined in SynthDefs.
  • Get help by highlighting a class name with the mouse and typing apple-d
  • Blocks of code surrounded by curly braces are functions
  • You play SynthDefs by using Synth
  • You can declare arguments to a function with the word arg: { arg agrument1, argument2;
  • Comma-separated lists surrounded by square brackets are Arrays
  • Synths use arrays to pass arguments: Synth.new(name, [\symbol, argument])

Problems

When you are writing code, if you want to syntax colorize it, so that reserved words are blue, strings are grey, symbols are green, and so on, you can find that in the Format menu or just type apple-‘ (apple-single quote).

  1. Write your own version of “hello world.”
  2. Write your own SynthDefs, using some of the oscillator UGens. To find a list of them, highlight the word “UGens” and type apple-d. Some oscillators to try are Saw and Pulse.

Friday, December 11, 2009

Variables

In previous posts, like, Object Oriented Programming, we talked a bit about objects and variables, but in an abstract way. Now, let's get more specific.

A variable is a name for an object. For example, let's think of a person, Nicole, as an object. Now, Nicole, herself is an object, but the word "Nicole" is a name that refers to Nicole the person. Similarly, we can give names to our objects. These names are called variables. Let's see an example:

(
  var greeting; 
  greeting = "hello world";
  greeting.postln;
)

To run this code, copy it into a new window in Supercollider and then double click just inside the opening parenthesis to highlight all of it. Then, click the enter key. (Not the return key.) (see also: this video for help.) If you look in the Post window, it should say, "hello world."

What's going on there? The first word, "var," is short for variable. A variable is a storage location. It's a place to store a piece of data. The contents of data stored may vary, which is why it's called a variable. The second word "greeting" is a name. It is the name of the variables. Here we are declaring to the interpreter that we will have a variable named "greeting." The interpreter is the part of SuperCollider that reads and runs our programs. So, when the interpreter is reading our program and sees the word "greeting" it will know that greeting is a variable that we've declared. Otherwise, it wouldn't know what we were talking about.

All variable names in SuperCollider must start with a lowercase letter and cannot be a reserved word. You cannot have a variable called "var" because var already has a special meaning to the interpreter.

Next we are assigning a value to the variable. greeting gets "hello world". The variable is on the left of the equal sign. It must always be on the left. There can only ever be one thing on the left of an equals sign. That sign is saying, 'hey, take whatever is on the right of this equals sign and store it under the variable name on the left.'

In the last line, we're sending a postln message to the variable. The SuperCollider interpreter sends that message to greeting. greeting is a String. Strings print themselves with the postln message. So because greeting is a String, the contents of greeting, "hello world", print out.

On the left is the variable name, an object. Then is a period. Then is the name of the message. There are a few different coding styles allowable in SuperCollider, but we're going to focus on receiver notation because it is common across many programming languages. That is:

object.message;

Notice the semi-colons. In Supercollider, all instructions must be separated with a semi-colon. A single instruction can span many lines, so the interpretter needs the semi colon to know when one thing is done and another is starting. Later on, when you're trying to figure out why some program isn't working, you're going to discover a missing semicolon. To be on the safe side, it's good practice to put one at the end of every instruction, as in the example.

Summary

  • Variables are named bits of memory which can store objects.
  • Variables must be declared. Their names must start with lowercase letters
  • You can assign data to a variable by putting it alone on the left side of an equals sign.
  • We send messages to objects with the notation object.message
  • Lines of code must be separated with semicolons

The next chapter will make sounds.

Monday, April 07, 2008

Conductors: a fast GUI

I usually use Conductors because they're quick and dirty and can very easily be used to control a synth or a pbind. This class is from Ron Kuivila and it's included in the Wesleyan build or else available as quark. (See the Quarks helpfile for more on how to get it.)

Here's a quick example:

SynthDef("sin", {arg freq = 440;
  
  Out.ar(0, SinOsc.ar(freq))
}).store;
  
  
Conductor.make({arg this_conductor, freq;
  
  freq.spec_(\freq);
  
  this_conductor.synth_(
    ( instrument: "sin" ),
    [freq: freq]
  )
}).show
  

The constructor method for a conductor is make, which takes a function for an argument. The first argument to the function refers to the freshly-created conductor. Then, for the rest of the arguments, list things you want to control. So I put here "freq". freq is initalised as something called a CV.

CVs are very handy for scaling things. They have a low value, a high value, an initial value and can be linear or exponential. So a fader with a CV can slide back and forth and give you meaningful numbers in the range that you want. There's a bunch of pre-defined CV settings that can be useful for certain applications. Like here, the freq CV is being set to a pre-defined specification for audible frequencies.

Next, we're telling the conductor that it should be controlling a synth.

The first argument here is a list. We tell it which synth to use. We could also say which server to send it to and it's group and things like that.

The second argument is an array of name: value pairs. [name_of_synth_arg: name_of_cv, name_of_another_synth_arg: name_of_another_CV] This usage here ties the CV freq to the synth argument freq.

Finally we show the whole thing and you get a box with some buttons on it and a slider. The syntax of of the Conductor is slightly obtuse, but, as you can see from the box that popped up, you get a lot of stuff for free. If you click on the > button, it will start playing. Wiggle the fader. [] stops it.

We can modify the above example a little bit to see this maybe a bit clearer:

SynthDef("sin2", {arg freq1 = 440, freq2 = 10;
  
  Out.ar(0, SinOsc.ar(freq1, SinOsc.ar(freq2)))
}).store;
  
  
Conductor.make({arg this_conductor, freq, pm;
  
  freq.spec_(\freq);
  pm.spec_(\widefreq);
  
  this_conductor.synth_(
    ( instrument: "sin2" ),
    [freq1: freq, freq2: pm]
  )
}).show;

Ok, so the newer synth does some silly phase modulation.

The conductor now has two CVs called freq and pm. We set pm to have the pre-definied spec for frequencies that include the sub-audible range. Other such specs include: unipolar, bipolar, freq, lofreq, midfreq, widefreq, phase, rq, audiobus, controlbus, midi, midinote, midivelocity, db, amp, boostcut, pan, detune, rate, beats, delay If you want to see what those other ones are like, you can create a bunch of Conductors with them and wiggle the sliders around and see what you get for values.

And finally, if you look at the array to attach the synth, it's a bit clearer that freq1 belongs to the synth and freq comes from the conductor. freq2 belongs to the synth and pm comes from the conductor. And when you show it, you get two sliders.

Using a Conductor with a pattern is really straight forward:

Conductor.make({arg this_conductor, freq, amp;
  
  freq.spec_(\freq);
  amp.spec_(\amp);
  
  this_conductor.pattern_(Pbind(\freq, freq, \amp, amp)); }).show

When you play it, you'll have to turn up the amp. And you can put anything you want in that Pbind. It's just like any other Pbind. Anything you want on a gui, you can put in the argument for the Conductor. You can also do things like:

Conductor.make({arg this_conductor, freq, amp;
  
  freq.sp(3, 0, 5, 1, 'linear');
  amp.spec_(\amp);
  
  this_conductor.pattern_(
  Pbind(
    \amp, amp,
    \freq, Pfunc({[100, 250, 374, 580, 687, 910].at(freq.value)})
  ));
}).show

Here, the freq can be integers between 0 and 5. In the Pbind, a Pfunc uses the value of the freq slider as the index for an array. You need to do a whatver_cv.value to get the value that the slider is giving you. You can also set the value with whatever_cv.value_, which can make your sliders wiggle themselves around. If you want to wiggle them with a joystick or something, you might use whatever_cv.input_ and pass in a value between 0-1. This helps with the scaling.

So, for example, here's a change to the amp:

Conductor.make({arg this_conductor, freq, amp;
  
  freq.sp(3, 0, 5, 1, 'linear');
  amp.spec_(\amp);
  
  amp.value_(0.2);   
  this_conductor.pattern_(
    Pbind(
      \amp, amp,
      \freq, Pfunc({[100, 250, 374, 580, 687, 910].at(freq.value)})
  ));
}).show

Update

Conductors have been moved to become a Quark. This is a plugin system for SuperCollider that allows you to install extra libraries. If you are using the latest version of SuperCollider (any version of 3.7 alpha or higher or any version of 3.6 downloaded in the last several months), simply type: Quarks.gui and evaluate it to get a nice gui for all the quarks. Click the 'update quarks listing' button, then find Conductor in the list and mark it for install and click apply. Restart the interpreter (command-shift-l) before using any newly-installed Quark. If this does not work and you are on 3.6 or lower, see these instructions. For more information about Quarks in general, see the help page.

Sunday, April 09, 2006

Fast Pbind-based Introduction for Experienced Programmers

You may wish to quickly scan the first post to find a bit of background

Online resources

The program

starting SuperCollider
  • Once the program is downloaded and running, it should look like the image at left.
  • Boot a server by clicking on the boot button of the localhost server or the internal server
  • Put your code in a new window
  • run code by selecting it and hitting enter (not return)
  • stop code by hitting command-.
  • get help by selecting the name of a class (or help topic) and typing command-?
  • if you enclose code in parenthesis, you can click on the parenthesis to select a block
  • SuperCollider is a dialect of SmallTalk
  • the server is a different process than the language. they communicate via a protocol called OSC

Code examples

First Program

 Pbind.new.play
  • Pbind is a class. All class names start with capital letters and nothing else can.
  • new is a message, which, by convention, causes a new instance of the class (an object) to be returned.
  • play is a message passed to the new object.
  • object.message
    is the most common syntax in supercollider and is called receiver notation. Other notations are allowed, including normal small talk notation and something called functional notation, which we'll talk about later
  • everything in Supercollider is an object
  • Pbind is part of a set of related classes called patterns

Symbols and Patterns

 Pbind(\dur, 0.5, \freq, 660).play
  • If you don't specify a message to a class, but pass it arguments, it's assumed you're calling new (this is confusing, but you'll see it all the time in help files)
  • Pbinds work by creating a type of object called an event. Events are kind of like the makenote in MAX. It has a bunch of possible parameters and handles a bunch of stuff for you, like computing midinote number based on a supplied frequency or vice versa. It also handles timings by keeping track of server latency (the delay between asking for something to happen and it happening) and sending OSC messages ahead of time
  • Pbinds take as arguments a comma separated list of symbols (which correspond to parameter names) and Patterns (or constant values)

Pfunc

   
 (
  Pbind(
   \dur, 0.4,
   \note, Pfunc({
   
      12.rand
     })
  ).play
 )
  • Pfunc starts with a capital letter, therefore, it must name a class
  • Pfunc is a class, since it's got an argument, there's a constructor being called
  • it takes a function as an argument. functions are declared by code surrounded by curly braces
  • the last line in a function is the value it returns
  • Pfunc is a subclass of Pattern
  • Pbind calls the Pfunc for every event and uses the return value to specify what value goes with \note
  • rand is a message sent to 12, which is an object and an instance of Integer, which is a subclass of SimpleNumber. objects understand all the messages that their class and superclasses understand. When an instance of SimpleNumber (or one of it's subclasses) receives a message of rand, it returns a number between 0 and the object

Declaring Variables and if

 (
  Pbind(
   \dur, 0.4,
   \note, Pfunc({
      var rand_num;
      
      rand_num = 13.rand;
      (rand_num >= 12). if ({
       rand_num = \rest;
      });
      
      rand_num;
     })
  ).play
 )
  • variables are named bits of memory which can store objects. they must be declared. their name must start with a lowercase letter. They only exist within their code block. rand_num only exists in it's Pfunc
  • SuperCollider gives you 26 variables wich it keeps track of all the time. They are named a - z. s holds a pointer to the server
  • = is an assignment so that rand_num holds the result of 13.rand
  • statements have to be separated by semicolons
  • (rand_num >= 12) is an expression which is either true of false. Therefore, it is a type of object called a Boolean. Booleans can take a message of if. If the boolean is true, it will evaluate the function passed as the first argument to if. If it's false, it will evaluate the second function, which is optional. If you don't tell it about second function, it does nothing.
  • \rest tells the Pbind to rest
  • the function returns rand_num, which has in it either an integer or \rest
  • you sometimes also see if with functional notation, which is message(object, arguments) so that would look like
    if((rand_num >= 12), { rand_num = \rest });
    This is not for any particular reason except that visually, it looks more like C or C++

Prout

  
 ( 
  Pbind(
   \dur,  0.4,
   \note,  Prout({
      var bangs, note;
      
      bangs = (4 * 3 * 7 * 2) -1;
      // do full loop twice
     
      bangs.do({ arg index;
    
       note = 10;  
      
       (index % 4 == 0).if ({
        note = note + 5;
       });
       (index % 3 == 0).if ({
        note = note - 3;
       });
       (index % 7 == 0).if ({
        note = note + 6;
       });
      
       note.yield;
      });
    })
  ).play;

  // code stolen from http://www.perl.com/lpt/a/2004/08/31/livecode.html
 )
  • Prouts are like Pfuncs, except they treat their function as a Routine. A Routine is a kind of function which can pause and restart.
  • math operations go form left to right, like a cheap calculator. + and - have the same precedence as * and /, so use parenthesis to specify order of operations
  • // indicates a comment as does /* comment is in between these */
  • giving the message do to an object which is an instance of SimpleNumber causes the function it gets as an argument to be evaluated object+1 times. When it evaluates the function, it gives it an argument the number of iteration it's on, from 0 - object
  • % is modulus, which is the remainder when doing division: 13 % 5 = 3 because 13 = (5 * 2) + 3
  • == tests for equivalency
  • yield is a message which you can pass to any object within a Routine. That object is then returned from the routine, so in this case, Pbind can use it for a note number. When the routine is restarted, it picks up from where it left off.
  • When the Prout eventually runs out, after going through the loop bangs times, it will return nil, which will cause the Pbind to stop playing

Ptpar

 (
 
  Ptpar([0, 
   Pbind(\dur, 0.8,
    \octave, 4,
    \degree, [1, 3, 5],
    \amp, 0.2),
   0, Pbind(\dur, 0.4,
    \octave, 5,
    \degree, Pseq([1, 4, 5, 7, 8, \rest, 5, 7, 5, 5, 6], inf),
    \amp, 0.3)
   ]).play
 )
  • Ptpar takes an array as an argument to it's constructor. Arrays can be declared by putting a list inside square brackets, so [1, 3, 5] is an array. Ptpar's array is made up of pairs of times and patterns. This synchronizes multiple patterns
  • \octave and \degree are just other ways of naming notes
  • if you list an array as one of the value pairs in a pattern, it will create multiple events, one for each item in the array, so the top Pbind sends three sets of OSC messages to the server every time it makes a note
  • Pseq steps through an array, returning each item in turn. It takes two arguments, the first is the array and the second is the number of times to step through it
  • inf is a reserved word which means infinite, so the Pseq never stops stepping through the array. If we gave a 2, instead, it would step through the array twice and then return nil, which will stop the Pbind it is inside, but not the other Pbind, which will keep running forever

Tying things together

 
   (
    Pbind(
     [\freq, \dur], Pfunc({
     
       var pitch, dur;
       
       pitch = (10.rand * 44) + 440;
       if ((pitch < 660), {
        dur = 0.45;
       } , {
        dur = 0.25;
       });
       
       [pitch, dur]
      })
     ).play
   )
  • You can put symbols in arrays and then give them an array of values. The symbol at index n is tied to the value at index n.
  • This lets you tie related parameters together
  • Make sure your Pfunc or Prout or other Pattern returns an array when an array is expected

Accessing Events

  
   (
    Pbind(
    
     \freq, Pwhite(440, 880),
     \dur, Pfunc({arg evt;
          
        (evt.at(\freq) < 660).if ({
         0.45;
        } , {
         0.25;
        });
     })
  ).play
 )
  • Pwhite is a Pattern which returns a value between it's first argument and it's second argument
  • Pfuncs and Prouts can have an argument to their function which is the event as it's been constructed so far. This gives you access to all the parameters which have already been set
  • ifs return the result of the function which they evaluated
  • You can find more Pthings in the help file for Patterns

Array.do and while

   (
    Pbind(
     \dur,  0.25,
     [\freq, \amp], Prout({
     
        var arr, amps;
        
        arr = [];
        amps = [0.2, 0.3, 0.4];
        
        {arr.size < 10}. while({
        
         arr = arr.add((10.rand * 44) + 440);
           arr.do({ arg item, index;
        
          [item, amps.wrapAt(index)].yield;
         })
         
        })
       })
    ).play
   )
  • functions understand the message while. the function is evaluated, if it returns true, the function passed as an argument to the while loop runs once. Then the receiver is evaluated again. This repeats until the receiver evaluates to false.
  • Array.size returns the size of the receiver
  • Array.do is a way to step through an array. The function takes as an argument the array item and the index of the item
  • Array.add is a message which adds an item to an array. Arrays cannot grow in size beyond what they were originally allocated. Therefore, the receiver may be unchanged, although the action returns a new array with the added item. Therefore, if you want to add an item to an array, assign the result to a variable
  • wrapAt is a way to access the contents of the array such, that indexes "wrap around"
    amps.wrapAt(index)
    is equivalent to
    amps.at(index % amps.size)
  • Arrays can be accessed by index

Arrays

Arrays can take other messages, like scramble, which returns a reordered array but does not change the receiver of the message. Choose returns a single randomly chosen item from an array. Many other messages can be found in the helpfiles for Array and it's super classes.

A quick GUI Example

   
   (
   c = Conductor.make({arg thisConductor, arrSize, vol, freq, dur;
    
    vol .spec_(\db);
    freq.spec_(\freq);
   arrSize.sp(10, 1, 50, 1); // sp( val, min, max, step, warp)
    dur.sp(0.45, 0.1, 1, 0);
     
    thisConductor.pattern_(Pbind (
     \db,  vol, 
     \dur, dur,
     \freq, Prout ({
     
         var arr, diff;
        
       arr = [];
       
        
       {arr.size < arrSize.value}. while({
        
        arr = arr.add(freq.value);
          arr.do({ arg item, index;
        
         item.yield;
      });
     });
    })
   ));
  });
  
  c.show;
  )
  • Conductor is a graphical class which gives you some gui widgets. It is included in the Wesleyan build.
  • the first argument is the conductor itself. we can't call it "this" because that's a reserved word
  • the additional arguments are control values, instances of CV. they get GUIS assigned to them usually
  • CV.spec_(\symbol) gives you a predefined ControlSpec which is is designed for a particular type of CV
  • Otherwise, you can define your own. step refers to how big the steps are between values
  • You can give the Conductor a pattern (such as a Pbind) and use the CVs as Patterns
  • If you have to refer to CV in a function or a routine and you want the value that the slider is currently at, use CV.value

Wednesday, February 16, 2005

Server

In the introduction, we executed some code:

 Sever.local.boot

Server is a class. We can tell it is a class because in SuperCollider, all class names start with capital letters. Nothing else may start with a capital letter.

local is a message. Supercollider uses a syntax called receiver notation, which looks like:

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

Or

 Class.message(argument1, argument2, . . . argumentN)

The arguments are optional. So “Server.local” takes the class “Server” and passes the message “local” to it.

When we send a message to an object or a class, we get a return value. The object or class gives us something back. Often, but not always, what we get back is the object or class that we just sent the message to. Sometimes, however, we get back something else. Server.local returns an object that refers to the localhost server.

Server.local is a getter message. The Server class contains some data that belongs to the class rather than to any particular instance of the class. We can get at that data by using a getter message. Getter messages return data stored within an object or a class. Setter messages have a similar concept. They set a piece of data within an object or a class. We’ll talk about them more later.

We then take the result of Server.local (which is an object that refers to the localhost server) and send that object the message “boot.” Expressions are evaluated left to right, so Server.local.boot is equivalent to (Server.local).boot. An expression is a bit of code that returns a value. Server.local is an expression because it returns something. Server.local.boot is also an expression because it also returns something.

Server.local is not, itself, the localhost server, as that is a separate process. However, Server.local contains information about the localhost server. The object knows how to communicate with the Server via OSC. So when we tell it “boot”, it translates that for us into an OSC message and sends that message to the separate Server process.

  • Class names must start with capital letters and are the only things that start with capital letters.
  • We pass messages in receiver notation as object.message(argument) or Class.message(argument)
  • Classes and objects return something when you send them messages.
  • Getter messages return data stored within an object or a class.
  • Expressions are bits of code that return something. They are evaluated from left to right.
  • Server.local is an object which can do OSC communication for us.

Object Oriented Programming

Programs and Algorithms

An algorithm is a step-by-step way to solve a problem or complete a task. You can think of it like a musical score. You play a score in order from left to right, playing each note or rest one after each other, jumping backwards in case of repeats and forwards in cases of things like second endings or codas. In the same way, you can tell your computer to play a B for two seconds and then an A and tell it to repeat back and so on. You're able to tell your computer to do more complicated things, like play a C if you move your mouse to the upper right hand corner and a D in the left hand corner.

For example, imagine making a toasted bagel with cream cheese. Your algorithm might go like this:

  1. Get a bagel
  2. Cut bagel in half
  3. Toast the bagel
  4. Spread cream cheese on each half, on the side that was cut.

Programs are coded according to algorithms. A program is a series of instructions that a computer follows to complete a task. In order to communicate instructions to your computer, you need to be able to speak a language in common with it. The native language of computers is called machine code and is made up of nothing but ones and zeros. Every kind of computer speaks a different dialect of machine code. Fortunately, there exist programming languages that are easier for humans to learn. You write your instructions (or code) in the SuperCollider language and when you evaluate them (by highlighting them and pressing enter), the interpreter translates them to machine code. Your program is made up of the lines of code that you write.

  • You start by thinking of your algorithm and then create a program to implement it.

Objects and Classes: a theoretical example

Let’s use our bagel algorithm to create pseudo-code that looks like object-oriented code. Pseudo-code is a mockup of a program. It will look like a SuperCollider program would look if it dealt with food instead of sounds.

Our algorithm starts with:

  1. Get a bagel

In real life, we would get a bagel from a bag of bagels. In an object-oriented language, we would think of the bagel as an object that we could manipulate. An object is an entity containing data and methods for accessing that data. The definition of a type object is called a class.

A bagel object would probably contain information about whether it was cut or toasted and what toppings it would have. Classes define objects and are a blueprint for object creation. They also can create objects. Classes contain methods called constructors that create new instances of the class and initialize them. We would send a message to the Bagel class asking it to make us a bagel object.

 Bagel.new;

Bagel is the class. new is the message. Once we have the new object, we need to remember it, so let’s change that to give it a name:

 my_bagel = Bagel.new;

my_bagel is the name of the newly created bagel. The equals sign is an assignment statement. my_bagel gets the new bagel. We’ll come back to this later.

  1. Cut bagel in half

In real life, we would do this with a knife. In object oriented programming, objects provide their own methods for changing their state. We send a message to the bagel saying we want it cut.

 my_bagel.cut;

my_bagel is the object. cut is the message.

  1. Toast the bagel

Again, in real life, we would put the bagel in a toaster. But, since this is an object, we send a message telling it we want it toasted.

 my_bagel.toast;
  1. Spread cream cheese on each half, on the side that was cut.

The bagel will know what to do. The author of the class has already written a method that applies a topping to each half. We just have to specify which topping we want, by using an argument. Arguments are additional data we pass along while sending a message to an object.

 my_bagel.spread(cream_cheese);

So, adding in the cream_cheese creation, our pseudo-code bagel program would look like:

 (

  var my_bagel, cream_cheese;
 
  my_bagel = Bagel.new;
  cream_cheese = Cheese.new(\cream);
 
  my_bagel.cut;
  my_bagel.toast;
  my_bagel.spread(cream_cheese);
 
 )
  • Objects are instances of the Classes that define them.
  • Objects contain data and methods to access them.
  • Classes are factories for creating new instances of objects.
  • We communicate with objects and classes by sending them messages.
  • We can specify additional information by providing arguments with our messages.

Additional reading on programs, algorithms and pseudo-code

Sunday, February 06, 2005

Introduction

What is SuperCollider?

According to http://supercollider.sf.net/:

SuperCollider is a state of the art, realtime sound synthesis server as well as an interpreted Object Oriented language which is based on Smalltalk but with C language family syntax. The language functions as a network client to the sound synthesis server.

SC was written by James McCartney over a period of many years. It is now an open source GPL'd project maintained and developed by James and a few others.

Which is to say that SuperCollider is a tool to help you use your computer to make sounds. It’s free and open source. That means that you can look at how SuperCollider was written and modify it, share it with other people and use it any way you want.

SuperCollider has a steeper learning curve than some other music programs like MAX, but it is more flexible and more powerful. This book is written for people who have not programmed before. If you can use your computer to do things like edit a document and surf the web, you can learn to program.

What Can You Do With It?

  1. Digital synthesis
    Supercollider can make any sound that can be created by DSP.
  2. FX processing
    SC can do delays, filters, etc and can tweak a line-in or a pre-existing sound file in any way that you can think to program
  3. Algorithmic composition
    Supercollider can generate sounds and play them, and it can also generate MIDI files that can be opened by Finale, Sibelius or other notation software and arranged for real instruments

Two Applications for the Price of One

SuperCollider is actually two applications. One application is an interpreter: an application designed to execute your Object Oriented programs written in the SuperCollider language.

The other program is an extremely fast and efficient sound synthesizer, which makes all the sound. This program is called a server. It can run from within the interpreter or as a separate process. An internal server (one run within SuperCollider) has a small speed advantage over a separate process. Also, there are some things that can only be run from an internal server, such as an oscilloscope plug in which lets you view the waveforms produced by the server. However, if you crash the server, the interpreter will also crash and vice versa.

A server run as a separate process is called the localhost server. Because the process is separate, there is a stability advantage because a server crash does not also crash your interpreter or vice versa. You can run a server on a separate computer, if you’d like and even communicate with it via Rendezvous. The interpreter client and audio server communicate via a network protocol called OSC. The interpreter sends OSC messages to the server based on your programs and the server sends messages back, based on what it’s doing.

About this book

Explanatory text looks like all the text we have seen so far.

Code examples look like this.

Vocabulary words are in bold and are usually followed by a definition. They can also be found in the glossary.

Key points are summarized at the end of every section in a bulleted list.

The first part of this book is about the interpreter and the programming language it uses. The second part of this book is about the server and sound design.

Getting Started

First, you need a copy of SuperCollider 3, otherwise known as SC3. You can download it from http://sf.net/project/showfiles.php?group_id=54622. SC3 exists for Mac OSX, Windows and Linux. The Mac version is the most developed and the most stable and the version referred to by this book. However, aside from the appearance and the key-shortcuts, the Windows and Linux versions should be virtually the same.

There are some websites designed to help SuperCollider users, including the SC home page at http://www.audiosynth.com/, the SWIKI at http://swiki.hfbk-hamburg.de:8888/MusicTechnology/6, and the Electronic Life SC Forum at http://electroniclife.co.uk/scforum/index.php

Using SuperCollider

Once you have downloaded SuperCollider and installed it, double click on the icon. Three windows should open on your screen. A big text window called "Untitled" should print out some information and there should be two smaller windows below it called "localhost server" and "internal server." If there is an error in the Untitled window, the two server windows will not open. Try downloading a different build of SuperCollider or running it on a different machine.

The Untitled window is where text output goes. The other two windows control two different versions of the audio Server. The examples in this document use the localhost server. If you want to hear audio, you must boot the audio server, which you can do by pressing the "Boot" button. When you press the “Boot” button on the audio server, the interpreter starts up the server application. When the server is finished booting, it sends an OSC message to the interpreter saying that it booted. Then the interpreter changes the color of the word “localhost” to red and the “Boot” button changes to say “Quit.” You can also boot a server from within a program, as we will see shortly.

To run code, you highlight it with the mouse and then press the Enter key, NOT the return key. (The enter key may be located next to your arrows or in your number pad.) To stop code that is running, hit apple-period.

To get help, hit apple-shift-?. To get help on a specific topic, for instance on Synth, highlight the word Synth and hit apple-shift-?.

Your First Program

Open a new window, which you can do under the File menu or by typing apple-n.

Boot the localhost server. You can do this from within the interpreter. To do this, type in the new window:

 Sever.local.boot

Highlight the code you just typed with the mouse and then press the enter key. Then, after the server finishes booting, type:

 Event.default.play

Highlight the code you just typed with the mouse and then press the enter key. You should hear a single short A. If you do not hear a sound, make sure that you can hear other sounds from computer and the volume is turned up. Make sure the localhost server is booted (and not the internal server). If you are still having trouble, try downloading a different build of SuperCollider or asking somebody knowledgeable for help.

Chapter Summary

  • SuperCollider is a free tool for making music that can do DSP and algorithmic composition.
  • SuperCollider is a synthesis server and an interpreter than can run together or separately.
  • You must run a server (either internal, localhost or on another computer) to hear sound.
  • Highlight code and press enter to execute it.
  • Press apple-period to stop execution
  • Press apple-question to get help