Linking keys - patterns


#1

Hi

I’m sure I’ve asked this before, but could someone tell me how to link key values from one pattern to another - in this case I want to use the \dur from one pattern to control the others.

I’m not sure if you want a solid example, but simply put I’d like this to work:

(
a = Pbind(
		\instrument, \default,
	\dur, Prand([1, 0.5], inf),
		\degree, 1,
);
)

(
b = Pbind(
\instrument, \default,
		\dur, Pkey(\dur),
		\degree, 3,
);
)

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

Now as long as I use a straight Pseq, or other, everything is fine, however, the important part (as you may have noticed) is the Prand. How can I get pattern ‘b’ to use exactly the same random stream coming from ‘a’? I’ve tried various ideas with Pchain, Pseed thinking maybe the seed might help, but as yet haven’t managed to get a result.

Any suggestions?

Thanks in advance - joesh

p.s. I’m often using Pdef, with the Pbind inside. So if someone has a better idea using Pdef, Pbindef etc …


#2

Not with Pdef, but this works:

a = Pbind(
	\dur, Prand([1, 0.5], inf),
	\degree, 1,
).collect({ |event|
    ~last_a_event = event;
});

b = Pbind(
    \aDur, Pfunc { ~last_a_event[\dur] },
    \dur, Pkey(\aDur),
	\degree, 3,
);

Ppar([a,b],4).play

Maybe it could be done with Pdefn()?


#3

Aha, that looks better. In fact I’d been looking at a similar solution (which I hadn’t tried yet) using Pfunc in a tutorial. As for Pdefn I’ve been playing with that, but not getting the results I needed.

Big thanks.


#4

I’ve run into similar requirements before, was never able to really figure out how to solve it… The real requirement is something like this: for an arbitrary number of parallel patterns (in a Ppar, presumably), use the same \dur value (or any value, for that matter…) for all events that occur at the same time.

Because linked \dur's means future events will always occur at the same time, they should stay in sync forever (barring e.g. direct overriding of the \delta key or somesuch). With that formulation in mind, I was able to get something like this working:

(
Pdefn(\durs, 
	Pclutch(
		Prand([1, 2, 3, 4, 5], inf) / 5,
		Pfunc({ 
			var sameTime = (~lastTime == thisThread.beats);
			~lastTime = thisThread.beats;
			sameTime.not.postln;
		})
	).trace
);

Pdef(\notes, 
	Ppar([
		Pbind(\degree, Pseq([1, 3, 5, 7], 2)),
		Pbind(\degree, Pseq([3, 0, 1, 5], 2)),
	]) <> Pbind(\dur, Pdefn(\durs).trace)
).play

The nice thing about this: you can specify the “do next() only once for all requests at the same time” feature at the lowest level (e.g. in your pattern definition), and it can be freely used regardless of what the larger structure looks like. This would look nicer if it was abstracted into a class - off the top of my head, something like:

// Only pull a value once per clock time - else, return the previous value
PtimeClutch {
	var pattern;
	*new {
		|pattern|
		^super.newCopyArgs(pat);
	}
	
	embedInStream {
		|input|
		var lastTime, lastVal;
		var stream = pat.asStream;
		
		loop {
			var thisTime = thisThread.beats;
			
			if (thisTime != lastTime) {
				lastVal = stream.next(input);
				input = lastVal.copy.yield;
			};

			input = lastVal.copy.yield;
			
			lastTime = thisTime;
		}
	}
}

+Pattern {
	timeClutch {
		^PtimeClutch(this)
	}
}

With that, I think you get something like:

Pdef(\dur, Pbind(\dur, Prand([1, 2, 3, 4], inf).timeClutch))

or even:

Ppar([a, b, c]) <> Pdef(\dur).timeClutch

#5

Firstly Scott, thanks for the idea.

Secondly, I’m glad I’m not the only one who is battling with such a simple (in my understanding) concept. After all it wouldn’t at first sight seem something difficult, and you could almost say a basic task which should have been solved years ago.

Anyhow, I’m going to look into your suggestions as I’d forgotten Pclutch, but had seen Pfunc. It looks as though everything is possible, but in a sort of round-about way. Maybe in the future there could be a Pattern method that could facilitate such a requirement.

Big thanks.


#6

You can poll from a common Stream:

(
d = Pstutter(2, Prand([1, 0.5], inf)).iter;

a = Pbind(\dur, Pfunc { d.next });
b = Pbind(\degree, 3) <> a;

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

Alternatively you could check my PSx stream pattern classes, two Streams polled from one PS refer to one underlying Stream:

(
d = PS(Pstutter(2, Prand([1, 0.5], inf)));

a = Pbind(\dur, d);
b = Pbind(\degree, 3) <> a;
Ppar([a, b]).play;
)

#7

Another option from PSx is PSdup. To ensure order I’d take Ptpar in this case:

a = Pbind(\dur, d = PS(Prand([0.5, 1], inf)));
b = Pbind(\dur, PSdup(d), \degree, 3);
Ptpar([0, a, 0.001, b]).play

#8

http://doc.sccode.org/Tutorials/A-Practical-Guide/PG_06g_Data_Sharing.html

BTW, PSDup looks very nice and renders some of the help document unnecessary.

After all it wouldn’t at first sight seem something difficult, and you could almost say a basic task which should have been solved years ago.

What this really boils down to (I heard James McCartney talk about this once) is the difference between a “push” model of calculation vs a “pull” model.

Pure Data and Max follow a push model – a “bang” at the top pushes values down the chain. If the “bang” triggers a random number generator, and that is feeding into multiple other inputs, then it’s the same value going forward to all of them.

Patterns follow a pull model – you “pull” a value from the outermost stream, and will pull values from any streams (made from patterns) contained within it.

  • Patterns are, as a design decision, stateless – meaning that multiple uses of the same pattern object will be treated as independent. Internally, each occurrence of the same pattern object makes an independent stream.

  • Even if it didn’t make an independent stream, each occurrence would pull from the stream once per stream, not per cycle. (For that matter, the stream doesn’t know how many times it’s going to be called within each cycle.)

This is good in a lot of ways – being sure patterns won’t interfere with each others’ internals – but, like every design decision, makes other things harder.

The problem here is that you want your \dur pattern to behave like a “push,” but patterns fundamentally don’t work that way. So, “you could almost say a basic task which should have been solved years ago” but it won’t be, not without fundamentally changing the nature of patterns and breaking everybody’s usage.

miSCellaneous_lib seems to have a good solution for this: an object (PS) that has characteristics of both patterns and streams, and which can be used (PSDup) in a push-style way.

hjh


#9

Thanks James and Daniel, you’ve more than answered the question, and I now understand the problem/situation. I’ll have a look through Daniels PS in miSCellaneous_lib, especially the idea PSdup sounds good!

Thanks again - joesh


#10

Here’s a more proper implementation of my time clutch sketch.

And, you’re right about it being a seemingly obvious problem for no one to have solved it before. I’ve run into this a lot - I always solve it with a variation on what @dkmayer suggested: tack a .stutter(n) onto my \dur pattern, where n is the number of things in my Ppar. This is fine, it’s just not so general - it feels like a hack.