Using and Writing Classes in SuperCollider

Server.default=s=Server.internal;

What is Object Oriented Programming? 

Think of a jelly mould; it is a template from which we can make as many jellies as we have time and gelatinous matter. There is only one mould, but lots of jellies. 

In object oriented programming we call the mould a class, and the jellies we make from it objects or instances.   

A class is an abstract description of properties of some category. We can make a class for whatever concept we choose- if we made a class for Giraffe, it might descibe the potential for long necks and four long legs and a certain range of allowable stripiness. Using the class, we can instantiate actual giraffe objects, and make as many of them as we like, a whole herd. The individual giraffes might vary slightly in the length of their necks and legs, the stripiness of their bodies and how aggressive they are towards tourists, but they will basically conform to the category, the Giraffe class, describing giraffedom. 

There is more to Object Oriented Programming than that, but I'll reveal it slowly.

SuperCollider is an Object Oriented Programming Language, and you should have noticed by now that words keep turning up with capital letters at the start:

SinOsc
LFSaw
LPF

These are the classes, the abstract descriptions for particular types of audio object. When we make objects from the recipes, we get particular instances of a sine oscillator, or a low frequency saw wave or a low pass filter. We can have as many sine oscillator objects as we like in our piece, but we create them from the SinOsc class.

{Mix.new([SinOsc.ar(440),SinOsc.ar(550),SinOsc.ar(660)])*0.3}.play

The way the SinOsc class is making each SinOsc object here is through a class method of the class, ar.

A class method is just a function, but it is a function tied to a class. Typically, such class methods are the recipes for making objects. In fact, the most common one is new:

w=SCWindow.new("new window object", Rect.new(100,100,400,400));

which is used so commonly in SC, that there is a special shortcut of missing it out:

w= SCWindow("new window object", Rect.new(100,100,400,400));

w= SCWindow(); //accept defaults

As well as class methods, we have instance methods. These are functions which any created instance (object) can respond to.

w.front;	//make window appear by calling an instance method on the instance (object) we just made

//call an instance method (backColor_) on the view object belonging to the window object
w.view.backColor_(rgb(255,0,45));

Note how in the first case we called a function (using the dot) on a class name- SCWindow.new and in the second we called a function on a variable holding an individual object- w.front

How do you know what the class methods and instance methods of any class are? You can see 
this as follows:

SCWindow		//select the text and press cmd+d to get the Help file

SCWindow		//select the text and press cmd+J to go to the code definition

new			//select the text and press cmd+Y to see all the classes that respond to new

Try the last procedure with the following:

ar

backColor_

series

Whenever you see a response like Meta_SomeClass:, this is a class method
When you see SomeClass:, this is an instance method. 

If you go to the class definition file using cmd+J, you'll see lists of functions. Some have names that begin with a *, like *ar or *new- these are class methods. Some have names with no asterisk- these are instance methods. 

Now, a feature of SuperCollider is that every class you ever make has some position within a class hierarchy. This is a taxonomy of all the different things you can make, and how they relate to one another. 

For instance, a SinOsc is a type of UGen (unit generator). 

An Array is a type of ArrayedCollection. 

This relating of types allows classes to share common properties and avoids the programmer 
having to code lots of similar functions again and again- they can be kept in one place.  

Witness:

Collection.dumpClassSubtree //show all classes derived from Collection

for further information on querying the class hierarchy see:

[Internal-Snooping]	//select the text within the brackets and press cmd+d

In fact, everything derives at root from a basic class, confusingly called Object. In SuperCollider, all other classes inherit the properties of the Object class. We say that all classes derive from Object.

Object.dumpClassSubtree		//try running this to see the whole hierarchy

There is a big tree structure of classes. As we go down the hierarchy from Object, classes become increasingly specialised.  Classes that specialise, that derive from something else, are called subclasses. The class they derive from is the superclass. 

When you create new classes you derive from an existing class using the colon. Hence

SinOsc  : UGen			//SinOsc is a subclass of UGen; UGen is the super class for SinOsc

This means that SinOsc is a more specialised UGen, one that deals exclusively with making sine waves. 

By default, you derive from Object, so if you don't specify any class to derive from explicitly, you are making a subclass of Object. 

In making our own derivations, we start general and get more specific, hence we might have

SavannahBeast: SavannahHerbivore: Giraffe: AlbinoGiraffe

As part of our hierarchy, but we could have a more extensive set of categories. The tree might look like:

						SavannahBeast
						/				\
SavannahCarnivore						SavannahHerbivore					
	/			\								/               \
