View this PageEdit this PageUploads to this PageHistory of this PageTop of the SwikiRecent ChangesSearch the SwikiHelp Guide

Cottle Chapter 5 - Variables and Comments

Home   How To   Code Pool   Public Library   Theory   Events
[Cottle examples]

5. Variables, Comments, Triggers

Variables and Comments


//5 Assignment:

s = Server.internal;
s.boot;	// start the server

// See the explanation of the rewrite under ex. 5.3.

// What happens if you replace Impulse.kr(8) with Dust.kr(3)?

(
var pitchCollection; 

pitchCollection = [60, 45, 67, 82, 55, 66, 62].midicps;

{ Out.ar([0, 1],	// [0, 1] duplicates the mono signal
				// between the left and right channels
	SinOsc.ar(
		Select.kr(		// pitch selector
			Stepper.kr(	// steadily increasing index
				Impulse.kr(8), //Trigger frequency
				0,	// reset trigger (not needed)
				0,	// min value
				pitchCollection.size-1, // max value
				1,	// step size
				pitchCollection.size-1 // reset value
			),
			pitchCollection
		),
		mul: 0.3
	)
) }.play(s)
)

//5.1 Variable declaration, assignment, and comments

//First patch

{ SinOsc.ar(LFNoise0.ar(10, 400, 800), 0, 0.3) }.play(s);

//First patch with variables

(
{
var freqRate, freqRange, lowValue;
freqRate = 10; //rate at which new values are chosen
freqRange = 1200; //the range of frequencies chosen
lowValue = 60; //the lowest frequency

	SinOsc.ar(
		LFNoise0.ar(freqRate, freqRange/2, freqRange + lowValue),
		0, 0.3)
}.play(s);
)

//Triggers, Gates

//5.2 Trigger 

// Synth.plot does not exist, so those examples are omitted.
// There is an ascii-based plotting mechanism, but writing the results
// of a ugenfunc into it is a bit more complicated than we should get
// into at this point. The examples with scope should illustrate the
// behavior of triggers well enough.

// Periodically, you will see single-sample spikes in the scope window.
// You won't see every impulse because of the way scope works.

{ Impulse.ar(4, mul: 0.5) }.scope;

{ Dust.ar(5) }.scope;

//5.3 Sequencer

// SC3 does not support Sequencer.kr.
// The same thing can be done with Select.kr, which addresses
// an array, and using a Stepper as the index.

// In Stepper, reset value should be set to the max value so that
// when the first trigger fires, the value wraps back around to the
// minimum value.

(
var pitchCollection; 

pitchCollection = [60, 45, 67, 82, 55, 66, 62].midicps;

{ Out.ar([0, 1],	// [0, 1] duplicates the mono signal
				// between the left and right channels
	SinOsc.ar(
		Select.kr(		// pitch selector
			Stepper.kr(	// steadily increasing index
				Impulse.kr(8), //Trigger frequency
				0,	// reset trigger (not needed)
				0,	// min value
				pitchCollection.size-1, // max value
				1,	// step size
				pitchCollection.size-1 // reset value
			),
			pitchCollection
		),
		mul: 0.3
	)
) }.play(s)
)

//Just For Fun
//
//5.4 Just for Fun

// This is a great illustration of one of the main architectural differences
// between sc2 and sc3.

// Here we have a couple of levels of synth activity:

// rate, delayFunction, frequency, and foldFunc are overall control processes
// that should apply to each event created by OverlapTexture in the original.
// The function in the OverlapTexture creates a new event.

// We can modularize this by creating separate synths for the control processes,
// playing their output onto **control buses**. Then, the synth that makes the events
// can use the values on the control buses to determine its behavior.

/* The original patch is written in a way that's very idiomatic for sc2. 
Here are two translations: first, more "literal" so you can compare 
more easily to the original. This first translation is less modularized 
(and, as we'll see in a moment, less flexible). */

