SuperCollider Quick Reference

From CCRMA Wiki
Jump to: navigation, search

SuperCollider Quick Reference

This page collects short and simple code examples of useful stuff. These are just quick "reminders" of how to do common things. Good page for beginners.

Quick system test

Start things up and play/see a sine wave:

Server.default = s = Server.local.boot; // start local server

g = SwingOSC.default.boot; // on Linux (gedit, emacs)

e = {SinOsc.ar(freq: 400, mul: 0.5)}.scope;  // you should hear a sine wave and see the waveform

e.free;

Play a sound file, vary speed, reverse

create a buffer

~pizza = Buffer.read(s, "path/to/sound/file.wav");

simple play

{PlayBuf.ar(1, ~pizza)}.play; // number of channels and buffer

get sound file info

[~pizza.bufnum, ~pizza.numChannels, ~pizza.path, ~pizza.numFrames];

varying playback speed

{PlayBuf.ar(1, ~pizza, 2, loop: 1)}.play; // play 2x faster

{PlayBuf.ar(1, ~pizza, 0.5, loop: 1)}.play; // play at half the speed

{PlayBuf.ar(1, ~pizza, Line.kr(0.5, 2, 10), loop: 1)}.play; // playback speed goes from 0.5 to 2 in 10 seconds

changing direction (reverse)

{PlayBuf.ar(1, ~pizza, -1, loop: 1)}.play; // reverse sound

{PlayBuf.ar(1, ~pizza, -0.5, loop: 1)}.play; // play at half the speed AND reversed

Play a MIDI file

On Fedora Linux, use MIDIFile.sc (installed in /usr/share/SuperCollider/Extensions/MIDIFile/ by Planet CCRMA package supercollider-midifile). See /usr/share/SuperCollider/Help/MIDIFile/MIDIFile-samplepatches.rtf for example usage. On other systems, such as a Mac, you can simply copy MIDIFile.sc into your SuperCollider Extensions directory (typically [$HOME]/Library/Application Support/SuperCollider/Extensions/ on Mac OS X). It's all in that one file.

poll and scope

Check what's going on with your UGens:

{LFNoise0.kr(5).round(0.01).poll(label: "watchThis")}.scope

// by default, poll is triggered 10 times per second, but you can change it:

{LFNoise0.kr(5).round(0.01).poll(trig: 1, label: "watchThis")}.scope  // triggers poll once every second

From SC's reference:

WARNING: Printing values from the Server in intensive for the CPU. Poll should be used for debugging purposes.

Random numbers

rrand

rrand(0, 5)   // generates a random number between 0 and 5 (inclusive)

rand

rand(10)  // generates a random integer number between 0 and 10 (10 not included)

rand(10.0) // generates a random decimal number between 0.0 and 10.0 (10.0 not included)

10.0.rand  // same as above

10.0.rand.round(0.01) // same as above with rounding

exprand(1, 10.0)  // generates a random decimal number between 0.0 and 10.0, mostly lower values

Note the difference between these two:

dup(rand(100), 5)   // picks a number, duplicates it

dup({rand(100)}, 5) // duplicates the function of picking a number

LFNoise0, LFNoise1, LFNoise2

Random values between -1 and +1 generated at a specified rate. Compare the three:

{LFNoise0.ar(5000)}.plot
{LFNoise1.ar(5000)}.plot
{LFNoise2.ar(5000)}.plot

{LFNoise0.kr(1).poll(label: "value")}.scope  // print the output in the post window with poll

// freq - approximate rate at which to generate random values.
// LFNoise0.ar(freq, mul, add)
// LFNoise0.kr(freq, mul, add)

// One way of hearing the difference - try replacing LFNoise0 by LFNoise1 and LFNoise2:

{SinOsc.ar(800, mul: LFNoise0.kr(7))}.scope

Dust

Generates random impulses from 0 to +1.

{Dust.ar(5000)}.plot;

// Dust.ar(density, mul, add)
// density: average number of impulses per second

TRand

Generates a random float value in uniform distribution from lo to hi each time the trig signal changes from nonpositive to positive values:

(
 {var trig = Dust.kr(10);
  SinOsc.ar(TRand.kr(300, 3000, trig)) * 0.1
 }.play;
)

Mouse input

