Part IV

Chapter 14 - Musical Patterns on SC Server

The SC Server is highly streamlined, small and functional piece of software. It does not have the whole of the SuperCollider language to do timing, data flow, etc., but it does have unit generators that can do much of the same.

Stepper and Select

The stepper is a pulse counter that outputs a signal.

A scale of frequencies from 500 to 1600 in steps of 100 (as it is multiplied by 100)

1 {SinOsc.ar( Stepper.kr(Impulse.kr(10), 0, 4, 16, 1) * 100, 0, 0.2)}.play;

And here the steps are -3 so there are more interesting step sequences

1 {SinOsc.ar(Stepper.kr(Impulse.kr(6), 0, 5, 15, -3).poll(6, "stepper") * 80, 0, 0.2)}.play;

We poll the Stepper to see the output.

And here we use Lag (generating a line from the current value to the next in specified time) for the frequency.

{SinOsc.ar(Lag.kr(Stepper.kr(Impulse.kr(6), 0, 5, 25, -4) * 90, 6.reciprocal), 0, 0.2)}.pla\
y;


// perhaps more understandable like this:
(
{
	SinOsc.ar( 			// the sine
		Lag.kr( 			// our lag
			Stepper.kr(Impulse.kr(6), 0, 5, 25, -4) * 90, // the stepper
			6.reciprocal),// the time of the lag
		0,  				// phase of the sine
		0.2) 			// amplitude of the sine
}.play;
)

NOTE: the lag time is the reciprocal of the Impulse frequency, i.e. the impulse happens 6 times per second, i.e. every 0.16666666666667 seconds. If you check the reciprocal of 6, you get that number. In this case it doesn’t matter whether we use 0.16666666666667 or 6.reciprocal, but if Impulse frequency is in a variable, it could be useful, as in:

f = {arg rate;	
	{SinOsc.ar(Lag.kr(Stepper.kr(Impulse.kr(rate), 0, 5, 25, -4) * 90, rate.reciprocal), 0, 0.\
2)}.play;
}

f.(6)
f.(12)
f.(24)

Select

(
{
	var scale, cycle;
	//scale = Array.fill(12,{ arg i; 60 + i }).midicps; // we fill an array with a scale
	scale = [60, 61, 63, 64, 65, 67, 68, 69, 70].midicps; // we fill an array with a scale
	cycle = scale.size / 2;

	SinOsc.ar(
			Select.kr( 
				LFSaw.kr(0.4, 1, cycle, cycle),
				scale
			)
	);
}.play;
)

Select and Stepper together

Here we use the Stepper to do what LFSaw did above, it is just stepping through the pitchArray and not generating the pitches like in the Stepper examples above.

(
var pitchArray; //Declare a variable to hold the array
	//load the array with midi pitches
pitchArray = [60, 62, 64, 65, 67, 69, 71, 72].midicps; 
{
	SinOsc.ar(
		Select.kr(
			Stepper.kr(Impulse.kr(8), max: pitchArray.size-1), // try with Dust
			pitchArray),
		mul: 0.5)
}.play
)

PulseCount and PulseDivider

We could also use PulseCount to get at the items of the array

(
{
	var scale, cycle;
	//scale = Array.fill(12,{ arg i; 60 + i }).midicps; // we fill an array with a scale
	scale = [60, 61, 63, 64, 65, 67, 68, 70].midicps; // we fill an array with a scale
	cycle = scale.size / 2;

	SinOsc.ar(
			Select.kr( 
				PulseCount.ar(Impulse.ar(scale.size), Impulse.ar(1)), // we go through the scale in 1 s\
ec
				scale
			)
	);
}.play;
)

PulseDivider is also an interesting UGen, it outputs an impulse when it has received a certain numbers of impulses

Here we use it to create a drummer in one synthdefinition. (quite primitive, and just for fun, but look at the CPU : )

(
SynthDef(\drummer, { arg out=0, tempo=4;
	var snare, base, hihat;
	tempo = Impulse.ar(tempo); // for a drunk drummer replace Impulse with Dust !!!

	snare = 	WhiteNoise.ar(Decay2.ar(PulseDivider.ar(tempo, 4, 2), 0.005, 0.5));
	base = 	SinOsc.ar(Line.ar(120,60, 1), 0, Decay2.ar(PulseDivider.ar(tempo, 4, 0), 0.005, 0.\
5));
	hihat = 	HPF.ar(WhiteNoise.ar(1), 10000) * Decay2.ar(tempo, 0.005, 0.5);
	
	Out.ar(out,(snare + base + hihat) * 0.4!2)
}).add;
)

a = Synth(\drummer);
a.set(\tempo, 6);
a.set(\tempo, 18);
a.set(\tempo, 180); // check the CPU! no increase.

Demand UGens

In Tutorial 2 we saw how we could use Patterns to control the server. Patterns are language side streams used to control the server. The Demand UGens are server side and don’t need the SC language. So you could use this from languages like Python, Java, etc.

The Demand UGens follow the logic of the Pattern classes of the SCLang - We will look further at Patterns in the next tutorial.

(
{
	var freq, trig, reset, seq1;
	trig = Impulse.kr(10);
	seq1 = SinOsc.ar(2, mul: 200, add: 700); 
	freq = Demand.kr(trig, 0, seq1);
	SinOsc.ar(freq + [0,0.7]).cubed.cubed * 0.1;
}.play;
)

// same as above, but here we Demand more frequently and the sine is slower
// and we poll the freq
(
{
	var freq, trig, reset, seq1, trigrate;
	trigrate = 20;
	trig = Impulse.kr(trigrate);
	seq1 = SinOsc.ar(1, mul: 200, add: 700).poll(trigrate.reciprocal, "freq"); 
	freq = Demand.kr(trig, 0, seq1);
	SinOsc.ar(freq + [0,0.7]).cubed.cubed * 0.1;
}.play;
)

Using LFSaw instead of a SinOsc

(
{
	var freq, trig, reset, seq1, seq2;
	trig = Impulse.kr(10);
	seq1 = LFSaw.ar(1, mul: 200, add: 700); 
	freq = Demand.kr(trig, 0, seq1);
	SinOsc.ar(freq + [0,0.7]).cubed.cubed * 0.1;
}.play;
)

Using LFTri and now we use the mouse to control the mul and add of the Freq osc.

(
{
	var freq, trig, reset, seq1, seq2;
	trig = Impulse.kr(10);
	seq1 = LFTri.ar(1, mul: MouseX.kr(200,1000), add: MouseY.kr(200,1000)).poll(10.reciprocal,\
 "freq"); 
	freq = Demand.kr(trig, 0, seq1);
	SinOsc.ar(freq + [0,0.7]).cubed.cubed * 0.1;
}.play;
)

There are useful Ugens like Dseq and Drand (compare to Pseq and Prand)

