class:: Pstep
summary:: timed, sample-and-hold embedding of values
related:: Classes/Pseg
categories:: Streams-Patterns-Events>Patterns>Time

description::

Pstep is a "sample and hold" pattern: The value returned by teletype::next:: is held at each value in teletype::levels:: for the corresponding duration in teletype::durs::. This cycling is similar to link::Classes/Ptuple::, in that the current repeat ends when eiher pattern returns nil.

Pstep measures elapsed time using the thread's logical time. That is, it assumes it will be evaluated only exactly when needed, not before.

See link::Classes/Pseg:: for a pattern whose value changes like an link::Classes/Env::.

ClassMethods::

method::new
Create an instance of Pstep.

argument::levels
A number, collection, or link::Classes/Pattern:: that returns the levels.

argument::durs
A number, collection, or link::Classes/Pattern:: that returns segments durations in beats.

argument::repeats
An integer, or teletype::inf::.

Examples::

code::
(
// select a chord and duration and repeat it for a random time interval
p = Pstep(
	Pbind(
		\ctranspose, [0, 4, 7],
		\note,	Pwhite(0, 12),
		\dur,	Prand([0.2, 0.4, 0.8], inf)
	),
	Prand([1, 2, 4], inf)/4
);
Ppar([p, p]).play;
)
// change degree independently of number of events that have been playing

(
Pchain(
	Ppar([
		Pbind(
			\degree, Pbrown(0, 12, 1),
			\dur, Pstep( Pseq([0.1, 0.2, 0.4, 0.8, 1.6], inf), 3.2)
		),
		Pbind(
			\degree, Pbrown(0, 20, 1),
			\dur, Pstep( Pseq([0.1, 0.2, 0.4, 0.8, 1.6], inf), 4.5)
		)
	]),
	Pbind(
		\scale, Pstep(Pseq([ [0, 2, 4, 5, 7, 9, 11], [0, 1, 2, 3, 4, 5, 6]], inf), 5),
		\db, Pstep(Pseq([2, -2, 0, -2], inf), 0.25) - 10
	)
).play;
)

// use a simple pattern
(
Pchain(
	Ppar([
		Pbind(
			\octave, [5, 6] + Prand([0, 0, \r], inf),
			\degree, Prout({ | ev | loop { ev = Pseq(ev[\degree]).embedInStream } }),
			\dur,	Prout({ loop { Pseq([0.2, 0.2, 0.2, 0.2, 0.3].scramble).embedInStream } })
		),
		Pbind(
			\octave, 4,
			\legato, 1.2,
			\dur, Prout({ loop { Pseq([0.2, 0.2, 0.2, 0.2, 0.3].scramble * 5).embedInStream }})
		),
	]),
	Pstep(Pbind(
		\db, Pseq([0, -4, -2, -4, -3, -4, -3, -4], inf) - 20
	), 0.2),
	Pstep(
		Pbind(
			\degree, 	Pfunc({ {10.rand}.dup(10) }),
			\scale,	Pfunc({ {rrand(1, 2)}.dup(7).integrate })
		),
		5
	)
).play
)



// change one parameter
(
Pbind(
	\degree, Pstep(Pseq([1, 2, 3, 4, 5]), 1.0).trace,
	\dur, Pseries(0.1, 0.1, 15)
).play;
)


// change degree independant of number of events that have been playing

(
var a, b;
a = Pbind(
	\degree, Pstep(Pseq([0, 2b, 3], 1), 1.0),
	\dur, Prand([0.2, 0.5, 1.1, 0.25, 0.15], inf)
);
b = Pbind(
	\degree, Pseq([0, 2b, 3], 1),
	\dur, 2,
	\ctranspose, -7
);
Pseq([Event.silent(1.25), Ppar([a, b])], inf).play;
)



// test tempo changes

(
var a, b;
a = Pbind(
	\degree, Pstep(Pseq([0, 2b, 3], 1), 1.0),
	\dur, Prand([0.2, 0.5, 1.1, 0.25, 0.15], 9)
);
b = Pbind(
	\degree, Pseq([0, 2b, 3], 1),
	\dur, 2,
	\ctranspose, -7
);

Ppar([a, b], inf).play;
)


SystemClock.sched(0, { TempoClock.default.tempo = [1, 2, 3, 5].choose.postln; 2 });

TempoClock.default.tempo = 1.0;



// timing test:
// parallel streams


(
var a, b, x;
var times, levels;

SynthDef(\pgrain,
	{ arg out = 0, freq=800, sustain=0.001, amp=0.5, pan = 0;
		var window;
		window = Env.sine(sustain, amp);
		Out.ar(out,
			Pan2.ar(
				SinOsc.ar(freq) * EnvGen.ar(window, doneAction: Done.freeSelf),
				pan
			)
		)
	}
).add;

times = Pseq([3.4, 1, 0.2, 0.2, 0.2], inf);
levels = Pseq([0, 1, 2, 3, 4], inf);

a = Pstep(levels, times);
b = Pbind(\instrument, \pgrain, \octave, 7, \dur, 0.12, \degree, a);
x = times;

Ppar([b, Pset(\mtranspose, 2, b) ]).play;

b.play;
r {
	var z = x.asStream; // direct times
	0.5.wait;
	loop {
		z.next.wait;
		s.makeBundle(0.2, {
			Synth(\pgrain, [\freq, 3000, \sustain, 0.01]); // signal tone
		})
	}
}.play(quant:1)
)
::