MouseX, MouseY

(
 {
  var freq = MouseX.kr(220, 440), amp = MouseY.kr(0, 0.3);
  SinOsc.ar(freq, mul: amp)
 }.play
)

Scale and Offset (mul, add)

SinOsc.kr.signalRange  // is the UGen bipolar (outputs between -1 and +1) or unipolar (outputs between 0 and +1)?

{SinOsc.kr(1, mul: 200, add: 1000).poll(label: "output")}.play // outputs numbers between 800-1200

{SinOsc.kr(1).range(800, 1200).poll(label: "output")}.play     // same result as above

LFPulse.kr.signalRange  // this one is unipolar

{LFPulse.kr(1, mul: 400, add: 800).poll(label: "output")}.play  // outputs 800-1200

{LFPulse.kr(1).range(800, 1200).poll(label: "output")}.play     // same result as above

Looping

While

(
i = 0;
while ( { i < 5 }, { i = i + 1; "boing".postln });
)

For

for (3, 7, { arg i; i.postln }); // prints values 3 through 7

ForBy

forBy (0, 8, 2, { arg i; i.postln }); // prints values 0 through 8 by 2's

Do

do(9, {"Wow".postln}); // print "Wow" nine times

9.do({"Wow".postln}); // same as above

5.do({ arg item; item.postln }); // iterates from zero to four

do(5, {arg item; item.postln}); // same as above

Compare the output of these:

do(9, {arg whatevername; whatevername.postln}); // give whatever name you want to the arg of 'do'

do([9, 8, 3, 5], {arg whatevername; whatevername.postln}); // elements from collection (not a counter)

do([9, 8, 3, 5], {arg whatevs1, whatevs2; [whatevs2, whatevs1].postln}); // second argument works as a counter

More examples:

["zero", "first", "second", "third", 500].do({ arg item, i; [i, item].postln; });

do(["zero", "first", "second", "third", 500], {arg item, i; [i, item].postln});

"you".do({ arg item; item.postln }); // a String is a collection of characters

'they'.do({ arg item; item.postln }); // a Symbol is a singular item

(8..20).do({ arg item; item.postln }); // iterates from eight to twenty 

(8,10..20).do({ arg item; item.postln }); // iterates from eight to twenty, with stepsize two

If...Else

Syntax is if(condition, {true action}, {false action}). Examples:

if(10 == 10, {"10 is indeed equal to 10"}, {"false"})

if(condition, {true action}, {false action});

if(10 == 10, {"10 is indeed equal to 10"}, {"false"})

if((1 < 20).and(1.8.isInteger), {"very true"}, {"hmmm..."})

10.do({arg count; [count, if(count.odd, {"odd"}, {"even"})].postln})

(
84.do({arg count; if([0, 4, 7].includes(count%12), 
	{count.post; " is part of a C triad.".postln}, 
	{count.post; " is not part of a C triad".postln})})
)

50.do({if(1.0.rand.round(0.01).post > 0.5,  {" > 0.5".postln}, {" < 0.5".postln})})

50.do({if(1.0.rand > 0.5,  {"play a note".postln}, {"rest".postln})})

50.do({if(0.5.coin, {"play a note".postln}, {"rest".postln})}) // same as above

if((10.odd).or(10 < 20), {"true".postln}, {"false".postln})

Arrays

Various array operations:

a = [10, 11, 12, 13, 14, 15, 16, 17]

a.reverse  // reverse

a.scramble // scramble

a.choose  // picks one element at random

a.size	  // returns size of array

a.at(0)   // retrieves item at specified position

a[0]	  // same as above

a.wrapAt(9) // retrives item at specified position, wrapping around if > a.size

a ++ 999  // ++ (concatenate) adds something to the end of the array

a ++ \hi  // a Symbol is a single character

a ++ 'hi' // same as above

a ++ "hi" // a String is a collection of characters

a.insert(5, "wow") // inserts "wow" at position 5, pushes other items forward

a // evaluate this and see that none of the above operations actually changed the original array

a.put(2, "oops") // put "oops" at index 2 (destructive; evaluate line above again to check)

a.add(44)    // adds new element at the end of the array (permanently) 

a.do({arg whatever, blech; [blech, whatever].postln}) 	// how to "do" an array