// pass 1: make all the required synthdefs.
(
SynthDef("ch5-controls", {

/* My very strong preference is never to hardwire bus numbers into a synthdef. 
By passing in the bus numbers as arguments, I can make two copies of this 
entire control-value complex, running on separate buses, without rewriting 
the synthdef. */

	arg	ratebus,
		dlyFuncbus,
		freqbus,
		foldFuncbus;
	
	var frequency, rate, seqTrigRate, foldFunc, foldFuncRate, 
	foldFuncWidth, foldFuncFocus, delayFunction, filterCutoff, 
	delayMax, delayMin, echoDecay, minFreq, maxFreq, freqRate; //line 5
	
	minFreq = 20;
	maxFreq = 40;
	freqRate = 1/3;
	foldFuncWidth = 0.05; //line 10
	foldFuncFocus = 0.001; 
	foldFuncRate = 0.5;
	delayMax = 0.4;
	delayMin = 0.01; //line 15

// As in the original, all these control values can be calculated in one UGenFunc. 
	seqTrigRate = Dust.kr(1/3); //rate trigger; one in three seconds

	rate = TChoose.kr(
			seqTrigRate,
			[1/4, 3, 6, 3/4, 14, 20]
		);

	delayFunction = LFNoise1.kr(
			rate, 
			mul: delayMax/2, add: delayMax/2 + delayMin);

	frequency = LFNoise1.kr( 
		freq: freqRate, 
		mul: (maxFreq - minFreq)/2, 
		add: ((maxFreq - minFreq)/2) + minFreq);
		//line 30
	foldFunc = LFNoise1.kr( 
		freq: foldFuncRate, 
		mul: foldFuncWidth, 
		add: foldFuncWidth + foldFuncFocus
		); //line 35

/*They must be output separately on to their respective buses.
There's a shortcut for this, which we'll see in later chapters.
Out.kr makes sure that control-rate buses are used. */

	Out.kr(ratebus, rate);
	Out.kr(dlyFuncbus, delayFunction);
	Out.kr(freqbus, frequency);
	Out.kr(foldFuncbus, foldFunc);
}).send(s);

SynthDef("ch5-synth", {
// The sound-making synth also needs to know the bus numbers, so they get passed in again. 
	arg	ratebus,
		dlyFuncbus,
		freqbus,
		foldFuncbus;

	var outMix, frequency, rate, foldFunc, 
	delayFunction, filterCutoff, 
	delayMax, echoDecay; //line 5

	filterCutoff = 1000;
	delayMax = 0.4;
	echoDecay = 4; 

	rate = In.kr(ratebus, 1);
	delayFunction = In.kr(dlyFuncbus, 1);
	frequency = In.kr(freqbus, 1);
	foldFunc = In.kr(foldFuncbus, 1);

	outMix = SinOsc.ar(frequency, mul: 0.8).fold2(foldFunc);
	outMix = CombN.ar(outMix, delayMax, 
					delayFunction, echoDecay, add: outMix); //line 40
	outMix = RLPF.ar(outMix, filterCutoff); 
	outMix = Pan2.ar(outMix, LFNoise1.kr(rate));

//Again, a fixed-length envelope is used to handle the overlap.

	outMix = outMix * EnvGen.kr(Env.linen(1, 3, 1), doneAction:2);
	Out.ar(0, outMix);
}).send(s);
)

/*This block of code assigns bus numbers, instantiates the control processes, 
and periodically triggers a new audio synth. Here I am manually assigning bus
numbers. There is a bus object which manages allocating the numbers for you. 
We'll use that in later chapters. */

(
var ratebus = 0, delayFuncbus = 1, freqbus = 2, foldFuncbus = 3;

// Start controls running.
// Observe the syntax to pass values into a synth at creation time.
// Arguments are given as pairs: the argument name from the function
// may be given as a \symbol or a "string" -- the next value in the array
// is the value the argument should receive.

Synth("ch5-controls", [\ratebus, ratebus,
	\delayFuncbus, delayFuncbus, \freqbus, freqbus,
	\foldFuncbus, foldFuncbus], target:s);

r = Routine({
	inf.do({
		Synth("ch5-synth", [\ratebus, ratebus,
			\delayFuncbus, delayFuncbus, \freqbus, freqbus,
			\foldFuncbus, foldFuncbus], target:s);
		2.5.wait;
	});
}).play(SystemClock);
)

