2 var <levels, <times, <curves;
3 var <releaseNode; // index of release level, if nil then ignore release
4 var <loopNode; // index of loop start level, if nil then does not loop
5 var <offset; // an offset to all time values (only works in IEnvGen)
7 var <array; // cache for osc-conform data
11 *new { arg levels = #[0,1,0], times = #[1,1], curve = \lin, releaseNode, loopNode, offset = 0;
12 times = times.asArray.wrapExtend(levels.size - 1);
13 ^super.newCopyArgs(levels, times, curve ? \lin, releaseNode, loopNode, offset)
16 *newClear { arg numSegments = 8;
17 // make an envelope for filling in later.
18 ^this.new(Array.fill(numSegments + 1, 0), Array.fill(numSegments, 1))
22 shapeNames = IdentityDictionary[
39 kr { arg doneAction = 0, gate = 1.0, timeScale = 1.0, mul = 1.0, add = 0.0;
40 ^EnvGen.kr(this, gate, mul, add, timeScale, doneAction)
43 ar { arg doneAction = 0, gate = 1.0, timeScale = 1.0, mul = 1.0, add = 0.0;
44 ^EnvGen.ar(this, gate, mul, add, timeScale, doneAction)
73 times = times.normalizeSum * dur
80 range { arg lo = 0.0, hi = 1.0;
81 ^this.copy.levels_(levels.linlin(levels.minItem, levels.maxItem, lo, hi))
84 exprange { arg lo = 0.01, hi = 1.0;
85 ^this.copy.levels_(levels.linexp(levels.minItem, levels.maxItem, lo, hi))
88 // methods to make some typical shapes :
90 // fixed duration envelopes
92 *triangle { arg dur=1.0, level=1.0;
100 *sine { arg dur=1.0, level=1.0;
109 *perc { arg attackTime=0.01, releaseTime=1.0, level=1.0, curve = -4.0;
112 [attackTime, releaseTime],
117 *linen { arg attackTime=0.01, sustainTime=1.0, releaseTime=1.0, level=1.0, curve = \lin;
119 [0, level, level, 0],
120 [attackTime, sustainTime, releaseTime],
126 var times, levels, curves, offset, order;
127 #times, levels, curves = xyc.flop;
128 if(times.containsSeqColl.not) { // sort triplets, if possible.
130 times = times[order];
131 levels = levels[order];
132 curves = curves[order];
135 times = times.differentiate.drop(1);
136 curves.asArray.drop(-1);
137 ^this.new(levels, times, curves, offset: offset);
140 *pairs { arg pairs, curve;
141 if(curve.isNil) { ^this.xyc(pairs) };
142 ^this.xyc(pairs +++ curve);
145 // envelopes with sustain
147 *cutoff { arg releaseTime = 0.1, level = 1.0, curve = \lin;
148 var curveNo = this.shapeNumber(curve);
149 var releaseLevel = if(curveNo == 2) { -100.dbamp } { 0 };
150 ^this.new([level, releaseLevel], [releaseTime], curve, 0)
153 *dadsr { arg delayTime=0.1, attackTime=0.01, decayTime=0.3,
154 sustainLevel=0.5, releaseTime=1.0,
155 peakLevel=1.0, curve = -4.0, bias = 0.0;
157 [0, 0, peakLevel, peakLevel * sustainLevel, 0] + bias,
158 [delayTime, attackTime, decayTime, releaseTime],
164 *adsr { arg attackTime=0.01, decayTime=0.3,
165 sustainLevel=0.5, releaseTime=1.0,
166 peakLevel=1.0, curve = -4.0, bias = 0.0;
168 [0, peakLevel, peakLevel * sustainLevel, 0] + bias,
169 [attackTime, decayTime, releaseTime],
175 *asr { arg attackTime=0.01, sustainLevel=1.0, releaseTime=1.0, curve = -4.0;
177 [0, sustainLevel, 0],
178 [attackTime, releaseTime],
184 *circle { arg levels, times, curve = \lin;
185 times = times.asArray.wrapExtend(levels.size);
186 curve = curve.asArray.wrapExtend(levels.size);
187 ^this.new(levels, times.drop(-1), curve.drop(-1)).circle(times.last, curve.last);
191 if(releaseNode.isNil) { ^0.0 };
192 ^times.copyRange(releaseNode, times.size - 1).sum
199 asMultichannelSignal { arg length = 400, class = (Signal);
200 ^this.asMultichannelArray.collect { |chan|
201 var duration, signal, ratio;
202 duration = chan[5, 9 ..].sum;
203 ratio = duration / (length - 1);
204 signal = class.new(length);
205 length.do { arg i; signal.add(chan.envAt(i * ratio)) };
210 asSignal { arg length = 400;
211 ^this.asMultichannelSignal(length).unbubble
214 discretize { arg n = 1024;
218 storeArgs { ^[levels, times, curves, releaseNode, loopNode] }
221 ^this.compareObject(that, [\levels, \times, \curves, \releaseNode, \loopNode, \offset])
225 ^this.instVarHash([\levels, \times, \curves, \releaseNode, \loopNode, \offset])
229 var data = this.asMultichannelArray;
230 time = (time - offset).max(0);
231 ^if(time.isSequenceableCollection) {
234 time.collect { |t| data.envAt(t) }
237 data.collect { |channel| channel.envAt(t) }
244 data.collect { |channel| channel.envAt(time) }
249 embedInStream { arg inval;
250 var startTime = thisThread.endBeat ? thisThread.beats;
251 thisThread.endBeat = this.duration + startTime;
253 inval = yield(this.at(thisThread.beats - startTime));
258 ^Routine({ arg inval; this.embedInStream(inval) })
262 var c = if(curves.isSequenceableCollection.not) { curves } { Pseq(curves, inf) };
263 ^Pseg(Pseq(levels), Pseq(times ++ [1.0]), c) // last time is a dummy
266 // blend two envelopes
267 blend { arg argAnotherEnv, argBlendFrac=0.5;
269 levels.blend(argAnotherEnv.levels, argBlendFrac),
270 times.blend(argAnotherEnv.times, argBlendFrac),
271 curves.blend(argAnotherEnv.curves, argBlendFrac),
277 // delay the onset of the envelope
279 ^Env([levels[0]] ++ levels,
281 if (curves.isArray) { [\lin] ++ curves } { curves },
282 if(releaseNode.notNil) { releaseNode = releaseNode + 1 },
283 if(loopNode.notNil) { loopNode = loopNode + 1 }
287 // connect releaseNode (or end) to first node of envelope
288 circle { arg timeFromLastToFirst = 0.0, curve = \lin;
290 if(UGen.buildSynthDef.isNil) { ^this };
291 first0Then1 = Latch.kr(1.0, Impulse.kr(0.0));
292 if(releaseNode.isNil) {
293 levels = [0.0] ++ levels ++ 0.0;
294 curves = [curve] ++ curves.asArray.wrapExtend(times.size) ++ \lin;
295 times = [first0Then1 * timeFromLastToFirst] ++ times ++ inf;
296 releaseNode = levels.size - 2;
298 levels = [0.0] ++ levels;
299 curves = [curve] ++ curves.asArray.wrapExtend(times.size);
300 times = [first0Then1 * timeFromLastToFirst] ++ times;
301 releaseNode = releaseNode + 1;
306 test { arg releaseTime = 3.0;
307 var s = Server.default;
308 if(s.serverRunning.not) { "Server not running.".warn; ^this };
310 var synth = { arg gate=1;
311 SinOsc.ar(800, pi/2, 0.3) * EnvGen.ar(this, gate, doneAction:2)
313 if(this.isSustained) {
314 s.sendBundle(s.latency + releaseTime, [15, synth.nodeID, \gate, 0])
319 *shapeNumber { arg shapeName;
320 ^shapeName.asArray.collect { |name|
322 if(name.isValidUGenInput) { 5 } {
323 shape = shapeNames.at(name);
324 if(shape.isNil) { Error("Env shape not defined.").throw };
330 curveValue { arg curve;
331 ^if(curve.isSequenceableCollection) {
333 if(x.isValidUGenInput) { x } { 0 }
336 if(curve.isValidUGenInput) { curve } { 0 }
341 if (array.isNil) { array = this.prAsArray }
342 ^array.unbubble // keep backward compatibility
345 asMultichannelArray {
346 if (array.isNil) { array = this.prAsArray }
350 // this version is for IEnvGen and has a special format.
351 // don't cache this version for now, but instead return it directly.
352 asArrayForInterpolation {
354 var levelArray = levels.asUGenInput;
355 var timeArray = times.asUGenInput;
356 var curvesArray = curves.asArray.asUGenInput;
358 size = timeArray.size;
359 contents = Array.new((size + 1) * 4);
360 contents.add(offset.asUGenInput ? 0);
361 contents.add(levelArray.at(0));
363 contents.add(timeArray.sum);
364 curvesArray = curves.asArray;
365 times.size.do { arg i;
366 contents.add(timeArray[i]);
367 contents.add(this.class.shapeNumber(curvesArray.wrapAt(i)));
368 contents.add(this.curveValue(curvesArray.wrapAt(i)));
369 contents.add(levelArray[i+1]);
377 var levelArray = levels.asUGenInput;
378 var timeArray = times.asUGenInput;
379 var curvesArray = curves.asArray.asUGenInput;
382 contents = Array.new((size + 1) * 4);
383 contents.add(levelArray.at(0));
385 contents.add(releaseNode.asUGenInput ? -99);
386 contents.add(loopNode.asUGenInput ? -99);
389 contents.add(levelArray.at(i+1));
390 contents.add(timeArray.at(i));
391 contents.add(this.class.shapeNumber(curvesArray.wrapAt(i)));
392 contents.add(this.curveValue(curvesArray.wrapAt(i)));