b = Array.series(5, 1); // create an array with 5 sequential numbers, starting at 1)

b.mirror  // makes it a palindrome

b.permute(3) // permute: item in position 3 goes to position 0, and vice-versa 

b.powerset // returns all possible combinations of the array's elements

Array.fill(10, "same"); // Another way of building an array

Array.fill(10, {arg counter; (counter + 1)*440}); 

More info: http://sc3howto.blogspot.com/2010/05/arrays.html

Order of operations

Evaluate the two lines separately. Note that, in SC, the first is NOT the same as the second.

12 + 5 * 42 - 1   // result is 713, because 12 + 5 happens before the multiplication
12 + (5 * 42) - 1 // result is 221 (the parentheses specified that the multiplication should happen first)

Sample and Hold

Using Latch:

// Sample and hold controlling frequency
(
{
var freq;
freq = Latch.kr(
	LFSaw.kr(1, mul: 300, add: 1000),  // ramp from 700 to 1300 every 5 seconds
	Impulse.kr(6)); // samples the ramp 6 times per second; try using 6.1 and hear the difference
SinOsc.ar(freq, mul: 0.5)}.play
)

Envelopes

Use Env.new to define an envelope from scratch using breakpoints. The first three arguments to Env.new are: levels, times, and curve (there are two more args, see help file). levels - an array of levels. The first level is the initial value of the envelope. times - an array of durations of segments in seconds. There should be one fewer duration than there are levels. curve - choose from 'step', 'linear', 'exponential', 'sine', 'welch' (see help file for details).

Env.new([0, 1, 0.3, 0.8, 0], [2, 3, 1, 4],'linear').test.plot; // this will play a test tone with envelope

How to "read" the two arrays above: Go from 0 to 1 in two seconds; then go from 1 to 0.3 in 3 seconds; then go from 0.3 to 0.8 in 1 second; finally go from 0.8 to 0 in 4 seconds.

If you choose 'exponential', you can't use zero in the first array, but something like 0.001 will do:

Env.new([0.001, 1, 0.3, 0.8, 0.001], [2, 3, 1, 4],'exponential').test.plot;

You can also create some standard frequently-used envelope shapes by supplying durations to the following methods:

*linen(attackTime, sustainTime, releaseTime, level, curve)

Env.linen(1, 2, 3, 0.6).test.plot;
Env.linen(0.1, 0.2, 0.1, 0.6).test.plot;
Env.linen(1, 2, 3, 0.6, 'sine').test.plot;
Env.linen(1, 2, 3, 0.6, 'welch').test.plot;

*triangle(duration, level)

Env.triangle(1, 1).test.plot;

*sine(duration, level) // hanning

Env.sine(1,1).test.plot;

*perc(attackTime, releaseTime, peakLevel, curve)

Env.perc(0.05, 1, 1, 0).test.plot;   // linear
Env.perc(0.05, 1, 1, -4).test.plot;  // exponential
Env.perc(0.001, 1, 1, -4).test.plot; // sharper attack
Env.perc(0.001, 1, 1, -8).test.plot; // change curvature
Env.perc(1, 0.01, 1, 4).test.plot;  // reverse envelope

In order to use these in real life, you need EnvGen:

{SinOsc.ar(800, 0, 0.5) * EnvGen.kr(Env.perc (0.001, 1), 1.0)}.play;

// Another way of writing the same thing:
(
 {
  var freq, env;
  freq = 800;
  env = EnvGen.kr(Env.perc(0.001, 1), 1.0);
  SinOsc.ar(freq, mul: env);
 }.play
)

Unanswered question... (2011-06-12): why do these two sound different? No idea.

 

Env.perc(0.001, 1).test.plot; // this is sharp and nice attack

{SinOsc.ar(800, 0, 0.5) * EnvGen.kr(Env.perc (0.001, 1), 1.0)}.play; // this should be the same, but it isn't...

// And if I use an envelope trigger, the following notes sound correct, but the first is still wrong: 
(
 {
  var freq, trigger, env;
  freq = 800;
  trigger = Impulse.kr(1/2); // trigger a note every 2 seconds
  env = EnvGen.kr(Env.perc(0.001, 1), trigger);
  SinOsc.ar(freq, mul: env);
 }.play
)

Routine

