supercollider.js 0.9

Chris Sattinger
5 min readJan 6, 2016

--

supercollider.js is a NodeJS library for working with SuperCollider.

This is the first release in a series intended to bring supercollider.js up to its full potential.

My goal is to make it a stable, high-performance library for controlling the SuperCollider synthesis engine and for flexibly mapping data flows and real time inputs to sound events.

The previous stages of development gave us simplified and reliable control over the synthesis engine process (scsynth) and the SuperCollider language interpreter (sclang).

It also gave us easy methods to evaluate sclang code and get result (or errors) returned as a Promise. This means easy two-way communication and data exchange between SuperCollider and node JavaScript. That enabled me to build atom-supercollider (a full REPL and IDE inside Atom).

This next series of releases are concentrated on working directly with the synthesis engine.

This release in particular is more of a nuts-and-bolts release. I built a lot of internals and laid the ground work for the forthcoming releases.

Feature list

  • Allocators for node ids, buffers and busses
  • Server state node watcher tracks all node go/end events
  • OSC message construction functions for all server commands
  • Server-callAndResponse for async server commands
  • SCLang-executeFile
  • Reactive JS streams for server events
  • Immutable JS for safe and efficient state storage
  • Includes some mapping functions for some uniquely supercollider transforms (midiToFreq, freqToMidi, linToLin, linToExp, expToLin, ampToDb, dbToAmp)
  • Full API documentation
  • Logging colors now work in browser as well as in terminal
  • Moved some internal support code from a script (interpreter.scd) to a dedicated class SuperColliderJS.sc
  • added supercolliderjs.lang.boot() and supercolliderjs.server.boot()
  • compile-synthdefs command line utility: boots sclang and compiles a file of SynthDefs (written in supercollider code), saving the compiled defs to a directory. Useful for building projects that don’t use sclang and wish to ship pre-compiled SynthDefs.
  • export-supercollider command line utility; not 100% done, but this exports your current supercollider executables, class library and quarks to a folder for use as an isolated stand-alone
  • (ALPHA) dryadic components for synth, group, compileSynthDef, etc. This are experimental and will be replaced with a new version in 0.10.0

Breaking Changes

  • Drop support for Node < 4

It probably still works fine on 0.12 but the testing library Jest no longer runs on < 4 and its time we all moved on.

Fixes

supercolliderjs.server.boot() and supercolliderjs.lang.boot() more accurately detect when it is connected and ready for business. Both of these methods return Promises.

Deprec

These are deprecated and will be removed in 1.0

require(‘supercolliderjs’).sclang 
// should change to:
require(‘supercolliderjs’).lang

require(‘supercolliderjs’).scsynth
// should change to:
require(‘supercolliderjs’).server

Server will not (as of 1.0) be a subclass of EventEmitter: instead of adding handlers to listen to emitted events, subscribe to the RxJS streams. These improve flexibility for debugging.

server.on('OSC', function(msg) { ... });
// should change to:
server.receive.subscribe(function(msg) { ... });

As these are RxJS streams you can also use .map .filter and a whole world of other methods.

New API Documentation

New APIs

Server state, node and bus allocators

The allocators are all written in a functional style using a single global state store. It is not a port of SuperCollider’s class based allocators.

Internally this uses Immutable.js and the usage of a global state is similar to Redux (though its not a Flux architecture).

This obeys the principal of “a single source of truth” and makes debugging and testing very easy.

All allocators are accessed by methods on server.state:

sc.server.boot().then(function(server) {
var nodeID = server.state.nextNodeID();
var audioBusID = server.state.allocAudioBus(2);
server.state.freeAudioBus(audioBusID, 2);
});

ServerState also installs a node-watcher. scsynth sends OSC notifications when synths or groups stop or start (or die unexpectedly). These will be used more extensively in future releases.

OSC message construction

Functions that construct the OSC messages for scsynth.

var sc = require('supercolliderjs');

var msg = sc.msg.nodeSet(1001, 'freq', 440);

Messages:

 quit()
