James McCartney Rethinking the 850 Stendhal Ln. Cupertino, California 95014 USA [email protected] Language: SuperCollider

Designing a new language requires of the connections between unit generators, and one to answer certain questions. Some of these instrument instantiation and de-allocation. These questions may at first glance seem trivial but on abstractions make writing signal-processing algo- further examination are rather deep. The following rithms easier, because they abstract a number of questions fit this category. What is a computer lan- cumbersome details. However, the Music N family guage? What is the difference between a high-level provides few control structures, no real data struc- and a low-level language? What do current com- tures, and no user functions. SAOL (www.saol.net) puter music languages do for you? What should a improves the Music N paradigm by providing the computer music language do? How can computer kinds of abstractions found in the language, such language abstractions be applied to computer mu- as control structures, functions, and some data sic? Is a specialized computer music language even structures. (www.cycling74.com/products/ necessary? This article discusses the above ques- maxmsp.html), which is quite a different kind of tions, how they led to creating the SuperCollider language, and some current development and fur- , provides an interesting set ther directions for SuperCollider. of abstractions that enable many people to use it without realizing they are programming at all. Max provides some object-oriented features, including dynamically typed data, dynamic binding, but no What Is a Computer Music Language? inheritance, limited data types, but only one data structure for inter-object messaging and few control A computer language presents an abstract model of structures. The Max language is also limited in its computation that allows one to write a program ability to treat its own objects as data, which without worrying about details that are not rele- makes for a static object structure. Later evolutions vant to the problem domain of the program. The of Max, such as jMax (www..fr/produits/ greater the power of abstraction of a language, the logiciels/log-forum/jmax-e.html) and Pd more the can focus only on the prob- (www.pure-data.org), do various things to expand lem and less on satisfying the constraints and limi- the data structure limitations of Max but still have tations of the language’s abstractions and the a generally static object structure. computer’s hardware. Some of the abstractions A computer music language should provide a set available (roughly in order from less abstract to of abstractions that makes expressing composi- more abstract) are listed in Table 1. tional and signal processing ideas as easy and direct The abstractions provided by the Music N lan- as possible. The kinds of ideas one wishes to ex- guages, including (www.csounds.com), are press, however, can be quite different and lead to the abstraction of a , the audio sam- ple computation loop, the representation very different tools. If one is interested in realizing a score that represents a piece of music as a fixed artifact, then a traditional orchestra/score model will suffice. Motivations for the design of Super- Collider were the ability to realize sound processes that were different every time they are played, to write pieces in a way that describes a range of pos- Computer Music Journal, 26:4, pp. 61–68, Winter 2002 sibilities rather than a fixed entity, and to facilitate ᭧ 2002 Massachusetts Institute of Technology. live by a /performer.

McCartney 61

Downloaded from http://www.mitpressjournals.org/doi/pdf/10.1162/014892602320991383 by guest on 01 October 2021 Table 1. Some abstractions available in modern computer programming languages

Abstraction Description and Purpose

Variable names Provide human readable names to data addresses Function names Provide human readable names to function addresses Control structures Eliminate ‘‘spaghetti’’ code (The ‘‘goto’’ statement is no longer necessary.) Argument passing Default argument values, keyword specification of arguments, variable length argument lists, etc. Data structures Allow conceptual organization of data Data typing Binds the type of the data to the type of the variable Static Insures program correctness, sacrificing generality. Dynamic Greater generality, sacrificing guaranteed correctness. Inheritance Allows creation of families of related types and easy re-use of common functionality Message dispatch Providing one name to multiple implementations of the same concept

Single dispatch Dispatching to a function based on the run-time type of one argument Multiple dispatch Dispatching to a function based on the run-time type of multiple arguments. Predicate dispatch Dispatching to a function based on run-time state of arguments Garbage collection Automated memory management Closures Allow creation, combination, and use of functions as first-class values Lexical binding Provides access to values in the defining context Dynamic binding Provides access to values in the calling context (.valueEnvir in SC) Co-routines Synchronous cooperating processes Threads Asynchronous processes Lazy evaluation Allows the order of operations not to be specified. Infinitely long processes and infinitely large data structures can be specified and used as needed.

