Time-aware merging of two Event Pattern streams

Hi there. I’m trying to merge two Event Pattern streams, using the “structure” from one (e.g. four events with duration of 0.5) and merging the values from another Event Pattern stream (e.g. two events with duration 1, repeated as needed). (the idea of this time-based merging is similar to the way TidalCycles works with different time patterns, where one of them defines the time “structure”, but the others provide their values for the corresponding times that would match (e.g. if they were running in parallel).

Maybe there’s a built-in class or a Quark to help with this (please redirect me, if so!), but I’ve managed to mock up something hacky that seems to work, as a test. It returns the expected values when I call next on it repeatedly (and also prints what I’d expect to see as the yielded values). But if I call nextN or all the events shown are wrong…I must be missing something fundamental with Routines and streams here…

Here’s my exploratory code that “kind of” works:

(
var pStructure = Pbind(\degree, Pseq([1, 2, 3, 4], 1), \dur, 0.5).asStream;
var pOther = Pbind(\instrument, Pseq([\a, \b], inf), \dur, 1).asStream;
r = Routine{ |ev|
	var next;
	var curTime = 0;
	var otherNextTime = 0;
	var otherEvent = pOther.next(ev);
	// "otherEvent: %".format(otherEvent).postln;
	otherNextTime = otherEvent.delta.value;
	while {
		next = pStructure.next(ev);
		// "next: %".format(next).postln;
		next.notNil;
	} {
		// "curTime: % otherNextTime: %".format(curTime, otherNextTime).postln;
		while {
			otherNextTime <= curTime;
		} {
			otherEvent = pOther.next(ev);
			// "otherEvent: %".format(otherEvent).postln;
			otherNextTime = otherNextTime + otherEvent.delta.value;
			// "otherNextTime updated: %".format(otherNextTime).postln;
		};
		otherEvent.putAll(next).postln.yield;
		curTime = curTime + next.delta.value;
		// "curTime: %".format(curTime).postln;
	}
};
r.nextN(10, ());
)

Prints:

( 'instrument': a, 'degree': 1, 'dur': 0.5 )
( 'instrument': a, 'degree': 2, 'dur': 0.5 )
( 'instrument': b, 'degree': 3, 'dur': 0.5 )
( 'instrument': b, 'degree': 4, 'dur': 0.5 )
-> [ ( 'instrument': a, 'degree': 2, 'dur': 0.5 ), ( 'instrument': a, 'degree': 2, 'dur': 0.5 ), ( 'instrument': b, 'degree': 4, 'dur': 0.5 ), ( 'instrument': b, 'degree': 4, 'dur': 0.5 ), nil, nil, nil, nil, nil, nil ]

The printed events from inside the Routine (or if you make four separate calls to next) look correct (degree 1,2,3,4 and instrument a,a,b,b, with duration 0.5), but the array of events returned from nextN (or all) has degree 2,2,4,4 (albeit with correct instrument and duration).

Also, there’s likely a better way to do this…any suggestions are welcome. It’s almost like Pchain-ing two Event patterns, except the way values are handled is different…it needs to be time-aware instead of just taking the next Event from each stream in sequence.

Thanks,
Glen.

Ah, never mind about the return bug…I see now that it’s because the Event itself (otherEvent) is being modified and returned. If I change this line:

		otherEvent.putAll(next).postln.yield;

to

		otherEvent.copy.putAll(next).postln.yield;

it works as expected:

( 'instrument': a, 'degree': 1, 'dur': 0.5 )
( 'instrument': a, 'degree': 2, 'dur': 0.5 )
( 'instrument': b, 'degree': 3, 'dur': 0.5 )
( 'instrument': b, 'degree': 4, 'dur': 0.5 )
-> [ ( 'instrument': a, 'degree': 1, 'dur': 0.5 ), ( 'instrument': a, 'degree': 2, 'dur': 0.5 ), ( 'instrument': b, 'degree': 3, 'dur': 0.5 ), ( 'instrument': b, 'degree': 4, 'dur': 0.5 ), nil, nil, nil, nil, nil, nil ]

But my other question still holds – about whether people know of (or can think of) a better way to do this kind of time-aware event combining.

Thanks.

The standard way to do this is data sharing with parallel Patterns / EventStreamPlayers

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

(
x = Pbind(
	\instrument, Pseq([\a, \b], inf).collect(~i=_), 
	\dur, 1,
	\type, \rest
);

y = Pbind(
	\instrument, Pfunc { ~i }, 
	\degree, Pseq([1, 2, 3, 4], 1), 
	\dur, 0.5
);
)

u = Ptpar([0, x, 0.0001, y]).asStream.nextN(6, ());

u.select { |e| e.type.isNil }

Time-shifting is necessary and can be compensated if wanted. See the discussion on this in a thread a few days ago:

Thanks, yes, I tried something like that, too. The problem is the “main” pattern (y in your example) needs to know about the keys it may be integrating. I wanted something that is more generally composable.

Here’s a modified version (of yours) that works without needing to know the incoming key values…but the other problem with this approach is the x pattern keeps running (producing rests) forever. How can I tell the overall Ptpar to stop running when one of its sub-patterns ends?

(
var savedEvent;
x = Pbind(
	\instrument, Pseq([\a, \b], inf),
	\dur, 1,
	\finish, Pfunc{ |e| savedEvent = e.copy; Rest() }
);

y = Pbind(
	\setup, Pfunc{|e|
		savedEvent.keysValuesDo{|k,v|
			if ([\finish, \dur].includes(k).not) { e.put(k,v) }
		};
		0
	},
	\degree, Pseq([1, 2, 3, 4], 1),
	\dur, 0.5
);
u = Ptpar([0, x, 0.0001, y]);
z = u.asStream;
)
10.do{ z.next(()).postln }

I’ll probably make a new class that takes care of the combining (similar to my original Routine example above), so you can do something like PtimeMerge(basePattern, otherPatterns) to combine the values from multiple streams. That way there’s no problem with knowing when the main stream ends, and also there’s no “collision” with the data sharing between x and y patterns (as there is if you play u several times…when several x streams are writing to the same savedEvent variable, you don’t know what you’ll get).

Thanks again; this discussion is helpful!

I’m not totally sure what you’re looking for, but I just built a Pattern that will do a Pchain style merge of two event streams, where each plays independently and stream A “samples” the current values of stream B (which is running on an entirely different clock). Happy to share this if it’s helpful, with the caveat that it’s only seen limited testing and I may or may not fix things , or just redo it from scratch :slight_smile:.

Sure, thanks @scztt , I’d be happy to take a look at yours – it sounds somewhat similar to what I want. I’ve got a version of what I want working, more or less…with lots of caveats (e.g. doesn’t work properly when doing multiple “merges”, one after another). I’ve never explored the “innards” of an Event Pattern class before… But in the simple cases it seems to work well enough (it’s a start, anyhow :wink: ).

Here’s what I have so far:

PtimeMerge : Pattern {
	var <> structurePattern;
	var <> valuePattern;
	*new { arg structurePattern, valuePattern;
		^super.newCopyArgs(structurePattern, valuePattern);
	}

	embedInStream { arg inVal;
		var structureStream = structurePattern.asStream;
		var valueStream = valuePattern.asStream;
		var inEvent = inVal.copy;
		var cleanup = EventStreamCleanup.new;
		var curTime = 0;
		var nextValueTime = 0;
		var otherEvent = valueStream.next(inEvent);
		// otherEvent.debug("otherEvent");
		nextValueTime = otherEvent.delta.value;
		loop {
			var structEvent = structureStream.next(inEvent);
			if (structEvent.isNil) { ^cleanup.exit(inVal) };

			// "curTime: % nextValueTime: %".format(curTime, nextValueTime).postln;
			while {
				nextValueTime <= (curTime + 0.0001);
			} {
				otherEvent = valueStream.next(inEvent);
				// otherEvent.debug("otherEvent");
				nextValueTime = nextValueTime + otherEvent.delta.value;
				// nextValueTime.debug("nextValueTime updated");
			};
			structEvent = otherEvent.copy.putAll(structEvent).postln;
			cleanup.update(structEvent);
			inVal = yield(structEvent);
			curTime = curTime + structEvent.delta.value;
			// curTime.debug("curTime");
		};
	}
}

+Pattern {

	<< { arg aPattern;
		// time-based pattern key merging
		^PtimeMerge(this, aPattern)
	}

}

Example usage:

a = Pbind(\degree, Pseq([1,2,3,4], 2), \dur, 0.5);
b = Pbind(\instrument, Pseq([\a,\b,\default], inf), \dur, Pseq([2,0.5,1.5], inf));
(a << b).play

// ( 'instrument': a, 'degree': 1, 'dur': 0.5 )
// ( 'instrument': a, 'degree': 2, 'dur': 0.5 )
// ( 'instrument': a, 'degree': 3, 'dur': 0.5 )
// ( 'instrument': a, 'degree': 4, 'dur': 0.5 )
// ( 'instrument': b, 'degree': 1, 'dur': 0.5 )
// ( 'instrument': default, 'degree': 2, 'dur': 0.5 )
// ( 'instrument': default, 'degree': 3, 'dur': 0.5 )
// ( 'instrument': default, 'degree': 4, 'dur': 0.5 )
1 Like

I haven’t looked into your implementation in detail but it seems to me that this could be a valuable extension!

Here’s my implementation: https://gist.github.com/scztt/b4a9fc59abc06780ba7b40c4b7dc09e9

For something like PparChain(a, b, c), each stream runs independently in time. The most recently yielded value from the downstream pattern is used as a proto when next is called. So, a.next(mostRecentValue(b)) and b.next(mostRecentValues(c)). The \delta values of the first pattern (a) deteremine the ultimate timing for the resulting stream.

I think ultimately this should work more like Ppar, where a single stream is responsible for calling next on all the chained streams at the right times - I can’t recall if I ran into problems with this approach, or just played them separately because it was easier.

Thanks, Scott, but I wasn’t able to get this to work as I’d expected. If I do this:

a = Pbind(\degree, Pseq([1,2,3,4], 2), \dur, 0.5);
b = Pbind(\instrument, Pseq([\a,\b], inf), \dur, 1);
PparChain(a,b).trace.play;

I get:

( 'dur': Rest(0), 'delta': 0 )
( 'instrument': a, 'degree': 1, 'dur': 0.5 )
( 'instrument': b, 'degree': 2, 'dur': 0.5 )
( 'instrument': b, 'degree': 3, 'dur': 0.5 )
( 'instrument': a, 'degree': 4, 'dur': 0.5 )
( 'instrument': a, 'degree': 1, 'dur': 0.5 )
( 'instrument': b, 'degree': 2, 'dur': 0.5 )
( 'instrument': b, 'degree': 3, 'dur': 0.5 )
( 'instrument': a, 'degree': 4, 'dur': 0.5 )
( 'instrument': a, 'degree': 4, 'dur': 0.5 )
... // (and this last Event keeps repeating forever)

Which is kind of close, but not what I’d expect. Instead, it should be repeating a,a,b,b as it runs twice through the degrees 1,2,3,4… And it should actually stop after running through the original pattern to its end.

// Expected:
( 'instrument': a, 'degree': 1, 'dur': 0.5 )
( 'instrument': a, 'degree': 2, 'dur': 0.5 )
( 'instrument': b, 'degree': 3, 'dur': 0.5 )
( 'instrument': b, 'degree': 4, 'dur': 0.5 )
( 'instrument': a, 'degree': 1, 'dur': 0.5 )
( 'instrument': a, 'degree': 2, 'dur': 0.5 )
( 'instrument': b, 'degree': 3, 'dur': 0.5 )
( 'instrument': b, 'degree': 4, 'dur': 0.5 )

(current output from my version: SuperCollider Event pattern similar to Pchain, except it merges "value events" from the right-hand patterns with the values and time structure of the first pattern. · GitHub)

The other disadvantage of actually playing the streams on a clock is you can’t run it in non-realtime. With yours, if you do:

PparChain(a,b).asStream.nextN(12, ())

You just get a long stream of instrument ‘a’ and degree ‘1’ forever.

Thanks, though – I’ll look through your embedInStream code… for sure I can learn something from it!

In case anyone’s interested, I’ve got a version working that is relatively stable and complete.

Here’s an example of its use:

	test_embedThreeWay {
		var degs = [0, 2, 4, 6, 8, 10];
		var noteDur = degs.size.reciprocal;
		// a = degree:     | 0  2  4  6  8  10 |
		// b = instrument: | a     b     c     |
		// c = pan:        |-1        1        |
		var a = Pbind(\degree, Pseq(degs, 1), \dur, noteDur);
		var b = Pbind(\instrument, Pseq([\a,\b,\c], inf), \dur, 3.reciprocal);
		var c = Pbind(\pan, Pseq([-1, 1], inf), \dur, 2.reciprocal);
		// Can also be written (a << b << c)
		var results = PtimeChain(a, b, c).asStream.nextN(degs.size+1, ());
		this.assert(results == [
			(degree: 0, dur: noteDur, instrument: \a, pan: -1),
			(degree: 2, dur: noteDur, instrument: \a, pan: -1),
			(degree: 4, dur: noteDur, instrument: \b, pan: -1),
			(degree: 6, dur: noteDur, instrument: \b, pan: 1),
			(degree: 8, dur: noteDur, instrument: \c, pan: 1),
			(degree: 10, dur: noteDur, instrument: \c, pan: 1),
			nil
		]);
	}

Again, this is inspired by the way TidalCycles uses one (the left-most) pattern to provide values and structure, and the rest just to provide values (but the ones that match at the correct time from the “structure” pattern). This is distinct from Pchain, which always pulls a new event from each pattern. PtimeChain will hold the “current” right-hand values until the required time comes along.

Given the above example:

       time:      0                  1
a = degree:     | 0  2  4  6  8  10 |
b = instrument: | a     b     c     |
c = pan:        |-1        1        |

Chaining in that order gives six events (with duration 1/6 in my example). But if you change the order of “time-chaining”, because the left-hand side is used for “structure”, you get different number of events. For example:

b << a << c

would give just three events: (0,a,-1), (4,b,-1) and (8,c,1) with duration 1/3.

c << a << b

would give just two: (0,a,-1) and (6,b,1) with duration 1/2.

3 Likes

You can get close with Pstep. I think it can be used to achieve a similar result but the implementation differs somehow in it its dur calculation. The first snippet gets it right but the other two are a bit off. If you nudge the dur value you can get the desired result.

(
var degs = [0, 2, 4, 6, 8, 10];
var noteDur = degs.size.reciprocal;
Pbind(
	\degree, Pseq(degs, 1),
	\dur, noteDur,
	\instrument, Pstep(Pseq([\a,\b,\c], inf), 3.reciprocal),
	\pan, Pstep(Pseq([-1, 1], inf), 2.reciprocal)
).trace.play;
)

(
var degs = [0, 2, 4, 6, 8, 10];
var noteDur = degs.size.reciprocal;
Pbind(
	\degree, Pstep(Pseq(degs, 1), noteDur),
	\dur, 3.reciprocal, //0.334 produces the desired result
	\instrument, Pseq([\a,\b,\c], 1),
	\pan, Pstep(Pseq([-1, 1], inf), 2.reciprocal)
).trace.play
)

(
var degs = [0, 2, 4, 6, 8, 10];
var noteDur = degs.size.reciprocal;
Pbind(
	\degree, Pstep(Pseq(degs, 1), noteDur),
	\dur, 2.reciprocal, // 0.51 produces desired result
	\instrument, Pstep(Pseq([\a,\b,\c], inf), 3.reciprocal),
	\pan, Pseq([-1, 1], 1)
).trace.play;
)

Hey, just revisiting this a while later. I ended up using your implementation vs mine, as it handles some of the edge cases a bit better, and the implementation is a little more straightforward. I made one change that you might be interested in:

nextValueEvent = valueStream.next(inevent);

becomes:

nextValueEvent = valueStream.next(cumulativeEvent.copy);

This allows downstream patterns to compose all the way up through the chain:

PtimeChain(
	Pbind(
		\c, Pkey(\a) + Pkey(\b)
	),
	Pbind(\a, Pseries(0, 0.1), \dur, 1),
	Pbind(\b, Pseries(0, 2), \dur, 2)
).asStream.nextN(10, ()).do(_.postln)
1 Like

Thanks Scott. But what should your example do? My current implementation does what I would expect for that case… Here’s what mine (without your cumulativeEvent.copy change) produces:

( 'c': 0, 'a': 0, 'b': 0, 'dur': 1 )
( 'c': 0.1, 'a': 0.1, 'b': 0, 'dur': 1 )
( 'c': 2.2, 'a': 0.2, 'b': 2, 'dur': 1 )
( 'c': 2.3, 'a': 0.3, 'b': 2, 'dur': 1 )
( 'c': 4.4, 'a': 0.4, 'b': 4, 'dur': 1 )
( 'c': 4.5, 'a': 0.5, 'b': 4, 'dur': 1 )
( 'c': 6.6, 'a': 0.6, 'b': 6, 'dur': 1 )
( 'c': 6.7, 'a': 0.7, 'b': 6, 'dur': 1 )
( 'c': 8.8, 'a': 0.8, 'b': 8, 'dur': 1 )
( 'c': 8.9, 'a': 0.9, 'b': 8, 'dur': 1 )

With your suggested change, I get failures for several of my unit tests… For example, the following would no longer work as I expected with your fix:

	var degs = [0, 2, 4, 6, 8, 10];
	var noteDur = degs.size.reciprocal;
	// degree:     | 0  2  4  6  8  10 |
	// instrument: | a     b     c     |
	// pan:        |-1        1        |
	var a = Pbind(\degree, Pseq(degs, 1), \dur, noteDur);
	var b = Pbind(\instrument, Pseq([\ia,\ib,\ic], 1), \dur, 3.reciprocal);
	var c = Pbind(\pan, Pseq([-1, 1], 1), \dur, 2.reciprocal);
	var results = PtimeChain(a, b, c).asStream.nextN(degs.size+1, ());
	this.assertEquals(results, [
		(degree: 0, dur: noteDur, instrument: \ia, pan: -1),
		(degree: 2, dur: noteDur, instrument: \ia, pan: -1),
		(degree: 4, dur: noteDur, instrument: \ib, pan: -1),
		(degree: 6, dur: noteDur, instrument: \ib, pan: 1),
		(degree: 8, dur: noteDur, instrument: \ic, pan: 1),
		(degree: 10, dur: noteDur, instrument: \ic, pan: 1),
		nil
	], "a << b << c");

With your change, the fourth Event (the one with degree 6) has an incorrect pan of -1 (should be 1). That is, it’s “incorrect” from the way I was wanting the class to behave… it’s possible you expect something different. I want all values to be taken from the times of events in the left-most pattern, and all “upstream” patterns (to the right of that) should return their corresponding values at the time of producing the downstream Event. This is trying to reproduce/simulate the way TidalCycles patterns combine (although in that environment they are not event-based…instead they are functions of time, which makes it “easier” to solve ;-).

Thanks for pointing me to Pstep, you’re right that it gets fairly close to the intended functionality. But besides the time nudging issues, like @scztt’s original version, it requires you to actually play it, you can’t just get a stream and pull values from it – it requires the thread clock to advance.

You’re right - this change introduces a subtle bug where values from newly pulled events get overwritten during composition if there are older events (and thus older values) in streams to the left. I tried this change so event streams to the left would receive the event as it is currently being composed. This enables progressively modifying values, e.g.:

PTimeChain (
      Pbind(\dur, 1),   // a
      Pbind(\dur, 1/2, \pan, Pseq([0, Pkey(\pan)], inf)),  // b
      Pbind(\dur, 1/3, \pan, Pseq([-1, 1], inf) // c
)

This produces a bit of a chicken-and-egg problem: if we compose with “old” version of b, then we overwrite a potentially newer version of the value produced by c. In the above expression, this is strictly-speaking correct, since we’ve explictly set the \pan value - but this overwrite also happens if we did NOT set the pan value in our Pbind at all - since it was mixed in from the stream to the right.

I think a more correct solution here might be:

if (inevent !== cumulativeEvent) {
    inevent.parent_(cumulativeEvent);
};
nextValueEvent = valueStream.next(inevent);

With this, left-wards streams can “look-up” values in the accumulating event, but those values are not mixed in to the result unless they’re explicitly set. I believe the broken test is fixed with this change.


There’s another issue that’s not so straightforward to solve as well: we don’t set the correct logical clock time when we execute streams in our chain, so any stream that has an absolute clock time dependency (Pseg, Pstep, and the like) will produce incorrect values. You pointed to this exact problem in your last post.

I was able to get these cases nominally working by setting the current thread time to the correct logical time, nextValueTime, before nexting (and tracking things like endBeat for each sub-stream as well).
This works in a few limited cases I’ve tested, though I definitely feel nervous about it…

Unfortunately, it introduces the problem that you pointed out: with this change, it’s no longer possible to nextN a PtimeChain stream. This, for me, is an inherent limitation with nextN and event streams: it’s simply incorrect to pull values from an Event stream without also updating the time based on Event:delta… This has bit me on the nose so many times over the years, that I’ll almost always just wrap an Event stream in an outer Routine that handles the equivalent time updating logic that EventStreamPlayer does for regular playback.

1 Like

Since I didn’t see it mentioned, despite the long discussion, Pfpar (that comes with and is used by Pproto) actually has this behavior.

More on the main topic here, I’m considering doing something like (symbolically) cued/dependant events, which would separate the notion/concern of timing for the dependent event themselves into a different domain. As a very basic use case, I have a different piece of code that fires some envelopes on one [or more] instrument[s] but simultaneously needs to \set various params on a long running instrument “in sync” with the envelopes fired. At least in that case it makes little sense to struggle with two explicit notions of time and their merging. More generally however, I can see that you’d want to first test e.g. some kick sequence but then e.g. cue it to a bass melody. That would need a way to auto-generate the symbolic cues (with my approach).

My basic idea is to exploit that in an Event callback you can play another event, and these basically happn in parallel, e.g.

 // one off
(dur: 1, pan: -1, callback: { (dur: 0.01, pan: 1, freq: 550).play } ).play

// on in a pattern
p = Pbind(*[dur: 1, pan: -1, callback: {(dur: 0.01, pan: 1, freq: 550).play}]).play

So you can stick the dependant/cued event in the callback that way. There is some potential trouble (esp. in NRT) as the callback-ed events don’t actually get played by the EventStreamPlayer fired by the Pbind… So even something like stop (sent to the ESP) won’t get sent to the dependant events with this simple approach. You’d need a more featureful ESP derivative that would handle these, and probably use a different key for them, not callback. (There’s a new, related feature in 3.11 with type: \composite but it can’t fire several sub-events of the same type alas. There’s no such limitation with a callback.)

By the way, beware of debugging such function-valued keys in patterns with trace of Ptrace as it will [also] eval function-valued fields when you don’t expect… i.e. at trace time. (It’s even more of a pain with cleanups, as Ptrace will fire them prematurely.)

Thanks, Scott. I tested this version and it doesn’t break any of my existing unit tests… (-; I hadn’t run across a need for this so far (I don’t normally do much with dependent parameters in Events), but probably I’ll add this to my version.

I would still like to get PtimeChain working with Ppar (using delta rather than dur), but this was too big a can of worms for me, last time I tried. I think I’d need to split the nextValueTime to be per parallel branch, and this could happen anywhere up or downstream in the chain of patterns…quite an explosion of complexity and too much to be worth it for me for now. At worst, if needed you can always work around it like this (duplicating/repeating the upstream patterns):

// Assume a1, a2, b, c, d are Event patterns (Pbinds, potentially with a mix of different event durations)

// Instead of this (which would be nice to support):
PtimeChain(Ppar([a1, a2]), b, c, d)

// You can always do:
e = PtimeChain(b, c, d);
Ppar([PtimeChain(a1, e), PtimeChain(a2, e)])

Thanks,
Glen.

Thanks @RFluff for the info about Pfpar, that’s interesting (and undocumented ;-).

The other funny thing – I didn’t read your message until last night, but I happened to run into exactly the same confusion you mention with Ptrace – I was getting very strange/unexpected results when debugging a pattern, and it turned out it was because pat.trace was evaluating/completing parameters in the events too soon (when doing my “time chaining”), and this led to a lot of confusion! Thanks – this is indeed something to watch out for, when doing these “strange” things with Event evaluation.

Partially so. The Pproto help says

The patternarray is played using Pfpar, a variant of Ppar that ends when any of its subpatterns end. In this way, you can use Pproto to create effects that can be controlled by a pattern that runs in parallel with the note generating pattern and ends together with that note generating pattern (see example 0 [should be 4] below).

In the example (4) given, the effects (actually changes to effects) aren’t really time-synchronized (except by dur coincidences) with the main tune/instrument.