title:: 19_Scheduling
summary:: Mark Polishook tutorial
categories:: Tutorials>Mark_Polishook_tutorial
related:: Tutorials/Mark_Polishook_tutorial/00_Introductory_tutorial

section::Routines and clocks

Use clocks to create automated, algorithmic scheduling. Among the things that clocks "play" are routines, tasks, and patterns.

To see how a clock "plays" a routine, first examine how a function works in a routine.

The first argument (and usually the only argument) to a routine is a function.

code::
// template for a routine
Routine({ ".... code within curly braces is a function .... "});
::

A .yield message to an expression in a function (in a routine) returns a value.

code::
r = Routine({ "hello, world".yield.postln });

// to evaluate a routine, send a .next message
// it will "hand over" the value of the expression to which the .yield message is attached
r.next;
::

Evaluate (again)

code::
r.next;
::

The routine above returns nil when its evaluated a second time. This is because once a routine "yields" and if there's no additional code after the .yield message, the routine is finished, over, and done - unless it receives a reset message. Then it can start over again.

code::
r.next;		// returns nil
r.reset;	// reset the routine
r.next;		// it works!
::

////////////////////////////////////////////////////////////////////////////////////////////////////

code::
(
r = Routine({
	"hello, world".yield;
	"what a world".yield;
	"i am a world".yield;
});
)
::

The first three .next messages return a string. The fourth .next message returns nil.

code::
r.next;	// returns a string
r.next;	// returns a string
r.next;	// returns a string
r.next;	// returns nil
::

Reset the routine.

code::
r.reset;

r.next;
r.next;
r.next;
r.next;
::

////////////////////////////////////////////////////////////////////////////////////////////////////

Use a .do message in a routine to make a loop.

code::
(
r = Routine({

	// setup code
	var array;
	array = [ "hello, world", "what a world", "i am a world" ];

	// the loop
	3.do({ array.choose.yield })

});
)
::

Evaluate the routine one more time than the loop in the routine allows.

code::
4.do({ r.next.postln });
::

The routine returned three strings followed by nil.

section::Scheduling routines

Rewrite the routine so that it includes a .wait message.

code::
(
r = Routine({

	var array;
	array = [ "hello, world", "what a world", "i am a world" ];

	3.do({
		1.wait; 		// pause for 1 second
		array.choose.postln;
	})

});
)
::

Then "play" the routine, eg, send it a .play message.

code::
r.play;
::

Append a .reset message to the routine so that it can start over.

code::
r.reset.play;
::

section::Clocks and the convenience of .play

When a routine receives a .play message, control (of the routine) is redirected to a clock. The clock uses the receiver of the .wait message as a unit of time to schedule ("play") the routine.

SuperCollider has three clocks, each of which has a help file.

code::
SystemClock		// the most accurate
AppClock		// for use with GUIs
TempoClock		// to schedule in beats
::

The .play message is a convenience that allows one to write

code::
r.reset.play;		// reset the routine before playing it
::

instead of

code::
SystemClock.play(r)
::

section::Scheduling synths with routines

Enclose synths within routines. It's often the case that the synthdef used by the synth in routines should have an envelope with a doneAction parameter set to 2 (to deallocate the memory needed for the synth after its envelope has finished playing).

code::
(
// DEFINE A SYNTHDEF
SynthDef("fm2", {
	arg bus = 0, freq = 440, carPartial = 1, modPartial = 1, index = 3, mul = 0.2, ts = 1;

	// index values usually are between 0 and 24
	// carPartial :: modPartial => car/mod ratio

	var mod;
	var car;

	mod = SinOsc.ar(
		freq * modPartial,
		0,
		freq * index * LFNoise1.kr(5.reciprocal).abs
	);

	car = SinOsc.ar(
		(freq * carPartial) + mod,
		0,
		mul
	);

	Out.ar(
		bus,
		car * EnvGen.kr(Env.sine(1), doneAction: Done.freeSelf, timeScale: ts)
	)
}).add;
)

(
// DEFINE A ROUTINE
r = Routine({

	12.do({
		Synth(
			"fm2",
			[
				\bus, 2.rand, \freq, 400.0.rrand(1200),
				\carPartial, 0.5.rrand(2), \ts, 0.5.rrand(11)
			]
		);
		s.queryAllNodes;
		"".postln.postln.postln.postln.postln;
		2.wait;
	})
});
)

// PLAY THE ROUTINE
r.reset.play;
::

////////////////////////////////////////////////////////////////////////////////////////////////////

Process synths spawned in a routine through effects that run outside of the routine.

code::
(
// DEFINE A SYNTHDEF
SynthDef("echoplex", {
	ReplaceOut.ar(
		0,
		CombN.ar(
			In.ar(0, 1),
			0.35,
			[Rand(0.05, 0.3), Rand(0.05, 0.3)],
			// generate random values every time a synth is created
			7,
			0.5
		)
	)
}).add;

// DEFINE GROUPS TO CONTROL ORDER-OF-EXECUTION
// attach a ~source group to the head of the rootnode and
// an ~effects group to the tail of the rootenode
~source = Group.head(s);
~effect = Group.tail(s);

// DEFINE A ROUTINE
r = Routine({

	// loop is the same as inf.do, eg, create an infinite loop that runs forever
	loop({
		Synth.head(	// attach the synth to the head of the ~source group
			~source,
			"fm2",
			[
				\outbus, 0, \freq, 400.0.rrand(1200), \modPartial, 0.3.rrand(2.0),
				\carPartial, 0.5.rrand(11), \ts, 0.1.rrand(0.2)]
		);
		s.queryAllNodes;
		2.wait;
	})
});

// TURN ON EFFECTS
Synth.head(~effect, "echoplex");
Synth.tail(~effect, "echoplex");
)
// PLAY THE ROUTINE
r.reset.play;
::

////////////////////////////////////////////////////////////////////////////////////////////////////

go to link::Tutorials/Mark_Polishook_tutorial/20_Debugging::