Applying Language Abstractions one writes a sound-processing function, one is to Computer Music actually writing a function that creates and con- nects unit generators. This is different from a pro- cedural or static object specification of a network The SuperCollider language provides many of the of unit generators. Instrument functions in Super- abstractions listed above. SuperCollider is a dynam- Collider can generate the network of unit genera- ically typed, single-inheritance, single-argument tors using the full algorithmic capability of the dispatch, garbage-collected, object-oriented lan- language. guage similar to (www.smalltalk.org). In For example, the following code can easily gener- SuperCollider, everything is an object, including ate multiple versions of a patch by changing the basic types like letters and numbers. Objects in values of the variables that specify the dimensions SuperCollider are organized into classes. The UGen (number of exciters, number of comb delays, num- class provides the abstraction of a unit generator, ber of allpass delays). In a procedural language like and the Synth class represents a group of UGens Csound or a ‘‘wire-up’’ environment like Max, a operating as a group to generate output. An instru- different patch would have to be created for differ- ment is constructed functionally. That is, when ent values for the dimensions of the patch.

62 Computer Music Journal

Downloaded from http://www.mitpressjournals.org/doi/pdf/10.1162/014892602320991383 by guest on 01 October 2021 ( { var exciterFunction, numberOfExciters, numberOfCombs, numberOfAllpass; var in, predelayed, out; // changing these parameters changes the dimensions of the patch. ;10 ס numberOfExciters ;7 ס numberOfCombs ;4 ס numberOfAllpass // a function to generate the input to the reverb, // a pinging sound. ;{ (rand(3000.0), 0.003 ם Resonz.ar(Dust.ar(0.2, 50), 200 } ס exciterFunction // make a mix of exciters // Mix.arFill fills an array with the results of // a function and mixes their output. ;(Mix.arFill(numberOfExciters, exciterFunction ס in // reverb predelay time : ;(DelayN.ar(in, 0.048 ס predelayed // a mix of several modulated comb delays in parallel: } ,Mix.arFill(numberOfCombs ס out CombL.ar(predelayed, 0.1, LFNoise1.kr(rand(0.1), 0.04, 0.05), 15) }); // a parallel stereo chain of allpass delays. // in each iteration of the do loop the Allpass input is the // result of the previous iteration. numberOfAllpass.do({ (AllpassN.ar(out, 0.050, [rand(0.050), rand(0.050)], 1 ס out }); // add original sound to reverb and play it: ;(out * 0.2) ם in }.play )

One way of conceiving of a composition is as a that can create multiple streams from a single spec- sequence of events. SuperCollider supports this ab- ification. straction via the concept of a stream. A stream is an object to which the next message can be sent to get the next element. A stream ends when it re- // a Pattern specifying a stream that turns nil in response to next. A finite stream is // returns 1, 2, 3, 1, 2, 3, nil ;(Pseq([1, 2, 3], 2 ס one that eventually returns nil; an infinite stream p ;p.asStream ס is one that never does. By default, all objects in s SuperCollider respond to next by returning them- // create the stream selves, so any object can be used as an infinite // from the Pattern s.next; stream of itself. There are also Stream classes that // get the next value 1 s.next; define operations on streams and Pattern classes // get the next value 2 s.next;

McCartney 63

Downloaded from http://www.mitpressjournals.org/doi/pdf/10.1162/014892602320991383 by guest on 01 October 2021 // get the next value 3 s.next; it could run in the background generating events // get the next value 1 s.next; ahead of time. SuperCollider Server is an architec- // get the next value 2 s.next; ture that goes in this direction. // get the next value 3 s.next; In SuperCollider Server, the synthesis engine and // get the next value nil the SuperCollider language engine are separate ap- plications. They communicate via a slightly modi- SuperCollider defines an event stream as one fied version of the (OSC) that responds to next by returning dictionaries protocol (Wright 1998). This allows users to run that map symbols to values. The synthesis instru- multiple copies of the synthesis engine either on ment looks up the parameters it needs to start an the same machine (to exploit multiple processors) event from those in the dictionary. Thus, the com- or on machines distributed across a network. Con- position code does not need to know anything trolling the synthesis engine is as simple as open- about an instrument’s argument list order. Using ing a socket and sending commands, so any dictionaries also means an event may contain any -pro םםprogram (e.g., Max, a Java applet, or a C/C set of parameters. gram) could control it, not just a program written .Pbind(\midinote, Pseq([60, 62, 63], in the SuperCollider language ס p 2), \dur, Pseq([0.5, 0.25], 3)); Both the synthesis and language engines are Mac- p.asEventStream(Event.new); intosh OS X command-line applications, with a full ס s s.next; graphical (GUI) version of the lan- Event[ (dur -Ͼ 0.5), (midinote -Ͼ 60) ] guage engine also available. s.next; Event[ (dur -Ͼ 0.25), (midinote -Ͼ 62) ] s.next; Features of the Synthesis Engine Event[ (dur -Ͼ 0.5), (midinote -Ͼ 63) ] s.next; The SuperCollider 3 Synth Sever is a simple but Ͼ Ͼ Event[ (dur - 0.25), (midinote - 60) ] flexible synthesis engine. While synthesis is run- s.next; ning, new modules can be created, destroyed, and Ͼ Ͼ Event[ (dur - 0.5), (midinote - 62) ] re-patched, and sample buffers can be created s.next; and reallocated. Effects processes can be created Ͼ Ͼ Event[ (dur - 0.25), (midinote - 63) ] and patched into a signal flow dynamically at s.next; scheduled times. Patching between modules is nil done through global audio and control busses. All commands are received via TCP or UDP us- ing a simplified version of Open Sound Control. SuperCollider Server The Synth Server and its client(s) may be on the same machine or across a network. The Synth SuperCollider was originally designed to be a close- Server does not send or receive MIDI; it is expected as-possible marriage between a high-level language that the client will send all control commands. If and a synthesis engine. SuperCollider version 2 MIDI is desired, it is up to the client to receive it (SC2) represents this approach. However, although and convert it to appropriate OSC commands for this close coupling has some advantages, it is not the synthesis engine. the only approach, and there are a number of rea- Synthesis definitions are stored in files generated sons to separate a composition language from a by the SuperCollider language application. Unit synthesis engine. In the closely coupled architec- generator definitions are Mach-O bundles (not to be ture, some synthesis processing time must be con- confused with CFBundles). The unit generator ap- sumed generating events. If the composition plications programming interface (API) is a simple language were removed from the synthesis engine, C interface.

64 Computer Music Journal

Downloaded from http://www.mitpressjournals.org/doi/pdf/10.1162/014892602320991383 by guest on 01 October 2021 I have written two versions of the SC Server syn- Figure 1 illustrates how the tree of nodes and the thesis engine. One uses a block computation model buses work together to create a synthesis architec- and unit generator plug-ins. Instruments are loaded ture. Circles represent nodes, and rectangles repre- as files that describe patches of these unit genera- sent busses. This tree of nodes is organized in a tors. Another version is implemented as a single- way that ensures that execution proceeds in order sample computation model with the instruments starting with control nodes, followed by instru- loaded as compiled plug-ins. ments, effects, and finally a mixer module.

