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
Symbol | Meaning | True Example | False Example |
---|---|---|---|
== | equal to? | 10 == 10 | 10 == 99 |
!= | not equal to? | 10 != 99 | 10 != 10 |
> | greater than? | 10 > 5 | 10 > 99 |
< | less than? | 10 < 99 | 10 < 5 |
>= | greater than or equal to? | 10 >= 10, 10 >= 3 | 10 >= 99 |
⇐ | less than or equal to? | 10 ⇐ 99, 10 ⇐ 10 | 10 ⇐ 9 |
odd | is it odd? | 15.odd | 16.odd |
even | is it even? | 22.even | 21.even |
isInteger | is it an integer? | 3.isInteger | 3.12.isInteger |
isFloat | is it a float? | 3.235.isFloat | 2.isFloat |
and | both conditions | and(10 > 1, 2 > 1) | and(10 < 1, 20 > 2) |
or | either condition | 1.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};
)
Functions and Function-Objects
You can define functions using curly brackets. Everything enclosed in the brackets becomes a Function object. This object is an instance of SuperCollider’s Function class, which has several methods implemented. These methods can be invoked by sending messages to the Function object — for example, the play method:
{ [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)] }.play;
By assigning your function to an instance variable, you can reuse it and send messages to the variable:
f = { "Function evaluated".postln };
f.value;
When you call the value
method on a function it will evaluate and return the result of its last line of code:
(
f = {
"Evaluating...".postln;
2 + 3
};
f.value;
)
// this will return '5'
There is also a shortcut for value
:
f.();
Functions can also have arguments. These are values which are passed into the function when it is evaluated:
(
f = { arg a, b;
a - b
};
f.value(5, 3);
)
You can pass arguments also via keyword arguments:
(
f = { arg a, b;
a / b
};
f.value(10, 2); // regular style
f.value(b: 2, a: 10); // keyword style
)
And also mix regular and keyword style:
(
f = { arg a, b, c, d;
(a + b) * c - d
};
f.value(2, c:3, b:4, d: 1); // (2 + 4) * 3 - 1
)
You can also set default values for arguments:
(
f = { arg a, b = 2;
a + b
};
f.value(2); // 2 + 2
)
And also use pipes to specify arguments:
(
f = { arg a, b;
a + b
};
g = { |a, b|
a + b
};
f.value(2, 2);
g.value(2, 2);
)
You can also use variables inside of functions. These are local to the function scope:
(
f = { arg a, b;
var firstResult, finalResult; // declare the variables via 'var' keyword
firstResult = a + b;
finalResult = firstResult * 2;
finalResult
};
f.value(2, 3); // this will return (2 + 3) * 2 = 10
)
(
g = {
var foo;
foo = 3;
foo
};
g.value;
foo; // this will cause an error as "foo" is only valid within f.
)
You can also declare variables outside of functions. These are then local to the scope of the block (defined by parenthesis):
(
var myFunc;
myFunc = { |input| input.postln };
myFunc.value("foo"); // arg is a String
myFunc.value("bar");
)
myFunc; // throws an error
The letters a
to z
are what are called interpreter variables. These are pre-declared when you start up SC, and have an unlimited, or “global”, scope. This makes them useful for quick tests or examples.
Objects in SuperCollider (and many other object-oriented languages) are polymorphic. This means that different objects can receive the same message but may respond differently, because they implement different methods for that message. So when you send the same message to different objects, you may get different results.
An example for polymorphism (check carefully to get it completly):
(
f = { arg a;
a.value + 3 // call "value" on a; polymorphism awaits!
};
)
f.value(3); // a.value is 3, so this returns 3 + 3 = 6
g = { 3.0.rand };
f.value(g); // here the arg is a Function. a.value evaluates 3.0.rand
f.value(g); // try it again, different result
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});