Skip to content

Instantly share code, notes, and snippets.

@scztt
Last active November 2, 2021 20:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save scztt/87bf6542e3cd60844113fd201258a82a to your computer and use it in GitHub Desktop.
Save scztt/87bf6542e3cd60844113fd201258a82a to your computer and use it in GitHub Desktop.
// Play many patterns according to their own delta's', composing the events from bottom to top.
// Deltas of first pattern are responsible for timing of output events
//
// (
// Pdef(\ptchain).clear;
// Pdef(\ptchain, PTChain(
// Pbind(
// \dur, Pseg([1/32, 1/2, 1/32], [16, 16], \exponential, inf),
// \velocity, Pwhite(40, 127),
// \strum, Pkey(\dur) / 2
// ),
// Pbind(
// \degree, Ptuple([Pkey(\degreeA), Pkey(\degreeB)])
// ),
// Pbind(
// \dur, 1/3,
// \degreeA, Ptuple([
// Pseq([0, 2, 6, 8, 5], inf),
// ])
// ),
// Pbind(
// \dur, 2,
// \degreeB, Ptuple([
// 12 + Pseq([0, -6], inf),
// ])
// ),
// )).play
// )
PTChain : Pattern {
var <>patterns;
*new { arg ... patterns;
^super.newCopyArgs(patterns);
}
*durs { arg durs ... patterns;
^super.newCopyArgs(
[Pbind(\dur, durs)] ++ patterns
);
}
<< { arg aPattern;
var list;
list = patterns.copy.add(aPattern);
^this.class.new(*list)
}
embedInStream { arg inval;
var structureStream = patterns[0].asStream;
var startTime = thisThread.beats;
// Store the value streams, their current time and latest Events
var valueStreams = patterns[1..].collect{ |p| [p.asStream, (), ThreadStateScope() ] };
var inevent, cleanup = EventStreamCleanup.new;
var timeEpsilon = 0.0001;
loop {
var structureEvent;
var cumulativeEvent = inevent = inval.copy;
// inevent.debug("inevent at start of loop");
valueStreams.reverseDo { |strData, i|
var valueStream, nextValueEvent, threadState;
#valueStream, nextValueEvent, threadState = strData;
while {
// "stream: % current time: % stream time: % timeToCheck: %".format(
// valueStream,
// thisThread.beats - startTime,
// threadState.beats - startTime,
// (thisThread.beats - startTime) + timeEpsilon
// ).debug;
threadState.beats <= (thisThread.beats + timeEpsilon);
} {
var delta;
threadState.use {
if (inevent !== cumulativeEvent) {
inevent.parent_(cumulativeEvent);
};
nextValueEvent = valueStream.next(inevent);
// "\tpulled new value: %".format(nextValueEvent).postln;
};
// nextValueEvent.debug("nextValueEvent");
// Q: Should we exit for value streams that end, or just the structure stream?
// A: Will have to look at concrete examples, for now: yes, we exit when
// any of the streams ends...
if (nextValueEvent.isNil) { ^cleanup.exit(inval) };
delta = nextValueEvent.delta.value;
if (delta.notNil) {
threadState.beats = threadState.beats + delta;
} {
// There is no time information, just use our next value
// for the next structure Event (as regular Pchain would do)
threadState.beats = threadState.beats + (timeEpsilon * 2);
};
// nextValueTime.debug("nextValueTime updated");
// inevent feeds from one into the next, gathering/replacing values
strData[1] = nextValueEvent;
};
// Combine the contributions of all the "current" value events
// that came before the main structure event.
cumulativeEvent = cumulativeEvent.composeEvents(nextValueEvent);
// cumulativeEvent.debug("updated cumulativeEvent");
};
structureEvent = structureStream.next(cumulativeEvent);
if (structureEvent.isNil) { ^cleanup.exit(inval) };
cleanup.update(structureEvent);
// structureEvent.debug("yielded structureEvent");
inval = yield(structureEvent);
// structureTime.debug("structureTime");
};
}
storeOn { arg stream;
stream << "(";
patterns.do { |item,i| if(i != 0) { stream << " <> " }; stream <<< item; };
stream << ")"
}
}
+Pattern {
<< { arg aPattern;
// time-based pattern key merging
^PTChain(this, aPattern)
}
}
ThreadStateScope {
var beats, endBeat;
*new {
^super.newCopyArgs(thisThread.beats, thisThread.endBeat);
}
use {
|function|
var oldValues;
if (beats.isNil) { beats = thisThread.beats };
if (endBeat.isNil) { endBeat = thisThread.endBeat };
protect {
oldValues = [thisThread.beats, thisThread.endBeat];
thisThread.beats = beats;
thisThread.endBeat = endBeat;
function.()
} {
this.beats = thisThread.beats;
endBeat = thisThread.endBeat;
thisThread.beats = oldValues[0];
thisThread.endBeat = oldValues[1];
}
}
beats_{
|value|
if (value.isNil) {
"setting beats to nil... weird".postln;
};
beats = value;
}
beats {
^(beats ?? { 0 })
}
endBeat {
^(endBeat ?? { 0 })
}
}
@vncntmchlk
Copy link

Thanks for this very useful class! If i exchange the \dur values of the last two Pbinds in the example the \degreeB never changes. Maybe i misunderstood how it is supposed to work, or maybe it is a small bug, i will try to figure out the code.
Here is the example that doesn't work like i had expected:

(
Pdef(\ptchain).clear;
Pdef(\ptchain, PTChain(
	Pbind(
		\dur, Pseg([1/32, 1/2, 1/32], [16, 16], \exponential, inf),
		\velocity, Pwhite(40, 127),
		\strum, Pkey(\dur) / 2
	),
	Pbind(
		\degree, Ptuple([Pkey(\degreeA), Pkey(\degreeB)])
	),
	Pbind(
		\dur, 2,
		\degreeA, Ptuple([
			Pseq([0, 2, 6, 8, 5], inf),
		])
	),
	Pbind(
		\dur, 1/3,
		\degreeB, Ptuple([
			12 + Pseq([0, -6], inf),
		])
	),
)).play
)

@scztt
Copy link
Author

scztt commented Nov 2, 2021

I believe this behavior is correct. One subtlety here is that the second Pbind has an implicit \dur of 1. You can more clearly see what's going on by adding some traces:

(
Pdef(\ptchain).clear;
Pdef(\ptchain, PTChain(
	Pbind(
		\dur, Pseg([1/32, 1/2, 1/32], [16, 16], \exponential, inf),
		\velocity, Pwhite(40, 127),
		\strum, Pkey(\dur) / 2
	),
	Pbind(
		\degree, Ptuple([Pkey(\degreeA), Pkey(\degreeB)]).trace(prefix:"Ptuple")
	),
	Pbind(
		\dur, 2,
		\degreeA, Ptuple([
			Pseq([0, 2, 6, 8, 5], inf),
		]).trace(prefix:"A")
	),
	Pbind(
		\dur, 1/3,
		\degreeB, Ptuple([
			12 + Pseq([0, -6], inf),
		]).trace(prefix:"B")
	),
)).play
)

@vncntmchlk
Copy link

Oh right, now i see what's going on. Thanks for the clarifying :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment