Overview

Here is a an overview on the SuperCollider Language - sclang.

sclang

Some Basics

A valid statement To end a valid statement you have to use a semicolon ;.

Comments In SuperCollider line comments start with a double slash //. A comment block is made of text in between slashes and asterisks /* comment text */.

Precedence SuperCollider does not know any arithmetic precedence rules. Operations are executed from left to right. You have to use parenthesis to encapsulate your operations in the right way. When combining messages and binary operations, messages take precedence.

Evaluation The last evaluated statement get’s allways postet in the Post window. This is true to single line statements and also code blocks.

Code Blocks To build semantic code blocks, that should evaluate together, you can enclose these in parenthesis.

Arithmetics

 

Classes, Objects and Messages

SuperCollider is an object-oriented programming language. To perform an action on an object, you send a message to it. This message invokes a method that defines how the object should respond. The object could perform an action or return some information.

For example, if you want to print a string (which is also an object) to the post window, you send it the postln message by appending .postln to the string. The line is terminated with a semicolon:

"Hello World!".postln;

An object is an instance of a class. A class could be something like Student. Every student has unique properties such as a name, an age, a gender, or a favorite subject. For example, Jane would be an instance of the Student class with her own specific values for these properties. Every class in SuperCollider begins with a capital letter.

In SuperCollider, everything is an object: an integer number, a UGen, a string, or an array. That means everything can receive messages and has methods.

Classes can have subclasses, which inherit properties and methods from their superclass. For example, the Float and Integer classes are child classes of the Number class, which in turn is a child of the Object class. Classes always starts with an uppercase letter.

The basic structure for working with Objects and messages is the receiver notation: Receiver.message(arguments). You can also chain messages, wherby the most left message is performed first and the following to the right one after another like this: 100.0.rand.round(0.01).dup(4); The other way of writing expressions in SuperCollider is the functional notation: message(Receiver, arguments).

Here some more examples:

5.dup(20); // receiver notation
dup(5, 20); // same thing in functional notation
 
3.1415.round(0.1); // receiver notation
round(3.1415, 0.1); // functional notation

You can also nest code like this:

// these both lines do exactly the same
100.0.rand.round(0.01).dup(4);
 
dup(round(rand(100.0), 0.01), 4);

Each class has its own methods that can act on its data. These methods are also inherited by their child classes. Because of this, sometimes you can’t find information on some methods of subclasses in the manual. These are then described in their superclasses. Here are some examples how to find out about realtionships and methods.

Group.superclass;                 // this will return 'Node'
Group.superclass.help;
Group.findRespondingMethodFor('set');        // Node-set
Group.findRespondingMethodFor('postln');    // Object-postln;
Group.helpFileForMethod('postln');         // opens class Object help file

There are just a few other Data Types in SuperCollider:

  • Collections: [list, of, items]
    • Arrays are also collections
  • Functions: { often multiple lines of code }
  • Strings: "words inside quotes"
    • strings are sequences of characters
  • Symbols: 'singlequotes' or preceded by a \backslash

Every Object understands the class method, which helps to find out the class of the object.

Variables in SuperCollider

In SuperCollider, we differentiate between local and global variables. Every object can be assigned to a variable.

The single-letter variables from a to z are already declared as global variables. This means they are accessible throughout your whole SuperCollider program. Some of them are already assigned to specific objects — for example, the variable s refers to the default local server.

If you want to use more descriptive names for global variables, you can define them by prefixing the name with a tilde ( ~ ), like ~myBuffer. They are also called envrionment variables, because they are only global to your current environment.

Local variables, on the other hand, are only visible within a local scope. A local scope could be a SynthDef, a Function, or a code block. You declare local variables using the keyword var.

// Environment variables / global variables
~galaApples = 4;
~bloodOranges = 5;
~limes = 2;
~plantains = 1;
 
["Citrus", ~bloodOranges + ~limes];
["Non−citrus", ~plantains + ~galaApples];
 
// Local variables: valid only within the code block.
// Evaluate the block once and watch the Post window:
(
var apples = 4, oranges = 3, lemons = 8, bananas = 10;
["Citrus fruits", oranges + lemons].postln;
["Non−citrus fruits", bananas + apples].postln;
"End".postln;
)
 
~galaApples; // still exists
apples; // gone

Variables can always be reassigned.

Environment Variables

