View this PageEdit this PageUploads to this PageHistory of this PageTop of the SwikiRecent ChangesSearch the SwikiHelp Guide

Cottle Chapter 10 - Karplus/Strong

Home   How To   Code Pool   Public Library   Theory   Events
[Cottle examples]

10. Karplus/Strong


// 10.1 noise burst

s = Server.internal.boot;

// Repeated triggers so you can see the scope better.

(
{ //Beginning of Ugen function
	var burstEnv, trig, trigFreq = 1, att = 0, dec = 1; //Variable declarations
	trig = Impulse.kr(trigFreq);
	burstEnv = EnvGen.kr(Env.perc(att, dec), trig); //Define envelope
	PinkNoise.ar(burstEnv); //Noise, amp controlled by burstEnv
}.scope;	//End Ugen function
)

// 10.2 burst and delay

(
{ //Beginning of Ugen function
	var burstEnv, att = 0, dec = 1; //Variable declarations
	var out, delayTime = 0.5, delayDecay = 10;
	burstEnv = EnvGen.kr(Env.perc(att, dec)); //Define envelope
	out = PinkNoise.ar(burstEnv); //Noise burst
	CombL.ar(
		out, 
		delayTime, 
		delayTime, 
		delayDecay, 
		add: out); //Echo chamber
}.scope;	//End Ugen function
)

// 10.3 reciprocal

// Note that postln is removed. The interpreter posts the result of the last expression automatically.

440.reciprocal;
 
// 10.4 midi to cps to reciprocal

69.midicps.reciprocal;

// 10.5 pluck

(
{ //Beginning of Ugen function
	var burstEnv, att = 0, dec = 0.05; //Variable declarations
	var drySignal, delayTime, delayDecay = 10;
	var midiPitch = 69; // A 440
	delayTime = midiPitch.midicps.reciprocal;
	burstEnv = EnvGen.kr(Env.perc(att, dec)); //Define envelope
	drySignal = PinkNoise.ar(burstEnv); //Noise burst
	CombL.ar(drySignal, delayTime, delayTime, 
		delayDecay, add: drySignal); //Echo chamber
}.scope; //End Ugen function
)

// 10.6 Spawn and pluck

// Now we have a style that's more amenable to sc3:
// write the instrument into a func, then spawn it.
// You've seen this before and you can probably
// convert it yourself by this point... but, to reiterate,
// make the instrument func into a SynthDef, then
// play the synthdef in a Routine or Task.

// But... he's doing his pitch calculations in the synthdef.
// It's faster to do all that on the client side and pass it in.

// Rather than apply doneAction to the overall envelope,
// in this case it's better to detect silence to kill the synth.

(
SynthDef("pluck", { //Beginning of Ugen function
	arg delayTime;
	var burstEnv, att = 0, dec = 0.05; //Variable declarations
	var out, delayDecay = 0.5, midiPitch;
	burstEnv = EnvGen.kr(Env.perc(att, dec)); //Define envelope
	out = PinkNoise.ar(burstEnv); //Noise burst
	out = CombL.ar(out, delayTime, delayTime, 
		delayDecay, add: out); //Echo chamber which produces pitch
	DetectSilence.ar(out, doneAction:2);
	Out.ar(0, out)
}).send(s);

r = Task({
	{	0.25.wait;
		Synth("pluck", [\delayTime, rrand(32, 55).midicps.reciprocal], target:s);
	}.loop;
}).play(SystemClock)
)

r.stop;

// Just For Fun: Karplus-Strong Patch

// 10.7 expanded pluck

// Most of the early part of the his instrument function is
// actually control and should be shifted to the Routine.
// It's also kind of sloppy to spawn the reverb. A true
// reverb runs external to the notes being played, so I
// will revise it that way here.

// DetectSilence isn't so good here because if both
// channels are silent from the start, it won't kill the synth.
// So I'll use a line, which also has a doneAction.

(
SynthDef("pluck", {
	arg	lDelayTime, rDelayTime, lAmp, rAmp, articulation;
	var burstEnv, att = 0, dec = 0.05; //Variable declarations
	var out;
	//The mul value (amplitude) for the envelope is set to either 1 (on)
	//or 0 (off). This is done for both channels.
	burstEnv = EnvGen.kr(Env.perc(att, dec)) * [lAmp, rAmp]; //Define envelope
	out = PinkNoise.ar(burstEnv); //Noise burst
	out = CombL.ar(out, lDelayTime.max(rDelayTime), [lDelayTime, rDelayTime], 
		articulation, add: out); //Echo chamber
//Filter -- each note can have a unique filter, that's OK
	out = RLPF.ar(out, LFNoise1.kr([0.5, 0.43], 2000, 2100), 0.5); 
	Line.kr(0, 1, articulation, doneAction:2);
	Out.ar(0, out) //return this value
}).send(s); //End Ugen function

v = SynthDef("reverb2chan", {
	arg bus, out;
	out = In.ar(bus, 2);
	2.do({out = AllpassN.ar(out, 0.01, rrand(0.005, 0.01), 4)}); 
	ReplaceOut.ar(bus, out);
}).play(s, [\bus, 0]);	// go ahead and start it now

r = Task({
	var midiPitch, delayTime, legalPitches, articulation, count = 0, lAmp, rAmp;
	legalPitches = [0, 2, 4, 6, 8, 10]; //whole tone scale
	articulation = [0.125, 0.25, 0.5, 1.0].choose;
	{	0.125.wait;
		//midiPitch is set to a L&R array of one of the legalPitch choices, plus
		//an octave. The left channel wraps through the choices.
		// only one of these is chosen in the Synth call below. This is what
		// would have happened in sc2 as well, by multiplying one channel by 0
		// and the other by 1.
		midiPitch = [legalPitches.choose + [36, 48, 60].choose,
					legalPitches.wrapAt(count) + [36, 48].choose];
		count = count + 1; //Count is used with wrapAt above
	//	[midiPitch, count].postln; //For checking values
		delayTime = midiPitch.midicps.reciprocal; //Calculate reciprocal
		articulation = [0.125, 0.25, 0.5, 1.0].choose;
			// this syntax means assign the first array element to lAmp and the 2nd to rAmp
		#lAmp, rAmp = [2.rand, 2.rand];
		Synth.head(s, "pluck", [\lDelayTime, delayTime[0], \rDelayTime, delayTime[1],
			\lAmp, lAmp, \rAmp, rAmp, \articulation, articulation]);
	}.loop;
}).play(SystemClock);
)

r.stop;
v.free;


Link to this Page