SuperCollider: Synchronous vs Asynchronous

The Problem

Most examples in this class can not be run as a complete script. They need to be evaluated line by line or block wise. One reason for this is the difference between synchronous and asynchronous execution. This problem is in detail explained in the SC guides: https://doc.sccode.org/Guides/Sync-Async.html This site just gives a shorter answer.

Issues usually arise, when a command has been sent to the server and following command depends on the completion of that action. Examples can be the creation of nodes or the loading and filling of buffers. Running this simple block at once will result in an error. It creates a node and does not wait for completion before it uses the .set() method. The result is a FAILURE IN SERVER /n_set Node 1000 not found:

(

// create white noise node with gain control
~test = {arg gain=1; WhiteNoise.ar(gain)}.play;

// try to set the gain
~test.set(\gain, 0.1);

)

A Solution

There are several ways of dealing with the above introduced problem. One solution is to wrap the code block in a Routine, which allows to control the order of execution. In the case of asynchronous tasks, the command s.sync can be used inside a routine. It waits for the sever to finish all asynchronous tasks and the below example works without errors:

(
Routine({

    // create white noise node with gain control
    ~test = {arg gain=1; WhiteNoise.ar(gain)}.play;

    // wait for the server to finish all asynchronous tasks
    s.sync;

    // try to set the gain
    ~test.set(\gain, 0.1);

}).play
)

Plans and Directions

The original SPRAWL System was designed as a means for enhanced interaction in Local Area Networks (LAN) (read more). Due to the needs for online teaching and musical performance in 2020, the concept was extended to a Wide Area Network (WAN) solution. In the coming months, the project with be extended in a cooperation with LTU, Sweden and Ritmo, Oslo. The following areas can be tackled in this period.


Multichannel Clients

In this semester we will extend the system to incorporate clients and endpoints with difeerent configurations, including larger loudspeaker setups:

/images/nsmi/sprawl_flow.png

Extended SPRAWL System, allowing Access Points with different configurations.


Gestural Control

All three sites in the project will be equipped with a motion tracking system and multichannel loudspeaker setups, using different configurations. Experiments will aim at sharing spatial sound between the sites and manipulating them with gestural input.

/images/nsmi/bol_setup_1.jpg

21 channel Ambisonics setup with motion capturing system at BOL.


Clocking and Syncing

Another focus will be experiments with shared clocks and sequences, in cooperation with the RITMO group in Oslo: https://www.uio.no/ritmo/english/projects/dr-squiggles/index.html

We have the possibility to join the Rhythm Production and Perception Workshop for our closing concert on June 22-25.


Back to NSMI Contents

Using OSC in Pure Data

Vanilla Only

Sending OSC

The default version of PD is referred to as Vanilla. OSC can be used in Puredata without further packages, by means of the ojects netsend, oscformat and oscparse. The patch osc-send-vailla.pd sends a message to port 6666 on the localhost (127.0.0.1). The message has the following structure and contains one float argument:

/oscillator/frequency/ [float]

/images/basics/pd-osc-send-vanilla.png

Receiving OSC

The corresponding receiver patch osc-receive-vanilla.pd listens on port 6666. Using the route object, the message is unwrapped until the single float argument can be processed by the number box:

/images/basics/pd-osc-receive-vanilla.png

Exercise

Send messages between the patches. If possible, use two computers and change the address in the send patch.

Using Externals

Dependencies

Sending OSC

The following example is based on additional externals. For using them, install the external mrpeach with the Deken tool inside Puredata: https://puredata.info/docs/Deken The send patch uses the hostname localhost instead of an IP address. The path /oscillator/frequency of the OSC message has been defined arbitrarily - it has to match between client and receiver. Before sending OSC messages, the connect message needs to be clicked.

/images/basics/pd-osc-send.png

Receiving OSC

Before receiving OSC messages, the udpreceive object needs to know which port to listen on. Messages are then unpacked and routed according to their path, using the routeOSC object.

/images/basics/pd-osc-receive.png

Exercise

Use both patches for a remote controlled oscillator. If possible, use two computers and change the address in the send patch.


References

1997

  • Miller S. Puckette. Pure Data. In Proceedings of the International Computer Music Conference (ICMC). Thessaloniki, \\ Greece, 1997.
    [details] [BibTeX▼]

1988

  • Miller S. Puckette. The patcher. In Proceedings of the International Computer Music Conference (ICMC). Computer Music Association, 1988.
    [details] [BibTeX▼]

Digital Filters

