//SuperCollider Workshop @ EM2013 //by Joo Won Park (www.joowonpark.net) joowon@joowonpark.net //SuperCollider is a text-based synthesizer and algorithmic composer. //SC makes you think in codes and numbers // Code: "algorithm written in programming language" (from Form & Code by Casey Reas) // Algorithm: "precise instructions on how to do things" (from Form & Code) //In other words, SC makes set of instructions to do music/audio related things. //how to execute things : press enter (shift+return), not return 3+6 (3*6)+(2/3)*(4.444432) 10%3 When SC does not understand a line, such as this one, there will be an error message in post window // when two slash lines are in front of a code, it is considered as comment. execute this line //Method can be thought as "do something" command. For list of common methods, go to Help->Browse->Common methods. "Hello".speak //Mac only speak("Hello") (3+6).neg neg(3+6) (3+6).neg.abs //SC's Server needs to be booted in order to execute audio-related codes. //Boot the Interpreter with command+B or Language->Boot //Press command+. (period) to stop the sound {Saw.ar(440,0.5)}.play //Note the syntax. //use command+D for help files. Double click on the word //how to use svariables ( //when executing multiple lines of code, separate them with ; //grouping codes with () is also very helpful x=4; y=8; x+y )//you can double click ) or ( to select all the contents inside () (x=4; y=8;x+y) x=4; y=8;x+y //when using other than a-z as variable, declar it with var (var words; words="double slash at the beginning of the line means comment"; words.speak ) ( a={Saw.ar(440)}; a.play ) //how to make a function {} //function is a set of instructions that changes value of input x into output y. //function processes things //use argument to make an input to the function y={ arg input; var distortion; distortion = input*3; }; y.value(4); y.value(20.5); y.value(5.rand); //execute this code multiple times ( var appletalk; appletalk={ arg word; var talk; talk = word.speak }; appletalk.value("I am an argument"); ) //note that the following line will not work because argument and var names are local to () appletalk.value("I am out of an argument") //this can be fixed using global variables (letters a-z and any var names starting with ~) z={ arg word; var talk; talk = word.speak }; z.value("I am an argument"); z.value("me too"); ~globalappletalk={ arg word; var talk; talk = word.speak }; ~globalappletalk.value("I am an argument"); ~globalappletalk.value("me too"); //one catch with using global var is that you won't be able to detect syntax (spelling & grammar) errors easily. ~globalappletalkkk.value("I won't be executed, but you won't know why"); //SynthDef : how to make a function with audio //use SynthDef (not Synthdef or synthDef) to make an audio-related code with var and arg SynthDef("FirstSaw",{ arg pitch; var sound,envelope,sound2; sound = Saw.ar(pitch); envelope = Line.ar(1,0,2); //make an envelope that changes from 1 to 0 in 2 secondes sound2 = sound*envelope; Out.ar(0,sound2) }).load(s) // make sure to load the synthdef into the server Synth.new("FirstSaw",[\pitch,440]) //make a new synth (an audio event) Synth("FirstSaw",[\pitch,440]) //.new can be omitted Synth.new("FirstSaw",[\pitch,440*2]) Synth.new("FirstSaw",[\pitch,69.midicps]) //midi to freq converter ( var randomizer; randomizer=60+(12.rand); Synth.new("FirstSaw",[\pitch,randomizer.midicps]) ) //how to change a parameter of a synth in realtime SynthDef("LongSaw",{ arg lforate=3,lfopitch=1,pitch; var sound,sound2; sound = SinOsc.ar(lforate,0,lfopitch); sound2 = Saw.ar(pitch+sound,0.5); Out.ar(0,sound2) }).load(s) a=Synth.new("LongSaw",[\pitch,60.midicps,\lforate,1,\lfopitch,10.midicps]) // use .set to change parameters in realtime a.set(\lforate,3) a.set(\lforate,5,\lfopitch,10.midicps) //the following will not work... for now a.set(\lforate, Line.kr(1,200,0)) a.free //.free deletes the specified synth from the server //Routine: Making algorithm on how/when to produce sound //(the bread and butter of sc) SynthDef("ForRoutine",{ arg pitch,vol,dur; var sound,envelope,sound2; sound = Saw.ar(pitch); envelope = Line.ar(1,0,dur); sound2 = sound*envelope; Out.ar(0,sound2*vol) }).load(s) //here's a simple randomization of the pitch Synth.new("ForRoutine",[\pitch,60.rand.midicps,\vol,0.5,\dur,2]) //know the difference 2.rand 2.0.rand //let's control the range of randomness ( ~randomize=60+(12.rand); Synth.new("ForRoutine",[\pitch,~randomize.midicps,\vol,0.5,\dur,2]) ) //Using a "do loop", we can repeat the task as fast as the computer can to make a chord 4.do{ ~randomize=60+(12.rand); ("MIDI note "++~randomize++" is chosen").postln; //this .postln monitors what note is played Synth.new("ForRoutine",[\pitch,~randomize.midicps,\vol,0.5,\dur,2]) } //a loop inside a Routine can execute the task with controlled timing Routine({ 4.do{ ~randomize=60+(12.rand); ("MIDI note "++~randomize++" is chosen").postln; Synth.new("ForRoutine",[\pitch,~randomize.midicps,\vol,0.5,\dur,2]); 1.wait } }).play ~sequence1=Routine({ 4.do{ ~randomize=60+(12.rand); ("MIDI note "++~randomize++" is chosen").postln; Synth.new("ForRoutine",[\pitch,~randomize.midicps,\vol,0.5,\dur,0.1+(1.0.rand)]); 0.5.rand.wait } }) ~sequence1.play ~sequence1.reset //a routine needs to be reset if you want to play the same routine again. //infinite loop ~sequence2=Routine({ loop{ ~randomize=60+(12.rand); ("MIDI note "++~randomize++" is chosen").postln; //this .postln monitors what note is played Synth.new("ForRoutine",[\pitch,~randomize.midicps,\vol,0.5.rand,\dur,0.5]); 0.5.rand.wait } }) ~sequence2.play ~sequence2.stop ~sequence2.reset //a routine needs to be reset if you want to play the same routine again. //infinite loop2 ~sequence3=Routine({ loop{ ~randomize=60+(24.rand2); ("MIDI note "++~randomize++" is chosen").postln; Synth.new("ForRoutine2",[\pitch,~randomize.midicps,\vol,0.5.rand,\dur,0.05]); 0.03.wait } }) ~sequence3.play //The above loop will eventually crash the SC in few seconds due to inappropriate memoey allocations (Check Peak CPU of localhost server) //Use doneAction function in envelopes to prevent it SynthDef("ForRoutine2",{ arg pitch,vol,dur; var sound,envelope,sound2; sound = Saw.ar(pitch); //use doneAction to minimize the CPU usage envelope = Line.ar(1,0,dur,doneAction:2); sound2 = sound*envelope; Out.ar(0,sound2*vol) }).load(s); ~sequence3=Routine({ loop{ ~randomize=60+(24.rand2); ("MIDI note "++~randomize++" is chosen").postln; Synth.new("ForRoutine2",[\pitch,~randomize.midicps,\vol,0.5.rand,\dur,0.05]); 0.05.wait } }) ~sequence3.play //Arrays : what makes SC a great program //the bread and butter 2 //array is a list enclosed with [] [0,4,7] 60+[0,4,7] 60+([0,4,7].choose) Array SequenceableCollection Array.series(6,0,2) //whole tone scale //applying array to make a controlled randomization SynthDef("ForRoutine2",{ arg pitch,vol,dur; var sound,envelope,sound2; sound = Saw.ar(pitch); envelope = XLine.ar(1,0.0001,dur,doneAction:2); sound2 = sound*envelope; Out.ar(0,sound2*vol) }).load(s) ~sequence4=Routine({ loop{ ~randomize=60+([0,4,7,11].choose); ("MIDI note "++~randomize++" is chosen").postln; Synth.new("ForRoutine2",[\pitch,~randomize.midicps,\vol,0.5.rand,\dur,0.1]); 0.1.wait } }) ~sequence4.play //applying array to make a controlled randomization of pitch and rhythm ~sequence5=Routine({ loop{ ~randomize=60+([0,4,7,11].choose); ("MIDI note "++~randomize++" is chosen").postln; Synth.new("ForRoutine2",[\pitch,~randomize.midicps,\vol,[0.5,0.8].choose.rand,\dur,0.1]); [0.1,0.2,0].choose.wait } }) ~sequence5.play //applying array to make a controlled randomization of pitch and rhythm ver2 ~pitchset = [0,4,7,11]; ~sequence6=Routine({ loop{ ~randomize=60+(~pitchset.choose); ("MIDI note "++~randomize++" is chosen").postln; Synth.new("ForRoutine2",[\pitch,~randomize.midicps,\vol,[0.5,0.8].choose.rand,\dur,0.1]); [0.1,0.2,0].choose.wait } }); ~sequence6.play //change ~pitchset to to play different harmony ~pitchset=[0,3,7,11]; ~pitchset=Array.series(6,0,2); ~pitchset=3 //this will give you an error // Also, synth.set will not work, or will work for only one note because each note triggered with Routine is a separate synth ~pitchset=[3] //make an array with one list instead //play a melody loop ~pitchset = [0,4,7,11]; ~pitchlist = ~pitchset.asList; //convert an array into a "list" that can be interpreted by do loops ~sequence7=Routine({ loop{ ~pitchset.do{ arg chosenpitch; //the first argument in a do loop corresponds to the firt value on the list ~randomize=60+chosenpitch; ("MIDI note "++~randomize++" is chosen").postln; //this .postln monitors what note is played Synth.new("ForRoutine2",[\pitch,~randomize.midicps,\vol,0.5,\dur,0.1]); 0.1.wait; } } }); ~sequence7.play ~pitchset=[0,4,7,11]-2 ~pitchset=Array.series(25,0,2) //Challenge : analyze the following code. run ~sequence7 first Routine({loop{~pitchset=[[0,4,7,11],[0,4,7,11]-2,[0,4,7,11]-4].choose;4.wait}}).play //Summary : why use SC? //SuperCollider makes users explore different modes of expression and composition //1. customization & precision not easily available to commercial DAW {SinOsc.ar(Line.kr(0,22000,20.39203),0,0.3)}.play //2. "If computers could be used to model what we know, then perhaps we could also use them to simulate what we don't know" (Form+Code by Reas) //If computers could be used to model what we can play, then perhaps we could also use them to simulate what we can't play //3. Powerful DSP //vanilla SynthDef("PulseThing",{ arg pitch=500,width=0.5, vol=0.8; //you can set initial value of arguments var sound1; sound1 = Pulse.ar(pitch,width,vol); Out.ar(0,sound1) }).load(s); Synth("PulseThing"); //vanilla + one topping SynthDef("PulseThing",{ arg pitch=500,width=0.5, vol=0.8; var sound1; sound1 = Pulse.ar([pitch,pitch*0.5],[width,width*0.5],vol); //array makes a new signal Out.ar(0,sound1) }).load(s); Synth("PulseThing"); //vanilla + many toppings SynthDef("PulseThing",{ arg pitch=500,width=0.5, vol=0.8; var sound1,cluster,mix; cluster= Array.fill(10,{0.3.rand}).postln; //fill an array with five radom values between 0 and 0.3 sound1 = Pulse.ar(pitch+(pitch*cluster),width*cluster,vol); //this will create a 10 channel audio mix = Mix(sound1); //mix 10 channel audio into a mono signal Out.ar(0,mix) }).load(s); Synth("PulseThing"); //note that you need to run the SynthDef again to make a new set of randomized array //mint SynthDef("PulseThing",{ arg pitch=500,width=0.5, vol=0.8; var sound1,cluster,mix,filter; cluster= Array.fill(10,{0.3.rand}); sound1 = Pulse.ar(pitch+(pitch*cluster),width*cluster,vol); mix = Mix(sound1); filter = HPF.ar(mix, MouseX.kr(300,10000)); //highpass filter controlled with x-axis of a mouse Out.ar(0,filter) }).load(s); Synth("PulseThing"); //Challenge: here's the above SynthDef in one line. {HPF.ar(Mix(Pulse.ar(500+(500*(Array.fill(10,{0.3.rand}))),0.5*(Array.fill(10,{0.3.rand})),0.8)), MouseX.kr(300,10000))}.play //banana split SynthDef("Voice",{ arg pitch=500,width=0.5; var voice,sound1,cluster,mix,delay; cluster= Array.fill(10,{0.3.rand2}); voice = AudioIn.ar(1); //get real-time audio sound1 = PitchShift.ar(voice,0.2,1+cluster); //this will create a 10 channel audio mix = Mix(sound1*0.3); //mix 10 channel audio into a mono signal delay = CombC.ar(mix,0.5,0.4,2); Out.ar(0,delay+voice) }).load(s); Synth("Voice"); //sundae ~sample1 = Buffer.read(s,"sounds/Fflies.aif"); //load a sample to a buffer //SC willtry to find the sound in the folder where the actual SC application is located SynthDef("Voice",{ var voice,sound1,cluster,mix,delay; cluster= Array.fill(10,{0.5.rand2}); //fill an array with five radom values between -0.3 and 0.3 sound1 = PlayBuf.ar(2,~sample1.bufnum,1+cluster); //this will create a 20 channel audio using a sample mix = Mix(sound1*0.3); //mix 10 channel audio into a mono signal Out.ar(0,mix) }).load(s); //press record button in localhost server to record a sound Synth("Voice"); //Examples: www.100strangesounds.com //Final SC Tips //- count your ()s and {}s. one missing ( can trigger a dramatic whining in the post window //- spelling is crucial. Synth and synth are different. Have a habit of naming your variable with lower-case letters so it it does not get syntax-colorized with blue. //- be extra careful in spelling when using a gloabal variable //- SC documentation is a great textbook. //For further studies, I recommend to look at the following features if In Out MIDIResponder OSCresponder GUI Patterns