/* So far, it looks like more code and more trouble to do what sc2 did in 
a very straightforward way. But here's something sc2 can't do so easily. 
After starting the previous processes, say you want to hear another copy 
of the same processes playing at the same time. In sc2, you would have 
to stop the synth, do some measure of surgery, and restart. In sc3, 
you can simply execute the following. Note that the bus numbers 
are different. */

(
var ratebus = 4, delayFuncbus = 5, freqbus = 6, foldFuncbus = 7;

// Start controls running.
Synth("ch5-controls", [\ratebus, ratebus,
	\delayFuncbus, delayFuncbus, \freqbus, freqbus,
	\foldFuncbus, foldFuncbus], target:s);

t = Routine({
	inf.do({
		Synth("ch5-synth", [\ratebus, ratebus,
			\delayFuncbus, delayFuncbus, \freqbus, freqbus,
			\foldFuncbus, foldFuncbus], target:s);
		2.5.wait;
	});
}).play(SystemClock);
)

r.stop;	// to prove it, stop the first. The second will still play.

t.stop;	// stop the second.

// Clean up: Press cmd-. to stop the control synths.

//end patch

/* Let's carry the modularization further. This version places all the 
control processes into separate synthdefs. Doing it this way makes 
the code more idiomatic to sc3. Although initially the code looks 
fussier, we'll see the advantage momentarily. */

(
	var frequency, rate, seqTrigRate, foldFunc, foldFuncRate, 
	foldFuncWidth, foldFuncFocus, delayFunction, filterCutoff, 
	delayMax, delayMin, echoDecay, minFreq, maxFreq, freqRate; //line 5
	
	minFreq = 20;
	maxFreq = 40;
	freqRate = 1/3;
	foldFuncWidth = 0.05; //line 10
	foldFuncFocus = 0.001; 
	foldFuncRate = 0.5;
	delayMax = 0.4;
	delayMin = 0.01; //line 15

SynthDef("ch5-trig", { arg trigbus;
	Out.kr(trigbus, Dust.kr(1/3)); //rate trigger; one in three seconds
}).send(s);

/*The rate function depends on the trigger, and the delay function depends 
on the rate. Therefore each must have an argument for the bus where the 
input value resides, in addition to the output bus number. The input value 
is retrieved by In.kr(bus_number, 1) (one channel) */

SynthDef("ch5-rate", { arg ratebus, trigbus;
	Out.kr(ratebus, TChoose.kr(
		In.kr(trigbus, 1), [1/4, 3, 6, 3/4, 14, 20]))
}).send(s);

SynthDef("ch5-dlyFunc", { arg dlybus, ratebus;
	Out.kr(dlybus, LFNoise1.kr(In.kr(ratebus, 1), 
		mul: delayMax/2, add: delayMax/2 + delayMin));
}).send(s);

SynthDef("ch5-freq", { arg freqbus;
	Out.kr(freqbus, LFNoise1.kr( 
		freq: freqRate, 
		mul: (maxFreq - minFreq)/2, 
		add: ((maxFreq - minFreq)/2) + minFreq));
}).send(s);

SynthDef("ch5-fold", { arg foldbus;
	Out.kr(foldbus, LFNoise1.kr( 
		freq: foldFuncRate, 
		mul: foldFuncWidth, 
		add: foldFuncWidth + foldFuncFocus));
}).send(s);

//The audio synth does not need be changed from the previous version. 

SynthDef("ch5-synth", {
	arg	ratebus,
		dlyFuncbus,
		freqbus,
		foldFuncbus;

	var outMix, frequency, rate, foldFunc, 
	delayFunction, filterCutoff, 
	delayMax, echoDecay; //line 5

	filterCutoff = 1000;
	delayMax = 0.4;
	echoDecay = 4; 

	rate = In.kr(ratebus, 1);
	delayFunction = In.kr(dlyFuncbus, 1);
	frequency = In.kr(freqbus, 1);
	foldFunc = In.kr(foldFuncbus, 1);

	outMix = SinOsc.ar(frequency, mul: 0.8).fold2(foldFunc);
	outMix = CombN.ar(outMix, delayMax, 
					delayFunction, echoDecay, add: outMix); //line 40
	outMix = RLPF.ar(outMix, filterCutoff); 
	outMix = Pan2.ar(outMix, LFNoise1.kr(rate));
	outMix = outMix * EnvGen.kr(Env.linen(1, 3, 1), doneAction:2);
	Out.ar(0, outMix);
}).send(s);
)

