This book is an independent project based on Designing Sound by Andy Farnell, all about the principles and techniques needed to design sound effects for real-time synthesis. The original book provides examples in the PureData language - here we have re-created some of the examples using SuperCollider.

The original book includes much more than what you see here - we're only recreating the examples and not the text! So in a sense this is not a stand-alone book and you'll get the most out of it if you have the original book with you. But we hope the examples are illustrative in themselves.

Any defects in the code we present should be assumed to be our own mistakes, and no reflection on Andy's fine book!

Then so we have some source material, we'll load the standard sound file that comes bundled with SC:

b=Buffer.read(s,"sounds/a11wlk01-44_1.aiff");// Hear it raw:b.play

Now here's the code which creates the reverb in a single Synth, with four separate delay lines cross-fertilising each other:

(Ndef(\verb,{varinput,output,delrd,sig,deltimes;// Choose which sort of input you want by (un)commenting these lines:input=Pan2.ar(PlayBuf.ar(1,b,loop:0),-0.5);// buffer playback, panned halfway left//input = SoundIn.ar([0,1]); // TAKE CARE of feedback - use headphones//input = Dust2.ar([0.1, 0.01]); // Occasional clicks// Read our 4-channel delayed signals back from the feedback loopdelrd=LocalIn.ar(4);// This will be our eventual output, which will also be recirculatedoutput=input+delrd[[0,1]];// Cross-fertilise the four delay lines with each other:sig=[output[0]+output[1],output[0]-output[1],delrd[2]+delrd[3],delrd[2]-delrd[3]];sig=[sig[0]+sig[2],sig[1]+sig[3],sig[0]-sig[2],sig[1]-sig[3]];// Attenutate the delayed signals so they decay:sig=sig*[0.4,0.37,0.333,0.3];// Here we give delay times in milliseconds, convert to seconds,// then compensate with ControlDur for the one-block delay// which is always introduced when using the LocalIn/Out fdbk loopdeltimes=[101,143,165,177]*0.001-ControlDur.ir;// Apply the delays and send the signals into the feedback loopLocalOut.ar(DelayC.ar(sig,deltimes,deltimes));// Now let's hear it:Out.ar(0,output);}).play)

And here's an alternative way of doing exactly the same thing, this time using a matrix to represent the cross-mixing of the delayed streams. The single matrix replaces all those plusses and minusses so it's a neat way to represent the mixing - see which you find most readable.

(Ndef(\verb,{varinput,output,delrd,sig,deltimes;// Choose which sort of input you want by (un)commenting these lines:input=Pan2.ar(PlayBuf.ar(1,b,loop:0),-0.5);// buffer playback, panned halfway left//input = SoundIn.ar([0,1]); // TAKE CARE of feedback - use headphones//input = Dust2.ar([0.1, 0.01]); // Occasional clicks// Read our 4-channel delayed signals back from the feedback loopdelrd=LocalIn.ar(4);// This will be our eventual output, which will also be recirculatedoutput=input+delrd[[0,1]];sig=output++delrd[[2,3]];// Cross-fertilise the four delay lines with each other:sig=([[1,1,1,1],[1,-1,1,-1],[1,1,-1,-1],[1,-1,-1,1]]*sig).sum;// Attenutate the delayed signals so they decay:sig=sig*[0.4,0.37,0.333,0.3];// Here we give delay times in milliseconds, convert to seconds,// then compensate with ControlDur for the one-block delay// which is always introduced when using the LocalIn/Out fdbk loopdeltimes=[101,143,165,177]*0.001-ControlDur.ir;// Apply the delays and send the signals into the feedback loopLocalOut.ar(DelayC.ar(sig,deltimes,deltimes));// Now let's hear it:Out.ar(0,output);}).play)// To stop it:Ndef(\verb).free;

First start the server, if you haven't already - this time making sure it's the server which lets use the oscilloscopes:

Server.default=s=GUI.stethoscope.defaultServer;s.boot;

In the following we use ".sin" and ".cos" as mathematical operations, so we can stick closely to the original design - these are much less efficient than using SC's SinOsc oscillator, which is what most people would use for additive synthesis in SuperCollider.

In the original diagram, freq starts as 122, index as 0.42. Here we connect these parameters to the mouse - left/right for freq, up/down for harmonic decay.

The amplitude will vary with the decay coefficient, so practically, you'd want some kind of normalisation.

(x={varfreq=MouseX.kr(100,1000,1)/SampleRate.ir;vardistance=3.00;varindex=MouseY.kr(0.42,0.99);vartheta,beta,num,denom,son;// Two phasors which will ramp from zero to 2pitheta=Phasor.ar(0,freq,0,2pi);beta=Phasor.ar(0,freq*distance,0,2pi);num=sin(theta)-(index*sin(theta-beta));denom=1+index.squared-(2*index*cos(beta));son=num/denom;Out.ar(0,Pan2.ar(son*0.3));}.freqscope;// Use ".freqscope" or ".scope", both are illustrative.)

The LFPulse here is used to provide a sharp-edged on/off for the sound. (LFPulse outputs a low of zero and a high of one, which is handy in this case so we don't need to re-scale it before we can use it as the multiplier.)

(Ndef(\dialtone,{// Note: the array here specifies two frequencies, so we get two separate channels.// We sum the two channels so they combine into one signal - otherwise we // would hear one note on left, one note on right.Pan2.ar(SinOsc.ar([350,440],0,0.2).sum)}).play)// To stop:Ndef(\dialtone).free;

// Doesn't make any sound in itself, but// filters whatever we put in Ndef(\phonesource) and outputs that(Ndef(\transmed,{varsig=Ndef(\phonesource).ar.clip2(0.9);sig=BPF.ar(sig,2000,1/12);sig=BPF.ar(sig*0.5,400,1/3)+(sig.clip2(0.4)*0.15);HPF.ar(HPF.ar(sig,90),90)*100;}).play)

// First run this block to create the synth... (the filter from earlier on should still be running!)(Ndef(\phonesource,{|t_trig=0,number=0|varonoff,trigs,son;number=if(number<0.5,10,number);// zero is represented by 10 clicks!onoff=Trig1.ar(t_trig,number*0.1);trigs=Impulse.ar(10)*onoff;son=Trig1.ar(trigs,0.04);son;});)// ...then dial some numbers by repeatedly running this line:Ndef(\phonesource).set(\t_trig,1,\number,10.rand.postln);

(// This data structure (like a "hashtable" or "associative array" in other languages) // maps from a phone key to a pair of frequencies in Hz. // We can push these frequencies to a synth.~tbl=IdentityDictionary[$1->[[697,1209]],$2->[[770,1209]],$3->[[852,1209]],$4->[[697,1336]],$5->[[770,1336]],$6->[[852,1336]],$7->[[697,1477]],$8->[[770,1477]],$9->[[852,1477]],$*->[[697,1633]],$0->[[770,1633]],$#->[[852,1633]],$A->[[941,1209]],$B->[[941,1336]],$C->[[941,1477]],$D->[[941,1633]]];// Here we define a SynthDef which plays a single "number" at a time.// Note that our strategy here is a bit different from the PD code in the book:// there, a single pair of sine-wave oscillators was re-used for each number,// whereas here, we create (and later free) an individual synth for each number.SynthDef(\dtmf,{|freq=#[770,1633],out=0,amp=0.2,gate=1|varson,env;son=SinOsc.ar(freq,0,amp).sum;env=EnvGen.ar(Env.asr(0.001,1,0.001),gate,doneAction:2);Out.ar(out,Pan2.ar(son*env*amp));}).add;)// Check that it works:x=Synth(\dtmf)// createx.set(\gate,0)// free(// This pattern generates a random "phone number" and dials itPbind(\instrument,\dtmf,\dur,0.2,// or for more "human" timing, try Pwhite(0.2, 0.5, inf)\sustain,0.15,\amp,0.3,\freq,Prand(~tbl.asArray,13)).play;)(// You could even dial a specific number:Pbind(\instrument,\dtmf,\dur,0.2,// or for more "human" timing, try Pwhite(0.2, 0.5, inf)\sustain,0.15,\amp,0.3,\freq,Pseq("012827743866".collectAs({|digit|~tbl[digit]},Array))).play;)

Note that we have separated this into multiple lines for clarity. Exercise: rewrite it as a single line - and use array expansion rather than writing "SinOsc" twice.

(Ndef(\alarm,{vartone1=SinOsc.ar(600);vartone2=SinOsc.ar(800);// We switch between the tones using LFPulse, but soften the crossfade with the low-pass:varcontrol=LPF.kr(LFPulse.kr(2),70);varout=SelectX.ar(control,[tone1,tone2]);Pan2.ar(out*0.1)}).play)

(Ndef(\alarm,{vartone1=SinOsc.ar(723);vartone2=SinOsc.ar(932);vartone3=SinOsc.ar(1012);// Stepper is perfect for stepping through the options:varcontrol=LPF.kr(Stepper.kr(Impulse.kr(2),0,0,2),70);varout=SelectX.ar(control,[tone1,tone2,tone3]);Pan2.ar(out*0.1)}).play)

Or we can write exactly the same thing a bit more generically using Demand units. The frequencies are simply given as an Array - change the values, or add new ones to the end:

This SynthDef is capable of a wide variety of sequences, as you'll see below:

(SynthDef(\dsaf_multialarm,{|length=0.05,freqs=#[600,800,600,800],timbre=1,repeats=inf|varfreq,out,operations;freq=Duty.ar(length,0,Dseq(freqs,repeats),doneAction:2);freq=LPF.ar(freq,70);out=LeakDC.ar(SinOsc.ar(freq));out=Select.ar(timbre,[out,(out*pi).sin,(out*pi).cos,((out+0.25)*pi).cos]);// NOTE: when writing a synthdef always remember the Out ugen!// (Handy shortcuts like Ndef and {}.play often add Out on your behalf)Out.ar(0,Pan2.ar(out*0.1))}).add;)

In SuperCollider we can provide an almost-direct physical model: the LFPulse represents a "raw" on/off signal before smoothing by capacitors, and the "lagud" provides exponential smoothing, with the handy feature of allowing different time-periods for the "on" and "off" convergence. This one-line example will plot the curve for you:

Now let's use this technique both for the pitch curve, and for waveform synthesis.

(SynthDef(\dsaf_horn1,{|rate=0.1|varfreq=LFPulse.kr(rate,0.99,0.4).lagud(0.4/rate,0.6/rate)*800+300;varson=LFPulse.ar(freq,0.99,0.2).lagud(0.4/freq,0.6/freq)*2-1;// This filtering is a simple approximation of the plastic horn acoustics:son=BPF.ar(son.clip2(0.2),1500,1/4)*4;// delay and reverb, to simulate the environment in which we hear the sirenson=son+DelayC.ar(son,0.1,0.1,0.3);son=son+FreeVerb.ar(son);Out.ar(0,Pan2.ar(son*0.4));}).add;)x=Synth(\dsaf_horn1);s.scope// Choose a ratex.set(\rate,3);x.set(\rate,0.1);

Exercise: instead of using the lagged-pulse implementation of the waveform, do as the book says and try using a simple triangle oscillator (LFTri) - this loses the physical-modelling "realism" of the authentic circuit but will be more efficient, and reasonably similar. How much of an effect on the tone quality do you get?

In the book, one partial of the resonance is modelled using a sinewave, modulated by an envelope that dies away on a quadratic-shaped curve. Groups of three such oscillators are then made.

Here we take advantage of a built-in unit in SuperCollider which does quite a lot of the work for us: Klank. A difference: it gives exponential rather than quadratic decay - which is not too bad since exponential decay is often what you want.

If you have sc3.3 or later, you can visualise a frequency representation of the filter's effect:

GUI.stethoscope.defaultServer.boot;(// Notice that for this shortcut visualisation, we define the filter as a function taking its // input as an argument, and returning its output (we don't use In.ar or Out.ar){|casein|vardelayA=CombC.ar(casein,0.00077,0.00077,0.1);vardelayB=CombC.ar(delayA,0.00088,0.00088,0.1);varbands=BPF.ar(delayB,[1243,287,431],1/12).sum;varson=bands.clip2(0.3);// Mouse to the LEFT means flat filter (no change), to the RIGHT means full bakeliteXFade2.ar(casein,son,MouseX.kr(-1,1));}.scopeResponse)

We will re-use the SynthDef from fig 29.14, so make sure you have run that so that the server has that SynthDef. (But stop the sound, if it is still running.)

// A tweaked version of the phonebell synthdef, to take an on/off from outside, and incorporate the striker(SynthDef(\dsaf_phonebell2,{|gate=1,freq=465,strength=1,decay=3,amp=1|vartrigs,striker,son;trigs=Impulse.ar(14)*gate;striker=WhiteNoise.ar(EnvGen.ar(Env.perc(0.0000001,0.01),trigs));son=Klank.ar(`[// frequency ratios[0.501,1,0.7,2.002,3,9.6,2.49,11,2.571,3.05,6.242,12.49,13,16,24],// amps[0.002,0.02,0.001,0.008,0.02,0.004,0.02,0.04,0.02,0.005,0.05,0.05,0.02,0.03,0.04],// ring times - "stutter" duplicates each entry threefold[1.2,0.9,0.25,0.14,0.07].stutter(3)],striker,freq,0,decay);Out.ar(0,Pan2.ar(son*amp));}).add)// We could launch the patch all at once, but let's do it bit-by-bit so we understand what's going on// Here we start the phone bells constantly ringing. We put them in a group for convenience~bellgroup=Group.new(s);~bell1=Synth(\dsaf_phonebell2,[\freq,650],~bellgroup);~bell2=Synth(\dsaf_phonebell2,[\freq,653],~bellgroup);// Now we add the bakelitey=Synth(\dsaf_phonecase1,[\mix,-0.65],target:~bellgroup,addAction:\addAfter);// OK, shush for now~bellgroup.set(\gate,0);// Now let's turn them on and off in a telephone-like pattern.// This could be done using a synth, but let's use a (client-side) pattern:p=Pbind(\type,\set,\id,~bellgroup.nodeID,\args,[\gate],\gate,Pseq([1,0],inf),\dur,2).playp.stop

The four clear resonances are identified using a spectrogram of a can.

(x={|t_trig=0|// This line just creates a sharp little spike whenever we want:varstrike=EnvGen.ar(Env.perc(0.0001,0.001,0.1),t_trig);// here's the resonances:varson=Ringz.ar(strike,[359,426,1748,3150],0.2).sum;// some distortion livens up the spectrum a little:son=HPF.ar(son.clip2(0.6),300);son*0.2}.play;)x.set(\t_trig,1);// Run this line to hit the can!

This simulates the regular turning of the drinks can. Since it's a control signal we'll plot it rather than play, for now.

(~regularroll={|rate=1|// In the original code, Andy uses a master phase control,// wrapping and re-scaling it before differentiating, to produce// a repeating but disorderly set of impulses.// Here we do it differently - we use Impulse.kr to generate the// impulses directly.// We evaluate this function multiple times using .dup so that// we get a whole set of impulses with random phase positions.{Impulse.kr(rate,1.0.rand,1.0.bilinrand)}.dup(10).sum};~regularroll.plot(2);)

(You must have run figs 31.6 and 31.7 to get their functions defined.)

The sound of a can rolling a little and coming to a stop.

(x={varrate,strike,son;// rate of motion starts fast and tails offrate=XLine.kr(4,0.001,8,doneAction:2);// This rate affects both the regular rolling, and the irregular ground contacts.strike=~irregularground.(rate*2)*0.04+K2A.ar(~regularroll.(rate)*0.1);// Force the strikes to die off in intensity:strike=strike*XLine.ar(1,0.0001,8);// And here are the tin-can resonances as in fig 31.3:son=Ringz.ar(strike,[359,426,1748,3150],0.2).sum;son=HPF.ar(son.clip2(0.6),300);son*0.2}.play;)

(~woodfilter={|input|varfreqs,rqs,output;// Note: these freqs are as given in the diagram:freqs=[62.5,125,250,395,560,790];// The Q values given in the diagram (we take reciprocal, since that's what BPF unit wants)rqs=1/[1,1,2,2,3,3];// in the text, andrew says that the freqs follow these ratios, // which give a very different set of freqs...:// freqs = 125 * [0.5, 1, 1.58, 2.24, 2.92, 2, 2.55, 3.16];//Now let's apply the parallel bandpass filters, plus mix in a bit of the original:output=BPF.ar(input,freqs,rqs).sum+(input*0.2);};// Now let's use this function in something - some dust impulses tap-tap-tapping on the door:x={Pan2.ar(~woodfilter.value(LPF.ar(Dust.ar(10),10000)))}.play;// Doesn't sound much like a door? Compare it to the raw tapping sound (edit the above).)

(~stickslip={|force|varinMotion,slipEvents,forceBuildup,evtAmp,evtDecayTime,evts;force=force.lag(0.1);// smoothing to get rid of volatile control changesinMotion=force>0.1;// static friction: nothing at all below a certain force// slip events are generated at random with freqency proportional to force.// I originally used Dust to generate random events at a defined frequency, but// that lacks the slight "pitched" sound of the creaky door. Here we use Impulse// to generate a frequency, but we add some noise to its frequency to try and // avoid it getting too perfectly regular.slipEvents=inMotion*Impulse.ar(force.linlin(0.1,1,1,1/0.003)*LFDNoise1.ar(50).squared.linexp(-1,1,0.5,2).poll);forceBuildup=Phasor.ar(slipEvents,10*SampleDur.ir,0,inf).min(1);// Whenever a slip event happens we use Latch to capture the amount of// force that had built up.evtAmp=Latch.ar(Delay1.ar(forceBuildup.sqrt),slipEvents);evtDecayTime=evtAmp.sqrt;// The book applies square-root functions to shape the dynamic range of the events.// Remember that square-root is computationally intensive, so for efficient // generation we might want to change it to (e.g.) a pre-calculated envelope.// Now we generate the eventsevts=EnvGen.ar(Env.perc(0.001,1),slipEvents,evtAmp,0,evtDecayTime*0.01);};// Let's plot 4 seconds worth, with steadily increasing force.// Events should appear more frequent but less violent as the plot progresses.{~stickslip.value(Line.kr(0,1,4))}.plot(4);)

(~squarepanel={|input|vartimes,filt;// times in milliseconds, converted to seconds:times=[4.52,5.06,6.27,8,5.48,7.14,10.12,16]*0.001;filt=DelayC.ar(input,times,times).mean;filt=HPF.ar(filt,125);filt*4};)

(~freemodes={|input,basefreq=100,res=80|varfiltfreqs;// The actual filter freqs take these harmonic relationships:filtfreqs=basefreq*[1,2.7565,5.40392,8.93295,13.3443,18.6379];BPF.ar(input,filtfreqs,1/res).sum*10};{~freemodes.(LFSaw.ar(4))}.plot(1))

Here we use Env to create an asymmetric waveshape - when the value is below zero, the ruler is touching the table and so is practically "shorter". Therefore it has a higher frequency (shorter wavelength) in the lower cycle than in the upper cycle.

~rulerwave=Env([1,0,-0.7,0,1],[0.3,0.1,0.1,0.3],[4,-4,4,-4]).asSignal(512).asWavetable;~rulerwave.plot;// Here let's plot it running at a frequency that speeds up.// This approximates the actual trajectory of motion of the end of the ruler:{Osc.kr(~rulerwave.as(LocalBuf),XLine.kr(50,100,1),mul:XLine.kr(1,0.001,1))}.plot(1)// Now, every time the wave passes zero in a downwards-going direction, that represents the ruler thwacking on the table and therefore transmitting energy into the resonances.// This code builds on the previous one to derive the thwacks - one at each downward zero crossing, with an energy proportional to the speed (==derivative of position, found using Slope)({varmotion,thwacks,isDown;motion=Osc.ar(~rulerwave.as(LocalBuf),XLine.kr(50,100,1),mul:XLine.kr(1,0.001,1));isDown=motion<0;thwacks=Trig1.ar(isDown,0)*(0-Slope.ar(motion))*0.01;thwacks=LPF.ar(thwacks,500);[motion,isDown,thwacks]}.plot(1))

OK, so now we need to make sound from this data. The base frequency for the resonator is higher if isDown==true, since the effective length is shorter. So we need to modulate the base frequency at the same time as pushing the thwacks through the resonators.

({varmotion,thwacks,isDown,basefreq;motion=Osc.ar(~rulerwave.as(LocalBuf),XLine.kr(10,100,1),mul:Line.kr(1,0.001,1,doneAction:2));isDown=motion<0;thwacks=Trig1.ar(isDown,0)*(0-Slope.ar(motion))*0.01;thwacks=LPF.ar(thwacks,500);basefreq=if(isDown,289,111);~freemodes.value(thwacks,basefreq,100)+~clampedmodes.value(basefreq,thwacks);}.play)// That was a model of a ruler-on-a-desk. The next one is... something else.({varmotion,thwacks,isDown,basefreq;motion=Osc.ar(~rulerwave.as(LocalBuf),80,mul:Line.kr(1,0.001,1,doneAction:2));isDown=motion<0;thwacks=Trig1.ar(isDown,0)*(0-Slope.ar(motion))*0.01;basefreq=if(isDown,289,111)*Pulse.ar(10).exprange(0.9,1.1);~freemodes.value(thwacks,basefreq,100)+~clampedmodes.value(basefreq,thwacks);}.play)

Add more variation by modulating the duration and the filter frequency for each event

({vartrigs,durscale,son,resfreq;trigs=Dust.kr(1);durscale=TRand.kr(1,1.5,trigs);// vary duration between default 20ms and 30msresfreq=TExpRand.kr(100,1000,trigs);// different resonant frequency for each oneson=WhiteNoise.ar*EnvGen.ar(Env.perc(0,0.02,curve:0),trigs,timeScale:durscale);son=son+BPF.ar(son,resfreq,20);}.play)

Fig 35.5: producing a repeating but random-seeming pattern of triggers[edit]

First we'll create a reusable synthdef that outputs triggers (but not sound):

(SynthDef(\bubbletrigs,{|out=0,probability=0.5|vartrigs,buf,a;// These two lines create a loop of zeroes // with some ones (i.e. triggers) placed at prime-number locationsa={0}.dup(200);[29,37,47,67,89,113,157,197].do{|val|a[val]=1};buf=a.as(LocalBuf);// playbuf by default will use the server's rate, but we want one item every 15mstrigs=PlayBuf.kr(1,buf,0.015.reciprocal/(s.sampleRate/s.options.blockSize),loop:1);// Randomly discard half of them, to remove too much obvious loopingtrigs=CoinGate.kr(probability,trigs);// Let's poll to watch the events appearingtrigs.poll(trigs);Out.kr(out,trigs);}).store)// Then we'll play it:x=Synth(\bubbletrigs);// watch the post window to see the bubble events happening (no sound yet!)x.free;

(SynthDef(\bubblebub,{|out=0,t_trig=0,attack=0.01,decay=0.08,pitchcurvelen=0.1,freq=1000,doneAction=0,amp=0.1|varpitch,son;amp=amp*EnvGen.ar(Env.perc(attack,decay).delay(0.003),t_trig,doneAction:doneAction);pitch=freq*EnvGen.ar(Env.new([0,0,1],[0,1]).exprange(1,2.718),t_trig,timeScale:pitchcurvelen);son=SinOsc.ar(pitch);// high-pass to remove any lowpitched artifacts, scale amplitudeson=HPF.ar(son,500)*amp*10;Out.ar(out,son);}).store)x=Synth(\bubblebub);x.set(\t_trig,1);// run this line multiple times, to get multiple (very similar) bubbles!x.free;

(s.bind{// Here we'll create busses to hold the triggers, passing them from synth to synth~maintrigbus=Bus.control(s,1);~bubtrigbus=Bus.control(s,4);// Note how we make sure things are running in the desired order, using \addAfter~trigs=Synth(\bubbletrigs,[\out:~maintrigbus]);// This reads the main trig and puts each trig on a randomly-chosen bus~randomdistrib={vartrig,which;trig=In.kr(~maintrigbus);which=TIRand.kr(0,3,trig);// or try the Stepper instead of TIRand for "round-robin" selection:// which = Stepper.kr(trig, 0, 0, 3);which=which.poll(trig);Out.kr(~bubtrigbus.index+which,trig);}.play(target:~trigs,addAction:\addAfter);s.sync;~bubs=[2400,2600,2500,2700].collect{|afreq|Synth(\bubblebub,[\freq,afreq],target:~randomdistrib,addAction:\addAfter);};s.sync;// "map" allows us to push the triggers from the control bus to the "t_trig" inputs:~bubs.do{|bub,bubindex|bub.map(\t_trig,~bubtrigbus.index+bubindex)};};)

Note, instead of using the "bubbletrigs" synth (which is a direct port of the pd example) we could use Patterns to trigger bubble synths. This is a different model for resource management: instead of having four always-running synths which re-trigger to create a new bubble, we create one synth whenever we need a bubble, and it frees itself after.

(p=Pbind(\instrument,\bubblebub,// The commented version is a bit like the above timings...// \dur, Pseq([29, 37, 47, 67, 89, 113, 157, 197, 200].differentiate * 0.015, inf),// ...but happily we have useful random-distrib generators. Ppoisson would be ideal but is misbehaving for me!\dur,Pgauss(0.3,0.2),\freq,Pwhite(0.0,1,inf).linexp(0,1,1000,3000),// doneAction of two allows the synths to free themselves. See "UGen-doneActions".openHelpFile\doneAction,2).play)p.stop

This next one's a bit more complex - we do as the book does and make smaller bubbles have (a) higher pitch (b) lower volume (c) shorter duration. To connect these values together we define a "sizefactor" and use Pkey to reuse it in each of the args.

(x={vartrigs,freq;trigs=Dust.kr(170);freq=// Generally choose from a varied base freqTExpRand.kr(800,2000,trigs)// Wobbly variation+LFNoise2.kr(20,mul:300)// General tendency for upward rise+EnvGen.kr(Env.perc(1).range(0,17),trigs);SinOsc.ar(freq,0,0.3)}.play;)x.free;// hmmm, let's try combining a few of these in parallel.// do we sound like a river yet?(x={vartrigs,freq;6.collect{trigs=Dust.kr(170);freq=// Generally choose from a varied base freqTExpRand.kr(800,2000,trigs)// Wobbly variation+LFNoise2.kr(20,mul:300)// General tendency for upward rise+EnvGen.kr(Env.perc(1).range(0,17),trigs);SinOsc.ar(freq,0,0.3)}.mean}.play;)x.free;

The Central Limit Theorem tells us that adding together independent noise sources will eventually give us gaussian noise. Twelve is certainly enough for auditory purposes.

x={{WhiteNoise.ar}.dup(12).mean.dup*0.5}.play;x.free;

In the book, Andy says that it is more efficient to use two white noise sources and to create a gaussian shape using the Box-Muller transform. However, this involves some relatively heavy mathematical operations (log, cos, sqrt) so it's not actually obvious which approach is more efficient on a given system. Compare the CPU usage (and the audio result) of the above, with the following: