Stream:all - add 'inval' arg, so it can be used with musical patterns
[supercollider.git] / SCClassLibrary / Common / Streams / Stream.sc
blob15090ba3df9203a99567380e7305969d04d134e6
2 Stream : AbstractFunction {
3         // 'reset' is defined in class Object to do nothing.
4         // reading
6         next { ^this.subclassResponsibility(thisMethod) }
7         iter { ^this }
9         value { arg inval; ^this.next(inval) }
10         valueArray { ^this.next }
12         nextN { arg n, inval;
13                 ^Array.fill(n, { this.next(inval) });
14         }
15         all { arg inval;
16                 // don't do this on infinite streams.
17                 var array;
18                 this.do({|item| array = array.add(item) }, inval);
19                 ^array
20         }
22         // writing
23         put { arg item;
24                 ^this.subclassResponsibility(thisMethod)
25         }
26         putN { arg n, item;
27                 n.do({ this.put(item); });
28         }
29         putAll { arg aCollection;
30                 aCollection.do {|item| this.put(item); };
31         }
33         do { arg function, inval;
34                 var item, i=0;
35                 while {
36                         item = this.next(inval);
37                         item.notNil
38                 }{
39                         function.value(item, i);
40                         i = i + 1;
41                 };
42         }
44         subSample {| offset= 0, skipSize = 0|
45                 ^Routine {
46                         offset.do{ this.next };
47                         loop {
48                                 this.next.yield;
49                                 skipSize.do { this.next }
50                         }
51                 }
52         }
54         generate { arg function, item;
55                 var i=0;
56                 while {
57                         item = this.next(item);
58                         item.notNil
59                 }{
60                         function.value(item, i);
61                         i = i + 1;
62                 };
63         }
65         // combination
66         collect { arg argCollectFunc;
67                 // modify a stream
68                 var nextFunc = { arg inval;
69                         var     nextval = this.next(inval);
70                         if ( nextval.notNil, {
71                                 argCollectFunc.value(nextval, inval)
72                         })
73                 };
74                 var resetFunc = { this.reset };
75                 ^FuncStream.new(nextFunc, resetFunc);
76         }
77         reject { arg function;
78                 // reject elements from a stream
79                 var nextFunc = { arg inval;
80                         var     nextval = this.next(inval);
81                         while {
82                                 nextval.notNil and: { function.value(nextval, inval) }
83                         }{
84                                 nextval = this.next(inval);
85                         };
86                         nextval
87                 };
88                 var resetFunc = { this.reset };
89                 ^FuncStream.new(nextFunc, resetFunc);
90         }
91         select { arg function;
92                 // select elements from a stream
93                 var nextFunc = { arg inval;
94                         var     nextval = this.next(inval);
95                         while {
96                                 nextval.notNil and: { function.value(nextval, inval).not }
97                         }{
98                                 nextval = this.next(inval);
99                         };
100                         nextval
101                 };
102                 var resetFunc = { this.reset };
103                 ^FuncStream.new(nextFunc, resetFunc);
104         }
106         dot { arg function, stream;
107                 // combine item by item with another stream
108                 ^FuncStream.new(
109                         { arg inval;
110                                 var x = this.next(inval);
111                                 var y = stream.next(inval);
112                                 if ( x.notNil and: { y.notNil }, {
113                                         function.value(x, y, inval)
114                                 });
115                         },
116                         { this.reset; stream.reset; }
117                 );
118         }
120         interlace { arg function, stream;
121                 // interlace with another stream
122                 var nextx = this.next;
123                 var nexty = stream.next;
124                 ^FuncStream.new({ |inval|
125                         var val;
126                         if ( nextx.isNil ) {
127                                 if ( nexty.isNil) {nil}{ val = nexty; nexty = stream.next(inval); val };
128                         }{
129                                 if ( nexty.isNil or: { function.value(nextx, nexty, inval) },
130                                         { val = nextx; nextx = this.next(inval); val },
131                                         { val = nexty; nexty = stream.next(inval); val }
132                                 );
133                         };
134                 },
135                 {
136                         this.reset; stream.reset;
137                         nextx = this.next;
138                         nexty = stream.next;
139                 });
140         }
142         ++ { arg stream; ^this.appendStream(stream) }
144         appendStream { arg stream;
145                 var reset = false;
146                 ^Routine({ arg inval;
147                         if (reset) {
148                                 this.reset;
149                                 stream.reset;
150                         };
151                         reset = true;
152                         inval = this.embedInStream(inval);
153                         stream.embedInStream(inval);
154                 });
155         }
157         collate { arg stream;
158                 // ascending order merge of two streams
159                 ^this.interlace({|x y| x < y }, stream);
160         }
162         <> { arg obj; ^Pchain(this, obj).asStream }
165         // function composition
166         composeUnaryOp { arg argSelector;
167                 ^UnaryOpStream.new(argSelector, this)
168         }
169         composeBinaryOp { arg argSelector, argStream, adverb;
170                 if(adverb.isNil) {
171                         ^BinaryOpStream.new(argSelector, this, argStream.asStream)
172                 } {
173                         if (adverb == 'x') {
174                                 ^BinaryOpXStream.new(argSelector, this, argStream.asStream);
175                         };
176                 };
177                 ^nil
178         }
179         reverseComposeBinaryOp { arg argSelector, argStream, adverb;
180                 if(adverb.isNil) {
181                         ^BinaryOpStream.new(argSelector, argStream.asStream, this)
182                 } {
183                         if (adverb == 'x') {
184                                 ^BinaryOpXStream.new(argSelector, argStream.asStream, this);
185                         };
186                 };
187                 ^nil
188         }
189         composeNAryOp { arg argSelector, anArgList;
190                 ^NAryOpStream.new(argSelector, this, anArgList.collect(_.asStream));
191         }
193         embedInStream { arg inval;
194                 var outval;
195                 while {
196                         outval = this.value(inval);
197                         outval.notNil
198                 }{
199                         inval = outval.yield;
200                 };
201                 ^inval
202         }
204         asEventStreamPlayer { arg protoEvent;
205                 ^EventStreamPlayer(this, protoEvent);
206         }
208         play { arg clock, quant;
209                 clock = clock ? TempoClock.default;
210                 clock.play(this, quant.asQuant);
211         }
213         trace { arg key, printStream, prefix="";
214                 ^Ptrace(this, key, printStream, prefix).asStream
215         }
217 //      constrain { arg sum, tolerance=0.001;
218 //              ^Pconst(sum, tolerance).asStream
219 //      }
221         repeat { arg repeats = inf;
222                 ^r { arg inval;
223                         repeats.value(inval).do {
224                                 inval = this.reset.embedInStream(inval)
225                         }
226                 }
227         }
232 OneShotStream : Stream {
233         var value, once = true;
234         *new { arg value;
235                 ^super.newCopyArgs(value)
236         }
237         next { ^if (once) {once = false; value} }
238         reset { once = true }
239         storeArgs { ^[value] }
242 EmbedOnce : Stream  {
243         var <stream;
244         *new { arg stream;
245                 ^super.newCopyArgs(stream.asStream)
246         }
247         next { arg inval;
248                 var val = stream.next(inval);
249                 if(val.isNil) { stream = nil }; // embed once, then release memory
250                 ^val
251         }
252         storeArgs { ^[stream] }
255 FuncStream : Stream {
256         var <>nextFunc; // Func is evaluated for each next state
257         var <>resetFunc; // Func is evaluated on reset
258         var     <>envir;
259         *new { |nextFunc, resetFunc|
260                 ^super.new.nextFunc_(nextFunc).resetFunc_(resetFunc).envir_(currentEnvironment)
261         }
262         next { arg inval;
263                 ^envir.use({ nextFunc.value(inval) })
264         }
265         reset {
266                 ^envir.use({ resetFunc.value })
267         }
268         storeArgs { ^[nextFunc, resetFunc] }
271 StreamClutch : Stream {
272         var <>stream, <>connected, value, >reset=true;
274         *new { arg pattern, connected = true;
275                 ^super.newCopyArgs(pattern.asStream, connected)
276         }
278         next { arg inval;
279                 if(reset) {
280                         reset = false;
281                         value = stream.next(inval)
282                 };
283                 if(connected.value(inval)) {
284                         value = stream.next(inval);
285                 };
286                 ^value
287         }
288         lastValue { ^value }
290         reset {
291                 stream.reset;
292                 reset = true
293         }
294         step { arg inval;
295                 value = stream.next(inval ? Event.default)
296         }
300 CleanupStream : Stream {
301         var <stream, <>cleanup;
303         *new { arg stream, cleanup;
304                 ^super.newCopyArgs(stream, cleanup)
305         }
306         next { arg inval;
307                 var outval = stream.next(inval);
308                 if (outval.isNil) {
309                         cleanup.value(this, inval);
310                         cleanup = nil;
311                 }
312                 ^outval
313         }
314         reset {
315                 stream.reset
316         }
319 // PauseStream is a stream wrapper that can be started and stopped.
321 PauseStream : Stream
323         var <stream, <originalStream, <clock, <nextBeat, <>streamHasEnded=false;
324         var isWaiting = false, era=0;
326         *new { arg argStream, clock;
327                 ^super.newCopyArgs(nil, argStream, clock ? TempoClock.default)
328         }
330         isPlaying { ^stream.notNil }
332         play { arg argClock, doReset = (false), quant;
333                 if (stream.notNil, { "already playing".postln; ^this });
334                 if (doReset, { this.reset });
335                 clock = argClock ? clock ? TempoClock.default;
336                 streamHasEnded = false;
337                 stream = originalStream;
338                 isWaiting = true;       // make sure that accidental play/stop/play sequences
339                                                 // don't cause memory leaks
340                 era = CmdPeriod.era;
341                 clock.play({
342                         if(isWaiting and: { nextBeat.isNil }) {
343                                 clock.sched(0, this);
344                                 isWaiting = false;
345                                 this.changed(\playing)
346                         };
347                         nil
348                 }, quant.asQuant);
349                 this.changed(\userPlayed);
350                 ^this
351         }
352         reset { originalStream.reset }
353         stop {
354                 this.prStop;
355                 this.changed(\userStopped);
356         }
357         prStop {
358                 stream = nil;
359                 isWaiting = false;
360         }
361         removedFromScheduler {
362                 nextBeat = nil;
363                 this.prStop;
364                 this.changed(\stopped);
365         }
366         streamError { this.removedFromScheduler; streamHasEnded = true;  }
368         wasStopped {
369                 ^streamHasEnded.not and: { stream.isNil } // stopped by clock or stop-message
370                 or: { CmdPeriod.era != era } // stopped by cmd-period, after stream has ended
371         }
372         canPause { ^this.streamHasEnded.not }
374         pause {
375                 this.stop;
376         }
377         resume { arg argClock, quant;
378                 ^this.play(clock ? argClock, false, quant)
379         }
381         refresh {
382                 stream = originalStream
383         }
385         start { arg argClock, quant;
386                 ^this.play(argClock, true, quant)
387         }
389         stream_ { arg argStream;
390                 originalStream = argStream;
391                 if (stream.notNil, { stream = argStream; streamHasEnded = argStream.isNil; });
392         }
394         next { arg inval;
395                 var nextTime = stream.next(inval);
396                 if (nextTime.isNil) {
397                         streamHasEnded = stream.notNil;
398                         this.removedFromScheduler;
399                 } {
400                         nextBeat = inval + nextTime
401                 };      // inval is current logical beat
402                 ^nextTime
403         }
404         awake { arg beats, seconds, inClock;
405                 stream.beats = beats;
406                 ^this.next(beats)
407         }
410 // Task is a PauseStream for wrapping a Routine
412 Task : PauseStream {
413         *new { arg func, clock;
414                 ^super.new(Routine(func), clock)
415         }
416         storeArgs { ^originalStream.storeArgs
417                 ++ if(clock != TempoClock.default) { clock }
418         }
421 ////////////////////////////////////////////////////////////////////////
424 EventStreamPlayer : PauseStream {
425         var <>event, <>muteCount = 0, <>cleanup, <>routine;
427         *new { arg stream, event;
428                 ^super.new(stream).event_(event ? Event.default).init;
429         }
431         init {
432                 cleanup = EventStreamCleanup.new;
433                 routine = Routine{ | inTime | loop { inTime = this.prNext(inTime).yield } };
434         }
436         // freeNodes is passed as false from
437         //TempoClock:cmdPeriod
438         removedFromScheduler { | freeNodes = true |
439                 nextBeat = nil;
440                 cleanup.terminate(freeNodes);
441                 this.prStop;
442                 this.changed(\stopped);
443         }
444         prStop {
445                 stream = nextBeat = nil;
446                 isWaiting = false;
447          }
449         stop {
450                 cleanup.terminate;
451                 this.prStop;
452                 this.changed(\userStopped);
453         }
455         mute { muteCount = muteCount + 1; }
456         unmute { muteCount = muteCount - 1; }
457         canPause { ^this.streamHasEnded.not and: { cleanup.functions.isEmpty } }
459         next { | inTime | ^routine.next(inTime) }
461         prNext { arg inTime;
462                 var nextTime;
463                 var outEvent = stream.next(event.copy);
464                 if (outEvent.isNil) {
465                         streamHasEnded = stream.notNil;
466                         cleanup.clear;
467                         this.removedFromScheduler;
468                         ^nil
469                 }{
470                         nextTime = outEvent.playAndDelta(cleanup, muteCount > 0);
471                         if (nextTime.isNil) { this.removedFromScheduler; ^nil };
472                         nextBeat = inTime + nextTime;   // inval is current logical beat
473                         ^nextTime
474                 };
475         }
477         asEventStreamPlayer { ^this }
479         play { arg argClock, doReset = (false), quant;
480                 if (stream.notNil, { "already playing".postln; ^this });
481                 if (doReset, { this.reset });
482                 clock = argClock ? clock ? TempoClock.default;
483                 streamHasEnded = false;
484                 stream = originalStream;
485                 isWaiting = true;       // make sure that accidental play/stop/play sequences
486                                                 // don't cause memory leaks
487                 era = CmdPeriod.era;
488                 quant = quant.asQuant;
489                 event = event.synchWithQuant(quant);
491                 clock.play({
492                         if(isWaiting and: { nextBeat.isNil }) {
493                                 clock.sched(0, this );
494                                 isWaiting = false;
495                                 this.changed(\playing)
496                         };
497                         nil
498                 }, quant);
499                 this.changed(\userPlayed);
500                 ^this
501         }