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

Cottle Chapter 6 - Envelopes, Reciprocals

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

6. Envelopes, Reciprocals

Envelopes


//6 Assignment

s = Server.internal;
s.boot;

(
{	var env, trig;
	
	trig = Impulse.kr(1);
	env = Env.linen(0.1, 0.2, 0.3, 0.5); 

	SinOsc.ar(400, mul: EnvGen.kr(env, gate: trig))
}.scope;
)

(
var pitchCollection, env; 
pitchCollection = [60, 45, 67, 82, 55, 66, 62].midicps;
env = Env.linen(0.01, 0.1, 0.3, 0.5); 

{	var trig;
trig = Impulse.kr(2);

	SinOsc.ar(
		Select.kr(		// pitch selector
			Stepper.kr(	// steadily increasing index
				trig,	// trigger
				0,	// reset trigger (not needed)
				0,	// min value
				pitchCollection.size-1, // max value
				1,	// step size
				pitchCollection.size-1 // reset value
			),
			pitchCollection
		),
		mul: EnvGen.kr(env, gate: trig)
	)
}.scope;
)

//6.1 Envelopes plotted
//6.2 Complex Envelope

// Plot examples will not work in sc3.

//6.3 Envelope and Envelope Generator controlling amplitude

(
{	var env, trig;
	
	trig = Impulse.kr(1);
	env = Env.linen(0.1, 0.2, 0.3, 0.5); 

	SinOsc.ar(400, mul: EnvGen.kr(env, gate: trig))
}.scope;
)

// A note about doneAction:
// In sc2, the Synth is supposed to stick around until the piece is done.
// In sc3, often you want a synth to die when its envelope is over.
// doneAction == 0 means that the synth persists. This is the default.
// doneAction == 2 means that the synth will free itself.
// The EnvGen helpfile lists all 13 possible doneActions.

// Watch the number of Synths in the server window. At first, it's 2
// (one for the player and one for the scope). After 0.6 seconds
// (total length of Env.linen), it drops to 1 (scope only).

(
{	var env, trig;
	
	trig = Impulse.kr(1);
	env = Env.linen(0.1, 0.2, 0.3, 0.5); 

	SinOsc.ar(400, mul: EnvGen.kr(env, gate: trig, doneAction:2))
}.scope;
)


//6.4 Scaled envelope for frequency

// EnvGen no longer has mul and add arguments. Use levelScale and levelBias instead.

EnvGen.kr(Env.linen(1.5, 2.5, 1.2, 0.3), levelScale: 1000, levelBias: 500)


//6.5 Duration, attack, decay

var dur, att, dec;

dur = 10;
att = dur*0.1;
dec = dur*0.9;


//6.6 Frequency and duration

{Impulse.kr(4)}.scope;	//freq = 4, dur = 1/4 or 0.25
{Impulse.kr(3)}.scope;	//freq = 3, dur = 1/3 or 0.33

{Impulse.kr(0.2)}.scope;	//freq = 0.2, dur = 1/0.2 or 10/2 or 5 
sec

{Impulse.kr(0.125)}.scope;	//freq = 4, dur = 1/0.125 or 1000/125 
or 8


//6.7 Frequency expressed as a ratio

{Impulse.kr(4)}.scope;	//freq = 4, dur = 1/4 or 0.25
{Impulse.kr(3)}.scope;	//freq = 3, dur = 1/3 or 0.33

{Impulse.kr(0.2)}.scope;	//freq = 0.2, dur = 1/0.2 or 10/2 or 5 
sec

{Impulse.kr(0.125)}.scope;	//freq = 4, dur = 1/0.125 or 1000/125 
or 8

//6.8 Duration, attack, decay

var dur, att, dec, trigFreq;

dur = 10;
att = dur*0.1;
dec = dur*0.9;
trigFreq = 1/dur;

//Just For Fun
//
//6.9 Just For Fun: Crotales

// As in the previous chapter, the approach is to shift some of the calculations
// into the controlling Routine.

// Since all synthdefs must be precompiled, and we have 5 envelopes to choose
// from, I must make 5 synthdefs in advance. Note the use of a loop to create them
// mechanically: I don't have to reproduce the synthdef code each time.

// This one's kind of cool. Very tape-studio.