Lion			Hyena						Giraffe           Thomson's Gazelle
												|
											AlbinoGiraffe

This is just hypothetical, and obviously real animals are much more complex than the simple models I'm suggesting here, but its enough for you to see the way we can go about classifying relationships between the categories we wish to work with. 

As well as methods, classes and objects can have associated variables. This is because in Object Oriented programming the data is stored with the functions that act on that data. You can see this is common sense, for going back to our Giraffe class, we might want data to tell us how tall an individual giraffe is (held in an instance variable) or we might want data telling us the natural predators of the giraffe species (held in a class variable, since I assume it's the same for every giraffe). 

in the class definitions, 

var <>height; 				//is an instance variable

classvar <>predators; 		//is a class variable

in the client code (the code that uses my hypothetical Giraffe class) I might have:

g= Giraffe.new; 

g.height = 2.4; 		//set the height in metres of this particular Giraffe

Giraffe.predators = [\lion, \hyena, \man];	//set the predators of the entire giraffe species

Note how the instance variable is called from an instance stored in a variable, but the class variable is called via the class name. 

An important issue is that of access. Some data is only used internally in a class, for secret calculations that normal users don't need to know about; other data is freely available to people who use the class. 

This is shown by the < and > signs. 

< is the getter method, meaning that someone outside the class can read this data
> is the setter method, meaning that someone outside the class can write new values for this data.

If neither is there, nobody outside the class can access that data, but it is freely available to the code written for the class itself. 

So if we have the simple class definition:   Giraffe { var <height; }

I can go 

g=Giraffe.new; 

h= g.height; 	      //there is a getter, so I can read the current value

but I cannot go

g.height = 2.3;	//there is no setter

Using extension libraries and recompiling the library

Go to the SCClassLibrary folder in the SuperCollider application directory. 

We can add or remove class definition files (those that end in .sc) from here, and then we recompile the library using cmd+K to update our class definitions. 

When you get hold of extension libraries for SuperCollider you're getting hold of lots of additional classes that people have written. Installing the third party library consists of you adding it into your SCClassLibrary so that it is compiled as part of SuperCollider.  

(note: you can also put the classes in a specific extensions directory on your system, which will be discussed with third party libraries next week)

The claret symbol ^

We are in a position to make our own classes now. The final syntax you just need to be aware of is the use of the claret symbol ^, which is used in the methods (the functions) of the class to denote where a function returns.

In defining your own normal functions 

f= {arg a; a*a}		//return the square of the argument  

In a method for a class, there's no need for an equals sign, this is defining an instance method called 'f':

f {arg a;  ^a*a}

You are probably wondering why the claret is used at all; why bother? The reason is that a method can return at any point, or at multiple points based on the logic:

mymethod {arg a;  if(a==4, ^7);  a=(a*9)%7; ^if(a==6, 5, a)}

A normal function could not do this. 

Making a new class

NastySynth {
	classvar <>wackiness=200;
	
	*ar {arg wackymult=1; 
		
		^CombN.ar(
			(SinOsc.ar(
				LFNoise0.ar(9, wackymult*wackiness, MouseY.kr(100,400)), 7, MouseX.kr(0.0,0.75)
			)%0.3)
		,0.3,0.3, 5)
	}

}

Put this definition in a new window, save it as NastySynth.sc in the SCClassLibrary folder.
Recompile- the class count should go up.  

Now run this client code one line at a time-

NastySynth.wackiness= 200; 
{NastySynth.ar}.play
{NastySynth.ar(0.01)}.play
{NastySynth.ar(SinOsc.kr(MouseX.kr(0,100)))}.play
NastySynth.wackiness= 4000; 
{NastySynth.ar}.play
{NastySynth.ar(0.01)}.play
{NastySynth.ar(SinOsc.kr(MouseX.kr(0,100)))}.play

Finally, lets show the use of derived classes

Put the file SuperMario.sc in your DefaultLibrary and recompile. 

Now run this client code:

(
var sm;

sm=SuperMario(1.5);
SuperMario.companyaffiliation= \Nintendo;
sm.graspofItalian= 1.0; 
sm.dostuff;
)

(
u=UltraPlumber.new.height_(1.5);

UltraPlumber.dungareesupplier= \Levi;
SuperMario.companyaffiliation.postln;

u.graspofItalian= 0.7; 

u.dostuff;
)

//and you can run these lines whilst its playing
u.craftiness= 1500;	
u.height= 7.3; 
u.ubendification= 0.1;
u.sizeoftool=2.0;