(
{
	var freq, trig, reset, seq1, seq2;
	trig = Impulse.kr(10);
	seq1 = Drand([72, 75, 79, 82]-12, inf).midicps; 
	seq2 = Dseq([72, 75, 79, Drand([82,84,86])], inf).midicps; 
	freq = Demand.kr(trig, 0, [seq1, seq2]);
	SinOsc.ar(freq + [0,0.7]).cubed.cubed * 0.1;
}.play;
)

Dseries

(
{ 
	var a, freq, trig;
	a = Dseries(0, 1.4, 20); // we build a series of values
	trig = Impulse.kr(MouseX.kr(1, 40, 1));
	freq = Demand.kr(trig, Impulse.kr(0.5), a) * 30 + 340; 
	SinOsc.ar(freq) * 0.1

}.play;
)

Dgeom

(
{ 
	var a, freq, trig;
	a = Dgeom(1, 1.4, 20); // we build a series of values
	trig = Impulse.kr(MouseX.kr(1, 40, 1));
	freq = Demand.kr(trig, Impulse.kr(0.5), a) * 30 + 340; 
	SinOsc.ar(freq) * 0.1

}.play;
)

The Dbrown and Dibrown Ugens are good for random walk (drunken walk)

(
{ 
	var a, freq, trig;
	a = Dibrown(0, 20, 2, inf);
	trig = Impulse.kr(MouseX.kr(1, 40, 1));
	freq = Demand.kr(trig, 0, a) * 30 + 340; 
	SinOsc.ar(freq) * 0.1
}.play;
)

Dwhite is whitenoise - not drunk anymore but jumping around madly

(
{ 
	var a, freq, trig;
	a = Diwhite(0, 15, inf);
	trig = Impulse.kr(MouseX.kr(1, 40, 1));
	freq = Demand.kr(trig, 0, a) * 30 + 340; 
	SinOsc.ar(freq) * 0.1

}.play;
)

Using TDuty to demand results from demand rate UGens

(
{
	var minDur = 0.1, delta = 0.01;
	var trig = TDuty.ar(Dbrown(minDur, minDur+delta), 0, Dwhite(0, 1));
	Ringz.ar(trig, TRand.ar(2000, 4050, trig), 0.1)!2
}.play
)

Chapter 15 - Musical Patterns in the SCLang

Throughout this tutorial we have been creating synthesizers, effects, routing them through busses, putting them into groups and more, but for many the question is how to make musical patterns or arrange events in time. For this we need some kind of a representation of the events, for example stored in an array, or generated algorithmically on the fly. Chapter 3 introduced some basic ways of controlling synths, but in this section we will explore in a bit more detail how to arrange musical events in time.

The SynthDefs

For now we’ll use two synth definitions.

SynthDef(\sine, {arg out=0, amp=0.1, freq=440, envdur=1, pan=0.0;
	var signal;
	signal = Pan2.ar(SinOsc.ar(freq, 0, amp**amp).cubed, pan); // note the pan
	signal = signal * EnvGen.ar(Env.perc(0.01, envdur), doneAction:2);
	Out.ar(out, signal);
}).add;

SynthDef(\synth1, {arg out=0, freq=440, envdur=1, amp=0.4, pan=0;
    var x, env;
    env = EnvGen.kr(Env.perc(0.001, envdur, amp), doneAction:2);
    x = Mix.ar([FSinOsc.ar(freq, pi/2, 0.5), Pulse.ar(freq,Rand(0.3,0.7))]);
    x = RLPF.ar(x,freq*4,Rand(0.04,1));
    x = Pan2.ar(x,pan);
    Out.ar(out, x*env);
}).add; 

Routines and Tasks

We have already explored how to play a melody using a Task and a Routine (check the documentation for each, but in short a Task is a Routine that can be paused).

Function has a method called “fork” which will turn the function into a Routine (co-routine, and some could think of it as a “thread” - although technically it’s not), but this allows for a process to run independently of what is happening elsewhere in the program.

Routine({
	1.postln; 
	Synth(\sine, [\freq, 220]);
	0.5.wait;
	2.postln;
	Synth(\sine, [\freq, 220*2]);
	0.5.wait;
	3.postln;
	Synth(\sine, [\freq, 220*3]);
	0.5.wait;
}).play

This could also be written as:

1 { 3.do({arg i; (i+1).postln; Synth(\sine, [\freq, 220*(i+1)]); 0.5.wait }) }.fork

Or unpacked:

{ 
	3.do({arg i; 
		(i+1).postln; 
		Synth(\sine, [\freq, 220*(i+1)]); 
		0.5.wait;
	}) 
}.fork

So with a little melody stored in an array we could play it repeatedly:

m = [60, 63, 64, 61];

{ inf.do({arg i; Synth(\sine, [\freq, m.wrapAt(i).midicps]); 0.5.wait }) }.fork

The “fork” is running a routine and the routine is played by SuperCollider’s default TempoClock.

If you keep that code running and then evaluate:

1 TempoClock.default.tempo = 2

You will see how the tempo changes, as the 0.5.wait in the Routine is half a beat of the tempo clock that has now changed its tempo.

Clocks in SuperCollider

All temporal tasks in SuperCollider run from one of the language’s clocks. There are 3 clocks in SuperCollider:

1 - SystemClock (the main clock that starts when you launch SC)
2 - TemploClock (same as SystemClock but counts in beats as opposed seconds)
3 - AppClock (musically unreliable, but good for communicating with GUIs or external hardware)

Routines, Tasks and Patterns can all run by these 3 different clocks. You pass the clocks as arguments to them.

SystemClock

Let’s have a quick look at the SystemClock:

(
SystemClock.sched(2.0,{ arg time;  
	time.postln; 
	0.5 // wait between next scheduled event
});
)

(
SystemClock.sched(2.0,{ arg time;  
	"HI THERE! Long wait".postln; 
	nil // no wait - no next scheduled event
});
)

// You can also schedule an event for an absolute time:
(
SystemClock.schedAbs( (thisThread.seconds + 4.0).round(1.0),{ arg time;
	("the time is exactly " ++ time.asString 
		++ " seconds since starting SuperCollider").postln;
});
)

AppClock

The AppClock works pretty much the same but uses different source clocks (Apples NSTimers).

You could try to create a GUI which is updated by a clock.

w = Window.new("oo", Rect(100, 100, 240, 100)).front;
x = Slider.new(w, Rect(20, 20, 200, 40));

// This works
{inf.do({x.value_(1.0.rand); 0.4.wait})}.fork(AppClock)

// However this won't work (as it's using the TempoClock by default)
{inf.do({x.value_(1.0.rand); 0.4.wait})}.fork

You will get an error message that could become familiar:

“Operation cannot be called from this Process. Try using AppClock instead of SystemClock.”

You can also get this done by “deferring” the command to the AppClock using .defer.

1 {inf.do({ {x.value_(1.0.rand)}.defer; Synth(\sine); 0.4.wait})}.fork

So here we are using the SystemClock to play the \sine synth, but deferring the updating of the GUI to the AppClock.

