Classes and Objects#

Disclaimer: The book is not about programming in general. Therefore, this section contains only a short description of the topic and assumes that the reader is familiar with one or more object-orientated programming (OOP) languages.

Before we start, note that we can not interactively define new classes. Thus, we can not use the interpreter window to define and execute the code that involves a new class. Instead, classes have to be compiled before we can use them! The following warning is copied from the official SuperCollider documentation from section Writing Classes.

Compiling Class Files

Class definitions are statically compiled when you launch SuperCollider or recompile the library. This means that class definitions must be saved into a file with the extension .sc, in a disk location where SuperCollider looks for classes. Saving into the main class library (SCClassLibrary) is generally not recommended. It’s preferable to use either the user or system extension directories.

It is not possible to enter a class definition into an interpreter window and execute it.

Instantiation#

In sclang the constructor of an object is called by Classname.new.

var numbers = Array.new(10);

However, we can omit the new.

var numbers = Array(10);

This calls the class method new of the Array class.

Class Definition#

All classes are Objects, i.e., they inherit from the fundamental base class Object automatically. This makes it necessary to call the constructor of Object.

To copy the arguments of the constructor to into the object variables we can call newCopyArgs of Object. Object.newCopyArgs(... args) creates a new instance and copies the arguments to the instance variable in the order that the variables were defined. Of course, class variables will be ignored. Therefore, the order is semantically significant!

MyClass {           // Object is implied
    var a, b, c;    // Object variables / attribtes
    classvar d;     // Class variabels / attributes

    *new {          // Class method
        arg arg1, arg2, arg3;
        // Will copy arg1, arg2, arg3 to variables a, b, c
        ^super.newCopyArgs(arg1, arg2, arg3);
    }

    add {           // Object method
        var sum = 0;
        sum = a + b + c;
        ^sum;
    }
}

If you need more control over the initiation of an object you can use the init object method: The expression a = arg1 ? a; makes sure that a will only change if it is not already initialized, i.e., in case you call init after the object has been created.

MyClass {           // Object is implied
    var a, b, c;    // Object variables / attribtes
    classvar d;     // Class variabels / attributes

    *new {          // Class method
        arg arg1, arg2, arg3;
        ^super.new.init(arg1, arg2, arg3);
    }

    init {          // Object method
        arg arg1, arg2, arg3;
        a = arg1 ? a;
        b = arg2 ? b;
        c = arg3 ? c;
    }

    ...
}

Instance variables (the attributes of the object) are defined by using the keyword var while class variabels (the attribute of the class which are shared by all objects of the class) are defined via the keyword classvar. Instance variables will shadow class variables of the same name.

Classes can contain class methods (static methods) and object methods. A class method starts with an *. For example, I implemented a new class MIDIRecorder which offers a class-method *new that calls init which initializes all the object variables.

MIDIRecorder {
    // Object variables / attribtes
    var name, instrument, history, synths, <events, clock, noteOn, mono, paused, pauseTime, midiDefOn, midiDefOff;

    *new {      // Class method
        arg name, instrument = \default, mono = false;
        ^super.new.init(name, instrument, mono);
    }

    init {      // Object method
        arg argname, arginstrument, argmono;
        name = argname ? name;
        instrument = arginstrument ? instrument;
        mono = argmono ? mono;
        history = Array.fill(127, {[]});
        synths = Array.newClear(127);
        clock = TempoClock();
        events = [];
        paused = true;
        noteOn = false;
        pauseTime = 0.0;

        clock.permanent = true;

        ...
    }

    record {    // Object method
        history = Array.fill(127, {[]});
        synths.do({arg synth; synth.set(\gate, 0);});
        synths = Array.newClear(127);
        events = [];
        paused = false;
    }

    ...

Another significant difference between functions and methods is that for methods, the return value has to be marked by a ^. The following code depicts the object-method reverse of ArrayedCollection which is the super-class of Array:

reverse {
    var i = 0;
    var res = this.copy;
    var size1 = res.size - 1;
    var halfsize = res.size div: 2;
    halfsize.do({ arg i;
        res.swap(i, size1 - i);
    });
    ^res
}

First the original array this is copied. Then elements are swapped accordingly. And finally the copy res is returned. The expression res.size div: 2; is equivialent to res.size / 2; or res.size.div(2);.

Getter and Setter#

By default, variables are not accessible outside of the class or instance. A method must be added to explicitly give access. These methods are called getter (returns the variable) and setter (sets the value of the variable). sclang allows these methods to be added via a shortcut by adding < (getter) and/or > (setter) in front of the variable definition.

Let us look at another example, i.e., the class Complex:

Complex : Number {
    var <>real, <>imag;

    *new { arg real, imag;
        ^super.newCopyArgs(real, imag);
    }

    + { arg aNumber, adverb;
        if ( aNumber.isNumber, {
            ^Complex.new(real + aNumber.real, imag + aNumber.imag)
        },{
            ^aNumber.performBinaryOpOnComplex('+', this, adverb)
        });
    }
...

Objects of this class have two (object)-attributes real and img indicated by the signal word var and they are accessible from outside indicated by the ugly rue <>. The instance variables are initialized by the constructor. The name and order is identical!

Furthermore, the class defines a method + which takes one non-optional and one optional argument. aNumber is another complex number and adverb is a modifier that modifies the plus operation if aNumber is not a number. If we ignore the second argument, the + method returns a new complex number by adding two complex numbers together. Therefore, + is a pure function.

I may come back to object-oriented programming with sclang but for now we do not really need it. The best way to learn more about it is to look into the source code pressing i + CMD.