Difference between revisions of "SuperCollider Tweets"

From CCRMA Wiki
Jump to: navigation, search
Line 312: Line 312:
// The exponential one not only favors lower notes in general, but also  
// The exponential one not only favors lower notes in general, but also  
// increases the likelihood of even louder pops to appear (leaps from
// increases the likelihood of louder pops to appear (leaps from
// very low to very high center-freq). That's probably why the original tweet
// very low to very high center-freq). That's probably why the original tweet
// uses a multiplier of 0.2 for the Saw:
// uses a multiplier of 0.2 for the Saw:

Revision as of 11:40, 16 July 2011

This pages analyzes a few "tweet-sized" SuperCollider examples.

"A popular way to share SuperCollider code is to post it to the social networking site Twitter. This site imposes a restriction on the length of posts, limiting them to 140 characters or less. Creating an interesting sound or piece of music within this constraint has proven to be a popular challenge."



For more 140-character SuperCollider code, visit the links above. Feel free to contribute to this page with more analyses! [if you don't have edit access to this wiki, send it to ruviaro at stanford edu and I'll post it]

SUGGESTION: copy and paste the analysis into your favorite SuperCollider text editor. Then go through the step-by-step analysis, evaluating individual lines of code that are provided as examples.


// =====================================================================
// SuperCollider code analysis
// Bruno Ruviaro, 2011-06-12
// Original tweet by @micromoog
// http://swiki.hfbk-hamburg.de:8888/MusicTechnology/899
// =====================================================================


// =====================================================================
// =====================================================================

// I'll start with the second part of the code, after the 'plus' sign.

// Quick reminder of what an LFPulse output looks like:


// Arguments: LFPulse.kr(freq, iphase, width, mul, add)
// LFPulse is unipolar. It outputs a high value of 1
// and a low value of 0. Default width is 0.5.

LFPulse.kr.signalRange     // tells us it's unipolar

{LFPulse.kr(1).poll}.play  // 0's and 1's, half a second each (1Hz)

// Listen to a LFPulse turning on and off some white noise.
// Frequency is 1 Hz, so one noise burst per second (quarter notes in a 4/4 bar).
// Notice that we actually have half a second of noise, and half second of silence.
// This is because the default "width" of LFPulse is 1/2.


// By controlling the width parameter we can then control the note duration.
// The examples below are still 1 Hz ("quarter notes"), but the actual durations are different:

{WhiteNoise.ar(LFPulse.kr(freq: 1, width: 1/10))}.play // note dur is 1/10 of the beat (very "staccato")
{WhiteNoise.ar(LFPulse.kr(freq: 1, width: 0.9))}.play // note dur is 9/10 of the beat ("non legato") 

// Now let's make this white noise pulsate 4 times per beat ("sixteenth notes")

{WhiteNoise.ar(LFPulse.kr(4))}.play              // standard staccato (width default = 0.5)
{WhiteNoise.ar(LFPulse.kr(4, width: 0.05))}.play // much more staccato, "hi-hat"

// What if we wanted to have one of the white noise bursts to simulate a snare drum?
// 1 out of every 4 would have to be longer. The width parameter has to change accordingly.
// We can use another LFPulse to do just that.

{LFPulse.kr(1, mul: 1/4, add: 0.05).poll(2, label: "out")}.play // outputs 0.05 and 0.3 for half a second each

// Plug the line above into the white noise:

{WhiteNoise.ar(LFPulse.kr(4, width: LFPulse.kr(1, mul: 1/4, add: 0.05)))}.play

// The beginning was a little off, so we adjust the iphase of inner LFPulse:

{WhiteNoise.ar(LFPulse.kr(4, width: LFPulse.kr(1, iphase: 3/4, mul: 1/4, add: 0.05)))}.play

   Note: I still haven't figured out why this works like this. If LFPulse is sending out
   its min and max values (0.05 and 0.3) at equal intervals (0.5 seconds each), shouldn't
   we hear two short notes and two long notes?