TempoClock

TempoClocks are typically used for musical tasks. You can run many tempo clocks at the same time, at different tempi or in different meters. TempoClocks are ideal for high priority scheduling of musical events, and if there is a need for external communication, such as MIDI, GUI or Serial communication, the trick is to defer that message with a “{}.defer”.

Let’s explore the tempo clock:

t = TempoClock(2); // tempo is 2 beats per second (120 bpm);

Many people who think in BPM (beats per minute) typically set the argument to the tempo clock as “120/60” (which equals to 2 beats per second), or “60/60” (which is 1 bps, and SuperCollider’s “default” tempo).

The clock above is now in a variable “t” and we can use it to schedule events (at a particular beat in the future):

t.schedAbs(t.beats.ceil, { arg beat, sec; [beat, sec].postln; 1});
t.schedAbs(t.beats.ceil, { arg beat, sec; "ho ho --".post; [beat, sec].postln; 1 });

And we can change the tempo:

t.tempo_(4)

t.beatDur // we can ask the clock the duration of the beats
t.beats // the beat time of the clock

t.clear

Polyrhythm of 3/4 against 4/4

(
t = TempoClock(4);
t.schedAbs(t.beats.ceil, { arg beat, sec;
	beat.postln;
	if (beat % 2==0, {Synth(\sine, [\freq, 444])});
	if (beat % 4==0, {Synth(\sine, [\freq, 333])});
	if (beat % 3==0, {Synth(\sine, [\freq, 888])});
	1; // repeat
});
)
t.tempo_(6)

Polyrhythm of 5/4 against 4/4

(
t = TempoClock(4);
t.schedAbs(t.beats.ceil, { arg beat, sec;
	if (beat % 2==0, {Synth(\sine, [\freq, 444])});
	if (beat % 4==0, {Synth(\sine, [\freq, 333])});
	if (beat % 5==0, {Synth(\sine, [\freq, 888])});
	1; // repeat
});

)

Or perhaps a polyrhythm of 5/4 against 4/4 where the bass line is in 4/4 and the high synth in 5/4.

(
t = TempoClock(4);

t.schedAbs(t.beats.ceil, { arg beat, sec;
	if (beat % 2==0, {Synth(\sine, [\freq, 60.midicps])});
	if (beat % 4==0, {Synth(\sine, [\freq, 64.midicps])});
	if (beat % 5==0, {Synth(\synth1, [\freq, 72.midicps])});
	if (beat % 5==3, {Synth(\synth1, [\freq, 77.midicps])});
	1; // repeat
});
)

Another version

(
t = TempoClock(4);

t.schedAbs(t.beats.ceil, { arg beat, sec;
	if (beat % 4==0, {"one".postln; Synth(\sine, [\freq, 60.midicps])});
	if (beat % 4==2, {"two".postln; Synth(\sine, [\freq, 72.midicps])});
	if ((beat % 4==1) || (beat % 4==3), {Synth(\sine, [\freq, 84.midicps])});
	
	if (beat % 5==0, {Synth(\synth1, [\freq, 89.midicps, \amp, 0.2])});
	if (beat % 5==2, {Synth(\synth1, [\freq, 96.midicps, \amp, 0.2])});
	1; // repeat
});
)

We can try to make this a bit more interesting by creating another synth:

(
SynthDef( \klanks, { arg freqScale = 1.0, amp = 0.1;
	var trig, klan;
	var  p, exc, x, s;
	trig = Impulse.ar( 0 );
	klan = Klank.ar(`[ Array.fill( 16, { linrand(8000.0 ) + 60 }), nil, Array.fill( 16, { rran\
d( 0.1, 2.0)})], trig, freqScale );
	klan = (klan * amp).softclip;
	DetectSilence.ar( klan, doneAction: 2 );
	Out.ar( 0, Pan2.ar( klan ));
}).store;
)

And play the same polyrhythm.

(
t = TempoClock(4);

t.schedAbs(t.beats.ceil, { arg beat, sec;
	if (beat % 4==0, {"one".postln; Synth(\klanks, [\freqScale, 40.midicps])});
	if (beat % 4==2, {"two".postln; Synth(\klanks, [\freqScale, 52.midicps])});
	if ((beat % 4==1) || (beat % 4==3), {Synth(\klanks, [\freqScale, 43.midicps])});
	
	if (beat % 7==0, {Synth(\synth1, [\freq, 88.midicps, \amp, 0.2])});
	if (beat % 7==3, {Synth(\synth1, [\freq, 96.midicps, \amp, 0.2])});
	if (beat % 7==5, {Synth(\synth1, [\freq, 86.midicps, \amp, 0.2])});

	1; // repeat
});
)

t.tempo_(8)

// an example showing tempo changes 

(
t = TempoClock(80/60); // 80 bpm
// schedule an event at next whole beat
t.schedAbs(t.beats.ceil, { arg beat, sec; 
	"beat : ".post; beat.postln;
	if (beat % 4==0, { Synth(\sine, [\freq, 60.midicps]) });
	if (beat % 4==2, { Synth(\sine, [\freq, 67.midicps]) });
	if (beat % 0==0, { Synth(\sine, [\freq, 72.midicps]) });
	1 // 1 here means that we are repeating/looping this
});
t.schedAbs(16, { arg beat, sec; 
	" ****  tempochange on beat : ".post; beat.postln; 
	t.tempo_(150/60); // 150 bpm
});
5.do({ |i| // on beats 32, 36, 40, 44, 48 
	t.schedAbs(32+(i*4), { arg beat, sec;
		" ****  tempo is now : ".post; (150-(10*(i+1))).post; " BPM".postln; 
		t.tempo_((150-(10*(i+1)))/60); // going down by 10 bpm each time
	});
});
t.schedAbs(60, { arg beat; t.tempo_(200/60) }); // 200 bpm
t.schedAbs(76, { arg beat;
	t.clear;
	t.schedAbs(t.beats.ceil, { arg beat, sec; 
		"beat : ".post; beat.postln;
		if (beat % 4==0, { Synth(\sine, [\freq, 67.midicps]) });
		if (beat % 4==2, { Synth(\sine, [\freq, 74.midicps]) });
		if (beat % 0==0, { Synth(\sine, [\freq, 79.midicps]) });
		1 // 1 here means that we are repeating/looping this
	});
	t.schedAbs(92, { arg beat; t.stop }); // stop it!
}); // 200 bpm
t.schedAbs(92, { arg beat; t.stop }); // if we tried to stop it here, it would have been "c\
leared"
)

A survey of Patterns

We can try to play the above synth definitions with Patterns and it will play using the default arguments of patterns (see the Event source file). Let’s start by exploring the Pbind pattern. As we saw in chapter 3, if you run the code below:

().play // "()" is an empty Event dictionary
Pbind().play // Pbind plays an empty Event

You can hear that there are default arguments, like a note played every second, an instrument is used (SuperCollider’s \default) and a frequency (440Hz).

In the example below, we use Pbind (Pattern that binds keys (synth def arguments) and their arguments). Here we pass the \sine synth def as the argument for the \instrument (again as defined in the Event class).

Pbind(\instrument, \sine).play // it plays our synth definition

Pbind(\instrument, \sine, \freq, Pseq([60, 65, 57, 62].midicps)).play // it plays our synth\
 definition

Our \sine synth has a frequency argument, and we are sending the frequency directly. However, if we wanted we could also send ‘note’ or ‘midinote’ arguments, but here the values are converted internally to the \freq argument of \sine.

Pbind(\instrument, \sine, \note, Pseq([0, 5, 7, 2])).play // it plays our synth definition

Pbind(\instrument, \sine, \midinote, Pseq([60, 65, 57, 62])).play // it plays our synth def\
inition

Pattern definitions (Pdef) are a handy way to define and play patterns. They are a bit like Synth definitions in that they have a unique name and can be recompiled on the fly.

(
Pdef(\scale, Pbind(
	\instrument, \sine,
	\freq, Pseq([62, 64, 67, 69, 71, 74], inf).midicps,
	\dur,  Pseq([0.25, 0.5, 0.25, 0.25, 0.5, 0.5], inf)
));
)

a = Pdef(\scale).play;
a.pause // pause. the stream
a.resume // resume it
a.stop 	// stop it (resets it)
a.play 	// start again

Then we can set variables in our instrument using .set

Pdef(\scale).set(\out, 20); // outbus 20 
Pdef(\scale).set(\out, 0); // outbus 0 

// here we set the duration of the envelope in our instrument
Pdef(\scale).set(\envdur, 0.2);

Patterns use default keywords defined in the Event class, so take care not to use those keywords in your synth definitions. If we had used dur instead of envdur for the envelope in our instrument, this would happen:

1 Pdef(\scale).set(\dur, 0.1);

because dur is a keyword of Patterns (the main ones are \dur, \freq, \amp, \out, \midi)

Resetting the freq info is not possible however :

Pdef(\scale).set(\freq, Pseq([72,74,72,69,71,74], inf).midicps);

One solution would be to resubmit the Pattern Definition:

(
Pdef(\scale, Pbind( \instrument, \sine,
				\freq, Pseq([72,74,72,69,71,74], inf).midicps, // different sequence
				\dur,  Pseq([0.25, 0.5, 0.25, 0.25, 0.5, 0.5], inf)
)); 
)
// and it's still in our variable "a", it's just the definition that's different
a.pause
a.resume

Patterns and environmental variables

We could also use Pdefn (read the helpfiles to compare Pdef and Pdefn) (here we are using envrionment variables to refer to patterns)

We use a Pdefn to hold the scale

1 Pdefn(\scaleholder, { |arr| Pseq(arr.freqarr) });

And we add an array to it

1 Pdefn(\scaleholder).set(\freqarr, Array.fill(6, {440 +(300.rand)} ));

Then we play a Pdef with the Pdefn

Pdef(\scale, 
		Pbind( 	\instrument, \synth1,
				\freq, Pn(Pdefn(\scaleholder), inf), // loop
				\dur, 0.4
			)
			
); 
a = Pdef(\scale).play;

And we can reset our scale

1 Pdefn(\scaleholder).set(\freqarr, Array.fill(3, {440 +(300.rand)} ));

Another example

(
Pdefn(\deg, Pseq([0, 3, 2],inf));

Pset(\instrument, \synth1, 
	Ppar([
		Pbind(\degree, Pdefn(\deg)),
		Pbind(\degree, Pdefn(\deg), \dur, 1/3)
])
).play;
)

Pdefn(\deg, Prand([0, 3, [1s, 4]],inf));
Pdefn(\deg, Pn(Pshuf([4, 3, 2, 7],2),inf));
Pdefn(\deg, Pn(Pshuf([0, 3],2),inf));

(
Pdefn(\deg, Plazy { var pat;
	pat = [Pshuf([0, 3, 2, 7, 6],2), Pshuf([3, 2, 6],2), Pseries(11, -1, 11)].choose;
	Pn(pat, inf)
});
)

Or perhaps:

(
Pdef(\player).set(\instrument, \synth1);

Pdef(\player,
	Pbind(
		\instrument, 	Pfunc({ |e| e.instrument }),
		\midinote, 	Pseq([45,59,59,43,61,43,61,61,45,33,31], inf),
		\dur, 		Pseq ([0.25,1,0.25,0.5,0.5,0.5,0.125,0.125,0.5,0.25,0.25], inf),
		\amp, 		Pseq([1,0.1,0.2,1,0.1125,0.1125,1,0.1125,0.125,0.25,1,0.5], inf)
	)
);
)

Pdef(\player).play;

Pdef(\player).set(\instrument, \synth1);
Pdef(\player).set(\envdur, 0.1);
Pdef(\player).set(\envdur, 0.25);
Pdef(\player).set(\envdur, 1);
Pdef(\player).set(\instrument, \sine);

( ~scale = [62,67,69, 77];

c = Pdef(\p04b, Pbind( \instrument, \synth1, \freq, (Pseq.new(~scale, inf)).midicps, // freq arg \dur, Pseq.new([1, 1, 1, 1], inf); // dur arg ) );

c = Pdef(\p04c, Pbind( \instrument, \synth1, \freq, (Pseq.new(~scale, inf)).midicps, // freq arg \dur, Pseq.new([1, 1, 1, 1], inf); // dur arg ) ); )

Pdef(\p04b).quant([2, 0, 0]); Pdef(\p04c).quant([2, 0.5, 0]); // offset by half a beat Pdef(\p04b).play; Pdef(\p04c).play;

// (quant can’t be reset in real-time, so we use align to align patterns). // align takes the same arguments as quant (see helpfile of Pdef)

Pdef(\p04c).align([4, 0, 0]); Pdef(\p04c).align([4, 0.75, 0]); // offset by 3/4 a beat

1 Another useful pattern is Tdef (Task patterns)
2 
3 {language= JavaScript, line-numbers=off}

Tdef(\x, { loop({ Synth(\sine, [\freq, 200+(440.rand)]); 0.25.wait; }) });

TempoClock.default.tempo = 2; // it runs on the default tempo clock

Tdef(\x).play(quant:1); Tdef(\x).stop;

// and we can redefine the definition “x” in realtime whilst playing Tdef(\x, { loop({ Synth(\synth1, [\freq, 200+(440.rand)]); 1.wait; }) });

Tdef(\y, { loop({ Synth(\synth1, [\freq, 1200+(440.rand)]); 1.wait; }) }); Tdef(\y).play(quant:1);

Tdef(\y).stop;

// to change the values in a pattern in realtime, use List instead of Array:

~notes = List[63, 61, 64, 65];

Pbind( \midinote, Pseq(~notes, inf), \dur, Pseq([0.4, 0.2, 0.1, 0.2], inf) ).play;

~notes[1] = 80

// yet another (known?) melody ( Pbind( \midinote, Pseq([72, 76, 79, 71, 72, 74, 72, 81, 79, 84, 79, 77, 76, 77, 76], 1), \dur, Pseq([4, 2, 2, 3, 0.5, 0.5, 4, 4, 2, 2, 2, 1, 0.5, 0.5, 2]/4, 1) ).play )

// ———– USING Pfx (effects patterns)

// make the synthdef and add it

SynthDef(\testenv2, { arg in=0, dur=2; var env; env = EnvGen.kr(Env.sine(dur), doneAction:2).poll; XOut.ar(0, 1, (In.ar(in, 1)+WhiteNoise.ar(0.1)) * env); // add noise for clarity }).add;

p = Pbind(\degree, Pseq([0, 4, 4, 2, 8, 3, 2, 0]), \dur, 0.5); p.play q = Pfx(p, \testenv, \dur, 4); // play it… all working (sine env is 4 secs) q.play

// now write the def to disk

SynthDef(\testenv2, { arg in=0, dur=2; var env; env = EnvGen.kr(Env.sine(dur), doneAction:2).poll; XOut.ar(0, 1, (In.ar(in, 1)+WhiteNoise.ar(0.1)) * env); // add noise for clarity }).writeDefFile;

// quit SuperCollider, open it again and now try this p = Pbind(\degree, Pseq([0, 4, 4, 2, 8, 3, 2, 0]), \dur, 0.5); q = Pfx(p, \testenv, \dur, 4); // not working (sine env is 2 secs, the synthdef default) q.play

// but here is the trick, read the SynthDescLib and try again!

SynthDescLib.global.read; q = Pfx(p, \testenv, \dur, 4); // not working (sine env is 2 secs, the synthdef default) q.play

// rendering the pattern as soundfile to disk (it will be written to your SuperCollider folder)

q.render( “ixi_tutorial_render_test.aif”, 4, sampleFormat: “int16”, options: Server.default.options.numOutputBusChannels_(2) );

1 ## TempoClock and Patterns
2 
3 Should we want to chage the tempo of the above Patterns, we can use the default TempoClock \
4 (as we didn't register a TempoClock for the pattern)
5 
6 {language= JavaScript, line-numbers=off}

TempoClock.default.tempo = 1.2 ~~~

But to have each pattern playing different TempoClocks, you need to create 2 clocks and use them to drive each pattern (this way one can do some nice phasing/polyrhytmic stuff).

(
t = TempoClock.new;
u = TempoClock.new;
Pdef(\p04b).play(t);
Pdef(\p04c).play(u);
u.tempo = 1.5
)

It is hard to get this clear as they are running the same pitch patterns so let’s redefine one of the patterns:

(
Pdef(\p04c, 
	Pbind(
		\instrument, \synth1,
		\freq, (Pseq.new(~scale.scramble, inf)).midicps*2, // freq arg
		\dur, Pseq.new([1, 1, 1, 1], inf);  // dur arg
	)
)
)
// and try to change the tempo 
u.tempo = 1;
u.tempo = 1.2;
u.tempo = 1.8;
u.tempo = 3.2;

Popcorn

An example of making a tune using patterns. For an excellent example take a look at spacelab, in examples/pieces/spacelab.scd

SynthDescLib.global.read;
// the poppcorn 

(
~s1 = [72, 70, 72, 67, 64, 67, 60];
~s2 = [72, 74, 75, 74, 75, 74, 72, 74, 72, 74, 72, 70, 72, 67, 64, 67, 72];

~t1 = [0.25, 0.25, 0.25, 0.25, 0.125, 0.25, 0.625];
~t2 = [0.25, 0.25, 0.25, 0.125, 0.25, 0.125, 0.25, 0.25, 0.125, 0.25, 0.125, 0.25, 0.25, 0.\
25, 0.125, 0.25, 0.5 ];

c = Pdef(\moogy, 
	Pbind(
		\instrument, \synth1, // using our synth1 synthdef
		\freq, 
			Pseq.new([
				Pseq.new([
					Pseq.new(~s1.midicps, 2),
					Pseq.new(~s2.midicps, 1)
				], 2),
				Pseq.new([
					Pseq.new((~s1+7).midicps, 2),
					Pseq.new((~s2+7).midicps, 1)
				], 2)	
			], inf),
		\dur, Pseq.new([ 
			Pseq.new(~t1, 2),
			Pseq.new(~t2, 1)
			], inf)
		)
);
Pdef(\moogy).play
)

Mozart

A little transcription of Mozart’s Piano Sonata No 16 in C major. Here the instrument has been put into a variable called “instr” so it’s easier to quickly change the instrument.

(
var instr = \default;
Ppar([
// right hand - using the Event-style notation
Pseq([
        (\instrument: instr, \midinote: 72, \dur: 1),
        (\instrument: instr, \midinote: 76, \dur: 0.5),
        (\instrument: instr, \midinote: 79, \dur: 0.5),
        (\instrument: instr, \midinote: 71, \dur: 0.75),
        (\instrument: instr, \midinote: 72, \dur: 0.125),
        (\instrument: instr, \midinote: 74, \dur: 0.125),
        (\instrument: instr, \midinote: 72, \dur: 1),
        (\instrument: instr, \midinote: 81, \dur: 1),
        (\instrument: instr, \midinote: 79, \dur: 0.5),
        (\instrument: instr, \midinote: 84, \dur: 0.5),
        (\instrument: instr, \midinote: 79, \dur: 0.5),
        (\instrument: instr, \midinote: 77, \dur: 0.25),
        (\instrument: instr, \midinote: 76, \dur: 0.125),
        (\instrument: instr, \midinote: 77, \dur: 0.125),
        (\instrument: instr, \midinote: 76, \dur: 1)
], 1),

// left hand - array notation
Pbind(\instrument, instr, 
        \midinote, Pseq([60, 67, 64, 67, 60, 67, 64, 67, 62, 67, 65, 67, 60, 67, 64, 67,
	                 60, 69, 65, 69, 60, 67, 64, 67, 59, 67, 62, 67, 60, 67, 64, 67 ], 1),
        \dur, 0.25
        )], 1).play
)

Syncing Patterns and TempoClocks

SynthDef(\string, {arg out=0, freq=440, pan=0, sustain=0.5, amp=0.3;
	var pluck, period, string;
	pluck = PinkNoise.ar(Decay.kr(Impulse.kr(0.005), 0.05));
	period = freq.reciprocal;
	string = CombL.ar(pluck, period, period, sustain*6);
	string = LeakDC.ar(LPF.ar(Pan2.ar(string, pan), 12000)) * amp;
	DetectSilence.ar(string, doneAction:2);
	Out.ar(out, string)
}).add;

SynthDef(\impulse, {
	Out.ar(0, Impulse.ar(0)!2);	
}).add

Synth(\impulse)

Pbind(
	\instrument, \impulse,
	\dur, 1
).play(TempoClock.default, quant:1)

// not working
TempoClock.default.play({
	Synth(\impulse, [\amp, 2]); // this is the problem
	1.0
	}, quant:[1, Server.default.latency] );

// working
TempoClock.default.play({
	s.sendBundle(0.2, ["/s_new", \impulse, s.nextNodeID, 0, 1]);
	1.0
	}, quant:[1, 0] );

TempoClock.default.tempo = 2.5

Pbind(
	\instrument, \string,
	\freq, Pseq([440, 880], inf),
	\dur, 1
).play(TempoClock.default, quant:1);

TempoClock.default.play({arg i;
	s.sendBundle(0.2, ["/s_new", \string, s.nextNodeID, 0, 1, \freq, if(i.asInteger.even, {660\
}, {770}), \amp, 0.3]);
	1.0
	}, quant:[1, 0] );

Chapter 16 - JIT lib and ProxySpace

JIT lib, or Just in Time library, is a system that allows people to write Ugen Graphs (signal processing on the SC server) and rewrite them in real time. This is ideal for live coding, teaching, experimenting and all kinds of compositional work.

ProxySpace

In order to use the JIT lib you create a ProxySpace which becomes the Environment or reference space for the synths that will live on it.

p = ProxySpace.new;
p.fadeTime = 3; // fadeTime is the time of crossfading

p[\snd].play; // we create a reference \snd in the environment
p[\snd] = { SinOsc.ar(440) };
p[\snd] = { Saw.ar(333, 0.4) };

p[\ctrl] = {LFSaw.ar(2)}
p[\snd] = {WhiteNoise.ar(p[\ctrl])}
p[\ctrl] = {LFNoise2.ar(2)}
p[\snd] = {Saw.ar([p[\ctrl]*1000, p[\ctrl]*1000+1], 0.3)}
p[\ctrl] = {LFSaw.ar(0.4)}

Or from the ProxySpace examples file. Here we find the "~" symbol used to reference signals\
 in the dictionary:

~lfo = { LFNoise2.kr(30, 300, 500) };
~out = { SinOsc.ar(~lfo.kr, 0, 0.15)  };
~out = { SinOsc.ar(~lfo.kr * [1, 1.2], 0, 0.1) * Pulse.ar(~lfo.kr * [0.1, 0.125], 0.5) };
~lfo = { LFNoise1.kr(30, 40) + SinOsc.kr(0.1, 0, 200, 500) };
~out = { SinOsc.ar(~lfo.kr * [1, 1.2], 0, 0.1)  };
~lfo = 410;

Ndef

Tdef

Chapter 18 - Tuning Systems and Scales

In this chapter we will look at how we can explore tuning systems, scales and microtonal composition using algorithmic means to generate tunings and scales.

The SynthDefs

For this chapter we want as pure waveform as possible so we can hear the ratios between the notes.

(
// We include two envelopes to choose from.
SynthDef(\pure, {arg freq=440, pan=0.0, vol=0.5, envdur=0.5, envType=0;
	var signal, envArray, env;
	env = EnvGen.ar(Env.perc(0.01, envdur), doneAction:2);
	signal = Pan2.ar(SinOsc.ar(freq), pan) * env  * vol;
	Out.ar(0, signal);
}).add;

// and another one almost identical that plays a sample
SynthDef(\puresample, {arg bufnum, rate=1, pan=0.0, vol=0.5, envdur=0.5, envType=0;
	var signal, envArray, env;
	envArray = [	
		EnvGen.kr(Env.linen(0.05, envdur, 0.1, 1), doneAction:2), 
			EnvGen.kr(Env.perc(0.01, envdur), doneAction:2)
			];
	env = Select.kr(envType, envArray);
	signal = Pan2.ar(PlayBuf.ar(1, bufnum, rate), pan) * env * vol;
	Out.ar(0, signal);
}).add;
)

Tuning systems are generally called “temperaments”. There are many different temperaments, but since the inventon of the piano the equal temperament has become the most common temperament and is typically used in computer music software.

For a bibliography and further information on scales and tunings, visit:

http://www.huygens-fokker.org/scala/

The scales can be found here: http://www.huygens-fokker.org/docs/scales.zip

A good source for microtonal theory is the Tonalsoft Encyclopedia of Microtonal Music Theory: http://tonalsoft.com/enc/

// NOTE: Tuning systems are not scales. We can have scales in different tuning systems.

Equal Temperament

Equal temperament is the most common tuning system in Western music. The octave is divided logarithmically into series of equal steps, most commonly the twelve tone octave. Other systems are also used such as the nineteen tone equal temperament (19-TET) or the thirty one tone equal temperament (31-TET).

Indian and Arabic music often uses a twenty four tone equal temperament (24-TET), although the instruments are frequently tuned using just intonation. Javanese Gamelan music is mainly tuned in a 5-TET

About the cent: The cent is a logaritmic unit (of equal steps) where 1200 represent an octave. In a 12-TET system, the half-note (of two adjacent keys on a keyboard) is 100 cents.

The logarithmic formula for pitch (exponential) for 12 tone equal temperament can be found in the following formula: fundFreq * 2.pow(n/12); (or roughly 1.05946309)

For Equal temperament of 12 notes in an octave these are the values we multiply the fundamental key with:

1 Array.fill(12, {arg i; 2.pow(i/12);})

// -> returns : [ 1, 1.0594630943593, 1.1224620483094, 1.1892071150027, 1.2599210498949, 1.33483985417, 1.4142135623731, 1.4983070768767, 1.5874010519682, 1.6817928305074, 1.7817974362807, 1.8877486253634 ]

(
var freq, n_TET;
n_TET = 25; // try, 5 19, 24, 31, 72...
freq = 440;
~eqTempFreqlist = Array.fill(n_TET, {arg i; freq * 2.pow(i/n_TET);});

~eqTempFreqlist = ~eqTempFreqlist.add(freq*2); // let's add the octave finally

[\freqlist, ~eqTempFreqlist].postln;

Task({
	~eqTempFreqlist.do({ arg freq, i; // first arg = item in the list, next arg = the index (i)
		Synth(\pure, [\freq, freq]);
		0.5.wait;
	});
}).start;
)

// now compare the list we've got (in a 12-TET)
~eqTempFreqlist.size
// to this:
[69,70,71,72,73,74,75,76,77,78,79,80].midicps // the midi notes in an octave starting with A

// and further... you can check the MIDI notes of a say 19-TET equal temperament:
~eqTempFreqlist.cpsmidi // where we get floating point MIDI notes

NOTE (SC LANG): If you are wondering about the ~freqlist.do compare this:

1 a = [111,222,333,444,555,666,777,888];
2 
3 a.do({arg item, i; [\item, item, \i, i].postln;}) // a is the array

To this:

1 a.size.do({arg i; [\item, a[i], \i, i].postln;}) // a.size is an integer.

Just Intonation

Just intonation is a very natural system frequently used by vocalists or instrumentalists who can easily tune the pitch. Instruments tuned in just intonation will have to be retuned in order to play in a different scale. This is the case with the Hapsichord for example.

Just intonation is a method of tuning intervals based exclusively on rational numbers (integers). It is based on the intervals of the harmonic series. Depending on context, the ratio might be different for the same note. (e.g. 9/8 and 10/9 for the major second). Any interval tuned as ratio of whole numbers is a just interval, but usually it is only ratios with small numbers.

Examples of intervals:

2/1 = octave 3/2 = fifth 4/3 = fourth 5/4 = major third 6/5 = minor third

Many composers (e.g. La Monte Yong and Terry Riley) prefer to compose for just intonation tuned instruments.

A major scale ~justIntFreqlist8 = [1, 9/8, 5/4, 4/3, 3/2, 5/3, 15/8];

A whole 12 note octave

1 ~justIntFreqlist = [1/1, 135/128, 9/8, 6/5, 5/4, 4/3, 45/32, 3/2, 8/5, 27/16, 9/5, 15/8, 2/\
2 1];

And we put in a fundamental note (A)

1 ~justIntFreqlist = ~justIntFreqlist * 440

Let’s play the scale:

(

Task({
	~justIntFreqlist.do({ arg freq, i; // 1st arg is the item in the list, 2nd is the index (i)
		Synth(\pure, [\freq, freq]);
		0.7.wait;
	});
}).start;
)

// test some versions of just intonation

~justIntFreqlist1 = [1/1, 135/128, 9/8, 6/5, 5/4, 4/3, 45/32, 3/2, 8/5, 27/16, 9/5, 15/8, 2\
/1];
~justIntFreqlist2 = [1/1, 16/15, 9/8, 6/5, 5/4, 4/3, 45/32, 3/2, 8/5, 5/3, 16/9, 15/8, 2/1]
~justIntFreqlist3 = [1/1, 25/24, 9/8, 6/5, 5/4, 4/3, 45/32, 3/2, 8/5, 5/3, 9/5, 15/8, 2/1];
~justIntFreqlist4 = [1/1, 16/15, 9/8, 6/5, 5/4, 4/3, 17/12, 3/2, 8/5, 5/3, 9/5, 15/8, 2/1];

~justIntFreqlist1 = ~justIntFreqlist1 * 440
~justIntFreqlist2 = ~justIntFreqlist2 * 440
~justIntFreqlist3 = ~justIntFreqlist3 * 440
~justIntFreqlist4 = ~justIntFreqlist4 * 440

// here we listen to different tunings in parallel and we can hear the difference:

(
Task({
	13.do({ arg freq, i; // first arg is the item in the list, next arg is the index (i)
		Synth(\pure, [\freq, ~justIntFreqlist1[i], \envdur, 1.56]);
		Synth(\pure, [\freq, ~justIntFreqlist2[i], \envdur, 1.56]);
		//Synth(\pure, [\freq, ~justIntFreqlist3[i], \envdur, 1.56]); // try these as well
		//Synth(\pure, [\freq, ~justIntFreqlist4[i], \envdur, 1.56]);
		1.4.wait;
	});
}).start;
)

Pythagorean tuning

Pythagorean tuning was invented by the Greek Philosopher Pythagoras in the 6th century BC. He was interested in harmony, geometry and beans. The Pythagorean tuning is based on perfect fifths, fourths and octaves.

~pythFreqlist8 = [1, 9/8, 81/64, 4/3, 3/2, 27/16, 243/128, 2/1]; // a major scale

~pythFreqlist = [1, 256/243, 9/8, 32/27, 81/64, 4/3, 729/512, 3/2, 128/81, 27/16, 16/9, 243\
/128, 2/1];

~pythFreqlist = ~pythFreqlist * 440;

(

Task({
	~pythFreqlist.do({ arg freq, i; // first arg is the item in the list, next arg is the inde\
x (i)
		Synth(\pure, [\freq, freq]);
		0.7.wait;
	});
}).start;
)

Now let’s compare Equal Temperament to the Pythagorean tuning.

First we make the equal temperament scale array

~eqTempFreqlist = Array.fill(12, {arg i; 440 * 2.pow(i/12);});
~eqTempFreqlist = ~eqTempFreqlist.add(440*2); // let's add the octave finally

(
Task({
	12.do({ arg freq, i; // first arg is the item in the list, next arg is the index (i)
		Synth(\pure, [\freq, ~pythFreqlist[i], \envdur, 1]);
		Synth(\pure, [\freq, ~eqTempFreqlist[i], \envdur, 1]);
		1.4.wait;
	});
}).start;
)

// and here we compare Just Intonation with Pythagorean tuning.

(
Task({
	12.do({ arg freq, i; // first arg is the item in the list, next arg is the index (i)
		Synth(\pure, [\freq, ~pythFreqlist[i], \envdur, 1]);
		Synth(\pure, [\freq, ~justIntFreqlist[i], \envdur, 1]);
		1.4.wait;
	});
}).start;
)

Scales

Scales are usually but not necessarily designated for an octave - so they repeat themselves over all octaves. There are countless scales with different note count, the most common in Western music is the diatonic scale. Other common scales (defined by note count) are chromatic (12 notes), whole tone (6 notes), pentatonic (5 notes) and octatonic (8 notes)

A dictinary of Scales

James McCartney wrote this dictionary of scales. (they are MIDI notes - no microtones and all are equal tempered)

(
z = (
// 5 note scales
	minorPentatonic: [0,3,5,7,10],
	majorPentatonic: [0,2,4,7,9],
	ritusen: [0,2,5,7,9], // another mode of major pentatonic
	egyptian: [0,2,5,7,10], // another mode of major pentatonic
	
	kumoi: [0,2,3,7,9],
	hirajoshi: [0,2,3,7,8],
	iwato: [0,1,5,6,10], // mode of hirajoshi
	chinese: [0,4,6,7,11], // mode of hirajoshi
	indian: [0,4,5,7,10],
	pelog: [0,1,3,7,8],
	
	prometheus: [0,2,4,6,11],
	scriabin: [0,1,4,7,9],
	
// 6 note scales
	whole: (0,2..10),
	augmented: [0,3,4,7,8,11],
	augmented2: [0,1,4,5,8,9],
	
	// hexatonic modes with no tritone
	hexMajor7: [0,2,4,7,9,11],
	hexDorian: [0,2,3,5,7,10],
	hexPhrygian: [0,1,3,5,8,10],
	hexSus: [0,2,5,7,9,10],
	hexMajor6: [0,2,4,5,7,9],
	hexAeolian: [0,3,5,7,8,10],
	
// 7 note scales
	ionian: [0,2,4,5,7,9,11],
	dorian: [0,2,3,5,7,9,10],
	phrygian: [0,1,3,5,7,8,10],
	lydian: [0,2,4,6,7,9,11],
	mixolydian: [0,2,4,5,7,9,10],
	aeolian: [0,2,3,5,7,8,10],
	locrian: [0,1,3,5,6,8,10],
	
	harmonicMinor: [0,2,3,5,7,8,11],
	harmonicMajor: [0,2,4,5,7,8,11],
	
	melodicMinor: [0,2,3,5,7,9,11],
	bartok: [0,2,4,5,7,8,10], // jazzers call this the hindu scale
	
	// raga modes
	todi: [0,1,3,6,7,8,11], // maqam ahar kurd
	purvi: [0,1,4,6,7,8,11],
	marva: [0,1,4,6,7,9,11],
	bhairav: [0,1,4,5,7,8,11],
	ahirbhairav: [0,1,4,5,7,9,10],
	
	superLocrian: [0,1,3,4,6,8,10],
	romanianMinor: [0,2,3,6,7,9,10], // maqam nakriz
	hungarianMinor: [0,2,3,6,7,8,11],	
	neapolitanMinor: [0,1,3,5,7,8,11],
	enigmatic: [0,1,4,6,8,10,11],
	spanish: [0,1,4,5,7,8,10],
	
	// modes of whole tones with added note:
	leadingWhole: [0,2,4,6,8,10,11],
	lydianMinor: [0,2,4,6,7,8,10],
	neapolitanMajor: [0,1,3,5,7,9,11],
	locrianMajor: [0,2,4,5,6,8,10],
	
// 8 note scales
	diminished: [0,1,3,4,6,7,9,10],
	diminished2: [0,2,3,5,6,8,9,11],
	
// 12 note scales
	chromatic: (0..11)
);
)
z.at('chromatic').postln;

// now we try one of those scales

(
x = z.at('hirajoshi').copy; // test the scales above by replacing the name
x = x.add(12); // add the octave
x = x.mirror;

Task({
	x.do({ arg ratio, i; // first arg is the item in the list, next arg is the index (i)
		Synth(\pure, [\freq, (69+ratio).midicps, \envdur, 0.94]);
		0.41.wait;
	});
}).start;
)

(
// do we get a nice melody?
Task({
	x.mirror.do({ arg ratio, i; // first arg is the item in the list, next arg is the index (i)
		Synth(\pure, [\freq, (69+x.choose).midicps, \envdur, 0.84]);
		0.18.wait;
	});
}).start;
)

{language= JavaScript, line-numbers=off}

The Scala Library

For a proper exploration of scales we will use the Scala project and the SCL class written in SuperCollider to use the Scala files.

The Scale Archive can be found here (with over 3000 scales): http://www.huygens-fokker.org/docs/scales.zip

And a SuperCollider class that interfaces with the archive can be found here (XiiScala.sc) https://github.com/thormagnusson/TuningTheory

Note that you have to provide the path to where you install your Scala libaray for example “~/scwork/scl/”

a = XiiScala(“bohlen-p_9”); a.tuning.octaveRatio a.degrees a.semitones a.pitchesPerOctave

z = x.degrees.mirror;

( Task({ z.do({ arg ratio, i; // first arg is the item in the list, next arg is the index (i) Synth(\pure, [\freq, 440*ratio]); 0.3.wait; }); }).start; )

( x = SCL.new(“cairo.scl”.standardizePath, 440); z = x.getRatios.mirror;

Task({ z.do({ arg ratio, i; // first arg is the item in the list, next arg is the index (i) Synth(\pure, [\freq, 440*ratio]); 0.3.wait; }); }).start; )

( x = SCL.new(“kayolonian_s.scl”.standardizePath, 440); z = x.getRatios.mirror;

Task({ z.do({ arg ratio, i; // first arg is the item in the list, next arg is the index (i) Synth(\pure, [\freq, 440*ratio]); 0.3.wait; }); }).start; )

Using Samples

We can of course control the pitch of sampled sounds too, and here the playback rate will control the pitch o f the sample.

First we load a sound and we get a sound with a simple tone (replace this sound with your own)

1 b = Buffer.read(s, "sounds/xylo/02.aif");

The pythagorean scale:

~pythFreqlist8 = [1, 9/8, 81/64, 4/3, 3/2, 27/16, 243/128, 2/1]; // a major scale

~pythFreqlist8 = ~pythFreqlist8.mirror;

(
{
	~pythFreqlist8.do({arg item;
		Synth(\puresample, [\bufnum, b.bufnum, \rate, item, \envType, 1]);
		Synth(\pure, [\freq, 752*item]); // 752 is just rough freq of the 02.aif sample
		0.5.wait;
	});
}.fork
)

x = SCL.new("degung5.scl".standardizePath, 440);

x = SCL.new("diaconv6144.scl".standardizePath, 440);

x = SCL.new("bagpipe1.scl".standardizePath, 440);

x.name
x.steps // how many notes are there in the scale
x.getRatios


Synth(\puresample, [\bufnum, b.bufnum, \rate, 1, \envType, 0]);
Synth(\pure, [\freq, 752]);


// p is our scale
p = x.getRatios
p.size

p = p.mirror // up and down again! (See Array helpfile for .mirror)

(
{
	p.do({arg item;
		Synth(\puresample, [\bufnum, b.bufnum, \rate, item, \envType, 1]);
		Synth(\pure, [\freq, 752*item]); // 752 is just rough freq of the 02.aif sample
		0.5.wait;
	});
}.fork
)

The Scale and Tuning Classes

SSuperCollider comes with Scale and Tuning classes. They make encapsulate and simplify the things we have done above in easy to use methods of the Scale and Tuning libraries.

An example - we choose a minor scale:

a = Scale.minor;
a.degrees; 
a.semitones;	
a.cents;	
a.ratios;	

d = Pdef(\minor_et12, Pbind(\scale, a, \degree, Pseq((0..7) ++ (6..0), inf), \dur, 0.5, \am\
p, 0.1)).play;

// we choose a tuning:
t = Tuning.just; // just intonation
a.tuning_(t);

e = Pdef(\minor_just, Pbind(\scale, a, \degree, Pseq((0..7) ++ (6..0), inf), \dur, 0.5, \am\
p, 0.1)).play;


// So let's listen to equal tempered and just intonation together. You can hear the beating
(
a = Scale.minor;
t = Tuning.et12; // equal tempered tuning 
a.tuning_(t);
d = Pdef(\minor_et12, Pbind(\scale, a, \degree, Pseq((0..7) ++ (6..0), inf), \dur, 0.5, \am\
p, 0.1)).play;

b = Scale.minor;
t = Tuning.just; // just intonation
b.tuning_(t);
e = Pdef(\minor_just, Pbind(\scale, b, \degree, Pseq((0..7) ++ (6..0), inf), \dur, 0.5, \am\
p, 0.1)).play;
)

Check the scale directory

1 Scale.directory

And the available tunings

1 Tuning.directory