// Streams

// streams are objects which respond to the next message:

7.next
"anton".next
[1,2,3,4].next
SinOsc.next

// -> everything in SC3 could be used as a stream !

// for most of the classes next and value give the same results:

3.next
3.value

\test.next
\test.value

(0 .. 10).next
(0 .. 10).value

// with the Pattern system one can build more useful streams:

// this is a sequential Pattern ...
p = Pseq([20, 30, 10, 45, 2, "Hello", 3.4], 1);

// ... converted to a Stream:
a = p.asStream;

// ask the Stream a for the next value several times:
a.next
a.next
a.next

// derive another Stream from the same Pattern p
b = p.asStream
b.next

// the Streams a and b are independant:
a.reset; b.reset;
a.next

b.next
a.next
a.next
b.next

// streams can be composed by operations on resp. combinations of other streams:
c = a + b; 
a.reset; 
b.reset; b.next;

c.next
c.next
c.next
c.reset

a.reset
c.next

d = a.sqrt;
d.next

// a Pattern is a like model or a template for a Stream
// a Stream is an Object the can produce a (an infinite) sequence of value
// the message next is a request for a new value of a stream
// the message reset starts the sequence from the beginning

// list pattern:

Pseq, Pser
Prand, Pxrand, Pwrand
Pshuf
Pslide, Ptuple
Pwalk, Pfsm, 

p = Prand([30, 20, 54, 12, 6, 8], 8).asStream;
p.next

p = Pxrand([30, 20, 54, 12, 6, 8], 8).asStream;

p = Pwrand([30, 20, 54, 12, 6], [0.5, 0.1, 0.2, 0.15, 0.05  ], 50).asStream;

// value pattern:

PFunc.new(nextFunc, resetFunc) 		// compare to Array.fill(n, func)
Pseries.new(start, step, length)		// compare to Array.series(n, start, step)
Pgeom.new(start, grow, length)		// compare to Array.geom(n, start, grow)
Pwhite.new(lo, hi, step, length)		// compare to Array.rand(n, min, max)
Pbrown.new(lo, hi, step, length)		// brownian motion

// -----------------
(
SynthDef("chick", {arg freq, dur;
	var res1, res2, sig;
	res1 = XLine.ar(8*freq, freq, dur * 0.7);
	res2 = XLine.ar(3*freq, freq, dur * 0.5);
	sig =  RLPF.ar(Saw.ar([freq, freq/4]),[res1, res2] , 0.09, 
		EnvGen.kr(Env.perc(0.01, dur, 0.5), doneAction: 2));
	Out.ar(0, sig)
	}).send(s);

SynthDef("rev", {
	var in, rev;
	in = In.ar(0, 2);	
	rev = Mix(in);	
	6.do({ rev = LPF.ar(AllpassN.ar(rev, 1.0, [0.05.rand, 0.05.rand], 2.0), 7000)});
	ReplaceOut.ar(0, (in * 0.6) + (rev * 0.3))
	}).send(s);
)	

( // Chick Corea meets Iannis Xenakis
var pnote, rhythms, weights, pdur, tempo, group, rev;

pnote = Pbrown(48, 92, 7, inf).asStream; 		// Randomwalk zwischen 36 und 86, maximale Schrittweite 7
tempo = 140;								// MM 
rhythms = [1/2, 1/4, 1/8, 1/16, 1/12, 1/6]; 	// verschiedene Dauern
weights = [2, 2, 4, 20, 12, 3];				// unterschiedlich gewichtet
pdur = Pwrand( rhythms * 60 * 4/tempo, weights / weights.sum, inf).asStream; 

group = Group(s);
rev = Synth.after(group, "rev");

t = Task({
	var dur, freq;
	loop({
		freq = pnote.next.midicps;		// neuen Pitch-Wert lesen und in Frequenz konvertieren
		dur = pdur.next;				// neuen Dauernwert lesen
		Synth("chick", [\freq, freq, \dur, dur], group);
		dur.wait;
		})
	})	
)