// Moving on... abbreviate the code above to make it "twitter friendly".
// Note that '0' is added as the iphase of first LFPulse in order to
// avoid having to declare the keyword 'width' afterwards, saving several characters...
// Also, the original code divides the whole thing by 8 to scale its global amplitude in the mix.


// =====================================================================
// =====================================================================

// Original tweet:


// Let's look at the inner LFPulse.kr(1/4, 1/4, 1/4)*2+2
// This inner LFPulse is scaled to output numbers between 2 and 4. 
// With freq = 1/4 and width = 1/4, the result is one pulse
// every 4 seconds, the duration of which will be 1 second.
// In other words: go to (and stay at) high value 4 for 1 second;
// then go back to (and stay at) low value 2 for 3 seconds.
// The iphase of 1/4 simply shifts the starting point.

{(LFPulse.kr(freq: 1/4, width: 1/4)*2+2).poll(1)}.play // 4 2 2 2 4 2 2 2 ...

{(LFPulse.kr(freq: 1/4, iphase: 1/4,  width: 1/4)*2+2).poll(1)}.play // 2 2 2 4 2 2 2 4 ...

{(LFPulse.kr(1/4,1/4,1/4)*2+2).poll(1)}.play // same thing without keywords

// The LFPulse above (2 2 2 4...) controls the frequency of a LFSaw, i.e.,
// we will have 2 full cycles of a sawtooth wave on every second for 3s,
// then 4 full cycles of the sawtooth wave in one second. In musical terms,
// the first three beats of a 4/4 bar are subdivided in eighth notes,
// while the fourth and last beat is subdivided in sixteenth notes. 

// A typical LFSaw looks like this:

{LFSaw.ar(500)}.plot // sudden drops from +1 to -1, followed by upward ramps

// But if we multiply it by a negative number, we invert it:

{LFSaw.ar(500, mul: -1)}.plot // sudden upward leaps from -1 to +1, followed by downward ramps

// In the original tweet, the range of this LFSaw is scaled.
// With mul = -20 & add = 50 the range becomes 30 (min) to 70 (max).
// Since mul is negative, we have downward ramps from 70 to 30.
// These ramps happen at the frequency specified by the the LFPulse (2 2 2 4...)

{LFSaw.kr(freq: LFPulse.kr(1/4,1/4,1/4)*2+2, iphase: 1, mul: -20, add: 50).poll}.play

// Finally, the LFSaw above controls the frequency of a LFCub.
// LFCub is more or less like a sine wave, with a slightly different timbre.
// Thus we have a bass line glissando downwards from 70 Hz to 30 Hz;
// The rhythm of the bass line is the 2 2 2 4 pattern.


// Compare how the same thing sounds using a SinOsc instead:


// PS. If you can't hear the low notes on your laptop built-in speakers,
// try using good headphones or external speakers.

// =====================================================================
// =====================================================================

// Here's the first and second half of the code, still isolated:



// A simple '+' mixes them together:

{LFCub.ar(LFSaw.kr(LFPulse.kr(1/4,1/4,1/4)*2+2,1,-20,50)) + (WhiteNoise.ar(LFPulse.kr(4,0,LFPulse.kr(1,3/4)/4+0.05))/8)}.play

// Note that the WhiteNoise has been enclosed in parentheses
// to force its division by 8 to happen *before* the sum.

// Listen to it without parentheses (white noise becomes too loud in the mix):

{LFCub.ar(LFSaw.kr(LFPulse.kr(1/4,1/4,1/4)*2+2,1,-20,50)) + WhiteNoise.ar(LFPulse.kr(4,0,LFPulse.kr(1,3/4)/4+0.05))/8}.play

// Now add a !2 at the end of the line to make it stereo...

