Chapter 19 - Creating Classes

In object oriented programming classes serve as the blueprint for objects, the genotype information that results in instances of phenotypes. Like a recipe for cookes. This can be extremely useful when creating data structures that have properties (parameters or variables) and have behaviours (methods or functions).

For further information on what a class is, then a good start is the Wikipedia:

// run this line in SuperCollider “open ‘http://en.wikipedia.org/wiki/Class_(computer_science)’“.unixCmd;

Creating Classes

A good introduction to writing classes are in the Help documentation

“Writing-Classes”.openHelpFile

So below we have two TestClasses, one that subclasses the first one (like the guitar is a subclass of a string instrument).

Save both classes in a document that could be called “TestClass.sc� and make sure it is saved in the classpath of SuperCollider (where it is compiled on recompilation or startup). On the Mac the classpath for third party classes is in ~Library/Application Support/SuperCollider/External.

TestClass {
	
	classvar <>myvar; // classvariables
	var <>addnr, >addnrSet, <addnrGet; // instance variables
	// this is a normal constructor method
	*new { arg argaddnr; 
		^super.new.initTest(argaddnr) 
	}
	
	initTest { arg argaddnr;
		addnr = argaddnr ? 3;
	    // do initiation here
	}
	
	calc {arg a, b;
		var c;
		c = a+b;
		^c // return
	}

}

TestClass2 : TestClass {
	calc { arg a, b;
		var c;
		c = a * b + addnr;
		^c;
	}
	
	setAddNr_ { arg newnr;
		addnr = newnr;
	}
	
	getAddNr {
		^addnr;
	}
}

When the classes have been saved and compiled we can now test the class:

t = TestClass.new // 
t.calc(3,4)

r = TestClass.new(9)
r.addnr

v = TestClass2.new
v.calc(3,4)

v.addnr_(55)
v.addnr // our new class
t.addnr // the other of course still is just 9

v.addnrSet = 33 // we can set this number (because of > (a setter) )
v.addnrSet_(33) // another way of setting a variable (same as = )


v.addnrGet = 33 // Wrong! we cannot set this number ( because it is a getter < )

// proper object orientated programming uses setter and getter methods 
// (rather than accessing variables directly)

// here we use the setAddNr_ method to set our variable.
v.setAddNr_(333)
// and we can look at it:
v.addnr 
// but should really look at it with the getter method we made:
v.getAddNr

You can see that the < > symbols in the class are so called setters (>) and getters (<), which, if specified in front of the property specification, can serve instead of methods that set the properties. Therefore you can equally write

v.addnr(44) // using a setter and v.addnrSet(44) // using a method

Another test class

SonicArtsClass {
	
	var win, textfield, textfield2, rect; // get text but set text2
	var name, <>profession; // a getter and setter variable
	var friends;

	*new { arg name, rect, color; 
		^super.new.initSAClass(name, rect, color);
		}
	
	initSAClass { arg argname, argrect, color;
		var scramblebutton;
		
		rect = argrect;
		name = argname;
		win = SCWindow(name, rect, resizable:false).front;
		win.view.background_(color);
		textfield = SCStaticText(win, Rect(10, (rect.height/2)-30, rect.width, 30));
		textfield.string_("");
		textfield.font_(Font("Helvetica-Bold", 24));
		textfield2 = SCStaticText(win, Rect(10, (rect.height/2)+30, rect.width, 30));
		textfield2.string_("");
		textfield2.font_(Font("Helvetica-Bold", 14));
		scramblebutton = SCButton(win, Rect(10,10, 200, 30))
				.states_([
				["change friends color",Color.black,Color.clear]]
				)
				.action_({
				friends.do({arg friend; friend.changeColor(Color.rand)});
				});

		friends = List.new;
	}
	
	speak_{arg string;
		textfield.string_(string);
	}

	speak2_{arg string;
		textfield2.string_(string);
	}
	
	updateGUI {
		win.refresh;
	}
	
	addFriend {arg friend;
		friends.add(friend);
	}
	
	getName {
		^name; // note the return symbol
	}
	
	setName_ {arg newname; // note the underscore used when you are setting
		name = newname;
	}
	
	removeFriend {arg friend;
		var friendindex;
		friendindex = friends.indexOfEqual(friend);
		friends.remove(friendindex);
	}
	
	showFriends {
		var namesOfFriends;
		namesOfFriends = List.new;
		friends.do({arg friend; namesOfFriends.add(friend.getName)});
		textfield2.string_(namesOfFriends.asString);
	}
	
	getFriends {
		^friends
	}
	
	getFriendNames {
		var namesOfFriends;
		namesOfFriends = List.new;
		friends.do({arg friend; namesOfFriends.add(friend.getName)});
		^namesOfFriends;
	}
	
	changeColor {arg color;
		win.view.background_(color);
		win.update;
	}
}

{language= JavaScript, line-numbers=off}

And here is some code to try the class

a = SonicArtsClass("john", Rect(50, 800, 300, 200), Color.red)
a.speak_("Hi! I'm John")
a.profession = "singer"
a.speak2_("I am a" + a.profession)

b = SonicArtsClass("george", Rect(350, 800, 300, 200), Color.blue)
b.speak_("Hi! I'm george")
b.profession = "bass player"
b.speak2_("I am a" + b.profession)

c = SonicArtsClass("paul", Rect(650, 800, 300, 200), Color.green)
c.speak_("Hi! I'm paul")
c.profession = "guitarist"
c.speak2_("I am a" + c.profession)

// let's fix the roles

b.profession = "guitarist"
b.speak2_("I am a" + b.profession)
c.profession = "bass player"
c.speak2_("I am a" + c.profession)

a.addFriend(b)
a.addFriend(c)
a.showFriends

b.showFriends
c.showFriends

b.addFriend(a)
b.addFriend(c)
b.showFriends // check his friends

// what if john wants to change his name?

a.setName_("ringo");
a.speak_("Hi! I'm"+a.getName)
// we can get the name like this
a.getName
// but not like this:
a.name
// however, we can get the profession like this
a.profession
// WHY?
// the reason is the < (get) and > (set) properties of the profession variable