Thomas Royal

Composer | Pianist | Technologist

Writings


Faking Objected-Oriented Programing in Supercollider with Events

May 30, 2014

For those seeking to extend sclang, Supercollider provides its excellent (I hear) object oriented programming capabilities. The weakness lies in the fact that, based on how it was designed, Supercollider doesn't support the use of classes and objects that are not meant to extend the language. Supercollider documentation explicitly states that one cannot execute a class definition in an interpreter window. If you want to deal with classes, it has to be part of the language as you've redefined it.

This angered by for some time. For me, object oriented programming (OOP) is not about extending the capabilities of a programming environment. Rather, it is a way of reasoning about code in terms of objects that maintain attributes and state and also interact with each other.

Little did I realize that I was an idiot. Supercollider DOES support ad hoc OOP through object prototypes, which uses the Event class and its status of a subclass of the Environment class to mimic objects.

The term "object prototype" shows a link between Javascript and Supercollider. This insight allows for one to utilize the work Douglas Crockford has done in popularizing Javascript as a serious language capable of OOP. To mimic OOP using Supercollider's interpreter, I will apply some of his ideas.

Events as Object Prototypes

Events are most easily created through parentheses:

var an_event = (); // an event

Events are an under-documented way of making quick objects in Supercollider. Their syntax is very lean, as well. I am surprised more people don't swear by them. Here is an example:

(

var counter = (
  number: 0,
  increment: { |self| self.number = self.number + 1 }
);

counter.number.postln; // 0

// increment the number. 
// notice there is not need to
// use the 'value' method as you
// do for functions
counter.increment;
counter.number.postln; // 1
counter.increment; 
counter.number.postln; //2

)

One can even use the closure provided by functions to emulate private variables:

(

var counter = {
  var num=0;
  (
    increment: {|self| num = num+1},
    current: { num }
  )
}.value;


counter.current.postln; // 0
counter.increment.postln; // 1
counter.increment.postln; // 2

counter.num = -4; // doesn't do anything of interest
counter.current.postln; // 2, still

)

In the above, num is a function variable whose scope is the function. Since the function is immediately evaluated, and the last item, an Event, is returned, the only thing that has access to num is the Event. Private variables are thus emulated.

Objects Derived from Classes

The above is okay if you only want to have one counter. If you wanted two counters, you would be out of luck. Of course, all you have to do is get rid of value in the declaration, and you have the ability to create a number of counters.

(

// counter Class
var cCounter = {
  var num=0;
  (
    increment: {|self| num = num+1},
    current: { num }
  )
};

var counter1 = cCounter.value;
var counter2 = cCounter.value;

counter2.increment;

counter1.current.postln; // 0 
counter2.current.postln; // 1

)

You can even pass variables into this function like a constructor.

(

// counter Class
var cCounter = {
  | init |
  var num=init;
  (
    increment: {|self| num = num + 1},
    current: { num }
  )
};

var counter1 = cCounter.value(4);
var counter2 = cCounter.value(-4);

counter1.increment.postln; // 5
counter2.increment.postln; // -3

)

You can even have private functions:

(

// counter Class
var cCounter = {
  | init |
  var num=init;
  // private function!
  var inc_function = { | num | num + 1 };
  (
    // call private function
    increment: { num = inc_function.value(num)},
    current: { num }
  )
};

var counter1 = cCounter.value(4);
var counter2 = cCounter.value(-4);

counter1.increment.postln; // 5
counter2.increment.postln; // -3

)

What you cannot do with this pattern, however, is have things like class variables, which are variables that, when referenced by each instance of a class, refer to the same object or value. Class variables are useful, for example, when you want to keep track of how many instances of a class you have.

One solution to this limitation is using an Event as a collection that itself contains variables and methods that allow for the generations of classes. For example:

(

// counter Class
var cFiveCountersAndThenSpam = (
  numCounters: 0,
  init: {
    |self,value=0| // first value passed to init is cFiveCountersAndThenSpam

    var return, num; // private variables

    if(self.numCounters < 5, { // if there are less than five counters
      num = value;
      return = (  // return a counter
        increment: {num = num + 1; num},
        cur: {num}
      )
      },
      { return = "spam"} // otherwise return the string "spam"
    );
    self.numCounters = self.numCounters + 1;
    return // this is where the return happens
  }
);

var counter1 = cFiveCountersAndThenSpam.init;
var counter2 = cFiveCountersAndThenSpam.init(-4);
var counter3 = cFiveCountersAndThenSpam.init;
var counter4 = cFiveCountersAndThenSpam.init;
var counter5 = cFiveCountersAndThenSpam.init;
var counter6 = cFiveCountersAndThenSpam.init;

counter1.increment.postln; // 1
counter2.increment.postln; // -3
counter6.postln; // spam

)

The above will only produce five counters, and then any remaining counters will be called as spam. There are issues here, the main one being that the numCounters variable isn't private. All one has to do to break the functionality of the class is this:

... declare five counters here

var counter6;

cFiveCountersAndThenSpam.numCounters = 0;

counter6 = cFiveCountersAndThenSpam.init;

counter1.increment.postln; // 1
counter2.increment.postln; // -3
counter6.increment.postln; // 1

cFiveCountersAndThenSpam.numCounters.postln; // 1

This can be solved as above, but enclosing the counter class in a function:

var cFiveCountersAndThenSpam = {
  var numCounters=0;
  (
  init: {
    |self,value=0|

    var return, num; 

    if(numCounters < 5, { // use numCounters at function scope
      num = value;
      return = (  
        increment: {num = num + 1; num},
        cur: {num}
      )
      },
      { return = "spam"} 
    );
    numCounters = numCounters + 1; // use numCounters at function scope
    return // this is where the return happens
  }
  )
}.value;

One might see all of this as a cumbersome way of introducing OOP to the interpreter. Composers using Supercollider should consider themselves lucky in this regard. Javascript is as cumbersome, but not only do you have to deal with software that is used daily by millions of people, and have managers breathing down your throat about it, you also have to deal with interpreter compatibility issues, i.e., IE8 and under.

Honestly, once you figure out the pattern, it becomes quite easy and fluid to deal with classes, objects, and object-like entities in the interpreter.

Gotchas

There are some gotchas that one working with these patterns have to remember.

The first Event method argument is always the event

The biggest obstacle for me in figuring out how to do this is that I failed to notice that in the documentation, event methods having arguments always had a self argument as the first one. Beware!

(

var print_value = (
  doit: {
    |val|
    val.postln;
  }
);

print_value.doit("now!"); // prints: ( 'doit': a Function )

)

(

var actually_print_value = (
  doit: {
    |self, val|
    val.postln;
  }
);

actually_print_value.doit("now!"); // prints: now!

)

You cannot use certain words for methods and properties

As explained in the documentation for Events, you cannot use method names that are also method names defined by a superclass of events.

(
var val_store = (
  value: 1
);

// prints: ('value': 1), the value of the Event
// and not the method value on val_store
val_store.value.postln; 

// trying again
val_store.value = 1; 
// WARNING: 'value' exists a method name, so you can't use it as pseudo-method.

)

Events are Events, meaning they can be played

Events were not really made for OOP, but rather, were meant be sound events that can be played:

(
s.boot;
)

(

var dont_play_me = (
  x: 1
);

dont_play_me.play; // plays a sound

)

This can be resolved, for some reason, by setting the parent to an empty event:

(
var silent_event = (x:1);
silent_event.parent = ();
silent_event.play; // returns ( 'x': 1 )
)

Extensions

This covers the very basics of OOP using the Supercollider interpreter. There is much more to OOP that has not been covered here.

Polymorphism is one of them, but it comes free. This is because Event's inherit from the IdentityDictionary class. Using the dot notation is a shortcut to Dictionary lookup using symbols. Because symbols are unique, meaning two identical symbols refer to the same object, you can use the same symbol on different IdentityDictionary's (or Event's) and have the same result.

I haven't experimented much with inheritance. I know that the parent parameter of the Event class allows you to specify a set of parameters that are "injected" into the new Event:

(
var par = (
  x:1
);

var child = (
  y:2,
  z:{|self| self.x+self.y}
);
child.parent = par; // add x to child

child.x; // 1
child.y; // 2
child.z; // 3
)

I haven't encountered the gotcha's associated with this pattern. Let that be an exercise for the reader.