Placing a ~ in front of a variable creates an environment variable. These variables do not need to be explicitly declared and persist within the current environment. You can think of them as global variables within your current workspace.

In most cases, you work within a single environment. However, there are situations where using multiple environments becomes useful — for example, when encapsulating state or isolating parts of a program.

(
~sources = Group.new;
~effects = Group.after(~sources);
~bus = Bus.audio(s, 2);
)
// to be sure, create a new Environment:
Environment.new.push;
 
// some code..
 
// restore old environment
currentEnvironment.pop;

Control Structures

Conditionals: if/else and case

The syntax for an if/else statement in Supercollider is: if(condition, {true action}, {false action}). The condition is a Boolean test, it returns true or false. The {false action} is optional and is not necessary by default.

(
if (100 < 50,
  { "very true".postln },
  { "very false".postln}
);
)

You can also use conditionals to assign a specific value to a variable.

(
var myNum = 0;
if (4 < 2) {myNum = 1} {myNum = 2};
myNum.postln;
)

Here an overview of all conditionals

SymbolMeaningTrue ExampleFalse Example
==equal to?10 == 1010 == 99
!=not equal to?10 != 9910 != 10
>greater than?10 > 510 > 99
<less than?10 < 9910 < 5
>=greater than or equal to?10 >= 10, 10 >= 310 >= 99
less than or equal to?10 99, 10 1010 9
oddis it odd?15.odd16.odd
evenis it even?22.even21.even
isIntegeris it an integer?3.isInteger3.12.isInteger
isFloatis it a float?3.235.isFloat2.isFloat
andboth conditionsand(10 > 1, 2 > 1)and(10 < 1, 20 > 2)
oreither condition1.odd.or(1.even)or(2.odd, 1.even)

A case structur works by defining pairs of functions to be evaluated in order until one of the tests returns true:

case
{condition1} {action1}
{condition2} {action2}
{condition3} {action3}
...
{conditionN} {actionN};

One condition after another is evaluated, until one is returning true. In this case, the functions body / action is executed and no further tests or actions are executed. To mark the end of the case statement you have to use a semicolon.

// case
(
~num = −2;
 
case
{~num == 0} {"WOW".postln}
{~num == 1} {"ONE!".postln}
{~num < 0} {"negative number!".postln}
{true} {"last case scenario".postln};
)

Plotting and Scoping

Function has two methods for plotting and scoping the audio output: plot and scope.

This makes a graph of the signal produced by the output of the Function. You can specify some arguments, such as the duration. The default is 0.01 seconds, but you can set it to anything you want.

{ PinkNoise.ar(0.2) + SinOsc.ar(440, 0, 0.2) + Saw.ar(660, 0.2) }.plot;
 
{ PinkNoise.ar(0.2) + SinOsc.ar(440, 0, 0.2) + Saw.ar(660, 0.2) }.plot(1);

This can be usefull for checking, if you get the output, you are expecting to get.

With scope you can get an oscilloscope-like display of the funtion’s output.

{ PinkNoise.ar(0.2) + SinOsc.ar(440, 0, 0.2) + Saw.ar(660, 0.2) }.scope;

You can also get the server output as oscilloscope output.

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

Syntax Shortcuts

There a few shorthand forms or alternate syntaxes for doing things withing SuperCollider. E.g. :

// this
{ Mix.new([SinOsc.ar(440, 0, 0.2), Saw.ar(660, 0.2)]).postln }.play;
// and this
{ Mix([SinOsc.ar(440, 0, 0.2), Saw.ar(660, 0.2)]).postln }.play;
// are equivalent

You can also switch between Functional and receiver notation:

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

*

Collections and Arrays

An Array is a type of a Collection, which is (surpise!) a collection of Objects. Collections are Objects themselves, and most types of Collections can hold any types of objects, mixed together, including other Collections.

An Array is an ordered collection of limited maximum size. You create an array by putting objects between two quare brackets, with commas in between:

a = ["foo", "bar"];    // "foo" is at index 0; "bar" is at index 1
a.at(0);
a.at(1);
a.at(2);        // returns "nil", as there is no object at index 2
 
// there's a shorthand for at that you'll see sometimes:
a[0];            // same as a.at(0);

In SupderCollider Arrays also have a special use: They are used to implement multichannel audio. When your Function resturns an Array of UGens, each slot of the Array correspond with an outputchannel (outputchannels in SuperCollider starts with 0).

