//-- writing algorithms that sound

//-- workshop 070310 @pickledfeet berlin



//--0. basics


supercollider is basically 2 programs - a language and a sound synthesis server.


sclang and scserver - 2 separate programs.


what you see is sclang, scserver runs in the background - locally or on another computer.


sclang is a modern oop language with concepts from smalltalk, lisp, j, etc.


scserver is a brute force program written in c that understands a limited set of commands.





the server can be used independently or run from other clients like haskel, scheme, python.



most of the things we will do in sc today deals with the sclang/scserver duality - important to understand.

we will program and define synths in the language, and then send them to sc server for playback.



//--workshop plan


1. syntax - quick intro to sclang

2. {}.play - create a synth and making first sounds

3. server - how they work, boot, scope, record

4. synthdef - how you are supposed to create synths

5. tdef - sequences and threads

6. modulation - more interesting sounds

7. music programming - writing algos that sound

8. other topics - questions and own projects



//--1. syntax

1+1

1.1+1 //no need to declare types


1+2*10 //always left-to-right order

1+(2*10) //parenthesis


10/3


1/3+(1/3)+(1/3)


2.sqrt

60.round(80)

2.sqrt.round


3.clip(0, 10).pow(2).div(2).cubed.cpsmidi.ceil




see [SimpleNumber], [Float], [Integer]




a= 100 //simple global variable

a= a+1

a


b= a/200

b= b.ampdb

b


though best used only for quick testing.

better to declare variables in more elaborate programs

( //scope of variables

var a;

a= 10;

a+b //note, local a + global b

)

(

var whateverOneWants= 33;

whateverOneWants+100;

)



a= 'asymbol'

a= "astring"

a= \alsoASymbol

a= 10 //back to a number


"this string"+a //becomes a string



[1, 2, \asdf, "qwer"] //an array


[1, 2, 3]+10

[1, 2, 3]*10

[1, 2, 3].pow(10)

[1, 2, 3]+[4, 5, 6]

[1, 2, 3]+[1.1, 2.2] //wraps - more advanced see [Adverbs]


[1, 2, 3].scramble //loads of these

//- see [Array], [SequenceableCollection], [Collection]


[3, 2, 1].sort



[1, 2, 3].indexOf(2) //testing

[1, 2, 3]>2




a= {|x| x+10} //a function

a.value(10) //call it with argument 10


a= {|x= 5| x+10} //set default for 1st argument to 5

a.value


a.value([1, 2, 3]) //anything that understands +10

a.value("lkj") //string as argument



a= {|x, y, z| x*y+z} //multiple arguments

a.value(1, 2, 3)

a.value(z:1, y:2, x:3) //swap order with keywords





10.do{|x| x.postln} //simple iteration


5.do{|x| x.pow(2).postln}


[1, 2, 3, 4].collect{|x| x+10} //iterate and create new array


[1, 2, 3, 4].select{|x| x.even} //test and create new from result


Array.fill(10, {|x| x.pow(2)}) //create array from scratch


Array.fill(100, {|x| 40.rrand(80)}) //100 random midinotes?


Array.fill(16, {|x| 1.0.linrand.round(0.5)}) //16step amplitudes?


a= Array.fill(16, {|x| x})

a= a.rotate(1)

a= a.rotate(2)

a= a.invert

a= a.normalize(-1, 1)

(a+100*2).round(0.5)



//--2. {}.play

first click the boot button on internal server gui


{SinOsc.ar}.scope //stop with cmd+.


//most of the operations from above will also work on signals

{SinOsc.ar.round(0.25)}.scope //-1 to 1 for most signals

{SinOsc.ar.pow(2)}.scope

{SinOsc.ar.sqrt.abs.round(0.1)-0.5}.scope


{Saw.ar.clip(-0.2, 0.8)}.scope

{Saw.ar.abs*0.3+(Saw.ar.round(0.2))}.scope


{(SinOsc.ar*0.4)+(BrownNoise.ar*0.2)}.scope //simple mixing


{SinOsc.ar.wrap(-0.5, 0.5)}.scope

{SinOsc.ar.fold(-0.5, 0.5)}.scope

{SinOsc.ar.clip(-0.5, 0.5)}.scope


see list and methods [UGens], [UGen]



{(SinOsc.ar*0.4).round(BrownNoise.ar)}.scope

{WhiteNoise.ar.max(SinOsc.ar*0.1)}.scope

{ClipNoise.ar*Pulse.ar.max(SinOsc.ar/3)}.scope



{SinOsc.ar.lag(Saw.ar.fold(0, 0.2))}.scope

{Pulse.ar.hypot(LFNoise0.ar)%0.5}.scope

