scide: LookupDialog - redo lookup on classes after partial lookup
[supercollider.git] / SCClassLibrary / Common / Audio / Env.sc
blobbfe08a8864d1b9b355466fa2ea9f7cb4e59fc8ab
1 Env {
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)
6         
7         var <array;             // cache for osc-conform data
8         classvar <shapeNames;
10         
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)
14         }
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))
19         }
21         *initClass {
22                 shapeNames = IdentityDictionary[
23                         \step -> 0,
24                         \lin -> 1,
25                         \linear -> 1,
26                         \exp -> 2,
27                         \exponential -> 2,
28                         \sin -> 3,
29                         \sine -> 3,
30                         \wel -> 4,
31                         \welch -> 4,
32                         \sqr -> 6,
33                         \squared -> 6,
34                         \cub -> 7,
35                         \cubed -> 7
36                 ];
37         }
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)
41         }
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)
45         }
47         levels_ { arg z;
48                 levels = z;
49                 array = nil;
50         }
51         times_ { arg z;
52                 times = z;
53                 array = nil;
54         }
55         curves_ { arg z;
56                 curves = z;
57                 array = nil;
58         }
59         releaseNode_ { arg z;
60                 releaseNode = z;
61                 array = nil;
62         }
63         loopNode_ { arg z;
64                 loopNode = z;
65                 array = nil;
66         }
67         offset_ { arg z;
68                 offset = z;
69                 array = nil;
70         }
72         duration_ { arg dur;
73                 times = times.normalizeSum * dur
74         }
76         duration {
77                 ^times.sum
78         }
80         range { arg lo = 0.0, hi = 1.0;
81                 ^this.copy.levels_(levels.linlin(levels.minItem, levels.maxItem, lo, hi))
82         }
84         exprange { arg lo = 0.01, hi = 1.0;
85                 ^this.copy.levels_(levels.linexp(levels.minItem, levels.maxItem, lo, hi))
86         }
88         // methods to make some typical shapes :
90         // fixed duration envelopes
92         *triangle { arg dur=1.0, level=1.0;
93                 dur = dur * 0.5;
94                 ^this.new(
95                         [0, level, 0],
96                         [dur, dur]
97                 )
98         }
100         *sine { arg dur=1.0, level=1.0;
101                 dur = dur * 0.5;
102                 ^this.new(
103                         [0, level, 0],
104                         [dur, dur],
105                         \sine
106                 )
107         }
109         *perc { arg attackTime=0.01, releaseTime=1.0, level=1.0, curve = -4.0;
110                 ^this.new(
111                         [0, level, 0],
112                         [attackTime, releaseTime],
113                         curve
114                 )
115         }
117         *linen { arg attackTime=0.01, sustainTime=1.0, releaseTime=1.0, level=1.0, curve = \lin;
118                 ^this.new(
119                         [0, level, level, 0],
120                         [attackTime, sustainTime, releaseTime],
121                         curve
122                 )
123         }
125         *xyc { arg xyc;
126                 var times, levels, curves, offset, order;
127                 #times, levels, curves = xyc.flop;
128                 if(times.containsSeqColl.not) { // sort triplets, if possible.
129                         order = times.order;
130                         times = times[order];
131                         levels = levels[order];
132                         curves = curves[order];
133                 };
134                 offset = times[0];
135                 times = times.differentiate.drop(1);
136                 curves.asArray.drop(-1);
137                 ^this.new(levels, times, curves, offset: offset);
138         }
140         *pairs { arg pairs, curve;
141                 if(curve.isNil) { ^this.xyc(pairs) };
142                 ^this.xyc(pairs +++ curve);
143         }
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)
151         }
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;
156                 ^this.new(
157                         [0, 0, peakLevel, peakLevel * sustainLevel, 0] + bias,
158                         [delayTime, attackTime, decayTime, releaseTime],
159                         curve,
160                         3
161                 )
162         }
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;
167                 ^this.new(
168                         [0, peakLevel, peakLevel * sustainLevel, 0] + bias,
169                         [attackTime, decayTime, releaseTime],
170                         curve,
171                         2
172                 )
173         }
175         *asr { arg attackTime=0.01, sustainLevel=1.0, releaseTime=1.0, curve = -4.0;
176                 ^this.new(
177                         [0, sustainLevel, 0],
178                         [attackTime, releaseTime],
179                         curve,
180                         1
181                 )
182         }
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);
188         }
190         releaseTime {
191                 if(releaseNode.isNil) { ^0.0 };
192                 ^times.copyRange(releaseNode, times.size - 1).sum
193         }
195         isSustained {
196                 ^releaseNode.notNil
197         }
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)) };
206                         signal
207                 }
208         }
210         asSignal { arg length = 400;
211                 ^this.asMultichannelSignal(length).unbubble
212         }
214         discretize { arg n = 1024;
215                 ^this.asSignal(n);
216         }
218         storeArgs { ^[levels, times, curves, releaseNode, loopNode] }
220         == { arg that;
221                 ^this.compareObject(that, [\levels, \times, \curves, \releaseNode, \loopNode, \offset])
222         }
224         hash {
225                 ^this.instVarHash([\levels, \times, \curves, \releaseNode, \loopNode, \offset])
226         }
228         at { arg time;
229                 var data = this.asMultichannelArray;
230                 time = (time - offset).max(0);
231                 ^if(time.isSequenceableCollection) {
232                         if(data.size <= 1) {
233                                 data = data[0];
234                                 time.collect { |t| data.envAt(t) }
235                         } {
236                                 time.collect { |t|
237                                         data.collect { |channel| channel.envAt(t) }
238                                 }
239                         }
240                 } {
241                         if(data.size <= 1) {
242                                 data[0].envAt(time)
243                         } {
244                                 data.collect { |channel| channel.envAt(time) }
245                         }
246                 }
247         }
249         embedInStream { arg inval;
250                 var startTime = thisThread.endBeat ? thisThread.beats;
251                 thisThread.endBeat = this.duration + startTime;
252                 loop {
253                         inval = yield(this.at(thisThread.beats - startTime));
254                 }
255         }
257         asStream {
258                 ^Routine({ arg inval; this.embedInStream(inval) })
259         }
261         asPseg {
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
264         }
266         // blend two envelopes
267         blend { arg argAnotherEnv, argBlendFrac=0.5;
268                 ^this.class.new(
269                         levels.blend(argAnotherEnv.levels, argBlendFrac),
270                         times.blend(argAnotherEnv.times, argBlendFrac),
271                         curves.blend(argAnotherEnv.curves, argBlendFrac),
272                         releaseNode,
273                         loopNode
274                 )
275         }
277         // delay the onset of the envelope
278         delay { arg delay;
279                 ^Env([levels[0]] ++ levels,
280                         [delay] ++ times,
281                         if (curves.isArray) { [\lin] ++ curves } { curves },
282                         if(releaseNode.notNil) { releaseNode = releaseNode + 1 },
283                         if(loopNode.notNil) { loopNode = loopNode + 1 }
284                 )
285         }
287         // connect releaseNode (or end) to first node of envelope
288         circle { arg timeFromLastToFirst = 0.0, curve = \lin;
289                 var first0Then1;
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;
297                 } {
298                         levels = [0.0] ++ levels;
299                         curves = [curve] ++ curves.asArray.wrapExtend(times.size);
300                         times  = [first0Then1 * timeFromLastToFirst] ++ times;
301                         releaseNode = releaseNode + 1;
302                 };
303                 loopNode = 0;
304         }
306         test { arg releaseTime = 3.0;
307                 var s = Server.default;
308                 if(s.serverRunning.not) { "Server not running.".warn; ^this };
309                 fork {
310                         var synth = { arg gate=1;
311                                 SinOsc.ar(800, pi/2, 0.3) * EnvGen.ar(this, gate, doneAction:2)
312                         }.play;
313                         if(this.isSustained) {
314                                 s.sendBundle(s.latency + releaseTime, [15, synth.nodeID, \gate, 0])
315                         };
316                 };
317         }
319         *shapeNumber { arg shapeName;
320                 ^shapeName.asArray.collect { |name|
321                         var shape;
322                         if(name.isValidUGenInput) { 5 } {
323                                 shape = shapeNames.at(name);
324                                 if(shape.isNil) { Error("Env shape not defined.").throw };
325                                 shape
326                         }
327                 }.unbubble
328         }
330         curveValue { arg curve;
331                 ^if(curve.isSequenceableCollection) {
332                         curve.collect { |x|
333                                 if(x.isValidUGenInput) { x } { 0 }
334                         }
335                 } {
336                         if(curve.isValidUGenInput) { curve } { 0 }
337                 }
338         }
340         asArray {
341                 if (array.isNil) { array = this.prAsArray }
342                 ^array.unbubble // keep backward compatibility
343         }
345         asMultichannelArray {
346                 if (array.isNil) { array = this.prAsArray }
347                 ^array
348         }
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 {
353                 var contents, size;
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));
362                 contents.add(size);
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]);
370                 };
372                 ^contents.flop
373         }
375         prAsArray {
376                 var contents, size;
377                 var levelArray = levels.asUGenInput;
378                 var timeArray = times.asUGenInput;
379                 var curvesArray = curves.asArray.asUGenInput;
381                 size = times.size;
382                 contents = Array.new((size + 1) * 4);
383                 contents.add(levelArray.at(0));
384                 contents.add(size);
385                 contents.add(releaseNode.asUGenInput ? -99);
386                 contents.add(loopNode.asUGenInput ? -99);
387                 
388                 size.do { arg i;
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)));
393                 };
395                 ^contents.flop;
396         }
397         
398