// stereo example
{ [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)] }.play;

Creating an Array

You create an empty array of a given size with the .newClear method.

a = Array.newClear(7); // creates an empty array of given size
a[3] = "wow"; // same as a.put(3, "wow")

There are also some other ways to create an Array with values created by several methods.

// Arithmetic series
Array.series(size: 6, start: 10, step: 3);
// Geometric series
Array.geom(size: 10, start: 1, grow: 2);
// Compare the two:
Array.series(7, 100, −10); // 7 items; start at 100, step of −10
Array.geom(7, 100, 0.9); // 7 items; start at 100; multiply by 0.9 each time
// Meet the .fill method
Array.fill(10, "same");
// Compare:
Array.fill(10, rrand(1, 10));
Array.fill(10, {rrand(1, 10)}); // function is re−evaluated 10 times
// The function for the .fill method can take a default argument that is a counter.
// The argument name can be whatever you want.
Array.fill(10, {arg counter; counter * 10});
// For example, generating a list of harmonic frequencies:
Array.fill(10, {arg wow; wow+1 * 440});

There is also the shortcut notation with an exclamation mark. This can create an array which contains a duplicated value by a number of times.

// Shortcut notation:
30!4;
"hello" ! 10;
// It gives the same results as the following:
30.dup(4);
"hello".dup(10);
// or
Array.fill(4, 30);
Array.fill(10, "hello");

There is anothe common syntax shortcut, to create arrays with a arithmetic series.

(50..79);
// It's a shortcut to generate an array with an arithmetic series of numbers.
// The above has the same result as:
series(50, 51, 79);
// or
Array.series(30, 50, 1);
// For a step different than 1, you can do this:
(50,53..79);//stepof3
// Same result as:
series(50, 53, 79);
Array.series(10, 50, 3);

Actions on Arrays

Here are some actions on Arrays:

// Create some array
a=[10,11,12,13,14,15,16,17];
 
// non destructive actions
a.reverse; // reverse
a.scramble; // scramble
a.choose; // picks one element at random
a.size; // returns size of array
a.at(0); // retrieves item at specified position
a[0] ; // same as above
a.wrapAt(9); // retrieves item at specified position, wrapping around if > a. size
["wow", 99] ++ a; // concatenates the two arrays into a new one
a ++ \hi; // a Symbol is a single character
a ++ 'hi'; // same as above
a ++ "hi"; // a String is a collection of characters
a.add(44); // creates new array with new element at the end
a.insert(5, "wow"); // inserts "wow" at position 5, pushes other items forward (returns new array)
a.permute(3); // permute: item in position 3 goes to position 0, and vice−versa
a.mirror; // makes it a palindrome
a.powerset; // returns all possible combinations of the array's elements
a.normalizeSum; // normalize all values, so the sum of the array is 1
a; // evaluate this and see that none of the above operations actually changed the original array
 
// destructive action
a.put(2, "oops"); // put "oops" at index 2

You can also do math with arrays:

[1,2,3,4,5]+10;
[1,2,3,4,5]*10;
([1, 2, 3, 4, 5] / 7).round(0.01); // notice the parentheses for precedence

Iterating / Looping on the elements of an Array

With the do method you can iterate (or loop) on the elements of an Array. When the iteration is finished, do returns the original Array. This is helpful when you just need the sideffects on these values and not to change the original array.

The do method takes a function as input, which takes two arguments by default:

  • an iterator, which is the item at the current iteration
  • a counter, which is the integer number of the current iteration

The counter is not necessary and you can just leave it out.

~myFreqs = Array.fill(10, {rrand(440, 880)});
 
// Now let's do some simple action on every item of the list:
(
~myFreqs.do({
  arg item, count;
  ("Item " ++ count ++ " is " ++ item ++ " Hz.Closest midinote is " ++ item.cpsmidi.round).postln
});
)
// If you don't need the counter, just use one argument:
~myFreqs.do({arg item; {SinOsc.ar(item, 0, 0.1)}.play});
~myFreqs.do({arg item; item.squared.postln});
// Of course something as simple as the last one could be done like this:
~myFreqs.squared;

When you need a new array as return value you can use collect instead.

~myFreqs = Array.fill(10, {rrand(440, 880)});
// compare the return value of these two
~myFreqs.collect({arg item; (item/10).postln});
~myFreqs.do({arg item; (item/10).postln});

SuperCollider - Data Types