Features of the Block Computation Version Tree of Nodes This version includes the control rate and an audio All running modules are ordered in a tree of nodes rate dichotomy like SC2, Csound, and other lan- that define an order of execution. A Node is an ad- guages. As in SC2, all unit generators are imple- dressable node in a tree of nodes run by the synthe- mented so that they always linearly interpolate a sis engine. There are two types of Nodes: Synths control input whenever not doing so would result and Groups. The tree defines the order of execution in a discontinuity. The language’s class can of all Synths, and all nodes have an integer identi- generate instrument definition files to be loaded by fier (ID). A Group is a collection of Nodes repre- the synthesis engine. sented as a linked list. A new Node may be added to the head or tail of the group. The Nodes within a Group may be controlled together. The Nodes in Features of the Single-Sample a Group may be both Synths and other Groups. At Computation Version startup, there is a top-level group with an ID of zero that defines the root of the tree. A Synth is a The language’s synthesis class library can generate code to be loaded by the synthesis engine. It is םםcollection of unit generators that run together. They C can be addressed and controlled by commands to not necessary to use the SuperCollider language to the synthesis engine. They read input and write generate synthesis code. The synthesis engine has a output to global audio and control busses. Synths simple C linkage API that allows anyone to write can have their own local controls that are set via loadable instruments. The code generator imple- commands to the server. mented in the class library has several interesting features, as described below. Instead of a single control rate, any unit genera- Audio and Control Busses tor may run at any power of two division of the au- dio clock rate and is by default automatically Synths send audio and control signals to each other linearly interpolated to the rate of any unit genera- via a pair of global arrays of audio and control tor to which it is an input. Only source unit gener- busses. Busses are indexed by integers beginning ators need to specify the computation rate. with zero. Using busses rather than connecting Modifier unit generators determine their rates from Synths to each other directly allows Synths to con- their inputs. Unit generators can be easily imple- nect themselves to the community of other Synths mented by overriding a few methods that generate without a priori knowledge about them. The code, illustrated by the following example of a saw- lowest-numbered audio busses get written to the tooth oscillator: audio hardware outputs. Immediately following the LFSaw : UGen { iphase ,440.0 ס audio output busses are the audio input buses, *new { arg cdiv, freq ;0.0סadd ,1.0סmul ,0.0 ס .which are read from the audio hardware inputs The number of bus channels defined as inputs and ^this.multiNew(cdiv, freq * (2.0 * add ם outputs do not have to match that of the hardware. SampleDur(cdiv)), iphase) * mul

McCartney 65

Downloaded from http://www.mitpressjournals.org/doi/pdf/10.1162/014892602320991383 by guest on 01 October 2021 Figure 1. The tree of nodes (circles) executes from left to right, operating on the busses (rectangles).

} New Features in SC3 decl { ^’’ Multiple synthesis engines can be supported either FLT @phase; on the same machine or distributed across a net- ‘‘ work, simply by running multiple copies of the } synthesis engine. This is a simple way to exploit start { multiple processors with the additional advantage ^’’ of having each process protected from a crash by -iphase; the other. Because the language engine and the syn$ ס phase@ this.calc thesis engine are separate applications, they are םם ‘‘ } also protected from each other’s crashing. This calc { makes live performances safer by providing more ’’ opportunity to recover gracefully from a crash in .phase; the middle of a performance@ ס out$ freq; Spawning new events is much faster with$ ם phase@ ס FLT nextphase SuperCollider Server. Because instruments are ס- f) nextphase.1 סif (nextphase Ͼ 2.f; compiled to a single object, the data for all unit f) generators can be allocated with a single call to the.±1סelse if (nextphase Ͻ f; allocator. In the single sample version, linking unit.2 סם nextphase nextphase; generators together requires no extra work, since ס phase@ ‘‘ they have been compiled into code. Spawned } Synths can also insert themselves into a graph via } global busses.

66 Computer Music Journal

Downloaded from http://www.mitpressjournals.org/doi/pdf/10.1162/014892602320991383 by guest on 01 October 2021 Features Lost from Version 2 // instruments and effects. Server.default.sendMsg(‘‘/g_new’’, 1, 0); Some features of version 2 are lost with this decou- // create group 1 (for instruments) pled architecture. Because instruments are com- Server.default.sendMsg(‘‘/g_new’’, 2, piled into code, it is not possible to generate 0); patches programmatically at the time of the event // create group 2 (for effects) as one could in SC2. The advantage is that it is // define an instrument much faster to allocate a new instrument, and CPU ,0 ס SynthDef(‘‘pulse’’, { arg ioutchan usage levels are more constant, with fewer ;1.0 ס gate ,400 ס freq ‘‘bursts.’’ Users can still design instruments pro- var env, out; grammatically in the same way as in version 2; // envelope generator however, instruments now get compiled. ס env It is not possible in SC Server to run EnvGen.kr(Env.linen(0.01,0.1,0.7,-2), composition-level code synchronously in response gate); to an audio trigger. (Asynchronous triggering is pos- FreeSelfWhenDone.kr(env); sible, however.) In SC2, an audio trigger could sus- // automatically deallocates synth when pend the signal processing, run some composition // done code, and then resume signal processing. In SC Server, messaging between the engines causes a // 2 channel pair of pulse waves into a certain amount of latency. // pair of filters. ס Because unit generators get compiled into instru- out ם ments, they do not exist as objects at synthesis RLPF.ar(Pulse.ar(freq [0,1],0.2,env), time. Thus, polling instantaneous values of unit freq*LinRand(2,16), 0.2); generators must be handled differently. // mix to the output bus Out.ar(ioutchan, out) }).writeDefFile; // load the instrument Using the Server from the Language Server.default.sendMsg(‘‘/d_load’’, ‘‘engine/synthdefs/pulse.scsyndef’’) // scheduling latency (depends on The following example shows how to control the // hardware and how far ahead you want server directly from the language. Some class li- // to generate events) braries like the Pattern classes will be able to en- ;0.05 ס lat capsulate management of some of the low-level // define a routine to generate events. interaction with the server illustrated below, mak- })Routine ס r ing it easier to use. 200.do({ arg i; In the example, the server is started, two groups var dur, freq; are created, an instrument definition is created and // random duration loaded, and then 200 randomly generated events ס are played. dur [0.15, 0.2, 0.3].choose; // random frequency ;rrand(36, 96).midicps ס Server.default; freq ס s // get the default // send OSC message to start the event // server address s.boot; // at time lat from thread’s current // start synth server program s.start; // time // start audio running s.sendBundle(lat, [‘‘/s_new’’, ;([i, 1, ‘freq’, freqםwe need two groups: one each for ‘‘pulse’’, 1000 //

