From Fedora Project Wiki
(wrote a lot)
(added more)
Line 551: Line 551:
}
}
</pre>
</pre>
== How to Get Help ==
!! This section needs to be written !!


== Collections ==
== Collections ==
Line 655: Line 658:
* <code>addFirst( item )</code> adds "item" to the beginning of a List.
* <code>addFirst( item )</code> adds "item" to the beginning of a List.


==== Accessing an Existing List ====
==== Accessing a List ====
These are "instance methods," meaning that they operate on a specific list.
These are "instance methods," meaning that they operate on a specific list.
* <code>at( index )</code> returns the Object assigned to the "index" index number.  If "index" is greater than the last element in the List, returns "nil".
* <code>at( index )</code> returns the Object assigned to the "index" index number.  If "index" is greater than the last element in the List, returns "nil".
Line 668: Line 671:


==== Examples ====
==== Examples ====
The following examples  
The following examples show different ways to use List's.
 
<pre>
(
  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;
)
</pre>
This code adds numbers to the end of a List, then removes them from the end of the List.
 
<pre>
(
  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;
)
</pre>
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.
 
<pre>
(
  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;
)
</pre>
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.
 
<pre>
(
  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;
)
</pre>
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.
 
<pre>
(
  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;
)
</pre>
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.
 
<pre>
(
  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;
)
</pre>
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.
 
<pre>
(
  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;
)
</pre>
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 ===
=== 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.
<ul>
<li><code>add( obj )</code> adds "obj" to a LinkedList as the last item.</li>
<li><code>addFirst( obj )</code> adds "obj" to a LinkedList as the first item.</li>
<li><code>pop</code> removes the last item in a LinkedList and returns it.</li>
<li><code>popFirst</code> removes the first item in a LinkedList and returns it.</li>
<li><code>first</code> returns a copy of the first item in a LinkedList.</li>
<li><code>last</code> returns a copy of the last item in a LinkedList.</li>
</ul>
==== 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.
<ul>
<li><code>at( index )</code> pretends the LinkedList is an Array, and returns a copy of what would be the element at the given index number.</li>
<li><code>put( index, obj )</code> pretends the LinkedList is an Array, and changes the element at the given index number to be "obj."</li>
<li><code>remove( obj )</code> searches through a LinkedList and removes the element judged to be equal to "obj," regardless of its index number.</li>
<li><code>removeAt( index )</code> pretends the LinkedList is an Array, and removes the element located at the given index number.</li>
</ul>
==== Example ====
This example uses a LinkedList as a queue, adding numbers to the tail, and removing and printing from the head.
<pre>
(
  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;
)
</pre>
=== 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,
<pre>do( aCollection, aFunction );</pre>
or this,
<pre>aCollection.do( aFunction );</pre>
This causes <code>aFunction</code> to be executed once for each element in <code>aCollection</code>, which can be any kind of Collection.  Each time <code>aFunction</code> is run, it is given two arguments, in this order: an element of <code>aCollection</code>, 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 <code>aFunction</code>.
=== "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,
<pre>aNumber.do( aFunction );</pre>
This causes <code>aFunction</code> to be executed <code>aNumber</code> times.  The interpreter still provdies two arguments to <code>aFunction</code>, but they are the same: it is the integers from zero to one less than <code>aNumber</code>.  You might also think of it as the number of times that <code>aFunction</code> has been executed ''prior'' to this particular execution.
=== Example "Do" Loops ===
These examples illustrate different ways to use "do" loops for trivial tasks.
<pre>
(
  var tL = List.new;
  tL = [27, 46, 102, 81, 34, 0, 39, 26, 203, 62];
 
  do( tL, { arg item, rep; [rep, item].postln; }; );
 
  nil;
)
</pre>
This example is of the first syntax shown.  For each element in <code>tL</code>, 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.
<pre>
(
  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;
)
</pre>
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 <code>tL</code> is not part of the Function.
<pre>
(
  10.do( { "repeat".postln; }; );
 
  nil;
)
</pre>
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:
<pre>
while( testFunc, bodyFunc );
</pre>
or
<pre>
testFunc.while( bodyFunc );
</pre>
The test condition, called <code>testFunc</code>, is a Function which returns a boolean value - either "true" or "false".  The loop's body, called <code>bodyFunc</code>, 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 !! (#Comparison_Operators and #Boolean_Expressions)?
The following three code blocks are equivalent:
<pre>
(
  10.do( { "repeat".postln; }; );
)
</pre>
and
<pre>
(
  var counter = 0;
  while( { counter < 10; }, { "repeat".postln; counter = counter + 1; } );
)
</pre>
and
<pre>
(
  var counter = 0;
  { counter < 10; }.while( { "repeat".postln; counter = counter + 1; } );
)
</pre>
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.
<pre>
play( some background music );
while( { is the user still inputting information? }, { keep playing music } );
stop( some background music );
</pre>
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 ==
=== Comparison Operators ===
=== Boolean Expressions ===


=== OrderedList ===


== Repetition ==


== The Mixer?? ==
== The Mixer?? ==
== How to Get Help ==


== SynthDefs and Synths ==
== SynthDefs and Synths ==

Revision as of 07:35, 20 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 ];
}

How to Get Help

!! This section needs to be written !!

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 !! (#Comparison_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

Comparison Operators

Boolean Expressions

The Mixer??

SynthDefs and Synths

Busses

Groups, Nodes, and Ordering

Buffers??

Scheduling and Clocks

Routines and Tasks

Patterns