t.start;
t.stop

d = t.copy;
d.start; t.start; 	// -> 2 tasks reading from the same stream, thus competing!

t.stop; 
d.stop;

// -----------------

( // Chick Corea meets Iannis Xenakis - version based on a musical scale
var scale, pnote, rhythms, weights, pdur, tempo, group, rev;

scale = [0, 2, 3, 5, 7, 9, 10]; 				// dorian mode
scale = scale ++ (scale+12) ++ (scale+24) ++ (scale+36);
pnote = Pindex(scale, Pbrown(0, scale.size - 1, 6, inf), inf).asStream + 48; 
tempo = 140;								// MM 
rhythms = [1/2, 1/4, 1/8, 1/16, 1/12, 1/6]; 	// verschiedene Dauern
weights = [2, 2, 4, 20, 12, 3];				// unterschiedlich gewichtet
pdur = Pwrand( rhythms * 60 * 4/tempo, weights / weights.sum, inf).asStream; 

group = Group(s);
rev = Synth.after(group, "rev");

t = Task({
	var dur, freq;
	loop({
		freq = pnote.next.midicps;		// neuen Pitch-Wert lesen und in Frequenz konvertieren
		dur = pdur.next;				// neuen Dauernwert lesen
		Synth("chick", [\freq, freq, \dur, dur], group);
		dur.wait;
		})
	}).start;
)		

// -----------------

// Demand rate UGens are Pattern-like UGens for use in synths
// see Demand.html

( // Kraftwerk ?, Pink Floyd ??, Tangerine Dream ???
SynthDef("blob", {
	var trig, seq, f1, f2, w, sig, amp;
	trig = Impulse.kr(11);	
	seq = Dxrand([0, 2, 5, 7, 9, 12] + 36 , inf);	f1 = Demand.kr(trig, 0, seq ).midicps; 
	w = Pulse.ar([f1, f1*1.01]);
	sig = Decay2.ar(K2A.ar(trig), 0.05, 0.2, w );	amp = Amplitude.kr(sig/10);			f2 = SinOsc.kr(0.1, 3/2 * pi, 800, 1000);	sig = RLPF.ar(sig, f2*(1+amp), 0.2, 0.01);
	Out.ar(0, sig);		 
	}).play(s);
)

// -------------------------

// Pattern and Events

/*
An Event is derived from IdentityDictionary, thus a set of assignments of symbols to values.
These symbols are representations of musical parameters and structures on the server.
See also [Streams-Patterns-Events4] and the following.

The class Pbind provides a bridge between value patterns and event patterns. 
It binds symbols in each event to values obtained from a pattern. 
Pbind takes arguments in pairs, the first of a pair being a Symbol and the second being a value Pattern. 
Any object can act as a Pattern, so constants can be used as values.

Pbind.new.play returns an EventStreamPlayer which stops playing when Pdind return nil.
The Pbind stream returns nil whenever the first one of its streams ends or if nil is passed in.

*/

Pbind.new.play

Pbind( \dur, 0.25 ).play

(
Pbind(
	\dur, 0.08,
	\legato, 2.5,
	\freq, Pwhite(80, 90, inf).midicps
).play
)

(
Pbind(
	\dur, 0.125,
	\legato, 0.2,
	\midinote, Pseq([0,2,4,5,7,9,11,12] + 60, inf)
).play
)

// The pitch model
/*
Event.default implements a multi level pitch model which allows composition using modal
scale degrees, equal division note values, midi note values, or frequencies in Hertz. 
These different ways of specifying the pitch can all be used interchangably.

\degree gets converted into \note (using \mtranspose, \scale and \stepPerOctave)
\note gets converted into \midinote (using \gtranspose, \root, \stepsPerOctave and \octave)
\midinote gets converted into \frequency (using \ctranspose and \harmonic)

See also [Streams-Patterns-Events5] and Event.html

*/

(
Pbind(
	\degree, Pseq([0,2,4,7], inf),  // Skalenstufen in (default) Dur-Skala
	\dur, 0.125, 
	\octave, 4
).play
)