{LFCub.ar(LFSaw.kr(LFPulse.kr(1/4,1/4,1/4)*2+2,1,-20,50)) + (WhiteNoise.ar(LFPulse.kr(4,0,LFPulse.kr(1,3/4)/4+0.05))/8)!2}.play

// Finally, to save ONE more character, put the 'play' in the beginning:


// Done!

// Outstanding unanswered question: how come the inner LFPulse of WhiteNoise.ar produces THREE short notes
// and ONE longer note? Why is it not two short and two long? (even with the phase change...)


// =====================================================================
// Bruno Ruviaro, 2011-07-01
// SuperCollider code analysis
// =====================================================================

// Original tweet by Nathaniel Virgo (headcube)
// http://swiki.hfbk-hamburg.de:8888/MusicTechnology/899


// ********************************************************
// ********************************************************

// Starting from the BPF, a bandpass filter.
// Below is a simple BPF filtering white noise.
// BPF.ar(in, freq, rq) --> input, center frequency, rq
// This example uses a fixed center frequency of 1000 Hz and a rq of 0.1:

{BPF.ar(WhiteNoise.ar(1), 1000, 0.01)}.play

// Now instead of a fixed center frequency, let's use a
// LFNoise0 to generate new center frequencies for the BPF.
// The LFNoise0 will output a new value between 500 and 5000
// twice per second in this example:

{BPF.ar(WhiteNoise.ar(1), LFNoise0.kr(2).range(500, 5000), 0.1)}.play

// Same thing as above, but now using 'poll' so we
// can see the new freqs values being generated:

{BPF.ar(WhiteNoise.ar(1), LFNoise0.kr(2).range(500, 5000).poll(2, label: "bpf-freq"), 0.1)}.play

// Same thing but now using single impulses (Impulse.ar)
// as the input of the BPF, instead of WhiteNoise. We use the *5
// at the end just to make it louder (too soft otherwise). Listen:

{BPF.ar(Impulse.ar(2), LFNoise0.kr(2).range(500, 5000).poll(2, label: "bpf-freq"), 0.1)*5}.play // filtered impulses

// Now let's step back for a moment. Compare the sound
// of a single impulse to that of a slow sawtooth wave:

{Impulse.ar(1)}.play    // hear it
{Impulse.ar(1000)}.plot // see it
{Saw.ar(1)}.play        // hear it (you hear the dip of the sawtooth from +1 to -1)
{Saw.ar(1000)}.plot   // see it

// Let's use a 1 Hz sawtooth wave as the sound input for the BPF filter.
// These filtered 'saw pops' have a very different timbre from
// the filtered impulse pops created earlier with Impulse.ar:

{BPF.ar(Saw.ar(4), LFNoise0.kr(4).range(500, 5000).poll(4, label: "bpf-freq"), 0.1)}.play // "saw pops" BPF
// compare to
{BPF.ar(Impulse.ar(4), LFNoise0.kr(4).range(500, 5000).poll(4, label: "bpf-freq"), 0.1)*5}.play // "impulse pops" BPF 

// Getting a bit closer to the original tweet...
// Let's have get the Saw.ar at 32 and 33 Hz:

{Saw.ar(33)}.play // the raw saw, mono [CAREFUL: LOUD!]
{BPF.ar(Saw.ar(33), LFNoise0.kr(2).range(500, 5000).poll(2, label: "bpf-freq"), 0.1)}.play // raw saw thru BPF

{Saw.ar([32, 33])}.play // two raw saws, stereo [CAREFUL: LOUD!]
{BPF.ar(Saw.ar([32, 33]), LFNoise0.kr(2).range(500, 5000).poll(2, label: "bpf-freq"), 0.1)}.play // raw saws thru BPF

// Now let's say we want the BPF frequencies to be
// in the range 18.75 Hz to 4800 Hz, which is the
// range of the original tweet (more on that later):

{BPF.ar(Saw.ar([32, 33]), LFNoise0.kr(2).range(18.75, 4800).poll(2, label: "bpf-freq"), 0.1)}.play

