From Fedora Project Wiki
(first complete prose version)
m (added "How to Get Help" and "Legal Whatever")
Line 551: Line 551:
}
}
</pre>
</pre>
== How to Get Help ==
!! This section needs to be written !!


== Collections ==
== Collections ==
Line 2,244: Line 2,241:


There are many other Functions in the TempoClock Class, related to absolute scheduling, scheduling with bars, and conversion of beats to and from seconds.
There are many other Functions in the TempoClock Class, related to absolute scheduling, scheduling with bars, and conversion of beats to and from seconds.
== How to Get Help ==
Knowing how to get help in SuperCollider is going to play a large part in determining whether you have a productive or frustrating relationship with the language and its components.  There are a large number of ways to get help, but here are some of the most helpful.
=== Use the SuperCollider Help Files ===
SuperCollider comes with an extensive collection of help files, which contain the answers to most of your problems.  The difficulty will be in finding the solution - it's not always located where you think it is, because it often isn't the solution you think it will be.
On Fedora Linux systems, the main help file is located at [file:///usr/share/SuperCollider/Help/Help.html this URL], and it can be viewed in any web browser.  It may also be helpful to browse the directory structure of the help files, located at [file:///usr/share/SuperCollider/Help this URL], which can also be viewed in your web browser.
If you're looking for further explanations of material in this tutorial, you could start by reviewing the [file:///usr/share/SuperCollider/Help/Tutorials/Getting-Started/Getting%20Started%20With%20SC.html Getting Started With SuperCollider] tutorial, on which this document is based.  The sections in that tutorial roughly correspond to the sections in this guide.
=== Internet Relay Chat ===
If you know how to use IRC, you can join the #supercollider channel on the Freenode network.  The channel does not usually have a large number of participants or a lot of activity, but the users are some of the most polite and helpful on the internet.
=== Email ===
If you feel comfortable sending an email to a mailing list, you can use the [http://www.beast.bham.ac.uk/research/sc_mailing_lists.shtml sc-users] list.  If you decide to subscribe to this list, be aware that it receives a large amount of mail every day.
=== The SuperCollider Website ===
The SuperCollider web site at SourceForge offers links to many resources.  It is available [http://supercollider.sourceforge.net/ here].
== Legal Attribution ==
I'm not sure yet exactly what this will be.

Revision as of 05:33, 24 July 2010

Address: User:Crantila/FSC/Synthesizers/SuperCollider/Basic_Programming

As with any programming language, you will start learning SuperCollider with the basic commands, that are of little use by themselves. However, since the language is so flexible, even the most basic commands can be combined in ways that create highly complex behaviours. The example program, "Method One," was written with the goal of illustrating how a single sound-generating object can be used to create an entire composition. This tutorial does not begin with audio-generating code, which helps to emphasize that SuperCollider is primarily a programming language.

This portion of the Guide is designed as a "reference textbook," which you can use both to learn the SuperCollider language in the first place, and to remind yourself about the language's features afterwards.

The Guide will probably be most effective when read only in small portions at once.


First Steps

The Different Parts of SuperCollider

As you discovered when installing SuperCollider, there are actually many different components involved with SuperCollider. Here is a list of some of them, with brief descriptions of their purpose:

  • Programming language: this is an abstract set of rules and guidelines that allow you to write down instructions for producing sounds.
  • Interpreter: this is what is run in GEdit; it transforms the "programming language" instructions written by you into useful instructions for the server; also called the "client."
  • Server: this is what synthesizes the sound, according to instructions sent to it by the interpreter.
  • Library: these contain commands and the instructions to be executed when you call the commands; the interpreter looks up commands in the library when you call them.

This modular design allows for several advanced capabilities and features. Any particular element could theoretically be replaced without affecting other elements, as long as the methods of communication remain the same. As long as the programming language is the same, portions of the library can be modified, removed, or added at will; this happens often, and Planet CCRMA at Home provides a collection of library extensions. One of the most exciting capabilities is the ability to run the interpreter and server on different physical computers. The networking component is built into these components - they always communicate by UDP or TCP, even when run on the same computer! Although this ability is not used in this Guide, it is not difficult.

The most important thing to remember is that the SuperCollider interpreter is what deals with the programs you write. The SuperCollider server is controlled by the interpreter, but is an independent program. For simple things, like the Hello World Programs below, the server is not even used - after all, there is no audio for it to synthesize.

"Hello, World!"

The first program that one traditionally makes when learning a new programming language is called "The Hello World Program." This is a simple and trivial application that simply prints outs the phrase, "Hello, World!" (or any variation of it). It might seem useless at first, but the ability to provide feedback to an application's user is very important, and this is essentially what the Hello World Program does.

Here is the program in SuperCollider:

"Hello, World!".postln;

Here is an extension to that program:

"Hello, World!".postln;
"Hello, SC!".postln;

As with all examples in this Guide, you should paste these programs into GEdit, and execute them with SuperCollider. Look at the output produced by the programs, but don't worry about it for now.

These programs are very small, but it highlights some key concepts of the SuperCollider language, described below.

Return Values

Every SuperCollider program must provide the interpreter with a value (some information) when it has carried out all of its instructions. This value is called a "return value," because it is the value given by a program when it "returns" control to the interpreter. In a SuperCollider program, it is the last value stated in a program that automatically becomes the return value - no special command is required. When program execution ends, and control is returned to the SuperCollider interpreter, the interpreter outputs the return value in the "SuperCollider output" pane.

In the single-line Hello World Program above, the program produces the following output:

Hello, World!
Hello, World!

The program appears to have been executed twice, but that is not the case. The first "Hello, World!" is printed by the program. The second "Hello, World!" appears because "Hello, World!.postln is the last (in this case, the only) value of the program. It is "returned" by the program, and the interpreter prints it.

In the two-line Hello World Program above, the program produces the following output:

Hello, World!
Hello, SC!
Hello, SC!

This makes it more clear that the program is not being executed twice, and that it is the last value of a program that is returned to the interpreter.

Try executing the following single-line programs. Look at the output produced by each, and determine whether it is printed by the program itself, the interpreter, or both.

  • "Hello, World!".postln;
  • "Hello, World!";
  • 5.postln;
  • 5;

Can you modify the two-line Hello World Program so that each line is printed only once?

Note: In reality, every "function" must return a value. Functions are described later in this Guide; the difference is not yet important.

Statements

A "statement" is a single instruction, which is always ended with a semicolon. Exactly what constitutes a statement will become clear as you gain experience, and including semicolons will quickly become an automatic action.

In the Hello World Programs above, all of the statements contain the single instruction to post a line to the output screen. What happens when you remove the first semicolon, which marks the end of the first statement? The SuperCollider interpreter produces an unhelpful error message, and tells you that an error occurred after the forgotten semicolon. This is why it is important to always remember statement-concluding semicolons.

Data Types: Numbers and Strings

In many programming languages, it is the programmer's responsibility to determine the type of data that is being used, and how it should be stored. The SuperCollider interpreter takes advantage of the power of modern computers, and deals with this on our behalf. This greatly simplifies basic tasks, because there are only two kinds of data to worry about, and they make perfect sense:

  • Numbers: These are numbers, written simply as numbers. Anything that can be done with real-world numbers can also be done with SuperCollider's numbers. They can be as large or small, positive or negative as you want. They can have any number of digits on either side of the decimal point.
  • Strings: These are a string of characters, written between two double-quote characters like "this." The double-quote characters are required so that SuperCollider knows where to begin and end the string of characters. A string of character can contain as many characters as you like, including one character and no characters. If you want to include a double-quote character in a string, you should put a blackslash before it. The following is interpreted by SuperCollider as a string with only a double-quote character: "\""

Here are some examples of numbers and strings:

  • 5;
  • 18920982341;
  • 0.00000000000001;
  • "characters";
  • "@";
  • "";
  • "6";

Is the last example a number or a string? You and I recognize that it is a number inside a string, but SuperCollider will only treat it as a string. You can do string things with it, but you cannot do number things with it. You cannot add "6" to something, for example. Notice also that each example ends with a semicolon, which makes them complete statements. The statements don't do anything but represent themselves.

Try executing the following single-line programs. Think about why the SuperCollider interpreter produces the output that it does.

  • 6 + 3;
  • "6" + 3;
  • "six" + 3;

Simultaneous Execution

Complex SuperCollider programs contain many parts, which all do different things. Sometimes, executing all of these together doesn't make sense, and it can be difficult to know which portions of the program are supposed to be executed when. To help with this, the interpreter allows you to mark portions of your program between ( and ) so that you will know to execute them together.

Here is an example:

(
  "Hello, Fred!".postln;
  "Hello, Wilma!".postln;
)
(
  "Goodbye, Fred!".postln;
  "Goodbye, Wilma!".postln;
)

It doesn't make sense to say "hello" and "goodbye" at the same time, so separating these sections with parentheses will serve as a reminder. In case we try to execute all of the code at once, the SuperCollider interpreter will give us an error.

Variables and Functions

The concepts in this section are related to the mathematical terms with the same names. This is a modern-day result of the first uses of computers and programming languages: the calculation of complex mathematical problems.

Variables

A variable is a symbol that can be assigned an arbitrary value. A "symbol" is a series of alphabetic and numeric characters, separated by whitespace (a space, a line-break, or the end of the file). When a variable is "assigned" a value, the variable name (the symbol) is understood to be a substitute for the assigned value.

Consider a traffic light, which has three possible symbols: green, yellow, and red. When you are driving, and you encounter a traffic light, you might see that its red symbol is activated (the red light is illuminated). What you see is a red light, but you understand that it means you should stop your car. Red lights in general do not make you stop - it is specifically red traffic lights, because we know that it is a symbol meaning to stop.

SuperCollider's variables work in the same way: you tell the interpreter that you want to use a symbol, like cheese. Then you assign cheese a value, like 5. After that point, whenever you use cheese, the interpreter will automatically know that what you really mean is 5.

Run the following two programs. They should result in the same output.

(
  5 + 5;
)
(
  var cheese;
  cheese = 5;
  cheese + cheese;
)

In the first example, the program calculates the value of 5 + 5, which is 10, and returns that to the interpreter, which prints it out. In the second example, the program tells the interpreter that it wants to use a variable called cheese then it assigns cheese the value 5. Finally, the program calculates cheese + cheese, which it understands as meaning 5 + 5, and returns 10 to the interpreter, which prints it out.

This trivial use of a variable does nothing but complicate the process of adding 5 to itself. Soon you will see that variables can greatly simplify your programs.

Using Variables

There are three words that describe the key stages of using a variable: declaration, initialization, and assignment.

A variable must be declared before use, so that the interpreter knows that you want to use that symbol as a variable. All variables must be declared before any statement that does not declare a variable; in other words, you should declare your variables before doing anything else. Variable names are declared like this:

var variableName;

Variables can also be declared in lists, like this:

var variableOne, variableTwo;

Variables can be assigned a value at any time after they have been declared. Any single object can be assigned to a variable. If a variable is already assigned a value, any subsequent assignment will erase the previous assignment; the previously-assigned value will is not retrievable.

The first assignment to a variable is said to "initialize" the variable. Initialization is a special kind of assignment, because a variable cannot be used before it is initialized. If a program attempts to use an un-initialized variable, the SuperCollider interpreter will cause an error. For this reason, you should always initialize a variable when you declare it. There is a special way to do this:

var variableName = nil;

Since you can't always assign a useful value, you can pick an arbitrary one. Assigning "nil" is common practice, because it means "nothing," but without actually being nothing (this avoids some errors). Assigning zero is another possibility; it is standard practice in many programming languages, and will avoid most errors, even if the variable is eventually supposed to hold another kind of object. Intialization and declaration of multiple variables can also be done as a list:

var variableOne = 0, variableTwo = 0;

Single-letter variable names have a special purpose in SuperCollider. They are already declared, so you don't have to declare them. They are also already initialized to "nil", so you don't have to do that either. These variable names are intended to be used as a quick fix, while you're experimenting with how to make a program work. You should not use them in good-quality programs.

The single-letter variable "s" is automatically assigned to the server on the computer running the interpreter. You should avoid re-assigning that variable.

Variable names should always begin with a lower-case letter.

Use variables to write programs that do the following tasks:

  1. Perform arithmetic with an uninitialized variable. An error should appear when the program is executed.
  2. Calculate the value of y, if all other values are known, for the quadratic equation: y = a * x * x + b * x + c
  3. Re-write the Hello World Program so that it will say "Hello" to a name stored in a variable. Remember that you can use the interpreter to automatically output the last line of a function.

Functions

A Function is a statement, or a series of statements, that we want to use many times. When a Function is assigned to a variable, you can execute the Function as many times as you wish. Any statements that happen between braces { like this; } are treated as a Function. Functions are executed by passing them the "value" message, as in the following example.

Here is a Function that is not assigned to a variable, and is executed once.

{ "Hello, World!".postln; }.value;

Notice that there are two semicolons: one after the statement within the Function, and one after the "value" message that tells the Function to execute.

Here is a Function with identical function, assigned to a variable, and executed twice.

var myFunction = { "Hello, World!".postln; }; // note two semicolons
myFunction.value;
myFunction.value;

Function Arguments

The most useful aspect of Functions is that they can produce varying results, depending on their input. For whatever reason, the input accepted by a Function is called an "argument." SuperCollider's Functions can accept any number of arguments - zero, one, or many. Argument values (called "parameters") are provided to a Function by adding them in parentheses after the name of the Function, separated with commas, like this: exampleFunction( 5, 7, 9 ); Argument variables are declared as the first statement in a Function, like this: arg oneNumber, twoNumber;

This program is significantly more complicated than previous examples, but it shows how useful Functions can be. Notice how the braces in that example are on different lines than the rest of the Function, which gives us more space within the Function to complete some useful work.

(
   var greeter =
   {
      arg name;
      ( "Hello" + name ).postln;
   };
   
   greeter.value( "Samantha" );
   greeter.value( "Jermain" );
   nil;
)

Here is how the program works:

  1. A variable named greeter is declared, and assigned a Function.
  2. The Function contains an argument called name, and outputs "Hello" plus the name given to it.
  3. The parentheses here ( "Hello" + name ) ensure that the two strings are added together before the "postln" message prints them out.
  4. The greeter variable is used to call the Function with two different names.
  5. The nil; statement is optional, and does not affect the operation of the program. What it does is return a "nothing" value to the interpreter after program execution completes, so that the last message is not repeated.

Since every argument has a name, SuperCollider allows you to use that name when executing the Function. This example executes the greeter Function from the last example:

greeter.value( name:"Myung-Whun" );

This is more useful if there are many arguments, and you do not remember the order that they appear in the Function's definition.

SuperCollider also allows you to specify default values for arguments, so that they do not need to be specified. This allows optional customization of a Function's behaviour, and is therefore very powerful.

This example modifies the one above by adding default-value arguments, and by calling arguments with their name. As you can see, I've been tricking you a bit: postln is actually a Function, but a special kind, explained later.

(
   var greeter =
   {
      arg name, greeting = "Hello";
      postln( greeting + name );
   };
   
   greeter.value( "Samantha" );
   greeter.value( "Jermain", "Goodbye" );
   greeter.value( name:"Myung-Whun" );
   greeter.value( greeting:"Bienvenue", name:"Marcel" );
   nil;
)

Any value can be used as a parameter, as long as the Function expects it. In fact, even Functions can be used as parameters for Functions!

Function Return Values

All SuperCollider Functions return a value to the interpreter when they have finished execution. As with programs, the value returned is the value of the last statement in the Function. The return value of a Function can be captured, assigned to a variable, and used again later.

This example assigns the result of a Function to a variable.

(
   var mysticalMath =
   {
      arg input = 0;
      input * 23;
   };
   var someNumber = 9;
   
   someNumber = mysticalMath.value( someNumber );
   someNumber.postln;
   nil;
)

Here is how the program works:

  1. A Function and variable are created, and assigned values.
  2. This line someNumber = mysticalMath.value( someNumber ); executes the mysticalMath Function, which multiplies its argument by 23 and returns the value. Then, it assigns the return value of the Function to someNumber. In any statement that contains an assignment, the assignment is always done last. In other words, the Function in this example will always be given an argument of 9, and only after the Function completes execution and returns a value will that value be assigned to someNumber.
  3. The new value of someNumber is displayed.

The program could have been shortened like this:

(
   var mysticalMath =
   {
      arg input = 0;
      input * 23;
   };
   
   var someNumber = mysticalMath.value( 9 );
   someNumber.postln;
   nil;
)

It could have been shortened even more like this:

(
   var mysticalMath =
   {
      arg input = 0;
      input * 23;
   };
   
   mysticalMath.value( 9 ).postln;
   nil;
)

Experiment with the shortened versions of the program, ensuring that you know why they work.

Variable Scope

A variable is only valid within its "scope." A variable's scope is determined by where it is declared. It will always last between either ( and ) or { and }, and applies to all statements within that block of code. Variable names can be re-declared in some contexts, which can be confusing.

Consider the scope of the variables in this example:

(
   var zero = 0;
   var function =
   {
      var zero = 8;
      var sixteen = 16;
      zero.postln; // always prints 8
   };
   
   function.value;
   zero.postln; // always prints 0
   sixteen.postln; // always causes an error
)

Because function declares its own copy of zero, it is modified independently of the variable zero declared before the Function. Every time function is executed, it re-declares its own zero, and the interpreter keeps it separate from any other variables with the same name. When function has finished executing, the interpreter destroys its variables. Variables declared inside any Function are only ever accessible from within that Function. This is why, when we try to execute sixteen.postln;, the interpreter encounters an error: sixteen exists only within function, and is not accessible outside the Function. By the way, in order to excute this example, you will need to remove the error-causing reference to sixteen.

Now consider the scope of the variables in this example:

(
   var zero = 0;
   var function =
   {
      var sixteen = 16;
      zero = 8;
      zero.postln; // always prints 8
      sixteen.postln;
   };
   
   function.value;
   zero.postln; // always prints 8
)

Why does the last line always print 8? It's because zero was set to 8 within function. More importantly, function did not declare its own copy of zero, so it simply accesses the one declared in the next "highest" block of code, which exists between ( and ) in this example.

This is why it's important to pay attention to a variable's scope, and to make sure that you declare your variables in the right place. Unexpected and difficult-to-find programming mistakes can occur when you forget to declare a variable, but it is declared elsewhere in your program: you will be allowed to use the variable, but it will be modified unexpectedly. On the other hand, it can be greatly advantageous to be able to access variables declared "outside the local scope" (meaning variables that aren't declared in the same code block in which they're used), but careful thought and planning is required.

Astute readers will notice that it is possible to re-declare the single-letter variable names, allowing you to control their scope. Consider the following program:

(
   var a = 0;
   b =
   {
      var c = 16;
      a = 8;
      a.postln;
      c.postln;
   };
   
   b.value;
   a.postln;
)

This example requires careful examination. What is the scope of a, b, and c? The answers may be surprising.

  • a is declared just after the ( character, so the interpreter destroys it upon reaching the ) character.
  • c is declared just after the { character, so the interpreter destroys it upon reaching the } character.
  • b is not declared in this program, so it refers to the automatically-declared variable with that name. The interpreter does not destroy it until it is restarted or stopped. This means that the Function assigned to b is still available after the program finishes execution. Try it! Execute the program above, and then execute this single-line program alone: b.value;

Object-Oriented SuperCollider

SuperCollider is difficult to describe precisely, because its syntax allows great flexibility. There are many different ways to accomplish the same task. Each one is subtly different, and gives you a different set of possibilities, but there is often no "best solution." One of the advantages to this is that it easily allows three "programming paradigms," although one is used much more often than the others.

Imperative Programming

Imperative programming is easy to understand: it is simply a list of commands, like this:

(
   var a, b, c;

   a = 12;
   b = 25;
   c = a + b;
   a.postln;
)

Declare the variables, set the variables, do a calculation, and print the result of the calculation. This is a simple example, and a simple model, but it is very difficult to escape completely. After all, humans think of large problems in terms of algorithms (the instructions needed to do something). Computers solve large problems, so being able to program them with a series of instructions makes sense.

Functional Programming

Functional programming is also easy to understand, but it can be a little bit more difficult to think about complex tasks. Functional programs use Functions to complete all of their work. This is not strictly possible in SuperCollider: it is more imperative than functional, but the creative use of Functions can easily solve some problems that are difficult to write with an imperative approach.

The following example is an extension of the "Imperative" example. Pretend that the following Functions exist, and do the following tasks:

  • getinput : allows the user to enter a number, and returns that number
  • add : adds together the numbers given as arguments, returning the sum
(
   postln( add( getinput, getinput ) );
)

SuperCollider will always execute the inner-most Functions first. This is how the interpreter executes the single-line program above:

  1. Execute the left call of getinput
  2. Execute the right call of getinput
  3. Execute add with the two numbers returned by getinput
  4. Execute postln with the number returned by add

Both imperative and functional programming have advantages and disadvantages. SuperCollider will allow you to use either approach, or a mix of both, when solving problems.

Object-oriented Programming

Object-oriented programming is more difficult to think about than imperative or functional. When using this paradigm (mode of thought), almost everything in SuperCollider is thought of as an abstract Object. In this way, it allows programmers to make compelling comparisons to the real world, where all tangible things are objects, and where it is not hard to conceive of most intangible things as objects, too. With object-oriented programming, computer science takes a break from mathematics, and is influenced by philosophy.

!!! Include that Classes always start with an upper-case letter !!!

Anything can be represented as an Object - like a bicycle, for instance. Let's pretend that we have an Object called a Bicycle. We don't yet have a particular bicycle - just the abstract class containing everything that is true about all bicycles. If a Bicycle class exists in SuperCollider, you generate a specific instance like this: var bike = Bicycle.new; All SuperCollider Objects can be instantiated in this way: you get a specific Bicycle from the generic class, and you can then modify and work with your own Object as you choose. The specific properties associated with a particular instance of a class are called instance variables.

There are certain things that Bicycles are designed to do: turn the wheels, turn the handlebar, raise and lower the seat, and so on. You can cause these things to happen by providing a certain input to a real-world Bicycle: if you want to turn the wheels, you might push the pedals. In SuperCollider, you cause things to happen by sending a message to the Object that tells it what you want: if you have a Bicycle, you might turn the wheels like this: bike.turnTheWheels; When you do this, you are actually executing the turnTheWheels Function, which is defined by the abstract Bicycle class. Because it doesn't make sense to turn the wheels of all bicycles in existence, you don't call the method (a synonym for "Function") from the Bicycle class itself, but from the particular instance whose wheels you want to turn. The proper way to access instance variables is by using instance methods.

If this kind of programming is new to you, it might seem extremely difficult. It can be intimidating at first, but it is actually not too difficult to understand once you start to use it. In fact, you have already been using it! Remember the postln command that was described earlier as a special kind of Function? It's actually a Function defined by SuperCollider's abstract class Object, which defines a set of messages that can be passed to any SuperCollider Object. Because most things in SuperCollider are Objects, we can send them the postln message, and they will understand that it means to print themselves in the "SuperCollider output" pane.

Why is it that all Objects respond to the postln message? SuperCollider classes are allowed to belong to other SuperCollider classes, of which they are a part. Consider the Bicycle class again. It is a kind of vehicle, and philosophers might say that "things that are members of the bicycle class are also members of the vehicle class." That is, real-world bicycles share certain characteristics with other real-world objects that are classified as "vehicles." The bicycle class is a "sub-class" of the vehicle class, and it inherits certain properties from the vehicles class. SuperCollider allows this behaviour too, and calls it inheritance. In SuperCollider, since all classes define Objects, they are all automatically considered to be a sub-class of the class called Object. All classes therefore inherit certain characteristics from the Object class, like knowing how to respond to the postln message.

equivalent notation: 5.postln versus postln( 5 )

You still don't know how to write new Classes and Objects in SuperCollider, but knowing how to use them is more than enough for now. By the time you need to write your own Classes, you will probably prefer to use the official SuperCollider help files, anyway.

Choosing a Paradigm

At this point you may begin worrying about which programming paradigm you should choose, and when. The answer is unhelpful: "Whichever seems best for the task."

Let's expand on this. If you are primarily a programmer, then you probably already know how to choose the best paradigm and algorithm for the task. If you are a musician, then you probably just want your program to produce the output that you want (in this case, a particular set of sounds). Part of the beauty of SuperCollider's flexibility is that it allows you to produce the same output in different ways. As a musician this means that, as long as your program works as you want it to work, it doesn't matter how you write it. Experience will teach you more and less effective ways of doing things, but there is no need for rules.

Even so, here are some guidelines that will help you to start thinking about music programs:

  1. Programs of all sorts often follow a simple, four-step flow. Not all parts are always present.
    1. Declare variables.
    2. Get input from the user.
    3. Calculate something with the input.
    4. Provide output to the user (i.e. "make noise").
  2. Repetition is the enemy of correctness, so if you're going to execute some code more than once, try writing a Function.
    1. If it's slightly different every time, try using arguments. Arguments with default values are a great way to expand a Function's usefulness.
  3. If it looks too complicated, then it probably is. The more difficult it is to understand something, the greater the chance of making a mistake.
  4. Don't forget the semicolons at the end of every statement.

Sound-Making Functions

It's finally time to start thinking about Functions that produce sound!

This example is discussed below in the following sections. Remember, when running SuperCollider code in GEdit, you can stop the sound by pressing 'Esc' on your keyboard.

{ SinOsc.ar( 440, 0, 0.2 ); }.play;

UGens

"UGen" stands for "unit generator." UGens are special Objects that generate either an audio or a control signal.

The UGen that will be used for most of the experimentation in this Guide, and which was primarily used to generate the "Method One" program that goes with this Guide, is called SinOsc, which generates a sine wave. The Class' name, "SinOsc," means "sine oscillator."

The example at the beginning of this chapter, SinOsc.ar( 440, 0, 0.2 ); produces an "instance" of the SinOsc Class, which continuously outputs a signal, based on the parameters given in parentheses. This instance produces an "audio rate" signal, which means that it is of sufficient quality to eventually become sound.

A slightly modified version of that code will give us a "control rate" signal: SinOsc.kr( 440, 0, 0.2 ); There is only one small difference between the two examples - for us - but for SuperCollider, the difference is huge. A control rate signal will not be of sufficient quality to become sound; it is used to control other UGens that do become sound.

Unlike other Classes, UGen Classes should not be instantiated with the new message. They should always be instantiated as either audio-rate (by passing the ar message), or control-rate (by passing the kr message). Control-rate signals are calculated much less often than audio-rate signals, which allows the SuperCollider interpreter and server to save processing power where it wouldn't be noticed.

The "play" Function

The play Function does exactly what it says: it plays its input. The input must be a Function with an audio-rate signal generator as the return value.

The following two examples produce the same output:

{ SinOsc.ar( 440, 0, 0.2 ); }.play;
play( { SinOsc.ar( 440, 0, 0.2 ); } );

The first example is written from an object-oriented perspective. Functions know how to play their return value, when passed the play message. This is true of all Functions whose return value is an audio-rate UGen. The second example is written from a functional perspective. The Function called play will play its input, which must be a Function whose return value is an audio-rate UGen. Whether you should write play in the functional or object-oriented way depends on which makes more sense to you.

Try to re-write the above example so that the play Function operates on a variable-defined Function.

The arguments to SinOsc, whether the audio- or control-rate generator, are these:

  • The first is called freq; it sets the frequency.
  • The second is called add; it is added to all values produced by the UGen.
  • The third is called mul; all values produced by the UGen are multiplied by this.

You now know enough to spend hours with the sine oscillator UGen. Try combining audio- and control-rate UGens, and try to figure out what happens when each of the arguments is adjusted. Be careful that your audio interface's volume isn't set too high! Experiment with this one:

(
   var myFrequency =  SinOsc.kr( freq:1, mul:200, add:400 );
   var sound = { SinOsc.ar( myFrequency, 0, 0.2 ); };
   
   play( sound );
)

WHY DOESN'T THAT WORK?!?!?!?!?!?

Multichannel Audio

By now you must be growing tired of the left-side-only sounds being produced by the examples.

Stereo Array

The easiest way to output multichannel audio in SuperCollider is to use a kind of "Collection" (defined later) called an "Array." SuperCollider will theoretically handle any number of audio output channels, but by default is usually only configured for two-channel stereo audio. Since humans have only two ears, this is sufficient for most tasks! A multichannel array is notated like this: [ LeftChannel.ar( x ), RightChannel.ar( y ) ]

Here is our simple sine oscillator expanded to produce stereo audio:

{ [ SinOsc.ar( 440, 0, 0.2 ), SinOsc.ar( 440, 0, 0.2 ) ]; }.play;

Not much has changed, except that the audio we hear is now being emitted from both the left and right channels. Change the frequency of one of the sine oscillators to 450 and the difference will become much more apparent.

Multichannel arrays can also be combined with each other, like this:

{
   var one = [ x, y, z ];
   var two = [ a, b, c ];
   [ one, two ];
}

If a, b, c, x, y, and z were all audio-rate UGens, this Function could be play'ed. It would produce stereo audio, and each channel would have three independent UGens.

Multichannel Expansion

You can automatically create multiple UGens by providing an Array as one of the parameters. The SuperCollider interpreter will automatically create multichannel UGens as a result.

The following two examples produce equivalent output:

{ [ SinOsc.ar( 440, 0, 0.2 ), SinOsc.ar( 440, 0, 0.2 ) ]; }.play;
{ SinOsc.ar( [440, 440], 0, 0.2 ); }.play;

The second example can be easier to read, because it is obvious that only the frequency is changing - or in this case, that nothing is changing. This technique is more useful in a situation like the following:

{ SinOsc.ar( [[440, 445, 450, 455, 460, 465],
              [440, 445, 450, 455, 460, 465]],
             0,
             0.2 ); }.play;

That's not exactly easy to read, but it's easier to figure out than the most obvious alternative:

{
   [[ SinOsc.ar( 440, 0, 0.2 ), SinOsc.ar( 445, 0, 0.2 ), SinOsc.ar( 450, 0, 0.2 ), SinOsc.ar( 455, 0, 0.2 ), SinOsc.ar( 460, 0, 0.2 ), SinOsc.ar( 465, 0, 0.2 ) ],
    [ SinOsc.ar( 440, 0, 0.2 ), SinOsc.ar( 445, 0, 0.2 ), SinOsc.ar( 450, 0, 0.2 ), SinOsc.ar( 455, 0, 0.2 ), SinOsc.ar( 460, 0, 0.2 ), SinOsc.ar( 465, 0, 0.2 ) ]];
}.play;

More importantly, multichannel expansion gives us another tool to avoid repetition. Repetition is the enemy of correctness - it's so much more difficult to find a mistake in the second example than in the first!

Method One

Believe it or not, you now know enough to understand a slightly-modified version of the first part of "Method One," a SuperCollider program written and heavily commented specifically for use with this Guide. You should play this example, and experiment with changing the frequencies, volumes, and so on. The fully-commented version provides a full explanation of how the Function works.

{
   // sets up the frequencies of both channels
   var frequencyL = SinOsc.kr( freq:10, mul:200, add:400 ); // oscillating
   var frequencyR = SinOsc.kr( freq:1, mul:50, add:150 ); // oscillating
   var frequencyL_drone = SinOsc.kr( freq:0.03, mul:20, add:100 ); // drone
   var frequencyR_drone = SinOsc.kr( freq:0.01, mul:20, add:210 ); // drone
   
   // changes the volume of the oscillating part in the left channel
   var volumeL = SinOsc.kr( freq:0.5, mul:0.02, add:0.03 );
   
   // left channel
   var left = [ SinOsc.ar( freq:frequencyL, mul:volumeL ), // this is the oscillating part
                SinOsc.ar( freq:[frequencyL_drone,2*frequencyL_drone], mul:0.02 ), // the rest make up the drone
                SinOsc.ar( freq:[5*frequencyL_drone,7*frequencyL_drone], mul:0.005 ),
                SinOsc.ar( freq:[13*frequencyL_drone,28*frequencyL_drone], mul:0.001 ) ];
   
   // right channel
   var right = [ SinOsc.ar( freq:frequencyR, mul:0.1 ), // this is the oscillating part
                 SinOsc.ar( freq:[frequencyR_drone,2*frequencyR_drone], mul:0.02 ), // the rest make up the drone
                 SinOsc.ar( freq:4*frequencyR_drone, mul:0.005 ),
                 SinOsc.ar( freq:[64*frequencyR_drone,128*frequencyR_drone], mul:0.01 ) ]; // high frequencies!
   
   [ left, right ];
}

Collections

A "collection" is just that - a collection of Objects. Collections are simply a means of organizing a large amount of data, without having to assign a variable name for each portion of data. Compared to other programming languages, SuperCollider provides a relatively large number of Collections in the standard library.

We have already seen an example of a Collection as multichannel audio arrays. An Array is a kind of Collection - in object-oriented terminology, the Array Class is a sub-class of the Collection Class, and inherits its behaviours. Conversely, the Collection Class is the super-class of the Array Class. The Collection Class itself is not to be used; it is designed to provide common features so that it is easier to write Classes for collections.

As with all the chapters from this point on, it is not necessary to read this in sequence. If you prefer, you can skip it and return later when you need to manage a large set of data.

Array

!! MAYBE I DON'T NEED THIS SECTION !!

Arrays have been traditionally been very popular with programmers. In SuperCollider, they are capable of storing a large number of Objects, and they provide advanced behaviours that are normally not associated with Arrays. They are not as indespensible as they used to be. Most programming languages now provide (or can easily be extended to add) Lists, Trees, and other kinds of data storage structures, which offer more capabilities, and are easier to use and to think about. Users new to programming might find the various kinds of Lists to be more helpful.

Building an Array

An Array is a Collection with a finite maximum size, determined at declaration time. It is the programmer's responsibility to maintain a meaningful order, and to remember the meaning of the data. Data in an Array is called "elements," each of which is assigned a specific "index number." Index numbers begin at 0. Any mix of Objects can be stored in an Array, including an Array.

This example declares an Array, adds some elements, then prints them out.

(
   var tA = Array.new( 2 ); // "tA" stands for "testArray"
   
   tA = tA.add( 5 );
   tA = tA.add( 3 );
   tA = tA.add( 17 );
   
   tA.postln;
   nil;
)

Notice that Array is a Class, and it must be instantiated before use. Here, the variable tA is assigned an Array with enough space for two objects. Notice that the elements are printed out in the order that you add them to the Array. They are not sorted or shuffled (unless you send a message like scramble). But why did I write tA = tA.add( 17 ); instead of tA.add( 17 );? Shouldn't the second method be sufficient for adding an Object to an Array, thereby making the re-assignment unnecessary? It does, but let's see what happens when we take it away:

(
   var tA = Array.new( 2 ); // "tA" stands for "testArray"
   
   tA.add( 5 );
   tA.add( 3 );
   tA.add( 17 );
   
   tA.postln;
   nil;
)

The 17 is missing - it doesn't get added into the Array! This is because the Array was only declared with two slots, and you can't add three Objects into two slots. So why did this work the first time? SuperCollider was programmed to help us fit additional items into an Array. If an Array has reached its capacity, SuperCollider will automatically make a new, larger Array for us, and returns that from the add method. Therefore, any time you add an element to an Array, you should always re-assign the result, so that you don't have to worry about whether you exceeded the Array's capacity.

Accessing an Array's Elements

There are two ways to access individual elements within an Array. One way is object-oriented, and one way is more traditional, inspired by programming languages such as the wildly popular "C" language. The object-oriented style uses the at and put methods. The traditional style uses square brackets with an index number.

The following examples produce equivalent output. The first uses the object-oriented style, and the second uses the traditional style.

(
   var tA = Array.new( 3 );
   
   tA = tA.add( 5 );
   tA = tA.add( 3 );
   tA = tA.add( 17 );
   
   tA.at( 0 ).postln; // outputs 5
   tA.at( 1 ).postln; // outputs 3
   tA.at( 2 ).postln; // outputs 17
   
   tA.put( 0, 24 ); // assigns 24 to element 0
   
   tA.at( 0 ).postln; // outputs 24
   
   nil;
)
(
   var tA = Array.new( 3 );
   
   tA = tA.add( 5 );
   tA = tA.add( 3 );
   tA = tA.add( 17 );
   
   tA[0].postln; // outputs 5
   tA[1].postln; // outputs 3
   tA[2].postln; // outputs 17
   
   tA[0] = 24 ; // assigns 24 to element 0
   
   tA[0].postln; // outputs 24
   
   nil;
)

Different people prefer different styles of accessing Arrays.

List

An List is a Collection with an infinite maximum size. It is the programmer's responsibility to maintain a meaningful order, and to remember the meaning of the data. Data in a List is called "elements," each of which is assigned a specific "index number." Index numbers begin at 0. Any mix of Objects can be stored in a List, including a List. Lists and Arrays are very similar, but SuperCollider manages some of the dirty work for you, when you use the List Class.

Building a List

There are four methods which instantiate a List. These are all "Class methods," meaning they do not operate on a specific List, but can be used to make any List.

  • List.new creates a List. You can also specify the initial number of elements as an argument, if you choose.
  • List.newClear( x ) creates a List with x number of slots, filled with nil.
  • List.copyInstance( aList ) creates a List which is a copy of aList.
  • List.newUsing( anArray ) creates a List with the same elements as anArray.

Adding to an Existing List

These are "instance methods," meaning that they operate on a specific list.

  • put( index, item ) adds "item" into the List at index number "index".
  • add( item ) adds "item" to the end of a List.
  • addFirst( item ) adds "item" to the beginning of a List.

Accessing a List

These are "instance methods," meaning that they operate on a specific list.

  • at( index ) returns the Object assigned to the "index" index number. If "index" is greater than the last element in the List, returns "nil".
  • clipAt( index ) returns the Object assigned to the "index" index number. If "index" is greater than the last element in the List, returns the last element in the List.
  • wrapAt( index ) returns the Object assigned to the "index" index number. If "index" is greater than the last element in the List, returns an element based on a "wrap-around" index number. For a three-element List, 0 will return element 0, 1 returns 1, 2 returns 2, 3 returns 0, 4 returns 1, 5 returns 2, 6 returns 0, and so on.
  • foldAt( index ) returns the Object assigned to the "index" index number. If "index" is greater than the last element in the List, returns an element based on a "fold-back" index number. Whereas wrapAt() will always continue from the lowest to the highest index number, foldAt() will change every time: low to high, high to low, low to high, and so on.

Removing from a List

One way to remove an element from a List is to re-assign that element's index number the value "nil". These two Functions also remove elements from a List. They are "instance methods," meaning that they operate on a specific list.

  • pop returns the last element in a List, and removes it from the List.
  • removeAt( index ) removes the element assigned to "index" index number, removing it from the List and shrinking the List. This will not leave a "nil" element in the List.

Examples

The following examples show different ways to use List's.

(
   var tL = List.new;
   
   tL.add( 42 );
   tL.add( 820 );
   
   postln( tL.pop ); // outputs 820
   
   tL.add( 7 );
   tL.add( 19 );
   tL.add( 23 );
   
   postln( tL.pop ); // outputs 23
   postln( tL.pop ); // outputs 19
   postln( tL.pop ); // outputs 7
   postln( tL.pop ); // outputs 42
   
   postln( tL.pop ); // List is empty, so we get "nil"
   
   nil;
)

This code adds numbers to the end of a List, then removes them from the end of the List.

(
   var tL = List.new;
   
   tL.addFirst( 42 );
   tL.addFirst( 820 );
   
   postln( tL.pop ); // outputs 42
   
   tL.addFirst( 7 );
   tL.addFirst ( 19 );
   tL.addFirst ( 23 );
   
   postln( tL.pop ); // outputs 820
   postln( tL.pop ); // outputs 7
   postln( tL.pop ); // outputs 19
   postln( tL.pop ); // outputs 23
   
   postln( tL.pop ); // list is empty, so we get "nil"
   
   nil;
)

This modification of the first example adds numbers to the beginning of a List, then removes them from the end of the List. This is one way to ensure that the List elements are removed in the same order that they are added.

(
   var tL = List.new;
   
   tL.add( 42 );
   tL.add( 820 );
   
   postln( tL.removeAt( 0 ) ); // outputs 42
   
   tL.add( 7 );
   tL.add( 19 );
   tL.add( 23 );
   
   postln( tL.removeAt( 0 ) ); // outputs 820
   postln( tL.removeAt( 0 ) ); // outputs 7
   postln( tL.removeAt( 0 ) ); // outputs 19
   postln( tL.removeAt( 0 ) ); // outputs 23
   
//   postln( tL.removeAt( 0 ) ); // would cause an error
   
   nil;
)

This modification of the first example adds numbers to the end of a List, then removes from the beginning of the List. This is another way to ensure that the List elements are removed in the same order that they're added. Note that, when the List is empty, using the "removeAt()" Function causes an error, because you try to access a List index which doesn't exist.

(
   var tL = List.new;
   
   tL = [42,820,7,19,23];

   tL.at( 0 ).postln; // outputs 42
   tL.at( 1 ).postln; // outputs 820
   tL.at( 2 ).postln; // outputs 7
   tL.at( 3 ).postln; // outputs 19
   tL.at( 4 ).postln; // outputs 23
   tL.at( 5 ).postln; // outputs nil
   tL.at( 6 ).postln; // outputs nil
   
   nil;
)

This example shows another way to add elements to an empty List, which also works for Arrays. Then it shows what happens when you try to access elements beyond the end of a List with the "at()"Function.

(
   var tL = List.new;
   
   tL = [42,820,7,19,23];

   tL.clipAt( 0 ).postln; // outputs 42
   tL.clipAt( 1 ).postln; // outputs 820
   tL.clipAt( 2 ).postln; // outputs 7
   tL.clipAt( 3 ).postln; // outputs 19
   tL.clipAt( 4 ).postln; // outputs 23
   tL.clipAt( 5 ).postln; // outputs 23
   tL.clipAt( 6 ).postln; // outputs 23
   
   nil;
)

This example shows what happens when you try to access elements beyond the end of a List with the "clipAt()" Function. For index numbers beyond the end of the List, the interpreter will simply return the last element.

(
   var tL = List.new;
   
   tL = [42,820,7,19,23];

   tL.foldAt( 0 ).postln; // outputs 42
   tL.foldAt( 1 ).postln; // outputs 820
   tL.foldAt( 2 ).postln; // outputs 7
   tL.foldAt( 3 ).postln; // outputs 19
   tL.foldAt( 4 ).postln; // outputs 23
   tL.foldAt( 5 ).postln; // outputs 19
   tL.foldAt( 6 ).postln; // outputs 7
   
   nil;
)

This example shows what happens when you try to aceess elements beyond the end of a List with the "foldAt()" Function. For index numbers beyond the end of the List, the interpreter will start moving back through the List, towards the first element, "folding" through the List.

(
   var tL = List.new;
   
   tL = [42,820,7,19,23];

   tL.wrapAt( 0 ).postln; // outputs 42
   tL.wrapAt( 1 ).postln; // outputs 820
   tL.wrapAt( 2 ).postln; // outputs 7
   tL.wrapAt( 3 ).postln; // outputs 19
   tL.wrapAt( 4 ).postln; // outputs 23
   tL.wrapAt( 5 ).postln; // outputs 42
   tL.wrapAt( 6 ).postln; // outputs 820
   
   nil;
)

This example shows what happens when you try to access elements beyond the end of a List with the "wrapAt()" Function. For index numbers beyond the end of the List, the interpreter will start again at the beginning of the List, "wrapping" around to the beginning.

LinkedList

Linked lists are very common structures for data management in computer science. They are more efficient than arrays for many tasks, particularly when it's impossible to know how many elements will be required in an array until the program is run. SuperCollider's List Class is implemented with arrays, and it offers nearly the same functionality as the LinkedList class.

A true linked list is accessed most efficiently from the start (called the "head" of the list) or the end (called the "tail"). Each element is linked to the one before it, the one after it, or both. SuperCollider's LinkedList Class has elements which are linked both to the preceding and following elements, so it is called a "doubly linked list."

Knowing when to use a LinkedList over a List is a question of efficiency, and for small collections of information, it isn't going to make a big difference - you might as well use a basic List. When you plan to store hundreds or thousands of elements, choosing the right Class becomes more important, and can save a lot of processor time. Here is how to know which Class you should use:

  • If you're going to be adding elements to the start or end of the list, and accessing from the start or end of the list, the LinkedList Class will be more efficient.
  • If you're going to be adding elements at arbitrary index numbers inside the list, and accessing elements at arbitrary index numbers inside the list, the List Class will be more efficient.
  • If you're going to be adding elements to the start or end, but accessing specific indices, or adding elements at specific indices, but accessing from the start or end, then you get to choose where to save computation time. In one of these cases, it might not matter which one you choose.

Efficient Functions

These Functions make use of the LinkedList Class in an efficient way. They are efficient because they access only the first or last element in the LinkedList.

  • add( obj ) adds "obj" to a LinkedList as the last item.
  • addFirst( obj ) adds "obj" to a LinkedList as the first item.
  • pop removes the last item in a LinkedList and returns it.
  • popFirst removes the first item in a LinkedList and returns it.
  • first returns a copy of the first item in a LinkedList.
  • last returns a copy of the last item in a LinkedList.

Inefficient Functions

These Functions make use of the LinkedList Class in an inefficient way, but they can be useful. They are inefficient because they may potentially have to review all of the elements in a LinkedList before completing.

  • at( index ) pretends the LinkedList is an Array, and returns a copy of what would be the element at the given index number.
  • put( index, obj ) pretends the LinkedList is an Array, and changes the element at the given index number to be "obj."
  • remove( obj ) searches through a LinkedList and removes the element judged to be equal to "obj," regardless of its index number.
  • removeAt( index ) pretends the LinkedList is an Array, and removes the element located at the given index number.

Example

This example uses a LinkedList as a queue, adding numbers to the tail, and removing and printing from the head.

(
   var tL = LinkedList.new;
   
   tL.add( 42 );
   tL.add( 89 );
   
   tL.popFirst.postln; // prints 42
   
   tL.add( 256 );
   
   tL.popFirst.postln; // prints 89
   
   tL.add( 900 );
   
   tL.popFirst.postln; // prints 256
   tL.popFirst.postln; // prints 900
   
   nil;
)

Other Collections

As mentioned previously, the SuperCollider language provides for many more kinds of data structures. The following Collections are useful, but much more complex than those listed above. For usage instructions, refer to the SuperCollider documentation.

  • Dictionary: stores and allows retrieval of data by arbitrary Objects (for example, by symbols, rather than by index numbers).
  • Library: a type of Dictionary. Objects inserted can be used by any Object in the program, like books in a real-world library can be used by anybody who walks in.
  • Set: an unordered Collection of like Objects, where no two elements are identical.
  • SortedList: a List where all elements are kept in a sorted order, regardless of how they are added. The inserted Objects should have a useful ordering method, numerical or lexicographic (alphabetic, for example).

Repeated Execution

Repeating boring tasks is one of the main uses of computers, which don't mind doing the same thing over and over again. More importantly, writing code once and using it many times is much more intelligent than writing the same code many times. Repetition of the same code is often problematic, and repetition with subtle differences is even worse. Errors in this kind of code are difficult to find in the first place, and more difficult to solve effectively. Thankfully, as with most other things, SuperCollider offers a wide variety of ways to repeat code without re-writing it.

The code structure used to create repetition is normally called a loop. "Do" loops are SuperCollider's most versatile and useful repetition structure, and there are a few different ways to think about and write it. The "while" loop is a standard of most programming languages.

"Do This to Everything in This Collection"

One way to write a "do" loop is basically the same as telling the interpreter to "do this Function to every element in this Collection." The syntax looks like this,

do( aCollection, aFunction );

or this,

aCollection.do( aFunction );

This causes aFunction to be executed once for each element in aCollection, which can be any kind of Collection. Each time aFunction is run, it is given two arguments, in this order: an element of aCollection, and the elements index number. For Collection's that don't have index numbers, it returns what the element's index number would have been. The loop always begins at the start of the Collection, and progresses with each element in order to the end. The second argument, really, is the integers from zero to one less than the number of elements in the Collection, increasing by one each time the loop executes aFunction.

"Do This, This Many Times"

Another way to write a "do" loop takes advantage of SuperCollider's flexibility, and is really the same as one of the methods above. It's basically equivalent to telling the interpreter to "run this Function this many times." The syntax looks like this,

aNumber.do( aFunction );

This causes aFunction to be executed aNumber times. The interpreter still provdies two arguments to aFunction, but they are the same: it is the integers from zero to one less than aNumber. You might also think of it as the number of times that aFunction has been executed prior to this particular execution.

Example "Do" Loops

These examples illustrate different ways to use "do" loops for trivial tasks.

(
   var tL = List.new;
   tL = [27, 46, 102, 81, 34, 0, 39, 26, 203, 62];
   
   do( tL, { arg item, rep; [rep, item].postln; }; );
   
   nil;
)

This example is of the first syntax shown. For each element in tL, the interpreter executes the Function once, giving it first the corresponding element of the Collection, and then the iteration counter, which happens to be equal to the element's List index number.

(
   var tL = List.new;
   var myFunc = 
   { 
      arg item;
      item.postln;
   };
   
   tL = [27, 46, 102, 81, 34, 0, 39, 26, 203, 62];
   
   tL.do( myFunc; );
   
   nil;
)

This example does several things differently, but maintains the same basic functionality as the previous example. In this case, the Function only uses the first argument that the interpreter provides, and completely ignores the iteration counter. The syntax here also puts the Collection outside the parentheses, which perhaps makes it more clear that tL is not part of the Function.

(
   10.do( { "repeat".postln; }; );
   
   nil;
)

This example simply prints the string "repeat" ten times. If the Function accepted one argument, it would receive the integers zero through nine. If it accepted two arguments, both of the arguments would be equal.

"Do This While"

"While" loops execute continuously while their "test condition" is evaluated to be "true". Upon reaching the loop, the SuperCollider interpreter executes the test condition. If it is "fale", the interpreter does not execute the loop, and continues with the code after the loop. If it is "true", the interpreter executes the code in the loop once, then re-executes the test condition. If the test condition is "true", the loop is executed, the test condition re-executed, and so on. Until the test condition returns "false", the interpreter will never leave the loop.

Here is the format of a "while" loop in SuperCollider:

while( testFunc, bodyFunc );

or

testFunc.while( bodyFunc );

The test condition, called testFunc, is a Function which returns a boolean value - either "true" or "false". The loop's body, called bodyFunc, is a Function which can do anything. The loop body function is not provided any arguments by the interpreter. You will have to use comparison operators and boolean expressions when writing the Function for the test condition. For information on how these work in SuperCollider, see !! THIS SECTION HERE !! (#Boolean_Operators and #Boolean_Expressions)?

The following three code blocks are equivalent:

(
   10.do( { "repeat".postln; }; );
)

and

(
   var counter = 0;
   while( { counter < 10; }, { "repeat".postln; counter = counter + 1; } );
)

and

(
   var counter = 0;
   { counter < 10; }.while( { "repeat".postln; counter = counter + 1; } );
)

You can see how it's easier to write this particular activity as a "do" loop. It's often the case that a "do" loop better reflects what you want to do, but not always.

Contemplate a situation where you are waiting for the user to input some information, which you're going to use to calculate the rest of the composition. The following example isn't real code. It's intended to simplify a complex situation, so you can see where a "while" loop makes more sense than a "do" loop.

play( some background music );

while( { is the user still inputting information? }, { keep playing music } );

stop( some background music );

The background music is begun, and then the interpreter would enter the loop. For as long as the user is still inputting information, the interpreter will then "keep playing music." When the user is not still inputting information, the interpreter will move on to the next command, which stops the music. An equivalent "do" loop would be very difficult to write, if not impossible. This is because we won't know when the user has finished inputting their information until after they've finished, so we can't plan in advance for how long to play background music.

Thus, the most appropriate use of a "while" loop is for cases where you cannot know in advance how many times something should be executed. For most other cases of repeated execution, a "do" loop is the most appropriate choice.

Other Loops

The default language provides two other loop structures, both of which are designed to iterate over a series of integer values: "for" loops and "forBy" loops. Their use is more limited than "do" loops. They are explained in the SuperCollider documentation.

Conditional Execution

Conditional execution tells the SuperCollider interpreter to execute code on the condition that something is true. SuperCollider offers three conditional execution structures, "if", "switch", and "case" statements. Each of these structures is controlled by one or a series of "boolean expressions" (sometimes called "conditional expressions"), which are composed of "boolean operators".

Boolean Operators

Boolean operators evaluate to "true" or "false", and are most useful in boolean expressions, where they help to determine which portion of a program to execute.

The following table lists "binary" boolean operators, meaning that they take two arguments (one on the left and one on the right), and produce either "true" or "false".

Operator Meaning
< less than
<= less than or equal to
> greater than
>= greater than or equal to
== equivalent (equal)
!= not equivalent (equal)
=== identical (the same Object)
!== not identical (not the same Object)
&& logical And
logical Or (two pipe characters... can't be shown in the Wiki table)

The following table lists "unary" boolean operators, meaning that they take one argument, and work with one of the following syntaxes: argument.operator; or operator( argument );

Operator Meaning
isPositive "True" if the argument is greater than or equal to 0
isStrictlyPositive "True" if the argument is greater than 0
isNegative "True" if isPositive is "false"

The use of these operators is explained below in the "Boolean Expressions" section.

Boolean Expressions

Boolean expressions are expressions which, when executed, result in either "true" or "false". Boolean expressions must use at least one boolean operator (as listed above), or a Function which returns a boolean value. Boolean expressions can also use other operators and Functions.

Simple Expressions

Here are a few simple boolean expressions. Of course, variables can be used in place of constant numbers:

5 < 60; // evaluates to "false"
42 != 42; // evaluates to "false"
42 == 42; // evaluates to "true"
0.isPositive; // evaluates to "true"
isNegative( -256 ); // evaluates to "true"

Assignment/Equality Mistake =

Beware the following pitfall, common to a large number of programming languages:

a == 42; // evaluates to "true" or "false", depending on the value in "a"
a = 42; // assigns the value 42 to "a", over-writing the previously-stored value

One possible work-around is to write the number first.

42 == a; // evaluates to "true" or "false", depending on the value in "a"
42 = a; // causes an error, because you can't assign a value to a constant number

This way, if you accidentally leave out one of the "=" signs, the interpreter will stop execution and produce an error, rather than continuing with an unexpected assignment.

Equality versus Identity

The identity-equivalence operators are not usually needed.

(
   var a = [12,24,48];
   var b = [12,24,48];
   
   a == b; // evaluates to "true"
   a === b; // evaluates to "false"
)

The "==" operator evaluates to "true" because "a" and "b" represent equivalent Objects - they are equal. The "===" operator evaluates to "false" because "a" and "b" represent different instances of the Objects - they are not identical.

(
   var a = [12,24,48];
   var b = a;
   
   a == b; // evaluates to "true"
   a === b; // evaluates to "true"
)

In this case, the "==" operator still evaluates to "true". The "===" operator also evaluates to "true", because "a" and "b" both represent the same Object. When the interpreter evaluates var b = a; in the example above, it actually assigns "b" the same value that "a" stores, not a copy of it.

Logical And and Or

The logical And and Or operators must receive two boolean arguments. Logical And returns "true" if both of its arguments are "true". Logical Or returns "true" if one of its arguments are "true".

The following table illustrates how the SuperCollider interpreter will evaluate each of the following situations.

If the left sub-expression evaluates to... ... and the right sub-expression evaluates to... ... then logical And would evaluate to... ... and logical Or would evaluate to...
true false false true
false true false true
true true true true
false false false false

The interpreter evaluates the expression on the left first, and then the expression on the right only if it will influence the outcome. This means that, if the left-side expression of a logical Or operator evaluates to "true", the interpreter will not test the right-side expression, because the result will be "true" anyway. Similarly, if the left-side expression of a logical And operator evaluates to "false", the interpreter will not test the right-side expression, because the result will be "false" anyway.

This can be exploited to help avoid errors like division-by-zero.

(
   var x = 5.rand; // assigns a pseudo-random number between 0 and 5
   
   ( x != 0 ) && ( { x = 17 / x; } ); // doesn't divide by x if it would cause division-by-zero
   
   x; // the interpreter automatically prints this value after execution
)

If the left-side expression of the logical And operator is "false", the interpreter doesn't evaluate the right-side expression; it simply moves on to the next expression. If the left-side expression is "true" (meaning that x is not zero), then the right-side expression is evaluated. The right-side expression happens to be a Function which assigns "x" the result of dividing 17 by its previous value. The result of the logical And operation is simply discarded in this case - it doesn't really matter to us. This isn't the most straight-forward code, and there are other ways to avoid division-by-zero. If you use this, it's probably best to include a brief explanation of what the code does, as a commment.

If you run this code many times, you will see that it gives many different results - one of which is zero, which proves that the code works as intended. If SuperCollider divides by zero, the result is "inf", representing infinity. Try modifying the code so that it will divide by zero, and see what happens.

Order of Precedence

In complicated boolean expressions, it's important to clarify the order in which you want sub-expressions to be executed. This order is called the "order of precedence," or "order of operations." In computer science, different programming languages enforce different orders of precedence, so you should use parentheses to clarify your intended order, to proactively avoid later confusion. The interpreter will evaluate an expression from left to right, and always fully evaluate parentheses before continuing.

Even simple expression can benefit from parentheses. These produce the same results:

(
   var a = 5 == 5 && 17 != 5;
   var b = ( 5 == 5 ) && ( 17 != 5 ); // parentheses help to clarify
   
   a == b; // evaluates to "true"
)
(
   var a = 5.isPositive && isNegative( 6 ) || 12 + 5 * 42 - 1 > 18 ;
   var b = ( 5.isPositive && isNegative( 6 ) ) || ( ((12 + 5) * 42 - 1) > 18 ); // parentheses help to clarify
   
   a && b; // evaluates to "true"
)

And perhaps even more surprisingly...

( 12 + 5 * 42 - 1 ) != ( (12 + 5) * 42 - 1 );

... evaluates to "false"! They're equal - the interpreter doesn't follow the standard mathematical order of precedence rules! SuperCollider evaluates from left to right, so it's important to clarify to the interpreter what you mean. Where would you put parentheses so that SuperCollider evaluates the expression as per the standard mathematical order of precedence rules, with multiplication before addition and subtraction?

"If This Is True, Then... "

The "if" structure is provided a boolean expression and two Functions. If the expression evaluates to "true", it executes one Function. If the expression evaluates to "false", it executes the other Function.

Here are the two ways to write an "if" structure:

if ( booleanExpression, trueFunction, falseFunction );

and

booleanExpression.if( trueFunction, falseFunction );

It's possible to exclude the falseFunction, which is like telling the interpreter, "If the boolean expression is true, then execute this Function. Otherwise, don't execute it."

(
   var test = [true,false].choose; // pseudo-randomly chooses one of the elements in the List
   
   if ( ( true == test ), { "It's true!".postln; }, { "It's false!".postln; } );
   nil;
)

This example prints out a nice message, saying whether test is "true" or "false". Because test is already a boolean value, we don't need to include it in an expression. The "if" statement could have been shortened like this: if ( test, { "It's true!".postln; }, { "It's false!".postln; } );

Suppose we only wanted to be alerted if test is "true".

(
   var test = [true,false].choose; // pseudo-randomly chooses one of the elements in the List
   
   test.if( { "It's true!".postln; } );
   nil;
)

In this example, the alternate "if" syntax is used, where the boolean expression is placed before the parentheses.

"If" structures can also be "nested," which is like telling the interpreter, "If this is true, do this; otherwise if this is true, do this; otherwise if this is true, do this." In this relatively simple example of nesting, the interpreter evaluates each "if" structure only if the previous one was "false".

(
   var test = [1,2,3].choose; // pseudo-randomly chooses one of the elements in the List
   
   if( 1 == test, { "It's one!".postln; },
      if( 2 == test, { "It's two!".postln; },
         if( 3 == test, { "It's three!".postln; } ) ) );
   
   nil;
)

This is a more complex example of nesting:

(
   var testA = [1,2,3].choose; // pseudo-randomly chooses one of the elements in the List
   var testB = [1,2,3].choose;
   
   if( 1 == testA, { ( 1 == testB ).if( { "It's one and one!".postln; },
                        ( 2 == testB ).if( { "It's one and two!".postln; },
                           ( 3 == testB ).if( { "It's one and three!".postln; } ) ) ); },
      if( 2 == testA, { ( 1 == testB ).if( { "It's two and one!".postln; },
                           ( 2 == testB ).if( { "It's two and two!".postln; },
                              ( 3 == testB ).if( { "It's two and three!".postln; } ) ) ); },
         if( 3 == testA, { ( 1 == testB ).if( { "It's three and one!".postln; },
                             ( 2 == testB ).if( { "It's three and two!".postln; },
                                 ( 3 == testB ).if( { "It's three and three!".postln; } ) ) ); } ) ) );
   
   nil;
)

As you can see, this type of nesting is not easy to figure out - from the standpoint of the original programmer or somebody else who wishes to use your code. In writing this example, it took me several attempts before getting the parentheses and braces right. Usually, if you have a long list of possibilities to test (like the nine in this example), it is better to use a "case" or "switch" structure. Not only does this help to make the code easier to understand, but the SuperCollider interpreter can apply optimizations that make the code run marginally faster.

"Switch Execution to This Path"

A "switch" structure can be most easily understood by comparison to a switch in a railway line. As a train approaches a railway switch, an operator inspects the train, and decides whether it should be going to the passenger station, the freight station, or the garage for storage. A "switch" structure - like a railways switch, can only act on one Object at a time, but the Object can be a Collection, allowing you to compare multiple things at once. Each case is tested with the boolean equality operator before being executed.

Here is the syntax of a "switch" statement:

case( compareThis,
   toThis1, { doThis; },
   toThis2, { doThis; },
   toThis3, { doThis; }
);

You can include any number of cases. Notice that there is no comma after the last case, and that I've put the concluding ");" on a separate line with the same indentation as the word "case", so that it's easy to see.

The following example shows a simple switch.

(
   var grade  = 11.rand + 1; // pseudo-randomly chooses 0 to 11, then adds 1 to give 1 to 12
   
   grade =
   switch( grade,
      1, { "D-" },
      2, { "D" },
      3, { "D+" },
      4, { "C-" },
      5, { "C" },
      6, { "C+" },
      7, { "B-" },
      8, { "B" },
      9, { "B+" },
      10, { "A-" },
      11, { "A" },
      12, { "A+" }
   );
   
   ("Your grade is" + grade).postln;
   nil;
)
<pre>
The code picks a pseudo-random number between 1 and 12, then uses a "switch" structure to convert that number into a letter-grade, assigning it to the same <code>grade</code> variable.  Then, it adds the "Your grade is" string to the value of <code>grade</code> (with a space between), and prints that result.

This example avoids the complex nested "if" structure from above.
<pre>
(
   var testA = [1,2,3].choose; // pseudo-randomly chooses one of the elements in the List
   var testB = [1,2,3].choose;
   
   switch( [testA,testB],
      [1,1], { "It's one and one!".postln; },
      [1,2], { "It's one and two!".postln; },
      [1,3], { "It's one and three!".postln; },
      [2,1], { "It's two and one!".postln; },
      [2,2], { "It's two and two!".postln; },
      [2,3], { "It's two and thre!".postln; },
      [3,1], { "It's three and one!".postln; },
      [3,2], { "It's three and two!".postln; },
      [3,3], { "It's three and three!".postln; }
   );
   
   nil;
)

This is an elegant way to inspect two otherwise-separate variables. Remember that the first argument to "switch" (in this case, it's [testA,tesB]) is compared to the first argument of possibe result with the equality operator: "==".

When evaluating which switch to use, the SuperCollider interpreter will always apply the last one that evaluates to "true".

(
   switch( 5,
      5, { "one".postln; },
      5, { "two".postln; },
      5, { "three".postln; }
   );
   
   nil;
)

All of these cases are true, but this will always result in "three" being printed.

"In This Case, Do This"

"Case" and "switch" structures look similar, but work in subtly different way. A "switch" structure is like a railway switch, allowing one train to be routed onto the right track, according to qualities of the train. A "case" structure, on the other hand, works like somebody trying to decide how to get to work. The person might ask themselves how far they are going, how long they have to get to work, how fast the available options are, what the available options cost, and so on. While in a "switch" structure, the path of execution is determined by examining only one Object, a "case" structure determines the path of execution based on any number of things.

Here is the syntax of a "case" structure:

case
   booleanFunction resultFunction
   booleanFunction resultFunction
   booleanFunction resultFunction
   booleanFunction resultFunction
;

Contemplate the following pseudo-code example, which represents a possible musical sitation, and a good use of the "case" structure.

(
   var coolFunction =
   {
      case
         { is there no music playing?
           AND people are in the room } { play music }
         { has the same song been playing for too long?
           OR is the song boring? } { change the song }
         { has everybody left the room? } { turn off the music }
         { has a song been requested? } { change to that song }
         { is the music too loud? } { lower the music's volume }
         { is the music too quiet? } { raise the music's volume }
         { is the music too fast? } { lower the music's tempo }
         { is the music too slow? } { raise the music's tempo }
         { is everything okay? } { wait for 10 seconds }
      ;
   };
   
   ( 5 == 5 ).while( coolFunction ); // executes coolFunction contiuously
)

It might seem like this example doesn't relate to a real SuperCollider programming situation, but in fact it might. If you could program Function's which determined all of those questions and all of the answers, this sort of "case" structure would be very helpful in a situation where a computer running SuperCollider were left in a room by itself, and expected to play music whenever anybody entered the room. Since five is always equal to five, the interpreter will run coolFunction forever. If the music needs adjustment in some way, the Function will adjust the music. If everything is okay, then the interpreter will wait for 10 seconds, and then the loop will cause the Function to be re-evaluated. Because many different criteria are evaluated in the "case" structure, this represents an efficient use of the structure.

"Case" structures can be used to do the same thing as "switch" structures, but it's usually less elegant solution. Also, it doesn't allow the interpreter to use an speed optimization that it would have used in an equivalent "switch" structure.

(
   var grade  = 11.rand + 1; // pseudo-randomly chooses 0 to 11, then adds 1 to give 1 to 12
   
   grade =
   case
      { 1 == grade; } { "D-" }
      { 2 == grade; } { "D" }
      { 3 == grade; } { "D+" }
      { 4 == grade; } { "C-" }
      { 5 == grade; } { "C" }
      { 6 == grade; } { "C+" }
      { 7 == grade; } { "B-" }
      { 8 == grade; } { "B" }
      { 9 == grade; } { "B+" }
      { 10 == grade; } { "A-" }
      { 11 == grade; } { "A" }
      { 12 == grade; } { "A+" }
   ;
   
   ("Your grade is" + grade).postln;
   nil;
)

This example is equivalent to one of the "switch" structure examples. This is not a good use of the "case" structure, because it requires a lot of code repetition.

Unlike a "switch" structure, a "case" structure will always follow the first case that evaluates to "true".

(
   case
      { 5 == 5; } { "one".postln; }
      { 5 == 5; } { "two".postln; }
      { 5 == 5; } { "three".postln; }
   ;
   
   nil;
)

This example will always result in "one" being printed.

Combining Audio, and the "Mix" Class

One of the requirements of multi-channel audio is the ability to combine a large number of UGen's into a small number of channels - normally just two. The SuperCollider interpreter allows you to accomplish this in a number of ways, which are explained here.

The "Mix" Class

The "Mix" Class allows you to combine a mutli-channel Array into one channel. It's just that simple: you put in an Array of UGens, and out comes a single-channel combination of them.

Here are the two possible syntaxes for the "Mix" Class:

Mix.new( ArrayOfUGens );

and

Mix( ArrayOfUGens );

The second form is simply a short-hand version of the first. The "Mix" Class doesn't really create "Mix" Objects either - it's just a Function that combines many UGen's into one.

Here's an example of the "Mix" Class in action:

{
   Mix( [SinOsc.ar(220, 0, 0.1),
         SinOsc.ar(440, 0, 0.1),
         SinOsc.ar(660, 0, 0.1),
         SinOsc.ar(880, 0, 0.1),
         SinOsc.ar(850, 0, 0.1),
         SinOsc.ar(870, 0, 0.1),
         SinOsc.ar(880, 0, 0.1),
         SinOsc.ar(885, 0, 0.1),
         SinOsc.ar(890, 0, 0.1),
         SinOsc.ar(1000, 0, 0.1)] );
}.play;

Notice how all of these SinOsc's are heard through the left channel only. The "Mix" Class mixes all the UGen's together into one. You could use a bus to send the audio to both the left and right channels. What happens if we don't use the Mix Class? Try removing the Function, and find out. You only hear some of the SinOsc's. Which ones? The first two, representing the left and right channels. If your audio interface has more than two channels, you may be able to hear more than those first two channels.

There is another Function offered by the Mix Class, and it is a kind of loop. The Function is called Fill, and it takes two arguments: the number of times to run a Function, and the Function to run. The Function is provided with one argument (like in a "do" loop), which is the number of times the Function has already been run.

(
   var n = 8;
   var sineFunc = 
   {
      arg iteration;
      
      var freq = 440 + iteration;
      SinOsc.ar( freq:freq, mul:1/n );
   };
   
   { Mix.fill( n, sineFunc ); }.play;
)

As you can see, the "fill" Function itself is quite simple: you provide the number of UGen's to create, and a Function that creates UGen's. It's the sineFunc Function that is a little confusing. The argument is called "iteration", because it holds how many times the Function has already been run - how many iterations have happened already. It uses this value to help calculate the frequency (stored in a variable called "freq"), and then creates a SinOsc UGen. The "mul" argument helps to automatically control the volume level. Since the total volume should be no more than 1.0, the "sineFunc" Function calculates UGen's' volume by dividing 1, the maximum level, by the number of UGen's that will be created. The slowly pulsating volume is part of the acoustic result of this many frequencies being so close together - it is not a hidden effect by SuperCollider.

Arrays of Arrays

There is another way to combine many UGen's into two channels for stereo output: rather than sending the Array's to the Mix class, combine them into a two-element Array.

{
   [
      [ SinOsc.ar(440, 0, 0.1), SinOsc.ar( 880, 0, 0.1 ), SinOsc.ar( 1660, 0, 0.1 ) ],
      [ SinOsc.ar(440, 0, 0.1), SinOsc.ar( 880, 0, 0.1 ), SinOsc.ar( 1660, 0, 0.1 ) ]
  ];
}.play;

Here, a two-element Array is the result of a Function, which gets sent the "play" message. Each of the elements is an equivalent, three-element Array where each element is a UGen.

This representation also offers another benefit: each UGen can have a different "mul" value, which will be preserved.

{
   [
      [ SinOsc.ar(440, 0, 0.2), SinOsc.ar( 880, 0, 0.1 ), SinOsc.ar( 1660, 0, 0.05 ) ],
      [ SinOsc.ar(440, 0, 0.2), SinOsc.ar( 880, 0, 0.1 ), SinOsc.ar( 1660, 0, 0.05 ) ]
  ];
}.play;

This sounds much less harsh than the first example. Try it with the Mix Class. Even with the different "mul" values, it sounds the same as the first example! This helps Mix to ensure that the total level doesn't exceed 1.0, but it has the disadvantage that careful level-balancing on your part will be erased.

Addition

This method of combinine UGen's into two channels uses the addition operator: +

{
   [
      ( SinOsc.ar(440, 0, 0.1) + SinOsc.ar( 880, 0, 0.1 ) + SinOsc.ar( 1660, 0, 0.1 ) ),
      ( SinOsc.ar(440, 0, 0.1) + SinOsc.ar( 880, 0, 0.1 ) + SinOsc.ar( 1660, 0, 0.1 ) )
  ];
}.play;

Notice that, like with the Mix Class, independent "mul" levels are not preserved.

SynthDef's and Synth's

The preceding sections of this "Basic Programming" guide have only created sound with Function's. The truth is that Function's are very useful for creating sound, but they represent a simplification of the actual commands and Functions that must be run by the interpreter in order to create sound.

// When you write this...
(
   { SinOsc.ar( freq:440, mul:0.2 ); }.play;
)

// The interpreter actually does this...
(
   SynthDef.new( "temp__963", { Out.ar( 0, SinOsc.ar( freq:440, mul:0.2 ) ); } ).play;
)

Yikes! Don't despair - it's easy to understand - it just looks scary!

Out

The "Out" UGen is one of the bits of magic automatically taken care of by the interpreter. It routes an audio signal from another UGen into a specific output (actually, into a specific bus - explained in the !! BUSSES !! section).

The following examples have the same effect:

{ SinOsc.ar( freq:500, mul:0.2 ); }.play;

and

{ Out.ar( 0, SinOsc.ar( freq:500, mul:0.2 ) ); }.play;

The first argument to "Out.ar" is the bus number for where you want to place the second argument, which is either a UGen or a multi-channel Array of UGen's. If the second argument is an Array, then the first element is sent to the first argument's bus number, the second argument is sent to one bus number higher, the third to two bus numbers higher, and so on. This issues is explained fully in the !! BUSSES !! section, but here's what you need to know for now, working with stereo (two-channel) audio:

  • If the second argument is a two-element Array, use bus number 0.
  • If the second argument is a single UGen, and you want it to be heard through the left channel, use bus number 0.
  • If the second argument is a single UGen, and you want it to be heard through the right channel, use bus number 1.

If you're still struggling with exactly what the "Out" UGen does, think of it like this: when you create an audio-rate UGen, it starts creating an audio signal; the "Out" UGen effectively connects the audio-rate UGen into your audio interface's output port, so it can be heard through the speakers. In the !! BUSSES !! section, it becomes clear that there are, in fact, other useful places to connect an audio-rate UGen (through an effect processor, for example), and the "Out" UGen can help you do that.

SynthDef's

A SynthDef is what we use to tell the server how to create sound. In order to truly understand what SynthDef accomplishes, we need to recall the disconnect between the interpreter and the server. In reality, the interpreter has no idea how to make sound or work with audio hardware. The server, likewise, has no understanding at all of the SuperCollider language. The interpreter takes the code that we write, and does one of a number of things, depending on the nature of the code:

  • executes it completely,
  • executes it partially, makes choices, and then does something else
  • send the server information about how to synthesize sound,
  • etc.

For simple code like 2.postln; the interpreter just executes it. For code like { SincOsc.ar; }.play; the interpreter expands it a bit, then sends instructions to the server, which deals with the rest of the synthesis process.

A SynthDef is part of this last process; SynthDef Objects represent the synthesis information that is sent to the server before (or at the same time as) telling the server to play the sound.

There are two steps to creating a useful SynthDef: making an interpreter Object, and sending the actual synthesis information to the server. There are two ways to write this, as follows:

someVariable = SynthDef.new( nameOfSynthDef, FunctionContainingOutUGen );
someVariable.send( nameOfServer );

and

SynthDef.new( nameOfSynthDef, FunctionContainingOutUGen ).send( nameOfServer );

The FunctionContainingOutUGen is simply that - a Function that, when executed, returns an "Out" UGen (meaning that the "Out" UGen must be the last expression in the Function). The nameOfSynthDef should be a symbol (described !!BELOW!!), but can also be a string. The nameOfServer is a variable that represents the server to which you want to send the SynthDef's information; unless you know that you need to use a different variable for this, it's probably just the letter "s", which the interpreter automatically assigns to the default server.

Here is a demonstration of both methods:

(
   var playMe =
   {
      Out.ar( 0, SinOsc.ar( freq:440, mul:0.2 ) );
   };
   
   var playMeSynthDef = SynthDef.new( \playMe, playMe );
   
   playMeSynthDef.send( s );
   
   nil;
)

and

(
   var playMe =
   {
      Out.ar( 0, SinOsc.ar( freq:440, mul:0.2 ) );
   };
   
   SynthDef.new( \playMe, playMe ).send( s );
   
   nil;
)

The only advantage to assigning something to a variable is the ability to refer to it later. If you use the first method, then you can send the SynthDef to more than one server. Since it's rare that you will want to use more than one server, it's usually better to use the second style. In fact, if you won't be using the "playMe" Function again, you don't need to assign it to a variable!

SynthDef.new( \playMe, { Out.ar( 0, SinOsc.ar( freq:440, mul:0.2 ) ); } ).send( s );

This is all that's really needed to create and send a synthesis definition to the server. It looks long and frightening, but now at least you understand what all of the parts do.

Load

There is another way to send a SynthDef to the server: the "load" Function. The "send" Function sends the synthesis information to the server, which stores it in memory. When the server stops running, all synthesis information given with "send" is lost. The "load" Function, on the other hand, sends the synthesis information to the server, which stores it on disk and in memory. Every time the server is started, it loads all of the synthesis information previously sent to it with the "load" Function. The definition remains until you delete it specifically. This is most useful for a SynthDef that takes up a lot of memory, and which would use considerable network time to transfer to the server whenever the server is run. It is also useful to use the "load" Function instead of "send", when there are a lot of SynthDef's, regardless of the size of each one. The idea is the same: avoid sending the SynthDef in order to save time.

The syntax and usage for "load" is the same as for "send".

Symbols

As stated in the section about variables, a symbol is simply something which represents something else. When used in the context of a SynthDef, a symbol is a string of characters that refers to a SynthDef that we've already sent to the server. What wasn't mentioned in the section about variables is that, in addition to the symbols that can be used as variable names, the SuperCollider language provides a distinct data-type (like numbers or strings) for symbols. Many programming languages don't provide a "symbol" data-type, so many programmers do not use them extensively, but they are very handy for situations like this. As local variable names are symbols representing data stored by the interpreter, here we are using the symbol data-type to refer to data stored on the server.

Symbols are a better way to name SynthDef's than strings. Not only do symbols take up less memory, they aren't actually the interpreter doesn't actually think of them as Objects, and neither should you. Symbols are universally unique; only one instance of a symbol with the same characters can exist. On the other hand, an infinite number of strings with the same characters can exist. When we use a symbol, we are defining it universally. When we use a string, the server pretends that all strings with the same characters are the same philosophical object, even though they aren't. This isn't a technical problem, but it can be difficult to think about, and is cognitively dissonant.

If all this seems a little abstract and ontological, that's because it is.

Symbols: the Easy Way

Symbols are things that you should use to identify a SynthDef sent to the server. That's all you really need to know.

Writing Symbols

There are two ways to write out a symbol: between single-quotation marks, and after a back-slash. Symbols given between single-quotation marks can contain any characters but a single-quotation mark. Symbols given after a back-slash can contain any characters but a space. Neither type of symbol name can cross onto a new line.

The following example contains some valid symbols, and some invalid symbols.

\stopSign \\ this is a symbol
\stop sign \\ this is a symbol called 'stop' followed by the unrelated word 'sign'
'stopSign' \\ this is a symbol
'stop sign' \\ this is a symbol
'stop
sign' \\ these lines are not a symbol, and will cause an error

The following example illustrates the differences between strings and symbols.

var a = "stop sign" \\ a string
var b = "stop sign" \\ a string with the same letters as the first string
a == b; \\ returns "true" because the strings are equivalent
a === b; \\ returns "false" because the strings are separate copies with the same characters

var c = 'stop sign' \\ a symbol
var d = 'stop sign' \\ the same symbol
c == d; \\ returns "true" because the symbols are equivalent
c === d; \\ returns "true" because the symbols are identical

SynthDef Becomes Synth

After you send a SynthDef to the server, you can put it into action. When the interpreter tells the server to play a synthesis definition (which the interpreter holds in a SynthDef Object), the server creates a synth from the definition, and starts generating sound. The interpreter gives us a Synth Object to represent each synth on the server, so that we can control the synth.

This is the syntax used to create a new synth, after its definition has been sent to the server.

Synth.new( nameOfSynthDef );

The name will be a symbol or a string - whatever you supplied when you ran the SynthDef.new() Function.

Because we're creating a new Synth Object, we should assign it to a variable, for later reference.

SynthDef.new( \playMe, { Out.ar( 0, SinOsc.ar( freq:440, mul:0.2 ) ); } ).send( s );
var mySynth = Synth.new( \playMe );

Recall that the interpreter automatically uses the Synth and SynthDef Classes when we send the "play" message to a Function. We can actually capture and use the Synth Object created from "play-ing" a Function, too. This example is almost the same as the previous one.

var mySynth = { SinOsc.ar( freq:443, mul:0.2 ); }.play;

The difference is subtle: after the second example, we have no control over what name the interpreter gives to the SynthDef that it sends to the server, so we can't re-use the SynthDef. On the other hand, because we assign the name \sillyTutorialSD to the SynthDef in the first example, we know what it's called, and we can re-use it. Theoretically, we can make an infinite number of synths from this single definition. Realistically, it's limited by the amount of memory the server can use; for most modern computers, this number is so high that we don't ever need to worry about it.

As usual, the interpreter provides us with an optional short-cut:

var mySynth = SynthDef.new( \playMe, { Out.ar( 0, SinOsc.ar( freq:440, mul:0.2 ) ); } ).play;

This automatically sends the synthesis information to the server, creates a synth, and plays it. What minor functionality is lost when we use this shortcut?

Shortcomings of SynthDef's

Consider the following program:

(
   var myRandFunc =
   {
      var frequency = 440.rand + 440; // produces an integer between 440 and 880
      SinOsc.ar( freq:frequency, mul:0.025 );
   };
   
   10.do( { myRandFunc.play; } );
)

Execute the program a few times. The result will be different each time: ten different SinOsc's with ten different frequencies.

What if we convert the program to use a SynthDef and multiple Synth's instead? This program will probably cause an error the first time - this is exaplained below.

(
   var myRandFunc =
   {
      var frequency = 440.rand + 440; // produces an integer between 440 and 880
      Out.ar( 0, SinOsc.ar( freq:frequency, mul:0.025 ) );
   };
   
   SynthDef.new( \myRandFunc, myRandFunc ).send( s );
   
   10.do( { Synth.new( \myRandFunc ); } );
)

Execute the program a few times. The result is still different each time, but it's the same ten SinOsc's, all with the same frequency. This is the nature of a SynthDef: once it's sent to the server, you can create a synth from the same instructions without re-sending them.

This program causes an error the first time you run it. Inspect the error messages, and see if you can determine why. It's because the server is processing commands asynchronously: things don't happen right when the interpreter asks, but very shortly thereafter. The result is that the server is asked to make a new synth before it deals with the synth definition. There are ways to get around this, but they're too complex for this section - for now (to simplify this text's examples), just accept that the error may happen the first time you run a Synth.

Creating Change Anyway

The way to create change and pseudo-randomness anyway is to incorporate another UGen to do it for you. Remember: when you send synthesis information to the server, that information can't change unless you replace it. This doesn't mean that the output produced by the synth can't change!

The way to do this is with control-rate UGen's. The following example uses a control-rate SinOsc to set the frequency of an audio-rate SinOsc.

(
   var myRandFunc =
   {
      var frequency = SinOsc.kr( freq:0.5, add:660, mul:220 ); // oscillates between 440 and 880, hitting each extreme every 2 seconds
      Out.ar( 0, SinOsc.ar( freq:frequency, mul:0.2 ) );
   };
   
   SynthDef.new( \myRandFunc, myRandFunc ).send( s );
   
   Synth.new( \myRandFunc );
)

When you use a UGen as a control-rate UGen, you have to think about its arguments quite differently than when using it as an audio-rate UGen. This table shows how the same argument gives a different result for an audio-rate vs. control-rate UGen used for pitch:

freq (frequency in Hertz) || controls pitch || controls the speed of oscillation add || ?? || re-sets the "zero" (middle) point of the sine wave by adding this to the output mul || controls volume level || sets the deviation from the "zero" point
parameter audio-rate UGen control-rate UGen for pitch

For an audio-rate SinOsc UGen, you set the frequency and the volume level. For a control-rate UGen, you set the mid-point of oscillation with "add", the extremes of oscillation which will be add - mul and add + mul, and the speed of oscillation with "freq". The end result is very different numbers.

There is a handy UGen designed specifically for replacing pseudo-randomness in Functions. The following example restores the "ten different pitches" to the example from the last section.

(
   var myRandFunc =
   {
      var frequency = Rand( 440, 880 ); // produces an integer between 440 and 880
      Out.ar( 0, SinOsc.ar( freq:frequency, mul:0.025 ) );
   };
   
   SynthDef.new( \myRandFunc, myRandFunc ).send( s );
   
   10.do( { Synth.new( \myRandFunc ); } );
)

If you run this multiple times, you will again hear ten different pitches. Depending on audio hardware, previous musical experience, and other factors, some people may have difficulty hearing that the pitches are different. Try reducing the number of synths created in the loop.

SynthDef's with Arguments

There are some siutations where you simply cannot pre-determine all of the material that you're going to use when creating a synth. It might be easier to resort to using a Function rather than a SynthDef, but there is yet another solution - creating an argument in your SynthDef Function.

With only a subtle change to our Function, we can add the possibility of passing arguments on synth creation:

var myRandFunc =
{
   arg frequency = Rand( 440, 880 ); // default value between 440 and 880
   Out.ar( 0, SinOsc.ar( freq:frequency, mul:0.025 ) );
};

I've decided to use the Rand UGen anyway, so that supplying a frequency is optional. This adds functionality while making the added complexity optional:

(
   var myRandFunc =
   {
      arg frequency = 440;
      Out.ar( 0, SinOsc.ar( freq:frequency, mul:0.025 ) );
   };
   
   SynthDef.new( \myRandFunc, myRandFunc ).send( s );
   
   10.do( { Synth.new( \myRandFunc ); } );
)

If you use the SynthDef in the old way, as in the example, you'll get the expected result: ten Synth's, all with the same frequency. But, if you add a "rand" Function call into the loop, you can get ten different frequencies!

(
   var myRandFunc =
   {
      arg frequency = 440;
      Out.ar( 0, SinOsc.ar( freq:frequency, mul:0.025 ) );
   };
   
   SynthDef.new( \myRandFunc, myRandFunc ).send( s );
   
   10.do( { Synth.new( \myRandFunc, [\frequency,(440.rand + 440)] ); } );
)

Notice how we supply arguments: an Array with elements alternating between a string-quoted parameter name, and the value of the argument itself. If we "parameterized" all three main fields of the SinOsc, we could supply them like this:

Synth.new( \mySinOsc, [\freq,440,\add,0,\mul,0.2] );

Thing to Do with a Synth: Set and Free

Once you send a synth definition to the server, and make some synths, you've collected a few Synth Objects, and you wonder what to do with them next. Of course, you could listen to them, but you can also change the arguments that you used, and stop it.

To change the arguments used by a synth, send it the "set" message, with a list of arguments:

variableHoldingSynth.set( [\argument1,value,\argument2,value,...] );

This helps to save even more time and memory: rather than destroying and creating synths all the time, you can simply change pre-existing ones.

This modification of the ten-pseudo-random-tones example includes an extra line that lets you change the tones without destroying and re-creating the synths.

// run this first
h = List.new;

// run this second
(
   var myRandFunc =
   {
      arg frequency = 440;
      Out.ar( 0, SinOsc.ar( freq:frequency, mul:0.025 ) );
   };

   SynthDef.new( \myRandFunc, myRandFunc ).send( s );
)

// run this third
10.do( { h.add( Synth.new( \myRandFunc, [\frequency,(440.rand + 440)] ) ); } );

// run this fourth, as many times as you please
h.do( { arg item; item.set( \frequency, (440.rand + 440) ); } );

The reason that you have to run each of those segments separately is two-fold: we need to store the List of Synth's in a single-letter variable because, for this simple demonstration, this is the most efficient way; second, for the asynchronous behaviour of the server that was previously noted as causing an error.

The only aspect of that example that's a little tricky to understand is the "do" loop. Remember that when you run a "do" loop on a List, the interpreter automatically loops over each of the elements in the List, running the Function that you provide. Each time the Function is run, it receives the current List item, and its index number in the List, in that order. So the Function in this loop simply uses "set" to change the "frequency" argument.

Take special note that the arguments in this case are not identical to those given with the "new" message. Compare the two forms below:

SynthDef.new( \SynthName, [\parameter1,value,\parameter2,value] );

and

existingSynth.set( \parameter1, value, \parmeter2, value );

To get rid of one synth without stopping all sound, send its corresponding Synth the "free" message:

variableHoldingSynth.free;

This stops the synth and frees the associated memory - on the server. Your Synth Object still exists in the interpreter, but you can't use it any more. A Synth Object represents a synth on the server; since you got rid of the synth on the server, the Synth Object represents something that doesn't exist. If you attempt to send the "free" message again, you'll get an error. For this reason, it's a good idea to get rid of the Synth Object at the same time:

variableHoldingSynth.free;
variableHoldingSynth = nil;

If you accidentally send "free" to an already-freed Synth, the interpreter will cause an error, and program execution will stop. If you accidentally send "free" to a variable set to "nil", nothing will happen. Proactively avoiding mistakes like this is good programming practice.

Busses

SuperCollider busses work just like busses in other audio creation contexts, which work similarly to busses used to transport humans. Busses are used to send audio from one place to another, and in SuperCollider they can also be used to send control-rate signals. Each SuperCollider bus is given an index number, which are integers starting at 0. Audio-rate busses and control-rate busses are independent, and are given an independent set of index numbers. Any number of unique signals can be routed into a bus, and any number of receivers can take signals from a bus - but the signal will be the sum of all the input signals. In other words, if you want to send two different sets of signals, you need two different busses with different index numbers.

Audio-Rate Bus Numbers

There are special audio-rate busses reserved automatically by the server. These interact with the audio interface, allowing you to get sound from its inputs, and send sound to its outputs. The lowest audio-rate bus numbers are reserved for audio interface outputs, each channel receiving an independent bus. The bus numbers just above those are reserved for audio interface inputs, each channel receiving an independent bus.

For a simple audio interface with two channels each for input and output, the pre-reserved audio-rate bus numbers would be these:

  • 0 for left channel output
  • 1 for right channel output
  • 2 for left channel input
  • 3 for right channel input

Now you can change the first argument of the "Out" UGen as desired!

"Out" and "In" UGen's

The "Out" UGen is discussed in the !!SynthDef section!!. What it does is take a signal and route it to the specified bus number. The "In" UGen performs a similar action: take a signal from a bus number, and make it available for use.

This is the syntax to use for "Out":

Out.ar( busNumber, audioRateUGen );

or

Out.kr( busNumber, controlRateUGen );

The UGen enclosed here should not be enclosed in a Function. If the UGen provides multi-channel output, "Out" will automatically route the lowest channel to the specified bus number, the next channel to the next highest bus number, and so on. This way, you can achieve stereo output with one "Out" UGen.

This is the syntax to use for "In":

In.ar( busNumber, numberOfChannels );

or

In.kr( busNumber, numberOfChannels );

Whereas "Out" automatically outputs the right number of channels based on how many you provide, "In" requires that you specify how many channels you want it to fetch for you.

When created with this form, both of these UGens automatically connect to the default server, stored in the single-letter "s" variable by the interpreter.

Bus Objects

As SynthDef and Synth represent things stored on the server, the interpreter provides us with an instantiable "Bus" Class that represents the server's busses. In many programs that you write, you won't need to use a Bus Object - particularly when you're doing simple input and output with the automatically-reserved bus numbers. But, like the SynthDef and Synth Classes make it easier to deal with synthdefs and synths on the server (which are very difficult to deal with directly), the Bus Class makes it easier to deal with busses on the server, and provides some extra functionality to boot!

The primary advantage of using Bus Objects is that you don't have to keep track of bus numbers, whether they're being used, and how mnany channels are being routed. For simple input and output of audio-rate signals, you're better off simply remembering the bus numbers

The "new" message creates a new Bus. Here is the syntax:

var myBusVariable = Bus.audio( serverName, numberOfChannels );

or

var myBusVariable = Bus.control( serverName, numberOfChannels );

The interpreter takes "numberOfChannels" busses on the "serverName" server, and groups them together for multi-channel use in one Bus Object, which it returns to you. The "numberOfChannels" argument is optional; if you leave it out, the Bus Object will have only one bus, for single-channel signals.

The interpreter also keeps track of which bus numbers are used for which Bus Objects, so the signals will never get confused. Of course, you can still route signals through those bus numbers without using the Bus Object, but the Bus Class helps us to keep things straight.

The following messages can also be used with Bus Objects:

Message Example Purpose
index b.index; Returns the lowest bus number used by this Bus
numChannels b.numChannels; Returns the number of busses used by this Bus
rate b.rate; Returns "audio" or "control," depending on whether the Bus is audio-rate or control-rate.
server b.server; Returns the name of the server that the Bus represents. The default server is "localhost".

When you are done with a Bus, you can release the channels for use by other Bus Objects:

myBusVariable.free;
myBusVariable = nil;

Like when sending the "free" message to a Synth Object, you should set the variable name of a "free'd" Bus to "nil". This will prevent you from accidentally sending audio there after the Bus is released.

Using Busses: Control-Rate Example

The best way to understand how and when to use a bus is to see them in action.

( // execute first: prepare the server
   var busAudioSynth = 
   {
      arg bus, freqOffset = 0;
      
      Out.ar( 0, SinOsc.ar( freq:( In.kr(bus) + freqOffset ), mul:0.1 ) );
   };
   
   var busControlSynth =
   {
      arg bus, freq = 400;
      
      Out.kr( bus, SinOsc.kr( freq:1, mul:( freq/40 ), add:freq ) );
   };
   
   SynthDef( \tutorialAudioBus, busAudioSynth ).send( s );
   SynthDef( \tutorialControlBus, busControlSynth ).send( s );
   
   b = Bus.control( s );
)

( // execute second: create synths
   x = Synth.new( \tutorialControlBus, [\bus, b] ); // control synth
   y = Synth.after( x, \tutorialAudioBus, [\bus, b] ); // low audio synth
   z = Synth.after( x, \tutorialAudioBus, [\bus, b, \freqOffset, 200] ); // high audio synth
)

( // commands to free each Object
   x.free; x = nil; // control synth
   y.free; y = nil; // low audio synth
   z.free; z = nil; // high audio synth
   b.free; b = nil; // control bus
)

This example contains three stages: prepare the server, create the synths, destroy the synths. These three stages will become familiar as you program in SuperCollider, whether or not you use busses frequently. The example is fairly complicated, so the code is explained here:

  • "busAudioSynth" Function: Accepts two arguments, and creates an audio-rate SinOsc, routed to the left output channel. The frequency is determined by a control-rate bus given as an argument, and optionally with an offset that can be supplied as an argument.
  • "busControlSynth" Function: Accepts two arguments, and creates a control-rate SinOsc, routed to the bus given as an argument. Can also be given a frequency; the value produced by the synth is intended to be used to control pitch. The centre pitch of the oscillation is "freq", and the range of oscillation is one-twentieth the size of "freq" (one-fourtieth both higher and lower than "freq").
  • SynthDef: These commands are straight-forward. They send the synthesis definitions to the server.
  • b = Bus.control( s ); : This should also be straight-forward. A single-channel control bus is created, and assigned to the pre-declared variable "b".
  • For synth creation, x is assigned a control-rate synth, while y and z are assigned audio-rate synths. Each synth is given the variable "b", which refers to our control-rate bus. "z" is also given an argument for \freqOffset, which makes its frequency 200 Hz higher than the synth assigned to "y".
  • Don't worry about the "after" message for now. It's explained in !! the section about Ordering !!

Why Use Global Variables

Since this is just an example, and not an actual program, the program uses four automatically-declared global variables: b, x, y, and z. Because these variables are shared with everything, it's especially important to set them to "nil" when you're done. If this were going to be written into a real program, it would be a good idea to change the variables to something which can't be accessed by other programs.

Why Use a Bus

The control-rate bus in this example might seem trivial and pointless to you, especially since the use of a UGen to control frequency has already been illustrated in other examples. For this particular program, a control-rate UGen would probably have been a better choice, but remember that this is just an example.

Here are some main advantages to using a control-rate Bus over a UGen:

  1. The signal can be changed without sending the "set" message to the audio-rate UGen, simply by changing the input to the bus.
  2. Input to the bus can be produced by any number of control-rate UGen's.
  3. The signal in the bus can be received by more than one UGen, as it is in this example. One thousand audio-rate UGen's powered by 25 control-rate UGen's is a much better solution than if each audio-rate UGen were powered by its own control-rate UGen.
  4. Busses can be accessed quickly and efficiently from any place in the program that has access to the variable holding the Bus. It's easier and safer (less error-prone) than making all of your UGen's equally accessible.

Some of these advantages could be seen as disadvantages. Whether you should use a Bus or a UGen depends on the particular application. The simpler solution is usually the better one, as long as you remember to avoid repetition!

Special Note about Control-Rate Busses

Control-rate Bus'ses are a great way to enhance the flexibility of your program. The best part is that, in order to use a control-rate Bus, the UGen doesn't even need to have been written to accomodate it.

{ SinOsc.ar( freq:In.kr( controlRateBus, 1 ), mul:0.2 ); }.play;

Now you've managed to spice up an otherwise-boring synth!

Also, control-rate Bus'ses don't need to be constantly changing. Unlike an audio-rate Bus, a control-rate Bus will hold the last-inputted value until another value is provided. You can the value of a control-rate Bus with the "set" message (and a single argument, which is the value). You can also get the current value, whether created by "set" or a UGen, by using the "get" message, and sending a Function with one argument.

(
   var bus = Bus.control( s );
   bus.set( 12 );
   bus.get( { arg val; val.postln; } );
   bus.free; bus = nil;
)

When running this example, you'll notice that the "12" doesn't get posted until after the program finishes with "nil". This is because of the latency between when the interpreter asks the server to do something, and when the server does it. The amount of time it takes for the server to complete a command is usually very small, but as you can see, it can make an important difference to your program. This latency is also the reason that you can't call "SynthDef.new( ... )" and "Synth.new( ... )" at the exact same time.

This latency is also the reason that we have to provide a single-argument Function as an argument to the "get" Function. Since the Function won't immediately be able to get the value of the bus from the server, we can't expect the value to be returned by the Function. Instead, when "get" gets the value of the bus from the server, it runs the Function that you gave it.

Using Busses: Audio-Rate Example

(
   var tutorialDecayPink =
   {
      arg outBus = 0, effectBus,
      direct = 0.5; // controls proportion of "direct" / "processed" sound
      var source;
      
      // Decaying pulses of PinkNoise.
      source = Decay2.ar( in:Impulse.ar( freq:1, phase:0.25 ),
                          attackTime:0.01,
                          decayTime:0.2,
                          mul:PinkNoise.ar
                        );
      
      Out.ar( outBus, (source*direct) ); // main output
      Out.ar( effectBus, (source*(1-direct)) ); // effects output
   };
   
   var tutorialDecaySine =
   {
      arg outBus = 0, effectBus,
      direct = 0.5; // controls proportion of "direct" / "processed" sound
      var source;
      
      // Decaying pulses of a modulating Sine wave.
      source = Decay2.ar( in:Impulse.ar( freq:0.3, phase: 0.25),
                          attackTime:0.3,
                          decayTime:1,
                          mul:SinOsc.ar( freq:SinOsc.kr( freq:0.2, mul:110, add:440) )
                        );
      
      Out.ar(outBus, (source*direct) ); // main output
      Out.ar(effectBus, (source*(1-direct)) ); // effects output
   };
   
   var tutorialReverb =
   {
      arg outBus = 0, inBus; // default outBus is audio interface
      var input;

      input = In.ar( inBus, 1 );
      
      16.do( { input = AllpassC.ar( in:input,
                                    maxdelaytime:0.04, 
                                    delaytime:{ Rand(0.001,0.04) }.dup,
                                    decaytime:3
                                  )
             }
           );
      
      Out.ar( outBus, input );
   };
   
   // send synthesis information to the server
   SynthDef( \tutorialReverb, tutorialReverb ).send( s );
   SynthDef( \tutorialDecayPink, tutorialDecayPink ).send( s );
   SynthDef( \tutorialDecaySine, tutorialDecaySine ).send( s );
   
   // reserve an effects Bus
   b = Bus.audio( s );
)

(
   x = Synth.new( \tutorialReverb, [\inBus, b] );
   y = Synth.before( x, \tutorialDecayPink, [\effectBus, b] );
   z = Synth.before( x, \tutorialDecaySine, [\effectBus, b, \outBus, 1] );
)

// Change the balance of "wet" to "dry"
y.set( \direct, 1 ); // only direct PinkNoise
z.set( \direct, 1 ); // only direct Sine wave
y.set( \direct, 0 ); // only reverberated PinkNoise
z.set( \direct, 0 ); // only reverberated Sine wave
y.set( \direct, 0.5 ); // original PinkNoise
z.set( \direct, 0.5 ); // original Sine wave

( // commands to free each Object
   x.free; x = nil;
   y.free; y = nil;
   z.free; z = nil;
   b.free; b = nil;
)

I'm not going to explain this example as extensively as the previous one. It's definitely the most complex example so far. It's better if you figure out what the parts do by playing with them yourself. The bus works by routing audio from the \tutorialDecayPink and \tutorialDecaySine synths into the \tutorialReverb synth. The first two synths can be controlled to put all, none, or some of their signal into the bus (so that it goes through the \tutorialReverb synth), or straight out the audio interface (bypassing the \tutorialReverb synth). Notice that the same effects processor is operating on two different input sources.

Ordering and Other Synth Features

This section discusses the important topic of creating and enforcing an "order" on the server. Because this is done with Functions (or methods) from the Synth Class, other useful Functions from the Class are discussed here.

Ordering

Ordering is instructing the server to calculate in a particular order. The audio synthesized by the server takes the same form as any other digital audio: a series of !!samples!! are played at a particular speed (called !!sample rate!!), each with a set number of bits per sample (called !!bit rate!! or sample format). For each sample, the server calculates the signal at that point in a pre-determined order. Each sample is calculated from scratch, so if a particular UGen depends on the output of another UGen, the other one had better be calculated first.

Consider the following example:

{ SinOsc.ar( freq:SinOsc.kr( freq:1, add:500, mul:10 ), mul:0.2 ); }.play;

What happens if the server calculates the audio-rate UGen first? It wouldn't have a frequency. This is another one of those things which the interpreter takes care of automatically when we run Function rather than create a Synth. Since it's often preferable to use a synth instead of a Function, we need some way to control the order of execution. The interpreter and the server are only so good at guessing what we need, after all.

There are two methods in the Synth Class that we can use to inform the server about our desired order of execution: "before" and "after". They represent a small extension to the "new" method, and they work like this:

Synth.before( variableHoldingSynth, nameOfSynthDef, ListOfArguments );

and

Synth.after( variableHoldingSynth, nameOfSynthDef, ListOfArguments );

And it works just as it looks, too: the server creates a new synth, adds it before or after the synth represented by "variableHoldingSynth" (depending on which Function you use), and uses "nameOfSynthDef" and "ListOfArguments" just as in the "add" method.

This example, from the !!"Bus"!! section, uses the "after" Function to ensure that the control-rate synth is calculated before the audio-rate synths that depend on it.

( // execute first: prepare the server
   var busAudioSynth = 
   {
      arg bus, freqOffset = 0;
      
      Out.ar( 0, SinOsc.ar( freq:( In.kr(bus) + freqOffset ), mul:0.1 ) );
   };
   
   var busControlSynth =
   {
      arg bus, freq = 400;
      
      Out.kr( bus, SinOsc.kr( freq:1, mul:( freq/40 ), add:freq ) );
   };
   
   SynthDef( \tutorialAudioBus, busAudioSynth ).send( s );
   SynthDef( \tutorialControlBus, busControlSynth ).send( s );
   
   b = Bus.control( s );
)

( // execute second: create synths
   x = Synth.new( \tutorialControlBus, [\bus, b] ); // control synth
   y = Synth.after( x, \tutorialAudioBus, [\bus, b] ); // low audio synth
   z = Synth.after( x, \tutorialAudioBus, [\bus, b, \freqOffset, 200] ); // high audio synth
)

( // commands to free each Object
   x.free; x = nil; // control synth
   y.free; y = nil; // low audio synth
   z.free; z = nil; // high audio synth
   b.free; b = nil; // control bus
)

In this case, the control-rate synth is created before the audio-rate synths - probably the easier way to think about it. Even so, it's possible to add them in the opposite order with a little extra thought.

The other example from the !!"Bus"!! section used the "before" Function to ensure that the "pink noise" and "sine wave" UGen's were calculated before the "reverberation" UGen. Especially since these are all audio-rate UGen's, the server would not reasonably know which to calculate first, so you need to let it know.

Changing the Order

SuperCollider offers equally easy-to-use methods to change the order of execution.

To move a synth's execution after another:

variableHoldingSynth.moveAfter( variableHoldingAnotherSynth );

To move a synth's execution before another:

variableHoldingSynth.moverBefore( variableHoldingAnotherSynth );

Replace a Running Synth

The server allows you to replace a running synth with a newly-created one, maintaining all of the ordering relationships.

This is the syntax:

variableHoldingNewSynth = Synth.replace( variableHoldingSynthToReplace, nameOfSynthDef, ListOfArguments );

The "variableHoldingNewSynth" will often be the same as the "variableHoldingSynthToReplace," but not always. When you use this Function, the synth being replaced is freed from the server (equivalent to running "free"), so that variable should always be assigned something.

Pausing and Restarting a Synth

The server allows you to temporarily pause and later re-start a synth, without freeing and re-creating it.

To pause a synth:

variableHoldingSynth.run( false );

To re-start a synth:

variableHoldingSynth.run( true );

Scheduling

The practice of scheduling allows you to making things happen in a pre-determined amount of time. Scheduling is very different from ordering: ordering is a primarily technical consideration to ensure that the server synthesizes the sound in the right order; scheduling is a primarily musical consideration that allows you to control the perceived time that things happen.

Clocks

SuperCollider's clocks have two main functions: they know what time it is, and they know what time things are supposed to happen.

There are three types of clocks, which each do slightly different things:

  • TempoClock: These clocks are aware of metre (time signature) changes, and have an adjustable tempo. They are to be used for scheduling musical events, and they run with a high priority.
  • SystemClock: This clock always runs in seconds. It can be used to schedule musical events, since it runs with a high priority. There is only one SystemClock.
  • AppClock: These clocks always run in seconds. They are to be used for graphic events and other non-musical things not discussed in this guide. These clocks do not run with a high priority, so they can be temporarily "side-lined" by a TempoClock or the SystemClock, if one of those has something to do urgently.

Default Clocks: SystemClock, TempoClock.default, and thisThread.clock

The SuperCollider interpreter provides two default clocks, and one default pseudo-clock.

The SystemClock always operates in seconds, and it can be used to schedule musical events, but usually this isn't necessary.

The TempoClock.default runs at 60 beats-per-minute by default (equal to one beat per second). Since it's accessible from anywhere within a program, any tempo changes will have an effect on the scheduling of the entire program - so be careful! If you don't want something to be effected by tempo changes, you can create a new TempoClock just for that part of the program. If you will be using this clock frequently, you can assign it to a variable like this:

var t = TempoClock.default;

The thisThread.clock is not really a clock in itself, but refers to the clock which is responsible for scheduling the part of the program where the command is written. It can be a little bit tricky working with this clock, since it may be either the SystemClock or a TempoClock.

Finding the Current Time

Using the "beats" method on a clock will return that clock's current time. Try running each of the following:

SystemClock.beats;
TempoClock.default.beats;
thisThread.clock.beats;

This can be useful for scheduling events in an absolute way, or for a number of other things.

Relative Scheduling

The standard way to schedule things is in a certain number of beats from now. If you're scheduling on a SystemClock, one beat is equal to one second. If you're scheduling on a TempoClock, one beat is equal to whatever the current setting is.

To schedule things on a clock, use the "sched" Function:

nameOfClock.sched( beatsFromNow, FunctionToExecute );

The interpreter will let you schedule just about anything, but there's no point in scheduling something other than a Function: scheduling a five won't have any effect - try it!

SystemClock.sched( 5, 5 );

It looks like nothing happens. The 5 does happen, but... well... it doesn't do anything. Scheduling a Function will do something:

SystemClock.sched( 5, { 5.postln; } );

When you run this, there are two things to notice:

  1. The interpreter prints out "SystemClock" first. This is to let you know that it did the scheduling as requested.
  2. The five prints out endlessly, in five-second intervals. For an explanation, see "Repeated Scheduling"

Repeated Scheduling

If you schedule a Function that returns a number, the interpreter will schedule the Function to re-run in that many beats.

This will print "5" every five seconds, until you press [Esc] to stop execution.

SystemClock.sched( 5, { 5.postln; } );

To avoid this, you can end your Function with "nil", which has been done sometimes through this guide.

SystemClock.sched( 5, { 5.postln; nil; } );

This will print "5" in five seconds, and then stop.

Working with the TempoClock Class

Here is a brief explanation of some Functions available with the TempoClock Class. Throughout this section, the variable "t" is used to represent any particular TempoClock.

var t = TempoClock.new( tempo, beats );

This creates a new TempoClock. The arguments are optional, and have the following meanings:

  • tempo: tempo of the clock, given in beats per second. To input a value in beats-per-minute, divide it by 60. Defaults to 60 beats per minute, or one per second.
  • beats: starts the clock at this time. Default is zero.
t.stop;
t = nil;

Equivalent to the "free" method for a Synth of Bus Object. This stops the clock, discards all scheduled events, and releases the resources used to run the clock. Setting the variable to "nil" afterwards is optional, but recommended, to avoid later programming mistakes.

t.clear;

Discards all scheduled events, but keeps the clock running.

t.tempo;

Returns the current tempo in beats-per-second.

t.tempo_( newTempo );

Allows you to change the clock's tempo. The new tempo should be in beats-per-second. To input a tempo in beats-per-minute, divide the value by 60.

t.play( Function );

Schedules the Function to begin execution on the next beat.

There are many other Functions in the TempoClock Class, related to absolute scheduling, scheduling with bars, and conversion of beats to and from seconds.

How to Get Help

Knowing how to get help in SuperCollider is going to play a large part in determining whether you have a productive or frustrating relationship with the language and its components. There are a large number of ways to get help, but here are some of the most helpful.

Use the SuperCollider Help Files

SuperCollider comes with an extensive collection of help files, which contain the answers to most of your problems. The difficulty will be in finding the solution - it's not always located where you think it is, because it often isn't the solution you think it will be.

On Fedora Linux systems, the main help file is located at [file:///usr/share/SuperCollider/Help/Help.html this URL], and it can be viewed in any web browser. It may also be helpful to browse the directory structure of the help files, located at [file:///usr/share/SuperCollider/Help this URL], which can also be viewed in your web browser.

If you're looking for further explanations of material in this tutorial, you could start by reviewing the [file:///usr/share/SuperCollider/Help/Tutorials/Getting-Started/Getting%20Started%20With%20SC.html Getting Started With SuperCollider] tutorial, on which this document is based. The sections in that tutorial roughly correspond to the sections in this guide.

Internet Relay Chat

If you know how to use IRC, you can join the #supercollider channel on the Freenode network. The channel does not usually have a large number of participants or a lot of activity, but the users are some of the most polite and helpful on the internet.

Email

If you feel comfortable sending an email to a mailing list, you can use the sc-users list. If you decide to subscribe to this list, be aware that it receives a large amount of mail every day.

The SuperCollider Website

The SuperCollider web site at SourceForge offers links to many resources. It is available here.

Legal Attribution

I'm not sure yet exactly what this will be.