McCartney 67

Downloaded from http://www.mitpressjournals.org/doi/pdf/10.1162/014892602320991383 by guest on 01 October 2021 // send OSC message to release the volving sharing data between two garbage-collected // gate of the envelope at time virtual machines running in different threads. durםlat ,’’dur, [‘‘/n_setםs.sendBundle(lat i, ‘gate’, 0]); Conclusionם1000 // sleep the thread for the duration // of the event Is a specialized computer music language even nec- dur.wait; essary? In theory at least, I think not. The set of ab- }); stractions available in computer languages today }); are sufficient to build frameworks for conveniently // play the routine expressing computer music. Unfortunately, in prac- SystemClock.play(r) tice, some pieces are missing in the implementa- tions of languages available today. Often, the garbage collection is not performed in real time, and often argument passing is not very flexible. If SuperCollider on MacOS X lazy evaluation is not included, then implementing Patterns and Streams becomes more complicated. I wrote SuperCollider to have the set of abstractions SuperCollider has been, mostly for historical rea- that I wanted for computer music to have some- sons, a MacOS application from the beginning. thing flexible to use. In the future, other languages Now that Apple is moving to OS X, work has been may be more appropriate. ongoing to port and redesign SuperCollider to take One goal of separating the synthesis engine and advantage of the benefits of this new operating sys- the language in SC Server is to make it possible to tem. The SuperCollider Server language application explore implementing in other languages the con- has been rewritten using the Cocoa framework in cepts expressed in the SuperCollider language and Objective-C. class library. Some other languages that I think The SuperCollider graphical user interface may have interesting potential in the future for classes now support ‘‘drag and drop’’ of any computer music are OCaml (www.ocaml.org), SuperCollider object. This will, I think, lead to an Dylan (www.gwydiondylan.org), GOO (www. easy way for non-programmers to use SuperCollider googoogaga.org), and also possibly Ruby (www. by using the ‘‘drag and drop’’ paradigm to make ruby-lang.org), a scripting language which coinci- connections between pre-built modules. All objects dentally shares many syntactic and semantic attri- can have inspectors opened for them, and their in- butes with the SuperCollider language. ternal structure and the structure of the entire sys- tem can be traced by clicking along, following links. References Another version of SuperCollider, called version 3, had a newly rewritten unit generator class li- Wright, M. 1998. ‘‘Implementation and Performance Is- brary and a dual language engine architecture, with sues with Open Sound Control.’’ Proceedings of the one engine running the graphical user interface and 1998 International Computer Music Conference. San another running the synthesis code. This architec- Francisco: International Computer Music Association, ture presented a number of difficult problems in- pp. 224–227.

68 Computer Music Journal

Downloaded from http://www.mitpressjournals.org/doi/pdf/10.1162/014892602320991383 by guest on 01 October 2021