{Pulse.ar.fold2(SinOsc.ar)*LFNoise1.ar*0.5}.scope

{(Pulse.ar+SinOsc.ar*PinkNoise.ar).thresh(Pulse.ar)}.scope














now play around with these for a while.  try to come up with some strange synths by only using simple oscillators and methods.


ugens to use... SinOsc, Pulse, LFPulse, Saw, LFSaw, LFTri, Phasor, Dust, Impulse, LFNoise0, LFNoise1, LFNoise2, BrownNoise, PinkNoise, GrayNoise, ClipNoise, WhiteNoise


mehtods to use... round, fold, clip, max, min, thresh, hypot, dist, lag, lag2, fold2, wrap, sqrt, pow, abs, +, -, *, /, %














//--3. server

as default there are 2 servers available.  internal and localhost.

internal is mainly used for .scope, localhost is the default one.



s.boot //boot localhost server


//this will create synth, send to current default server and play

{SinOsc.ar}.play


Server.default.name

s.name


s.quit

{SinOsc.ar}.play //no go


s.boot //server must be running to receive&play

{SinOsc.ar}.play


scope only works on internal.  use jscope for linux/win



{SinOsc.ar}.play

s.prepareForRecord

s.record

s.stopRecording


file should now end up in the directory called recordings.


often you want to record in int16 instead of 32 float as default

s.recSampleFormat_("int16")


the other defaults are stereo aiff

s.recChannels_(2)

s.recHeaderFormat_("aiff")










//--4. synthdef


synth definition.  describes the structure of a synthesis network

SynthDef('mySynth', {Out.ar(0, SinOsc.ar)}).send(s)


then create a synth from that definition and start playing

Synth('mySynth')


same as {}.play did for us.  but synthdefs is the recommended way.



Out.ar writes to an audio bus.  In.ar reads from one.  as default 0-7 are hardware output busses, and 8-15 are your hardware input busses.  above that - up to 127 - are free for internal routing.


see [ServerOptions] and [Bus] on how to change these defaults






(

SynthDef(\mySynth, {|out= 0| //argument with default 0

Out.ar(out, Saw.ar)

}).send(s)

)

now we just overwrote the above synthdef.  pick unique names.


Synth(\mySynth) //play on bus 0= left channel

Synth(\mySynth, [\out, 1]) //play on bus 1= right channel


(

SynthDef(\mySynth, {|out= 0, in= 8|

Out.ar(out, SinOsc.ar*In.ar(in)) //sine times mic input

}).send(s)

)

Synth(\mySynth)


a= Synth(\mySynth, [\out, 0, \in, 9]) //cross connect left<->right

b= Synth(\mySynth, [\out, 1, \in, 8])

a.free //stop and clear synth

b.free //as cmd+. but stop only one



//--5. modulation

(

{

SinOsc.ar(2000, 0, 0.5) //arguments to the ugen

+

BrownNoise.ar(0.5)

}.play

)


SinOsc.ar(freq, phase, mul, add) //see helpfile or sourcecode (cmd+? or cmd+j)


BrownNoise.ar(mul)


Pulse.ar(freq, width, mul, add) //differs from ugen to ugen








( //a couple of sines with different arguments

{

SinOsc.ar(100, 0, 0.5)

+ SinOsc.ar(400, 0, 0.4)

+ SinOsc.ar(900, 0, 0.3)

* SinOsc.ar(3, 0, 1)

}.play

)


( //audiorate vs controlrate (.ar vs .kr)

{

SinOsc.ar(100, 0, 0.5)

+ SinOsc.ar(400, 0, 0.4)

+ SinOsc.ar(900, 0, 0.3)

* SinOsc.kr(3, 0, 1) //no need to run these 2 in audiorate

* SinOsc.kr(1.03, 0, 1)

}.play

)


//rule of thumb - use .kr for anything below 20hz = inaudiable


( //separate lfo for each sine - note the parenthesises

{

(SinOsc.ar(100, 0, 0.5)*SinOsc.kr(1, 0, 1))

+

(SinOsc.ar(400, 0, 0.4)*SinOsc.kr(0.5, 0, 1))

+

(SinOsc.ar(900, 0, 0.3)*SinOsc.kr(0.25, 0, 1))

}.play

)


( //lfo sine range is -1000 to 1000

{

SinOsc.ar(SinOsc.kr(2, 0, 1000), 0, 0.5)

}.play

)


( //lfo sine range is 500 to 1500

{

SinOsc.ar(SinOsc.kr(2, 0, 500, 1000), 0, 0.5)

}.play

)