// pass 2: flow of control, playback
(
var ratebus = 0, delayFuncbus = 1, freqbus = 2, foldFuncbus = 3, trigbus = 4;

//Now we need to make separate synths for each of the control processes.
 
a = Synth("ch5-trig", [\trigbus, trigbus], target:s);
b = Synth("ch5-rate", [\ratebus, ratebus], target:s);
c = Synth("ch5-dlyFunc", [\dlybus, delayFuncbus], target:s);
d = Synth("ch5-freq", [\freqbus, freqbus], target:s);
e = Synth("ch5-fold", [\foldbus, foldFuncbus], target:s);

r = Routine({
	inf.do({
		Synth("ch5-synth", [\ratebus, ratebus,
			\delayFuncbus, delayFuncbus, \freqbus, freqbus,
			\foldFuncbus, foldFuncbus], target:s);
		2.5.wait;
	});
}).play(SystemClock);
)

//OK. Now we get to the real strength of sc3. Let's play what if. 

/*What if the trigger occurs regularly every half second, instead of 
randomly distributed? Note that I'm first stopping the original 
trigger synth, then substituting a new one. */

a.free; a = { Out.kr(4, Impulse.kr(2)) }.play(s);

//Not so much difference, let's switch back to the original trigger. 
a.free; a = Synth("ch5-trig".postln, [\trigbus, 4], target:s);

//What if the frequency rises, then jumps to the minimum value and rises again? 
d.free; d = { Out.kr(2, LFSaw.kr(0.4, 0, 10, 30)) }.play(s);

//What if it oscillates rapidly around a base frequency that rises and falls slowly? 
d.free; d = { Out.kr(2, LFPulse.kr(7, 0, 0.5, 10, SinOsc.kr(0.2, 0, 30, 50))) }.play(s);

/*What if the base frequency moves from plateau to plateau, sliding at 
varying rates of speed, and the oscillation rate changes as well? */

(
d.free; d = {
	var base;
	base = Lag.kr(
		LFNoise0.kr(
			LFNoise1.kr(0.1, 0.4, 0.5),
			30, 50
		),
		LFNoise1.kr(0.1, 0.3, 0.31)
	);
	Out.kr(2, LFPulse.kr(SinOsc.kr(0.05, 0, 5, 7), 0, 0.5, 10, base));
}.play(s);
)

//What if the delay time follows a sawtooth pattern as well? 
c.free; c = { Out.kr(1, LFSaw.kr(0.07, 0, 0.2, 0.21)) }.play(s);

// Now stop the audio (let it fadeout), and then free all the control synths at once. 

r.stop;
[a, b, c, d, e].do({ arg synth; synth.free });

/* In sc2, to make these changes, you would have to stop the synth completely, 
do some rewriting, then play it from the beginning again. Or, you would have 
to plan for all the possible alternatives you would want and include a mechanism 
to switch between them in real time. (Or use Julian Rohrhuber's just-in-time 
library, but let's stick to out of the box functionality.) 

In sc3, you can mix and match parts of the processes at will, depending on 
the degree to which you have modularized your code. Since all the processes 
are independent, you can stop and start them independently. I think this 
makes it easier to experiment, and makes the creative process more fluid.
*/



Link to this Page