8 -SC3 Introduction, The Language, Programming Basics 

Basics 

This section of the text began as a course in electro-acoustic music. It was originally written for SC2. SC3 changed the way the code editor (client) and synthesis engines (servers) work  together. For the most part the result is greater flexibility. For nearly a year I avoided SC3 because I thought I would have to relearn the program. To my surprise most of the old code still works, and there are some backward compatibility tools that help SC2 users make the transition.   All the examples in this edition work in SC3, though their SC2 origins may be apparent. 

When you first launch SC a window appears with information about the program, most of which can be ignored. But this window will continue to print useful information such as errors or results of your code. I'll call it the post window. It is possible to use this window for editing and entering code, but I prefer to open a new window. 

SuperCollider is a text editor, programming language, compiler, and digital synthesizer all in one. This means you use SC to create and edit lines of code that contain instructions for playing sounds. You can then use SC to evaluate and debug the code, compile or run the 
code, and the result should be real time audio. The text editing functions (select, copy, replace, etc.) are similar to any basic editor. But there are a few handy editing features that are unique to, and useful when editing code. If you've never written code their value is not immediately apparent. So I suggest you come back and review these often. 

Com-, Go to line number 
Com-/ Make selected lines a comment 
Opt-Com-/ Remove comment marks on selected lines 
Shift-Com-B Balance enclosures 
Com-] Shift code right 
Com-[ Shift code left 
Com-. Stop all playback 
Shift-Com-/ (Com-?) Open help file for selected item 
Com-' Syntax colorize 
Double click enclosure 

Balance enclosures 
Com-\ Bring post window to front 
Shift-Com-K Clear post window 
Shift-Com-\ Bring all to front 

Once you have written lines of code you can use SC to evaluate the expression. Evaluation can result in either a message from the system about the code, or numbers (data) resulting from the expression or functions (like a calculator), or sounds from a synthesizer patch. The 
data from an SC patch could be pitches for transcription or instructions to the performer or the results of analysis. But our goal for the next few chapters will be sound. 

To evaluate selected code you press the enter key (not return, but enter). If you want to evaluate a single line you don't need to select it, just position the cursor anywhere in the line. 

For each example in this text, type the line in SC, position the cursor anywhere within the line, and press enter. All texts that teach programming are bound by geek law to use this as the first example. 

8.1. Hello World 

"Hello World".speak; 

To run an audio example we have to start the synth server, which actually makes the sound.  There are two servers, internal and localHost. Don't worry about the differences for now.  Think of them as two rack-mounted synthesizers to which you will send instrument 
definitions and playback instructions. Internal will work best for the examples in this text. 

You only need to boot (i.e. power on) the server once for each session. You can leave it running. You can either boot the server by clicking the boot button10 on the user interface or run the line of code below. I prefer running the code because it names the server "s." Unless otherwise indicated all the examples in this book will assume the server is running, that it is the default server, and that it is called "s." Run or evaluate the first line, then run the second example. Let me remind you that command-period stops all playback (but leaves the server running). This text assumes you will stop playback after each example. 

8.2. Booting the server 

Server.default = s = Server.internal.boot;

Now you can run this example. 

8.3. First Patch (play, SinOsc, LFNoise0) 

{SinOsc.ar(LFNoise0.ar([10, 15], 400, 800), 0, 0.3)}.play

For this example select everything from { to and including play 

8.4. Second Patch (RLPF, LFSaw, LFNoise1, choose) 

{
RLPF.ar(
       LFSaw.ar([8, 12], 0, 0.2),
       LFNoise1.ar([2, 3].choose, 1500, 1600), 0.05, mul: 0.4
       )
}.play

In this case you should also click the "default" button. 

Error messages 

One thing you will notice right away is that the compiler is very unforgiving about syntax, spelling, and punctuation. Every comma, period, upper and lower case, and semicolon needs to be correct. If something is incorrect the program will print an error message. Sometimes the error messages are hard to decipher. But usually they will indicate the spot where it stopped understanding your code. Here is a typical error message. 

• ERROR: Parse error
in file 'selected text'
line 1 char 45 :
{SinOsc.ar(LFNoise0.ar([10, 15], 400, 800), 0 0.3•)}.play
-----------------------------------
• ERROR: Command line parse failed

"Parse error" means that it didn't understand the code. The third line of the error message tells you where in the file the error was encountered. In this case, line 1 char (character) 45 indicated by a "•." This is where the parsing failed. However, you usually have to look carefully at the characters just before the "•" to find the problem. The most common errors are typos. 

Compare the line in the error message to the original above to see if you can find the error. 

There is a missing comma. Don't be discouraged with error messages. They are normal. While I was writing one of the examples below (about 12 lines of code) I had to run and correct errors six times (not just six errors, but running it, correcting it and running it again six times).   I should probably be more careful, but my point is even people who have been doing this a lot make small typing errors. It's so easy to run the code then correct the errors I 
don't really think of it as making a mistake, but rather using the program to spell check my code. Text files with all the examples from the text are available, which should save some typing. I've removed the first examples so you can get used to entering code yourself. 

Objects, messages, arguments, variables, functions, and arrays 

It is essential that you learn to identify these elements in code examples. 

Objects 
Any uppercase word, numbers, quoted text: Synth, SinOsc, LFNoise, EnvGen, 5, 6.7, "string", etc. 

Messages 
These follow objects separated by a dot (.): play, scope, ar, max, rand, midicps 

Argument lists 
A list of items separated by commas, enclosed in parentheses: (1, 2, 4), ("string", 45, myVar), ({function}, 0.1, [1, 2, 3]) 

Variables 
User defined names of memory locations, beginning with lower case, but often words strung together with caps in the middle: pitchClass, nextCount, freqArray, myVar, etc. 

Enclosures 
Parentheses, brackets, and braces, offsetting functions, arguments, lists, and arrays: (0, 100, 555), {arg next; next = 100}, [100, 200, 500] 

Functions 
Anything enclosed in braces: {arg next; next = 100}, {possibly many pages of code}, {SinOsc.ar(60.midicps)} 

Arrays 
A list of items separated by commas, enclosed in brackets: [1, 2, 3], ["C", "D"], [a, b, c] 

Ugens 
Unit generators are a combination of object, message, and argument list that result in some type of signal. Ugens are connected together to create a patch: LFNoise0.ar(0.5), SinOsc.ar(440, 0, 0.4), Sequencer.ar(s, r, j) 

Before we discuss each of these separately look again at the examples above and practice identifying each of the items. I'll do the first one: The objects are SinOsc, LFNoise0. The messages are play and ar. Functions and arguments are a little harder to spot. All of the text 

{SinOsc.ar(LFNoise0.ar(10, 400, 800), 0, 0.3)}

is a function (everything between { and }).   Arguments are confusing because they can often be nested. The way you spot them is to look for a message such as .ar followed by an opening parenthesis. All the items between that opened parenthesis and the matching closing one are the arguments. In the code 

LFNoise0.ar(10, 400, 800)

the .ar is the message, so (10, 400, 800) is a list of three arguments for the ar message. 

Arguments represent controls. They are similar in function to the knobs on analog synthesizer that change frequency range, filter cutoff, amplitude, sequencer speed, etc. You can think of arguments as the value a knob would point to. 

Take the following patch as an example. The arguments for LFNoise0.ar are (10, 400, 800).   Change these three values and run the code to see how they change the sound. The only rule in this example is that the third number (700) should always be at least 50 higher than the second number. If you make the second number 1200, then the third number should be at least 1250. Try 15, 25, 30, or 40 for the first argument. Try 100 and 700, 30 and 100, 1600 and 1700 for the second two arguments. 

8.5. Arguments (scope, SinOsc, LFNoise0) 

{SinOsc.ar(LFNoise0.ar(10, 400, 800), 0, 0.3)}.play

Now that we've identified the arguments for LFNoise0.ar, what are the arguments for the ar message that follows SinOsc? They are all enclosed in the parentheses following the SinOsc.ar: (LFNoise0.ar(10, 400, 800), 0, 0.3). Notice that the first argument for this list is all the code we were just looking at in the previous example; LFNoise0.ar(10, 400, 800). The second argument is 0, and the third argument is 0.311 . (Try changing the 0.3 to values between 0.1 and 0.9) So the arguments for LFNoise0.ar are combined with LFNoise0.ar as the first argument for SinOsc.ar. This is called nesting. LFNoise0.ar(10, 400, 800) is nested in the argument list for SinOsc.ar. 

There is one more object and message to identify. The function is an object and play is the message. 

Identify all the objects in the examples above. Identify the messages, arrays, functions, and arguments (there are no variables—we'll cover them later). 

Enclosures (parentheses, braces, brackets) 

After you've programmed for a while you get used to reading nested code, but it takes a little practice. The trick is to see where the opening parenthesis, bracket, and brace are matched with a closing parenthesis, bracket, and brace. When the program is run the computer matches up these enclosures and runs or evaluates the code inside the inner most matching enclosures, then works outward to the outer lines of code. To understand the flow of a program you need to be able to follow nested expressions. One method for keeping it clear is to use progressive levels of indenting. 

Another way to quickly see where two enclosures are matched is to use Shift-Com-B or by double clicking on any enclosure. SC will shade everything within matching enclosures. To see how this works, place the cursor in any example and repeatedly press Shift-Com-B. 

Sandwich.make 

I find from observing students that the terminology of object oriented languages is more difficult because the object names are so foreign (Synth, LFNoise0, etc.). So I'll use familiar, but fictitious objects and messages to explain how they work. (These examples won't work in SC!) If you are comfortable with these terms, you can skip this section. 

Suppose we had a virtual sandwich maker and a virtual tofu based meat substitute both of which understood smalltalk commands. I'll call these fictitious objects Sandwich and Tofu. 

Every object understands a collection of messages. The messages tell the object what to do.  Likewise, there are many objects that understand any given message. The power of object-oriented languages lies in the way you can mix and match messages and objects. 

For starters, let's assume that Sandwich understands three messages: make, cut, and bake.  And that Tofu understands three messages: bake, fry, and marinate. The syntax for sending the make message to the Sandwich might be this: 

Sandwich.make; 

If you wanted the Tofu to be baked you might write: 

Tofu.bake; 

You may be wondering if we need to give the make message and the bake message some arguments to describe how the sandwich is made and the tofu is baked. Actually we don't.  Most messages have default values built into the code so you can leave them off provided the defaults are appropriate for your project. Try running the line below, which uses no arguments in the .ar message, in SC. 

8.6. Defaults (play, ar, SinOsc) 

{SinOsc.ar}.play

The result is a sine tone at 440 Hz, 1.0 amplitude, at 0 phase. Those are the defaults for SinOsc. Often you are able to use one or two of the defaults, but rarely will you use a message with defaults only. 

Before adding arguments we need to know what each of them means. To find out you use the help files. To read a help file you highlight the item you want help with and press Shift-Com-/ 
(or Com-?). Try it with all the objects we've used so far. 

In each of the help files are prototypes of all the messages understood by that object, with the list of arguments the message needs. Sandwich and Tofu might be documented this way: 

Sandwich 

*make(vegArray, bread, meat) 

*cut(angle, number) 

*bake(temp, rackLevel) 

Tofu 

*bake(temp, baste, rackLevel) 

*fry(temp, length, pan, oil) 

*marinate(sauce, time) 

The secret to smalltalk, and SC, is this; knowing what messages an object understands and knowing what arguments the message uses to describe how the object acts. It is also important to understand that for any given object the arguments for each message are different. Also note that a message will have a different set of arguments when used with a different object. (For example the bake message used with Sandwich has two arguments, while when used with Tofu it has three. Not understanding this, and using the same arguments with a message to different objects is a common beginner error.) 

Now that we understand what the arguments for Sandwich.make are, we could put together a Sandwich with this mock code. 

Sandwich.make([lettuce, tomato, pickle], rye, chicken) 

or 

Sandwich.cut(90, 1) 

and 

Tofu.marinate(peanut, 160) 

The first line will make the Sandwich using the list of vegetables12, bread, and chicken. The second line will make one cut of the Sandwich at an angle of 90 degrees. The Tofu will be marinated with peanut sauce for 160 minutes. 

Another powerful aspect (the whole point, really) of SC and object oriented languages is nesting. What if I wanted to make the sandwich using marinated Tofu? I would replace the variable chicken with the entire section of Tofu code. 

Sandwich.make([lettuce, tomato, pickle], rye, Tofu.marinate(peanut, 160)) 

We can continue to nest. You may have noticed in an earlier example the message "choose" which was sent to the object [2, 3]. [2, 3] is an array of values and it (the array) understands the choose message. It will return one of the values in the array. 

The second argument in marinate could be replaced with [160, 100, 30].choose, then each time the code is run, one of those values will be chosen for a marinate time. This is how it would be written out. 

Sandwich.make( 
[lettuce, tomato, pickle], 
rye, 
Tofu.marinate(peanut, [160, 100, 30].choose) 
) 

When a program evaluates the code it begins with the inner most parts, and uses the results of those values to run the subsequent upper layers. In English, it might read like this: Marinate tofu in peanut sauce for either 160, 100, or 30 seconds, pick one. After marinating the tofu, use it as the meat (third argument) for a sandwich with lettuce, tomato, and pickle, on rye bread. 

If we wanted to bake the sandwich or the tofu, note that we would have to use different arguments for each one. Sandwich.bake(130, 2) will bake the sandwich at 130 degrees on rack level 2. But if I used these same arguments for the bake message to tofu (Tofu.bake(130, 2)) then the tofu will be baked at a temperature of 130 degrees, but with a baste of 2, not a rack level of 2. It is likely that 2 is an invalid argument for baste, and even if it were valid it probably wouldn't be the value we wanted. Remember; argument lists are not interchangeable. 

Normally arguments have to be in the correct order. This can be cumbersome if the list is long and you only want to change the fourth or fifth. The solution is keyword assignment. The syntax for keywords is this: 

Sandwich.make( 

vegList: [lettuce, tomato, pickle], 

bread: rye, 

meat: Tofu.marinate(sauce: peanut, time: [160, 100, 30].choose)) 

Look in the help files to find what the exact keyword for each argument is. 

Using keywords also allows the programmer the option of leaving off arguments (thus using the defaults). Suppose the default for vegList was [lettuce, tomato, pickle]. We wouldn't need to enter that argument, so the code could be done like this: 

Sandwich.make( 

bread: rye, 

meat: Tofu.marinate(sauce: peanut, time: [160, 100, 30].choose)) 

It is possible to link messages. For example Sandwich.make.bake.cut would first make the sandwich (in this case using defaults), then bake it (using defaults), then cut it (with defaults).  You can also use an Object/message nested in the same Object/message. For example, you could write Tofu.marinate(Tofu.marinate(peanut, 60), 60). In this case, a batch of tofu will be marinated in peanut sauce for 60 minutes, then another batch of tofu will be marinated in that batch of marinated tofu (ick!). 

Experimenting With a Patch (Practice) 

Try experimenting with the patches below. The first argument of the LFNoise0.ar represents the tempo of the sequence. The argument 400 is the range (+ and -) of frequencies generated and 800 is the center frequency. They are discussed in depth later. For now we can just say the LFNoise0 is generating values between 400 and 1200 ten times per second. If you increase the 400 to 500, then it generates frequencies between 300 and 1300 ten times per second. 

So how does this fit into the entire patch? Look at the documentation for SinOsc and you will see that the first argument is "freq." The entire LFNoise0.ar(etc.) is being used as the freq argument in the SinOsc. To confirm this, try replacing the LFNoise0.ar(10, 400, 800). with a static value such as 300. In this case you will hear a single pitch, 300 Hz. 

Look at the help documentation for the second patch by selecting items and using Com-?.  See if you can make sense of the arguments in relation to the actual sound. Try to predict how the changes you make will change the sound before you run the code. I've bolded those arguments that you can change. 

8.7. experiment (LFNoise0, SinOsc, RLPF, LFSaw, LFNoise1, choose) 

{SinOsc.ar(LFNoise0.ar(10, 400, 800), 0, 0.3)}.play
(
// Select everything between the two parentheses

{
RLPF.ar(
LFSaw.ar([8, 12], 0, 0.2),
LFNoise1.ar([2, 3].choose, 1500, 1600),
0.05
)
}.play
)

The LFSaw in the second patch uses an array ([8, 12]) as the freq argument. The first value is used for the left channel, the other for the right (more on this later). The LFNoise1 is generating values between 100 and 3100 and is used as a cutoff in the RLPF (resonant low pass filter). The LFSaw is the input frequency. If you change the 1500 and 1600 be sure the second value (1600) is larger than the first (1500). 

Feel free to experiment. Here is one of the reasons I like to teach with SC. You can experiment safely. When working with the old analog synthesizers entering incorrect values or patching errors (e.g. outs to outs) could actually damage the equipment. But in SC you can try just about anything. There is indeed a danger of crashing the machine or getting error messages or possibly damaging your ears if the sound is too loud. But otherwise you can experiment at will. 

Here is another nice thing about working with code: If you make changes that result in an interesting sound you can easily document or save the patch. To do this choose save as and name the file or copy and paste the patch into a new file. 

Practice 

8.8. Modified from "rising bubbles" 

(
// select everything between the two parentheses
{
CombN.ar(
 SinOsc.ar(
  LFNoise1.kr(
   4, // freq
   24, // range
  LFSaw.kr(
   [8,7.23],//freq
   0,
   3, // range
   80 // offset
   )
  ).midicps,
 0,
 0.04
),
0.2, // max delay
0.2, // actual delay
66,
4 // decay
)
}.play
)

8. Exercises 
8.1. Identify all objects, functions, messages, arrays, keywords, and argument lists. 

{ 
RLPF.ar( 
LFSaw.ar([8, 12], 0, 0.2), 
LFNoise1.ar([2, 3].choose, 1500, 1600), 
0.05, 
mul: 0.4 

)
}.play

8.2. 
Identify the two errors in this example.
{SinOsc.ar(LFNoise0.ar([10, 15], 400 800), 0, 0.3}.play

8.3. 
Explain what each of the numbers in the argument list mean (using help).

MIDIOut.noteOn(0, 60, 100)

8.4. 
Modify this code to include keywords (using help).

CombN.ar(WhiteNoise.ar(0.01), 0.01, XLine.kr(0.0001, 0.01, 20), -0.2)

8.5. 
In the above example, which Ugens are nested? 

8.6. 
Which of these are not legal variable names? 
lfNoise0ar, 6out, array6, InternalBus, next pitch, midi001, midi.01, attOne; 

8.7. 
Look up the help file for SinOsc. Transcribe the arguments in this line of code: 
SinOsc.ar(12, 0, 300, 1000) so that they are correct for this ugen: LFNoise0.ar(?)