Digital filters are delay-based processing units. In short: they affect a signal by overlapping it with delayed versions of the same signal. There are two basic categories of digital filters:

FIR filters

Finite Impulse Response (FIR) filters can be considered simple convolution processors. They are implemented without recursion, respectively feedback. IIR filters are robust and easy to design, yet they are more CPU expensive.

IIR Filters

Infinite Impulse Response (IIR) filters are recursive computational structures. They are used for many time-critical operations, since they are less CPU hungry. In contrast to FIR filters, they can become unstable and may affect the signal in unwanted ways.


Both categories will be introduced in the following sections. In a detailed comparison, they show a couple of differences, both having advantages and disadvantages.

Using the Git Repository

Git is a distributed version control system. Changes to (text) files are grouped in chunks called commits. You can create new branches of a repository for specific features or tasks and merge those branches after you finished your changes.

Cloning a Git Repository

git clone https://github.com/anwaldt/SPRAWL.git

This creates a directory with the name SPRAWL and clones the git repository locally.

With git log you can see all recent commits.

Create Branches, Adding Changes and Committing

Let's create a new branch for our changes:

git checkout -b new_changes

Now we are on a new created branch called new_changes. If you omit the -b you checkout a branch that is on the remote repository.

The easiest way to committing changes is to commit every changes of files.

git add file.txt
git add file2.txt
git commit -m "Fixes wording of file.txt and file3.t wsgh s"

Sometimes it happens that you commited your changes too early but didn't pushed your changes to the remote server. If you only want to change the commit message you can use git commit --amend. The same command works for adding more changes to the last commit. Don't forget to use git add filename.

Pushing Changes to the Remote Server

With git you can have more than one remote repository. After you cloned the sprawl repository you will have a remote repository with the name origin.

student@h2912420:~/SPRAWL$ git remote -v
origin  https://github.com/anwaldt/SPRAWL.git (fetch)
origin  https://github.com/anwaldt/SPRAWL.git (push)

But you don't have any push access to this repository. To get your changes into the mainline SPRAWL repository you have to fork the project on github. At the right top corner at the sprawl's repo you must click on fork. Then you can add your own repo to your local SPRAWL clone:

$ git remote add ntonnaett https://github.com/ntonnaett/SPRAWL.git
$ git remote -v
ntonnaett       https://github.com/ntonnaett/SPRAWL.git (fetch)
ntonnaett       https://github.com/ntonnaett/SPRAWL.git (push)
origin  git@github.com:anwaldt/SPRAWL.git (fetch)
origin  git@github.com:anwaldt/SPRAWL.git (push)
git push ntonnaett

Exchange ntonnaett with your personal remote name. After you committed all your changes you can open a pull request on the mainline sprawl repository.

Using the Git Repository

Git is a distributed version control system. Changes to (text) files are grouped in chunks called commits. You can create new branches of a repository for specific features or tasks and merge those branches after you finished your changes.

Cloning a Git Repository

git clone https://github.com/anwaldt/SPRAWL.git

This creates a directory with the name SPRAWL and clones the git repository locally.

With git log you can see all recent commits.

Create Branches, Adding Changes and Committing

Let's create a new branch for our changes:

git checkout -b new_changes

Now we are on a new created branch called new_changes. If you omit the -b you checkout a branch that is on the remote repository.

The easiest way to committing changes is to commit every changes of files.

git add file.txt
git add file2.txt
git commit -m "Fixes wording of file.txt and file3.t wsgh s"

Sometimes it happens that you commited your changes too early but didn't pushed your changes to the remote server. If you only want to change the commit message you can use git commit --amend. The same command works for adding more changes to the last commit. Don't forget to use git add filename.

Pushing Changes to the Remote Server

With git you can have more than one remote repository. After you cloned the sprawl repository you will have a remote repository with the name origin.

student@h2912420:~/SPRAWL$ git remote -v
origin  https://github.com/anwaldt/SPRAWL.git (fetch)
origin  https://github.com/anwaldt/SPRAWL.git (push)

But you don't have any push access to this repository. To get your changes into the mainline SPRAWL repository you have to fork the project on github. At the right top corner at the sprawl's repo you must click on fork. Then you can add your own repo to your local SPRAWL clone:

$ git remote add ntonnaett https://github.com/ntonnaett/SPRAWL.git
$ git remote -v
ntonnaett       https://github.com/ntonnaett/SPRAWL.git (fetch)
ntonnaett       https://github.com/ntonnaett/SPRAWL.git (push)
origin  git@github.com:anwaldt/SPRAWL.git (fetch)
origin  git@github.com:anwaldt/SPRAWL.git (push)
git push ntonnaett