r = Routine.new({"begin".postln; 1.wait; "middle".postln; 1.wait; "last one".postln;});
r.play;
r.stop;
r.reset;
r.next;

// message wait can also be yield (synonyms)

Amplitude Modulation

Frequency Modulation

Additive Synthesis

Using Mix and Array:

// Most basic example:

{SinOsc.ar(440,0,0.5) + SinOsc.ar(880,0,0.3) + SinOsc.ar(1320,0,0.1)}.play // A4 + 2 harmonics

{SinOsc.ar(440,0,0.5) + SinOsc.ar(870,0,0.3) + SinOsc.ar(1330,0,0.1)}.play // A4 + 2 slightly detuned harmonics

// Next: six sine waves in harmonic relationship, using "Mix". Still not that flexible...

(
{
 var fund = 220;
 Mix.ar(
		[
		SinOsc.ar(fund*1),
		SinOsc.ar(fund*2),
		SinOsc.ar(fund*3),	
		SinOsc.ar(fund*4),
		SinOsc.ar(fund*5),
		SinOsc.ar(fund*6)
		]
        ) * 0.1  // scale amplitude down to avoid clipping
}.play
)

// A more sophisticated example using an Array and controlling individual amplitudes:

(
{Mix.ar(
	Array.fill(20,
		{arg count;
		 var harm;
		 harm = (count + 1) * 110;
			SinOsc.ar(harm)*1/(count+1) // higher harmonics will be softer
		}))*0.5 // global amplitude scaling
}.play
)

// The array above is doing something like this: 

Array.fill(20, {arg cnt; cnt + 1 * 110}); // harmonics built on 110

// ... except that we fill it with SinOsc's, not just numbers.
//
// Last two examples adapted from SC Book, p. 36-37.

[add short examples like bell, trumpet, clarinet, plucked string, etc]

Subtractive Synthesis

Done and getSynchronous

A little more advanced and obscure, but still useful:

///////////////////////////////////////////////////////////
// 2012-07-11 - SuperCollider Workshop @ CCRMA
// How to know, language-side, that a Synth is done.
// How to poll values from the server back to the language.
// [Using Done.kr, control buses, getSynchronous message]

/////////////
// Example 1
/////////////
(
SynthDef("Done-help", { arg krbus;
	var snd, env;
    	env = EnvGen.kr(Env.perc(0.01, 4), doneAction: 2);
	snd = SinOsc.ar(440,0,0.1) * env;
	Out.kr(krbus, Done.kr(env));
	Out.ar(0, snd);
}).add;
)

~kontrol = Bus.control; // create a control bus
r = Routine.new({loop({~kontrol.getSynchronous.postln; 0.1.wait})}); // we will "poll" the control bus
r.reset.play; // run the Routine to start polling
~kontrol.set(22); // writing a single value into the bus 
s.plotTree; // watch the nodes in the server just for fun
// r.stop;
Synth("Done-help", [\krbus, ~kontrol]); // 0 when EnvGen is running, 1 when it's done

// From the Bus help file:
/*
Synchronous Control Bus Methods
Synchronous access to control busses only works for servers with a shared memory interface. You can check with hasShmInterface if the server provides these methods.
*/

s.hasShmInterface; // if it's true, it will work (it should on SC versions 3.5+)


/////////////
// Example 2
/////////////

// Create another control bus:
k = Bus.control;

// Start writing some control data into that bus
{Out.kr(k, LFNoise0.kr(100).range(100, 200))}.play

k.getSynchronous // Evaluate this; you will see the instantaneous value that is in the control bus.

// Using a routine, you can get many values per second (like a "poll"):
r = Routine.new({loop({k.getSynchronous.postln; 0.1.wait})});
r.reset.play;
r.stop;


/////////////
// Example 3
/////////////

// Yet another example

~k2 = Bus.control; // another control bus

(
{    var    freq;
    freq = LFNoise1.kr(2, 600, 800);
    Out.kr(~k2, freq); // write freq data into this control bus
    SinOsc.ar(freq, 0, 0.03) ! 2
}.play;
)

l = List.new;
r = Routine.new({ loop { l.add(~k2.getSynchronous); 0.1.wait } }).play; // get values, add to list
r.stop;
l.array.plot;  // to view the results graphically