• jon@schemawound.com

Server.sync

Server.sync is another case of a useful function in Supercollider that you might not find out about unless you stumble upon it in the help files.

Every tutorial I have ever seen for Supercollider has shown code using SynthDefs as a two step process:

  1. Create the SynthDef in one code block.
  2. In a seperate code block you can create Synths based on your SynthDef.
Here is a simple example:
(
	SynthDef(test, 
		{Out.ar(0, 
			SinOsc.ar(440)) 
			* EnvGen.ar(Env.perc, doneAction:2)
		}
	).add
)

(
	Synth(test);
)

This is usually split into two blocks because there is a slight delay between the request to create the SynthDef on the server and when it is actually available for use.  I do understand the need to keep things simple for example code but in day to day use I prefer to keep everything in a single block that can be executed.  I have several reasons for this:

  • I often share code with people I am collaborating with, It is much easier to have them run a single  block then to explain the order of blocks that need to be run.
  • If you make changes to the SynthDef you do not need to remember to execute the SynthDef block again, it will automatically be updated everytime you run your code.
  • Unless you are making very complicated SynthDefs the time between the request and when it is available is so small it will not be noticed.

Server.sync allows you to bypass all of this by pausing execution until the server has finished all asynchronous actions.  The one catch is Server.sync must be called from inside of a routine.

(
	{
		SynthDef(test, 
			{Out.ar(0, 
				SinOsc.ar(440)) 
				* EnvGen.ar(Env.perc, doneAction:2)
			}
		).add;

		Server.default.sync; //could also state this as s.sync

		Synth(test);
	}.fork;
)

Server.sync is useful for more than just SynthDefs. Another common use is when loading Bufers:

(
	Routine.run {
		//Variables
		var buf;

		//SynthDef
		SynthDef(buffPlay)
		{|outbus = 0, buf|
			var bufScale = BufRateScale.kr(buf);
			var playBuf = PlayBuf.ar(1, bufnum: buf, rate: bufScale);
			var env = EnvGen.ar(Env.perc, doneAction:2);
			var output = playBuf * env;
			Out.ar(outbus, output);
		}.add;

		//Load Buffer
		buf = Buffer.read(Server.default, Platform.resourceDir +/+ "sounds/a11wlk01.wav");
		
		//Sync Load
		Server.default.sync;

		Synth(buffPlay, [buf, buf]);
		
		//Cleanup
		CmdPeriod.doOnce({
			buf.free;
		});
	};
)

Most example code for buffers will work in one of two ways:

  1. The Buffer must be read before the rest of the code and stored in some form of persistent variable (ex. Instance Variables)
  2. A function must be placed in the action argument of the read method to be called when the Buffer has loaded.

The first can encourage an over-reliance on persistent variables and the second can become convoluted in more complex scenarios such as trying to load multiple Buffers before proceeding.

NOTE: The Buffer is being filled each time this code is run.  To make sure we don’t end up filling all our available buffers I free the Buffer when CmdPeriod is executed.

Like many other things in Supercollider Server.sync is not right for all circumstances, but it can be a very useful tool when you want to execute all your code in a single block.

Tags :