[
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;