(
Pbind(
	\scale, [0,2,3,5,7,8,10],
	\degree, Pseq([0,2,4,7], inf),  // Skalenstufen in Moll-Skala
	\dur, 0.125
).play
)

(
Pbind(
	\scale, Pseq( [ Pn([0,2,3,5,7,8,10], 8), Pn([0,2,4,5,7,9,11], 8) ], inf),
	\degree, Pseq([0,2,4,7], inf), 
	\dur, 0.125
).play
)

(
Pbind(
 	// Skalenstufen in (default) Dur-Skala
	\degree, Pseq([ Pxrand([0,2,4,7], 4), Pxrand([0,2,5,-2], 4), Pxrand([0,3,6, 1], 4), Pxrand([-1, 1, 3, 4], 4)], inf),	\dur, 0.125, 
	\octave, [4,5]
).play
)

// chords are arrays of pitches (degrees, midinotes, frequencies etc.)
(
Pbind(
	\dur, 0.8,
	\legato, 1.0,
	\instrument, \default,
	\scale, Pseq([Pseq([[0,2,4,5,7,9,11]], 5), Pseq([[0,2,3,5,7,8,10]], 5)], inf),
	\degree, Pseq([[-7, 2,4,7 ], [ -4, 3,5,7], [-3, 1,4,7], [-10, 1,4,6], [-7, 2,4,7 ] ], 2)
).play;
)

(
Pbind(
	\dur, 0.15,
	\degree, Pseq([ 
		Pshuf( [-7,-3,0,2,4,7], 4) + [0,2,4],
		Pseq(  [0,1,2,3,4,5,6,7] ) + [0,2]
	], inf)
).play
)

// pairs of names can be used to group several parameters
(
Pbind(
	[\degree, \dur], Pseq([
			Pseq([[0,0.1],[2,0.1],[3,0.1],[4,0.1],[5,0.8]],2),
			Ptuple([Pxrand([6,7,8,9],4), 0.4]),
			Ptuple([Pseq([9,8,7,6,5,4,3,2]), 0.2])
		],inf),
	\amp, 0.05, 
	\octave, 5, 
	\mtranspose, 0
).play;
)

// parallel pattern:  Ppar, Ptpar
(
var testSequence;
testSequence = Pseq([60,62,64,65,67,69,71,72], inf); // a sequence as Pattern
Ppar([				// define a parallel pattern
	Pbind(			// "voice 1"
		\dur, 0.4, 	// duration
		\midinote, testSequence			// here a stream copy is generated
	),
	Pbind(			// "voice 2"					
		\dur, 0.3, 	// duration
		\midinote, 12 + testSequence	// here a second stream copy is generated
	)
]).play
)

(
var chord;
chord = Prand([[53, 58, 64],[53, 60, 64],[57,60,65]]);
Paddp(\ctranspose, Prand([-1,0,2,4,5], inf), 
	Ppar([
		Pbind(	// melody part
			\dur, Prand([0.2, 0.4, 0.6], inf), 
			\midinote, Pxrand([71, 72, 74, 76, 77, 79], 10),
			\db, -26,
			\legato, 1.1
		),
		Pbind(	// harmony part
			\pan, 0.4, 
			\dur, Pseq([0.1, 0.5, 0.4, 0.6], 4), 
			\midinote, Pseq([chord,\rest,chord,\rest], 4)
		),
		Pbind(	// bass part
			\pan, -0.4, 
			\dur, 0.4, 
			\midinote, Pseq([38, 45, 38, 36], 4)
		)
	])
).play
)

// -------------------------

// Steve Reich, Piano Phases:

(// the piano pattern in a flat and direct notation (as written in the score):
var p1;
p1 = Pseq([64, 66, 71, 73, 74, 66, 64, 73, 71, 66, 74, 73], inf);
// e f# b c# d f# e c# b f# d c#

p = Pbind( 
	\midinote, p1,
	\dur, 1/8,
	\legato, 0.8
).play
)