(

// Make a separate synthdef for each envelope.
// Five possible envelopes.
// The total length of each Env is 1.0 sec; this will be scaled by dur in the EnvGen.

[	Env.perc(0.99, 0.01), //long attack, sharp decay
	Env.perc(0.0001, 0.9999), //sharp attack, long decay
	Env.perc(0.01, 0.99), //softer attack, long decay
	Env.perc(0.1, 0.9), //soft attack, long decay
	Env.perc(0.2, 0.8) //soft attack, long decay
].do({ |env, i|	// |env, i| is a shortcut syntax for "arg env, i;"
		// the defs will be named ch6-fun0, ch6-fun1, etc.
	SynthDef("ch6-fun" ++ i, {
		arg	freq,	// Cottle has the synth in the TSpawn calculate the freq.
				// I will pass it in from the controlling Routine.
			indexRange, dur;

		var ratioa, ratiob, ratioc, ratiod, factor, envs;
		var panCont, freqCont, index;
			
		//Calculate ratios, carrier and modulator frequency multiples. I'm using
		//the same calculations from on of McCartney's example. Changes here
		//won't lead to anything more interesting.

		// It would be more efficient to do these calculations in the language-side
		// routine as well, then pass them into the synthdef. But I'm not doing that here.
		
			// IRand calculates a new random integer each time the
			// synth starts. If I used rrand, there would be one value
			// used for every instantiation of this synthdef. The value
			// would be different for each of the 5 synthdefs. Why?
		ratioa = IRand(1, 12); 
		ratiob = IRand(1, 12); factor = gcd(ratioa, ratiob);
		ratioa = div(ratioa, factor); ratiob = div(ratiob, factor);
		
		ratioc = IRand(1, 12); 
		ratiod = IRand(1, 12); factor = gcd(ratioc, ratiod); 
		ratioc = div(ratioc, factor); ratiod = div(ratiod, factor);
		
		//use one of these freqCont lines
		freqCont = 1;
//		freqCont = EnvGen.kr(env, timeScale: 2*dur, levelBias: 1, levelScale: Rand(100, 400));
		//freqCont = EnvGen.kr(env, timeScale: 2*dur, levelBias: 1, levelScale: Rand(60, 1200));
		//freqCont = (EnvGen.kr(env, timeScale: 2*dur, levelBias: 1, 
		//					levelScale: Rand(60, 1200)))
		//	* (IRand(0, 1) * 2 - 1);
		
		//use one of these panCont lines
		// [1, -1].choose again will evaluate once only at compile time.
		// to get it to evaluate every time the synth starts, it has to be done like this:
//		panCont = 0;
		panCont = (EnvGen.kr(env, timeScale: 2*dur, levelBias: -1, levelScale: 2))
			* (IRand(0, 1) * 2 - 1);  // 0*2-1 = -1, 1*2-1 = 1
		//panCont = LFNoise0.ar(20);
		//panCont = LFNoise1.ar(2);
		//panCont = 1.0.rand2;
		
		o = PMOsc.ar(
			ratioa*freq, //or try ratioa*freqCont,
			ratiob*freq, //or try ratioa*freqCont,
			pmindex: EnvGen.kr(env, timeScale: 2*dur, levelBias: 1, levelScale: indexRange), 
			mul: EnvGen.kr(env, timeScale: 2*dur, levelScale: 0.3));
		
		p = PMOsc.ar(
			ratioc*freq, //or try ratioa*freqCont, 
			ratiod*freq, //or try ratioa*freqCont, 
			pmindex: EnvGen.kr(env, timeScale: 2*dur, levelBias: 1, levelScale: indexRange), 
			mul: EnvGen.kr(env, timeScale: 2*dur, levelScale: 0.3));
	
		o = Mix.ar([o, p]);
		o = Pan2.ar(
			o, 
			panCont
		);
		
		o = o * EnvGen.kr(env, timeScale: 2*dur, levelScale: Rand(0.1, 0.5),
			doneAction:2);  // only 1 EnvGen in the synthdef needs to have
						// doneAction:2 for the synth to free itself
		Out.ar(0, o);
		
	}).send(s);
});
)

(
	// a Task is like a Routine. It's designed to be paused and resumed.
r = Task({
	var freq, indexDepth, indexRange, synthIndex, dur;
	
		// This is not a SynthDef, so rrand is appropriate here.
	freq = rrand(20, 4000);
	indexDepth = 1;
	
	inf.do({
		//Each new frequency is a ratio of the previous freq
		freq = (freq * ([3/2, 2/1, 4/3, 3/4, 2/3, 1/2].choose)).wrap(20, 600);
		//Index depth is how quickly the sound becomes richer
		indexDepth = indexDepth + 0.3; 
		indexRange = (indexDepth).round(1.0).wrap(1, 24)
			.rand2;
		synthIndex = 5.rand;
		dur = rrand(0.1, 2.0); //total duration of each event
		
			// place at head of server, so reverb effect will work below
		Synth.head(s, "ch6-fun" ++ synthIndex, [\freq, freq,
			\indexRange, indexRange, \dur, dur]);

			// linexp maps a linear range onto an exponential one
			// this puts more emphasis on shorter values,
			// making the texture more lively	
		rrand(0.02, 2).linexp(0.02, 2, 0.02, 2).wait;	// random time between triggers
	});
}).play(SystemClock);
)

// Cottle sez: try adding this reverb, but I like it without
// I say: Note that the reverb is removed from the code inside the synthdefs
// that are being spawned. The reverb can be applied to the whole signal,
// once. This is a lot more efficient than putting it inside.

(
SynthDef("rvb", {
	var o;
	o = In.ar(0, 2);	// get the output signal
	4.do({ o = AllpassN.ar(o, 0.05, [0.05.rand,0.05.rand], 1) });
	Out.ar(0, o);
}).send(s);
)

// the reverb must go after the playing synths
Synth.tail(s, "rvb");

r.stop;

s.scope;


Link to this Page