Thursday, June 24, 2010

Streams

There's a very useful helpfile set for Streams Patterns and Events, which is available by looking for the helpfile for Streams.

We know what a pattern is, after looking at Pseq and he other Pthings. But what is a stream? "A stream represents a lazy sequence of values," says the helpfile "Understanding Streams, Patterns and Events - Part 1," which you should go read. Streams respond to the message next. Here's an example:

(
 var stream;
 
 stream = Pseq([1, 2, 3], 1).asStream;
 
 3.do({
  stream.next.postln;
 })
)

Any kind of pattern can be converted to a stream by sending it the message asStream. This can be more useful than you might think. For example, if you have two threads stepping through the same data:

(

 var stream, task1, task2;
 
 stream = Pseq(["Mary", "had", "a", "little", "lamb", "whose", "fleece"], 1).asStream;
 
 task1 = Task({
 
  inf.do({
  
   "task1: ".post;
   stream.next.postln;
   1.wait;
  });
 });
 
 task2 = Task({
 
  inf.do({
  
   "task2: ".post;
   stream.next.postln;
   1.51.wait;
  });
 });
 
 task1.play;
 task2.play
)

The two tasks can strep through a shared list. However, note that the stream starts returning nil when it runs out of other data to return. You have to test for that:

(

 var stream, task;
 
 stream = Pseq(["Mary", "had", "a", "little", "lamb", "whose", "fleece"], 1).asStream;
 
 task = { |id, wait|
  var next;
  Task({
 
   next = stream.next;
   
   {next.notNil}.while({
  
    ("task" ++ id ++ ": " ++ next). postln;
    wait.wait;
    next = stream.next;
   });
  });
 };
 
 task.value(1, 1).play;
 task.value(2, 1.51).play
)

You can also do math on streams:

(
 var stream, squared;
 
 stream = Pseq([1, 2, 3], 1).asStream;
 squared = stream.squared;
 3.do({
  squared.next.postln;
 })
)

And you can do binary math operations on two streams:

(
 var stream1, stream2, sum;

 stream1 = Pseq([1, 2, 3], 1).asStream;
 stream2 = Pseq([4, 5, 6], 1).asStream;
 sum = stream1 + stream2;

 3.do({
  sum.next.postln;
 })
)

You can use streams with Pbinds, but you have to wrap them in Prout or Pfunc:

(
 var notes, pbind;
 
 notes = Pseq([ 1/1, 3/2, 4/3, 9/8, 16/9, 5/4, 8/5 ].pyramid(1), 2).asStream;
 notes = notes * 440;
 
 pbind = { |dur = 0.5|
 
  Pbind(
   \dur, dur,
   \freq, Pfunc({notes.next})
  )
 };
 
 Ppar([pbind.value(0.5), pbind.value(0.751)]).play;
)

Note that when notes.next returns nil, the Pbind quits playing.

Summary

  • Streams can be create from patterns. The respond to the message next.
  • When streams run out of values, they start to return nil.
  • Multiple tasks can access the same stream.
  • You can do math on streams.
  • Streams can be used in Pbinds, provided a function or a routine calls next

No comments: