Basics of SuperCollider

Types

Below are examples of several types in SuperCollider. Every evaluated block of code (in Jupyter Notebooks, this is simply a cell) always returns the last expression calculated by the block of code. The returned value is indicated in the post window by the prompt ->. In the SuperCollider IDE, a block of code is determined by highlight the block and hitting CMD plus Enter (MacOS).

To get the type of any object in SuperCollider, use the .class method.

Operators and Operator Precedence

Here are some examples of some of the important operators for sclang.

Predict the output of the cell below

Operator precedence is determine from left to right regardless of your "intuition" about operator precedence. For example, PEMDAS is not used for mathematical operators. Parentheses are the only way to force the precedence of some expression.

Predict what the output of the following expressions are:

Posting

Like any good programming language, printing can be done using the .postln or .post methods. .postln prints to the post window and adds a newline character to the end. .post simply prints to the post window. Remember that any block of code returns the last expression evaluated so you will see both the return value and the posted text in the post window.

Strings vs. Symbols

SuperCollider has three forms of text: strings, symbols and characters. Strings are denoted with double quotes, symbols with single quotes, and characters are actually created using the .ascii or .asDigit methods from the Integer class. Both strings and symbols are sequences of characters.

The difference between strings and symbols according to the documentation:

"Unlike strings, two symbols with exactly the same characters will be the exact same object. Symbols are optimized for recreating the same symbol over and over again. In practice, this means that symbols are best used for identifiers or tags that are only meaningful within your program, whereas you should use a string when your characters are really processed as text data. Use symbols to name things, use strings for input and output."

Practically, strings provide much more flexibility for text processing with many additional methods.

Symbols can also be created by using a backslash. If using the backslash, then the symbol must contain no spaces and only alphanumeric characters.

Strings offer many convenience methods for string processing that symbols simply do not have.

Char

Strings are simply an array of characters. We can access the characters in a string using indexing.

Characters are distinct types from strings.

To write a literal character use the $ before the character name.

Note that while symbols are a collection of characters, we cannot access the characters within a symbol. All text-based operations should be done with strings. Symbols are a convention for naming.

The Semicolon

Multiple expressions and statements need to be separated by semicolons. Evaluating a single expression or statement does not require the use of a semicolon.

Variables

There are several different kinds of variables in SuperCollider:

  1. Interpreter variables
  2. Environment variables
  3. Local variables

Interpreter Variables

Interpreter variables are global variables that persist throughout any sclang session. SuperCollider provides 26 interpreter variables (the letters a through z). The interpreter variable s is reserved specifically for communication with the audio server. YOU SHOULD NEVER REASSIGN S.

Environment Variables

An environment is similar to a namespace in other languages. An environment holds a series of global variables assigned through the use of a tilde. SuperCollider can switch between various environments.

In general, you should not worry about in which environment you are working. We will rarely, if ever, have a need to put global variables in separate environments. Feel free to use interpreter variables as global variables throughout your program.

Local Variables

Local variables are variable whose scope extends only to the code block in which it was declared.

We cannot access the variable myVar again because it was local only to block above. This will produce an error that complains that the variable myVar is not defined.

Functions

Below is a simple function in sclang. Functions are declared using curly brackets. Parameters are declared using vertical pipes. Functions return the last expression evaluated. There is no explicit return statement. Functions can be assigned to a variable in order to refer to them.

Function parameters can also be declared using the keyword arg.

Local variables can also be declared within functions. All local variables must be declared at the top of the function's body.

Note how the code below which computes the same result throws an error because the variable result is not declared at the top.

Conditionals

SuperCollider uses the following control structure for conditionals:

if(expr, trueFunc, falseFunc)

A conditional in sclang evaluates the expression. If the expression is true, then the true function is evaluated. If the expression is false, then the false function is evaluated. Furthermore, the if syntax is not a statement, it is an expression and will return the value of the true branch or the false branch.

You can omit the else branch if you like as well.