p.stop;  // stop the EventStreamPlayer

(// the piano pattern expressed in a more structural way (L.H. + R.H., modal scale related):
var h1, h2, p1;
h1 = Pseq([1, 5, 7] - 1, inf);	
h2 = Pseq([2, 6] - 1, inf);

p1 = Pswitch1([h1, h2], Pseq([0, 1], inf));

p = Pbind( 
	\ctranspose, 2,	// chromatic transpose: D-Major (2 semitones above C-Major)
	\mtranspose, 1,	// modal transpose: 1 scale step higher, i.e. from Ionian on D to Dorian on E
	\degree, p1,		// read the degrees (scale steps) from the pattern p1
	\dur, 1/8,
	\legato, 0.8
).play
)

p.stop; 

(// two players, simplified continuous tempo shifting:
var p1;
p1 = Pseq([64, 66, 71, 73, 74, 66, 64, 73, 71, 66, 74, 73], inf);

Ppar([
	Pbind( \midinote, p1, \dur, 1/8, \legato, 0.8, \pan, -1),
	Pbind( \midinote, p1, \dur, (1/8) * 0.99, \legato, 0.8, \pan, 1),
	]
).play
)

(// two players, the true tempo shifting scheme (but abridged):
var p1, measures, d2;
measures = 4;
p1 = Pseq([64, 66, 71, 73, 74, 66, 64, 73, 71, 66, 74, 73], inf);
d2 = Pseq([ Pseq([1/8], 12*measures), Pseq([1/8 * (12*measures / (12*measures + 1))], 12*measures)], inf);

Ppar([
	Pbind( \midinote, p1, \dur, 1/8, \legato, 0.8, \pan, -1),
	Pbind( \midinote, p1, \dur, d2, \legato, 0.8, \pan, 1),
	]
).play
)

// ------------------------------------------------

// using you own synthdefs and effects with patterns

(
SynthDef(\blah, { arg freq, amp, pan, gate=1; // sustain
	var eg, sig;
	eg = EnvGen.kr(Env.adsr(0.04, 0.1, 0.7, 0.1), gate, doneAction: 2);
	sig = GrayNoise.ar(eg * 60);
	sig = Resonz.ar(sig, freq, 0.0005, 4).distort;
	sig = Pan2.ar(sig, pan, eg * amp);
	Out.ar(0, sig);
}).store;

SynthDef(\simplerev, { arg decay=3;
	var sig;
	sig = In.ar(0, 2);
	6.do({ sig = AllpassN.ar(sig, 0.04, [0.01.rand + 0.02, 0.01.rand + 0.02], decay); });
	Out.ar(0, sig);
}).store;
)	

(
// Lindenmayer systems take a trip south of the border
var degdict, durdict;

// the scale degree rewrite dictionary:
degdict = IdentityDictionary[
	0 -> [0, 1, 2],
	1 -> [0, 2],
	2 -> [3, 2],
	3 -> [0, 3, \, 4],
	4 -> [5, 4, 2],
	5 -> [8, 7, 6, 7, 3],
	\ -> [\, 0, \]
];

// the duration rewrite dictionary:
durdict = IdentityDictionary[
	0.5 -> [0.25, 0.5, 0.25],
	1.0 -> [0.5, 0.125, 0.125, 0.25],
	0.25 -> [1.0, 0.5],
	0.125 -> [0.25, 0.25, 0.25]
];

p = Pbind(
		// do six levels of rewrite from a single seed value.
		\degree, Prewrite( Pseq([0]), degdict, 6) + 0,  
		\dur, Prewrite( Pseq([0.5]), durdict, 6),
		\pan, Pbrown(-0.7, 0.7, 0.2, inf),
		\octave, 6,
		\amp, 0.2,
		\instrument, \blah
	);
	
e = Pfx(p, \simplerev, \decay, 2);		
e.play;	
)

Fred Lehrdahl & Jackendorff
Chomsky
Aristid Lindenmayer