( //more complicated modulations - just sines

{

SinOsc.ar(SinOsc.ar(25, 0, 500, SinOsc.kr(0.2, 0, 500, 1000)), 0, SinOsc.ar(201, 0, 0.25, 0.25))

}.play

)


( //more complicated modulations - mixed ugens

{

SinOsc.ar(Saw.ar(LFPulse.kr(0.5, 0.3, 5, LFNoise0.kr(4, 6, 8)).round(2), 500).abs)

}.play

)





now try to create some interesting synths with ring, amplitude, and frequency modulation.

combine with the methods from above.



//--6. tdef


here is first a more complex synthdef.

//(

//SynthDef(\feet, {arg .....;

// _env_

// _src_

// _efx_

// Out.ar(_bus_, ....)

//}).send(s)

//)


(

SynthDef(\feet, {|out= 0, freq= 400, amp= 1|

var e, z, x;

e= EnvGen.ar(Env.perc, doneAction:2);

z= LFPulse.ar(freq, 0, 0.5, amp);

x= LPF.ar(z, freq*3);

Out.ar(out, Pan2.ar(x*e));

}).send(s)

)


lots of new things here.  envelopes, filters and panners.

see corresponding help files.


Synth(\feet)

Synth(\feet, [\freq, 800, \amp, 0.5])



Tdef= task definition.  see [Routine], [Task], [Tdef]


Tdef(\pickle).play //create and start playing a task


( //define the task ie give a function

Tdef(\pickle, { //name and what to do

10.do{

100.rand.postln; //just post 10 random numbers

0.1.wait; //little time inbetween

}

})

)




( //define the task ie give a function

Tdef(\pickle, {

inf.do{ //loop forever (remember wait time!)

Synth(\feet, [\freq, 800, \amp, 0.5]);

1.wait;

}

})

)


and now, while it is playing, we can rewrite it


(

Tdef(\pickle, {

inf.do{

Synth(\feet, [\freq, 200.rrand(800).round(100), \amp, 0.5]);

0.5.rand.wait;

}

})

)

Tdef(\pickle).stop

Tdef(\pickle).play


also we can redefine the synth definition while it is playing

(

SynthDef(\feet, {arg out= 0, freq= 400, amp= 1;

var e, z, x;

e= EnvGen.ar(Env.perc, doneAction:2);

z= LFPulse.ar(freq, 0, 0.5, amp);

x= BPF.ar(z, 1-e*freq+freq, 1-e+0.01);

Out.ar(out, Pan2.ar(x*e));

}).send(s)

)


(

SynthDef(\feet, {arg out= 0, freq= 400, amp= 1;

var e, z, x;

e= EnvGen.ar(Env.perc, doneAction:2);

z= LFSaw.ar(freq, 0, amp);

x= BPF.ar(z, 1-e*freq+freq, 1-e+0.01);

Out.ar(out, Pan2.ar(x*e));

}).send(s)

)



(

SynthDef(\feet, {arg out= 0, freq= 400, amp= 1, dist= 100;

var e, z, x;

e= EnvGen.ar(Env.perc, doneAction:2);

z= LFSaw.ar(freq, 0, amp);

x= BPF.ar(z, 1-e*freq+freq, 1-e+0.01);

x= (x*dist).tanh*0.25; //distort

Out.ar(out, Pan2.ar(x*e));

}).send(s)

)


(

Tdef(\pickle, {

inf.do{

Synth(\feet, [\freq, 4.rand*100+80, \amp, 0.5, \dist, 100.rand]);

0.25.wait;

}

})

)



(

Tdef(\pickle, {

inf.do{|i|

Synth(\feet, [\freq, i%10*100+100, \amp, 0.5, \dist, 10]);

(i.div(10)%3*0.1+0.05).wait;

}

})

)


create another task

Tdef(\pickle2).play


(

Tdef(\pickle2, {

inf.do{|i|

Synth(\feet, [\freq, [80, 100].choose, \amp, 0.5, \dist, 500]);

1.wait;

}

})

)


stop the first one

Tdef(\pickle).stop


stop the second

Tdef(\pickle2).stop



start them again

(

Tdef(\pickle).play;

Tdef(\pickle2).play;

)



now create a synthdef and start playing tasks.  be careful to always include some wait time >0 if using inf.do.  0.wait in a inf.do will crash the program.  save document often when experimenting.





//--7. music programming


(show code from livecoding practice sessions)


livecode some examples


















//--8. other topics


midi, osc, poll, sendtrig, pen, gui, patterns...


randomly constructed synths.  really hard to do in pd/max.

a= [SinOsc, Saw, LFTri]

{a.choose.ar(freq:a.choose.ar.exprange(200, 2000), mul:a.choose.ar)}.play


(show genetics classes)