cmd(command, args=[])
dumpOSC(code=1)
clearSched()
error(on=1)
defFree(defName)
nodeFree(nodeID)
nodeRun(nodeID, on=1)
nodeSet(nodeID, pairs)
nodeSetn(nodeID, valueSets)
nodeFill(nodeID, triples)
nodeMap(nodeID, pairs)
nodeMapn(nodeID, triples)
nodeMapAudio(nodeID, pairs)
nodeMapAudion(nodeID, triples)
nodeBefore(moveNodeID, beforeNodeID)
nodeAfter(moveNodeID, afterNodeID)
nodeTrace(nodeID)
nodeOrder(addAction, targetID, nodeIDs)
synthNew(defName, nodeID, addAction=AddActions.HEAD, targetID=0, args=[])
synthNoid(synthIDs)
groupNew(nodeID, addAction, targetID)
parallelGroupNew(groupID, addAction, targetID)
groupHead(groupID, nodeID, …rest)
groupTail(groupID, nodeID, …rest)
groupFreeAll(groupID)
groupDeepFree(groupID)
groupDumpTree(groupID, dumpControlValues=0)
ugenCmd(nodeID, uGenIndex, command, args)
bufferSet(bufferID, sets)
bufferSetn(bufferID, startFrame, values=[])
bufferFill(bufferID, startFrame, numFrames, value)
controlBusSet(pairs)
controlBusSetn(triples)
controlBusFill(triples)

callAndResponse

Some scsynth commands have a structured OSC call message and subsequent OSC response from the server.

Use `server.callAndResponse()` to call any of these:

Messages with call and response:

notify(on=1)
status()
sync(id)
defRecv(buffer, completionMsg=null)
defLoad(path, completionMsg=null)
defLoadDir(path, completionMsg=null)
nodeQuery(nodeID)
synthGet(synthID, controlNames)
synthGetn(synthID, controlName, n)
groupQueryTree(groupID, dumpControlValues=0)
bufferAlloc(bufferID, numFrames, numChannels, completionMsg=null)
bufferAllocRead(bufferID, path, startFrame=0, numFramesToRead= -1, completionMsg=undefined)
bufferAllocReadChannel(bufferID, path, startFrame, numFramesToRead, channels, completionMsg=undefined)
bufferRead(bufferID, path, startFrame=0, numFramesToRead= -1, startFrameInBuffer=0, leaveFileOpen=0, completionMsg=undefined)
bufferReadChannel(bufferID, path, startFrame=0, numFramesToRead= -1, startFrameInBuffer=0, leaveFileOpen=0, channels=[], completionMsg=undefined)
bufferWrite(bufferID, path, headerFormat=’aiff’, sampleFormat=’float’, numFramesToWrite= -1, startFrameInBuffer=0, leaveFileOpen=0, completionMsg=undefined)
bufferFree(bufferID, completionMsg=null)
bufferZero(bufferID, completionMsg=null)
bufferGen(bufferID, command, args)
bufferClose(bufferID)
bufferQuery(bufferID)
bufferGet(bufferID, framesArray)
bufferGetn(bufferID, startFrame, numFrames)
controlBusGet(busID)
controlBusGetn(startBusIndex, numBusses)
nonRealTimeEnd()

Be sure to check out the API Documentation

New Libraries

Reactive Extensions for JavaScript (RxJS) is used for composing asynchronous data streams. Its useful for both push and pull streams and has functionality for filtering, merging, mapping and reducing streams.

In RxJS terminology these are Observeables. They are streams that can be subscribed to, can publish for multicasting to multiple listeners. They emit events which are usually JavaScript objects, so they are similar to the Stream and Event classes that SuperCollider uses.

Sometimes RxJS docs are a bit annoying to read through with a bit too much esoteric code poetry.

I had previously used Bacon.js and there is a good chance I’ll go back to that.

Immutable data cannot be changed once created, leading to much simpler application development, no defensive copying, and enabling advanced memoization and change detection techniques with simple logic. Persistent data presents a mutative API which does not update the data in-place, but instead always yields new updated data.

Used internally. Unless you are working directly with ServerState then you won’t run into this much. Its lovely and clearly documented.

The Promises are now using Bluebird which avoids the all too common problem of silent failures. If you forgot to add an error handler to a Promise chain then if any function threw an error you would never hear about it. Bluebird Promises will raise the Error.

Note that ES2015 JavaScript does have native Promises. Bluebird’s implementation is a drop-in replacement and is at this point still runs faster than the native versions.

--

--

Chris Sattinger

Mattermind Labs. Data + Art, AWS, TypeScript, React, Node, Python, Django