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 )

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: https://gist.github.com/totalgee/caf219b34b5bd54de1089c349c768b8e)

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.

2 Likes