// Note that whenever the output of LFNoise0 produces a big leap from a very low to a 
// very high number (center frequency for BPF), there's a loud "pop" as a consequence.
// These are the interesting "accented notes" we often hear in the final result.
// Watch the Post window as you listen, and check when the louder pops happen.

// One more step closer to the original tweet:
// Let's make the LFNoise0 freq to be 4/3, that is,
// 4 new values every 3 seconds (note that we change
// the poll frequency accordingly):

{BPF.ar(Saw.ar([32, 33]), LFNoise0.kr(4/3).range(18.75, 4800).poll(4/3, label: "bpf-freq"), 0.1)}.play

/* What does this mean? If you think of this rhythm in a Tempo of quarter note = 60,
   which will be the case in the final result, you have something like this
   (imagine these are representations of 16th notes):

>  >   >   >
|||| |||| ||||
   In other words: the frequency of change of the BPF center-freqs (4/3 Hz)
   actually promotes a certain "syncopated" feel of the final result, especially 
   when the louder pops appear (created by the occasional sudden change from a very low 
   to a very high BPF center-freq)

// Now take a look at how the scaling of the output of LFNoise0
// is actually accomplished in the original tweet. The author does
// NOT use .range(18.75, 4800). Instead we see this:

2**LFNoise0.kr(4/3,4)*300 // output range is 18.75 to 4800 Hz

// With 4 as the "mul", the range of this LFNoise0 becomes -4 to + 4.
// "2 to the power of (-4 up to + 4), and this result multiplied by 300"

2**(-4)*300 // Evaluate this: if LFNoise0 outputs its lowest -4, result is 18.75
2**(0)*300  // Evaluate this: if LFNoise0 outputs its middle value 0, the result is 300
2**(4)*300  // Evaluate this: if LFNoise0 outputs its highest +4, result is 4800

// So, in fact, the range boundaries are the same as in our earlier examples
// using .range(18.75, 4800), but, unlike before, the distribution is NOT linear.
// Compare how often you see numbers below 300 appearing in these two examples:

{LFNoise0.kr(4/3).range(18.75, 4800).poll(4/3, label: "linear")}.play  // Watch the Post window
{(2**LFNoise0.kr(4/3,4)*300).poll(4/3, label: "exponential")}.play     // Watch the Post window

// You can see from the math above that, in the second case, the distribution
// of random values is not anymore linear. Basically, there's 50% of chance that
// the selected value will be BELOW 300; and 50% of chance that it will be above 300.
// In other words, lower center-freqs for the BPF are now being favored. 

// This is how the linear distribution sounds like with the rest of our code so far:

{BPF.ar(Saw.ar([32, 33]), LFNoise0.kr(4/3).range(18.75, 4800).poll(4/3, label: "bpf-freq"), 0.1)}.play

// And this is how the exponential one sounds like:

{BPF.ar(Saw.ar([32,33]),(2**LFNoise0.kr(4/3,4)*300).poll(2, label: "bpf-freq"),0.1)}.play

// The exponential one not only favors lower notes in general, but also 
// increases the likelihood of louder pops to appear (leaps from
// very low to very high center-freq). That's probably why the original tweet
// uses a multiplier of 0.2 for the Saw:

{BPF.ar(Saw.ar([32,33],0.2),(2**LFNoise0.kr(4/3,4)*300).poll(4/3, label: "bpf-freq"),0.1)}.play

// Finally, a 'distort' method is added to this to smooth things out a bit:

{BPF.ar(Saw.ar([32,33],0.2),(2**LFNoise0.kr(4/3,4)*300).poll(4/3, label: "bpf-freq"),0.1).distort}.scope

// A few visual examples of what distort does:

{LFSaw.ar(300)}.plot          // sawtooth
{LFSaw.ar(300).distort}.plot  // sawtooth with distort

{SinOsc.ar(300)}.plot          // sine
{SinOsc.ar(300).distort}.plot  // sine with distort

{LFTri.ar(300)}.plot          // triangle
{LFTri.ar(300).distort}.plot  // triangle with distort

// Back to the tweet, inside the BPF. Here's the original tweet again:


// There is still this LocalIn.ar(2)*7.5+ in the code, 
// preceding the Saw, and which we have not analyzed yet.
// Forget about it for now. Let's look at the the CombN,
// which has the entire BPF code as its first argument.

// ********************************************************
// ********************************************************

// This is a comb delay line.
// CombN.ar(in, maxdelaytime, delaytime, decaytime, mul, add)
// Simple example with Impulses:

{CombN.ar(Impulse.ar(1/2), 2, 0.25, 3)}.play

// Input signal: one impulse every 2 seconds (1/2 Hz)
// Maximum delay time: 2 seconds
// Delay time: 0.25 seconds
// Decay time: 3

// You can hear the 'echoing' impulse fading out.
// Now let's plug that BPF code into a CombN:

	BPF.ar(Saw.ar([32,33],0.2),(2**LFNoise0.kr(4/3,4)*300),0.1).distort, // input signal
	2, // max delay time
	0.25, // delay time
	3) // decay time

// Same thing, with longer decay time (10s), and delay of 1 second:

	BPF.ar(Saw.ar([32,33],0.2),(2**LFNoise0.kr(4/3,4)*300),0.1).distort, // input signal
	2, // max delay time
	1, // delay time
	10) // decay time

// Now with the actual values used in the original tweet,
// that is, very long decay time (40s) and delay = 2 seconds

	BPF.ar(Saw.ar([32,33],0.2),(2**LFNoise0.kr(4/3,4)*300),0.1).distort, // input signal
	2, // max delay time
	2, // delay time
	40) // decay time

// Because the delay is now 2 seconds, we hear it more as 'meter'
// rathern than 'echo'. The result is a kind of 2-beat metric structure
// (say, a 2/4 with quarter note = 60), which gradually gets filled
// with "sixteenth notes" (the 4:3 pattern of the LFNoise0)
// as the CombN accumulates decaying echoes of these attacks.

// Remember the earlier example with a linear distribution of the
// random numbers between 18.75 and 4800? If we use THAT one now,
// a lot LESS pops (attacks) are generated, and the whole thing is
// much less rhythmic as a result. The exponential distribution
// of random numbers is then directly relevant to the 
// effectiveness of the final rhythmic result. Here's how
// this same bit of code sounds with the linear distribution:

	BPF.ar(Saw.ar([32,33],0.2),LFNoise0.kr(4/3).range(18.75, 4800),0.1).distort, // input signal
	2, // max delay time
	2, // delay time
	40) // decay time

//  Another variation below. A version with white noise instead of the
//  sawtooth reveals an interesting aspect about the sawtooth:

	BPF.ar(WhiteNoise.ar([1,1]),(2**LFNoise0.kr(4/3,4)*300),0.1).distort, // input signal
	2, // max delay time
	2, // delay time
	40) // decay time

// We still get occasional "rhythmic" pops; but note that the distance
// in amplitude between the pops and the 'sustained' portions of
// the texture is smaller; the continuous white noise 
// competes for the foreground with the pops. In the original Saw
// version, the dynamic distance between 'explosive' pops and the
// continuous notes is much bigger, so that the pops (thus the rhythm)
// becomes clearly the foreground within an overall 'quieter' texture.

// Here's another version, back with Saw, but with a more limited
// range of center freqs for the BPF. The attacks (pops) disappear, since
// there are no more big leaps from low to high center-freqs. A more continuous
// texture becomes prevalent, but you can still hear the underlying rhythm:

	BPF.ar(Saw.ar([32,33],0.2),LFNoise0.kr(4/3).range(500, 1500),0.1).distort, // input signal
	2, // max delay time
	2, // delay time
	40) // decay time

// ********************************************************
// THIRD PART: LocalIn and LocalOut
// ********************************************************

// LocalIn.ar and LocalOut.ar are internal buses (see help file).
// In the example below, we feed one impulse every 3 seconds
// into this local bus. LocalIn is inside LocalOut, thus a feedback
// is created. The impulse is repeated every 64 samples (block size),
// each time multiplied by 0.99 (so it fades out quickly).


// Assuming the current sampling rate (sr) is 44100, we can find out the
// frequency of this "note" by dividing sr by 64 (block size):

44100/64  // result is 689.0625 Hz if your sr = 44100

// Check it with a sine wave, they should sound the same pitch:

{SinOsc.ar(44100/64, mul: 0.5)}.play // freq = sampling rate / block size

{LocalOut.ar(a=LocalIn.ar(1)+Impulse.ar(1/3)*0.99);a}.play // freq = sampling rate / block size

// With a delay line we can hear a gradual accumulation
// of impulses. One impulse is generate every 1 second, and
// mixed (+) with the ones played before. Because there is
// an extra delay of 64 samples "built-in" due to the use of
// LocalIn & LocalOut, the successive impulses do not pile up
// in a simultaneous attack; instead they are gradually
// juxtaposed one after the other, 64 samples apart each time.
// This builds up as a repeated note (689 Hz) of increasing length:

{LocalOut.ar(a=DelayN.ar(LocalIn.ar(1)+Impulse.ar(1),1,1));a}.play // thanks to Nathaniel for this example

// The variable 'a' above works like in the simpler example below.
// There are two statements separated by a semicolon:


// Finally, this is how the original tweet
// uses the LocalIn LocalOut structure:


// The variable 'a' is the CombN code we analyzed earlier:


As of 2011-07-01, I was not sure what's the exact role of the LocalIn / LocalOut structure in this example, nor why LocalIn.ar(2) is multiplied by 7.5. I contacted Nathaniel Virgo (the author of the original tweet) and he kindly sent me an explanation which I reproduce below.

// =====================================================================
// Extra explanation by Nathaniel Virgo, 2011-07-02
// =====================================================================

// This rings at 689 Hz

// But we can make it into a much longer echo by adding a delay.
// This never decays away:


// So we multiply the feedback signal by 0.75
// to get something more like a syncopated echo:


// Now if we apply an effect (e.g. a BPF) we can hear it being applied
// again each time the sound passes through the delay line (I've changed
// the timings to make it easier to hear what's happening):


// We can add in some of the 'dry' signal to the delay line by
// changing the DelayN to a CombN. (However, by doing it this way, the
// feedback signal gets an additional delay of 64 samples that the dry
// signal doesn't get, which is why this example builds up a nasty ringing
// sound after a while.)


// So let's modulate the BPF and add some distortion, which
// also gets applied on each pass through the feedback loop:


// (Note that 2**LFNoise0.kr(4/3,4)*300 is equivalent to
// LFNoise0.kr(4/3).exprange(18.75, 4800), but it takes up less space)

// Compare the above to this one without the LocalOut & LocalIn (*)
// [(*) Note: I added this line to Nathaniel's explanation. BTR]


// Now all that's left is to replace the impulse by a stereo Saw to
// get the original tweet. The distortion stops it from becoming too
// loud and occasionally adds some nice dirty sounds.



// Original tweet by mathk
// http://swiki.hfbk-hamburg.de:8888/MusicTechnology/899

{k=LFNoise1.kr(8.0.rand+2,0.5,0.5);SinOsc.ar([[333,444],[222,555]]*(k+(rrand(1.0,5.0))),0,k).sum.cubed * 0.1}.play
// #supercollider #babies


// Original tweet by mutantsounds
// http://swiki.hfbk-hamburg.de:8888/MusicTechnology/899