Remember that if is an expression. If one of the branches is not defined, the if expression will resolve to nil if called.

Practice

Write a function ~sparta that always returns the number 300.

Write a function called ~excited that takes a string and returns the string with three exclamation points. String concatenation can be done with the ++ operator.

Write a function called ~absValue that returns the absolute value of a number.

Write a function called isSpeeding that takes a number and prints out "Slow down!" if the number is greater than 65 and does nothing otherwise.

Write a recursive function ~fact that computes the factorial of a number.

Here's a little bit more a challenge. Write a recursive function that takes a number i and computes the ith fibonacci number. Here's a little info on the Fibonacci sequence: https://en.wikipedia.org/wiki/Fibonacci_number

Scope

SuperCollider has both lexical and dynamic scoping. Lexical scoping allows for inner contexts to have access to outer variables as shown in the example below. The variable num referred to in func references the num declared on the first line.

With environment variables, we can use dynamic scoping. This means we can use environment variables in functions or other parts of our code before they have been defined. All that matters is that the environment variable has been defined by the time the function/code is called/executed.

When we evaluate below, this throws an error because ~bar is not defined.

Once we assign ~bar to a value though, everything will work.

Iteration

While Loops

While loops are the best control structure for iteration in SuperCollider. SuperCollider does have syntatic sugar for for loops; however, there are edge cases with sclang for loops that are unintuitive. We will discuss them momentarily.

While loops in sclang have the following syntax:

while(testFunc, evalFunc)

A while loop in sclang evaluates the test function. If the result is True then the evaluating function is evaluated. If the result is False, the loop terminates.

Here is an example below of using iteration within the context of a function. This function computes the factorial of a number using iteration.

For loops

For loops in sclang are different than other languages. For loops iterate over an integer series.

for ( startValue, endValue, function )

The for loop begins at the start value and increments by one up to and including the end value. The function passed as the third argument can capture the iteration value using an argument.

If the second number is less than the first, then the loop decreases by one.

IMPORTANT: for loops in sclang can work improperly with certain edge cases if there are scenarios where the loop should never execute.

Do loops

An alternative to for loops are do loops which are actually a method for integers.

int.do(function)

The function like the one in the for loop can capture the iteration number using an argument.

When in doubt, use a while loop. There are other syntactical structures for iteration that are conveniences. A while loop will work in any scenario where you need iteration.

Iteration Exercises

Write a function called ~partials which takes a number fund that represents the frequency of a particular sound and numPartials which represents the number of partials. We will learn soon that sound is composed of frequencies and that single notes often contain multiple frequencies called partials. These partials tend to follow a particular pattern called the harmonic series.

For example, given a frequency 440 (Concert A), the partials of 440 are simply multiples of 440. The first five partials of 440 are 440, 880, 1320, 1760, and 2200. Your function ~partials should print out numPartials partials of a given frequency fund indicating the fundamental frequency of a note.

Write a function ~countNumsInString which takes a string and reports the number of decimal digits (i.e., the characters 0 - 9) in the string. For example, the string "abc123" has three digits. To solve this, you need to know that strings are arrays of characters. To get the ith character from a string str simply use str[i]. The .isDecDigit method returns a boolean stating whether the character is a decimal digit and the .size method gets the length of a string.

Arrays

Arrays in sclang are an ordered collection of any item. In many ways, they are similar to lists from Python. Arrays though are used for fixed size storage. We do not accumulate arrays like we typically do in Python. It turns out that this is not something we typically need for musical purposes.

Literal arrays are declared using brackets with commas to delineate each item in the list.

You can use indices and the indexing operator [ ] to get the elements in the array.

You can also change the elements in the array as well using an assignment statement

You can also create an immutable array using the hashtag in front of the array.

Iterating over an array is easy with a .do loop. The function for the .do loop can accept arguments for both the item and the index.

Array Exercises

Write a function called ~allPositive that takes a list of numbers and returns true if every element in the list is positive and false otherwise.

The end.