Exchange ntonnaett with your personal remote name. After you committed all your changes you can open a pull request on the mainline sprawl repository.

Using Arrays in SuperCollider

Simple Arrays

In SC, arrays are collections of objects of any kind. They can be defined and accessed using brackets:

// define simple arrays:
a = [0,1,2,3];
b = [0,1,2,"last_value"];

// access indices:
a[3];

Dynamic Creation

The array class offers numerous methods for creating arrays, including fill():

c = Array.fill(4,{arg i; 10/(i+1) });

Arrays of Buses

Especially in multichannel projects and larger mixing setups, arrays of buses can be helpful. Make sure to boot the server to actually use (scope) the buses:

// an array of 16 buses, each with 4 channels:
~busArray = Array.fill(16,{Bus.control(s, 4)})

// scope the second bus in the array:
~busArray[1].scope

// set the third bus of the second bus in the array:
~busArray[1].setAt(2,0.5);

Array of Nodes/UGens

The same array approach can be used to generate multiple nodes, for example sine waves at different frequencies and amplitudes:

// an array of 16 sine oscillators:
~sineArray = Array.fill(16,{arg i;{SinOsc.ar(200*i)}.play})

Array of Synths

The previous example can also be used with SynthDefs, which is a good starting point for additive synthesis:

// a simple synthdef
(
SynthDef(\sine,
{|f = 100, a = 1|

   Out.ar(0, a * SinOsc.ar(f));

}).send(s);
)

~busArray = Array.fill(16,{arg i;Synth.new(\sine,[f:200*(i+1),a:0.2])})

Warning

The second argument of fill has to be a function in curly brackets. If not, the array will contain multiple pointers to the same object (try)!

Wavetable Oscillator with Phase Reset

The Faust oscillators.lib comes with many different implementations of oscillators for various waveforms. At some point one might still need a behavior not included and lower level approaches are necessary.

This example shows how to use a phasor to read a wavetable with a sine waveform. This implementation has an additional trigger input for resetting the phase of the oscillator on each positive zero crossing. This can come handy in various applications, especially for phase-sensitive transients, as for example in kick drums.

The example is derived from Barkati et. al (2013) and part of the repository:

import("stdfaust.lib");

// some basic stuff
sr = SR;
twopi = 2.0*ma.PI;

// define the waveform in table
ts =  1<<16; // size = 65536 samples (max of unsigned short)
time = (+(1) ~ _ ) , 1 : - ;
sinewave =  ((float(time) / float(ts)) * twopi) : sin;

phase = os.hs_phasor(ts,freq,trig);

// read from table
sin_osc( freq) = rdtable(ts ,sinewave , int(phase)) ;

// generate a one sample impulse from the gate
trig =  pm.impulseExcitation(reset);

reset = button ("reset");
freq = hslider("freq", 100, 0, 16000, 0.00001);

// offset = hslider("offset", 0, 0, 1, 0.00001);

process = sin_osc(freq);

2013

  • Karim Barkati and Pierre Jouvelot. Synchronous programming in audio processing: a lookup table oscillator case study. ACM Computing Surveys (CSUR), 46(2):1–35, 2013.
    [details] [BibTeX▼]

Sending OSC from SuperCollider

For sending OSC from SuperCollider, a NetAddr object needs to be generated. It needs an IP address and a port:

~out_address  = NetAddr("127.0.0.1", 6666);

Sending Values Once

This first example sends an OSC message once when the following line is evaluated. The previously created NetAddr object can be used to send OSC messages with its sendMsg method:

~out_address.sendMsg('/test/message', 1);

Sending Values Continuously

Based on the previous example, a routine can be created which continuously reads values from control rate buses to send their instantaneous value via OSC. The osc_routine runs an infinite loop with a short wait interval to limit the send rate and the CPU load:

  ~cBus = Bus.control(s,1);

  ~osc_routine = Routine({

        inf.do({

      // read value from bus
                  var value      = ~cBus.getSynchronous(~nVbap);

      // send value
                  ~out_address.sendMsg('/oscillator/frequency', value);

                  // wait
                  0.05.wait;

          });
});

Once created, the routine can be started and stopped with the methods play() and stop(). While running, bus values can be changed to test the functionality:

~osc_routine.play();

~cBus.set(300);

~cBus.set(700);

~osc_routine.stop();

Exercise

Exercise

Run the PD patch osc-receive.pd to receive values from SuperCollider via OSC and control the pitch.