Skip to content

Instantly share code, notes, and snippets.

@totalgee
Last active April 29, 2020 02:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save totalgee/caf219b34b5bd54de1089c349c768b8e to your computer and use it in GitHub Desktop.
Save totalgee/caf219b34b5bd54de1089c349c768b8e to your computer and use it in GitHub Desktop.
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.
PtimeChain : Pattern {
var <>patterns;
*new { arg ... patterns;
^super.newCopyArgs(patterns);
}
<< { arg aPattern;
var list;
list = patterns.copy.add(aPattern);
^this.class.new(*list)
}
embedInStream { arg inval;
var structureStream = patterns[0].asStream;
// Store the value streams, their current time and latest Events
var valueStreams = patterns[1..].collect{ |p| [p.asStream, 0, ()] };
var inevent, cleanup = EventStreamCleanup.new;
var structureTime = 0;
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, nextValueTime, nextValueEvent;
#valueStream, nextValueTime, nextValueEvent = strData;
// [i, nextValueTime, nextValueEvent].debug("next time/Event");
while {
nextValueTime <= (structureTime + timeEpsilon);
} {
var delta;
nextValueEvent = valueStream.next(inevent);
// 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) {
nextValueTime = nextValueTime + delta;
} {
// There is no time information, just use our next value
// for the next structure Event (as regular Pchain would do)
nextValueTime = structureTime + (timeEpsilon * 2);
};
// nextValueTime.debug("nextValueTime updated");
// Store the values for our next iteration
strData[1] = nextValueTime;
// inevent feeds from one into the next, gathering/replacing values
strData[2] = 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 = structureTime + structureEvent.delta.value;
// 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
^PtimeChain(this, aPattern)
}
}
TestPtimeChain : UnitTest {
setup {
// called before each test
}
tearDown {
// called after each test
}
test_embedSimple {
var a = Pbind(\degree, Pseq((1..4), 2), \dur, 0.5);
var b = Pbind(\instrument, Pseq([\a, \b], inf), \dur, 1);
var results = PtimeChain(a, b).asStream.nextN(9, ());
var desired = Pbind(\degree, Pseq((1..4), 2), \dur, 0.5,
\instrument, Pseq([\a, \a, \b, \b], inf)).asStream.nextN(9, ());
this.assert(results == [
(degree: 1, dur: 0.5, instrument: \a),
(degree: 2, dur: 0.5, instrument: \a),
(degree: 3, dur: 0.5, instrument: \b),
(degree: 4, dur: 0.5, instrument: \b),
(degree: 1, dur: 0.5, instrument: \a),
(degree: 2, dur: 0.5, instrument: \a),
(degree: 3, dur: 0.5, instrument: \b),
(degree: 4, dur: 0.5, instrument: \b),
nil
], "literal array of Events");
this.assert(results == desired, "equivalent Pbind");
}
test_embedDifferentDurs {
var a = Pbind(\degree, Pseq((1..4), 2), \dur, 0.5);
var b = Pbind(\instrument, Pseq([\a,\b,\default], inf), \dur, Pseq([2,0.5,1.5], inf));
var results = PtimeChain(a, b).asStream.nextN(9, ());
var expected = [
(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: \a),
(degree: 1, dur: 0.5, instrument: \b),
(degree: 2, dur: 0.5, instrument: \default),
(degree: 3, dur: 0.5, instrument: \default),
(degree: 4, dur: 0.5, instrument: \default),
nil
];
this.assert(results == expected, "literal array of Events");
results = (a << b).asStream.nextN(9, ());
this.assert(results == expected, "using << operator");
}
test_embedThreeWay {
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([\a,\b,\c], inf), \dur, 3.reciprocal);
var c = Pbind(\pan, Pseq([-1, 1], inf), \dur, 2.reciprocal);
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
], "a << b << c");
results = (b << a << c).asStream.all(());
noteDur = 3.reciprocal;
this.assert(results == [
(degree: 0, dur: noteDur, instrument: \a, pan: -1),
(degree: 4, dur: noteDur, instrument: \b, pan: -1),
(degree: 8, dur: noteDur, instrument: \c, pan: 1)
], "b << a << c");
this.assert(results == (b << c << a).asStream.all(()), "...same as b << c << a");
results = (c << a << b).asStream.all(());
noteDur = 2.reciprocal;
this.assert(results == [
(degree: 0, dur: noteDur, instrument: \a, pan: -1),
(degree: 6, dur: noteDur, instrument: \b, pan: 1)
], "c << a << b");
this.assert(results == (c << b << a).asStream.all(()), "...same as c << b << a");
}
test_timelessStream {
// Event streams without duration/delta
var degs = [0, 2, 4, 6, 8, 10];
var noteDur = degs.size.reciprocal;
var a = Pbind(\degree, Pseq(degs, 1), \dur, noteDur);
var b = Pbind(\pan, Pseq([-1, 0, 1], inf)); // no \dur keys
var results = PtimeChain(a, b).asStream.nextN(degs.size+1, ());
this.assert(results == [
(degree: 0, dur: noteDur, pan: -1),
(degree: 2, dur: noteDur, pan: 0),
(degree: 4, dur: noteDur, pan: 1),
(degree: 6, dur: noteDur, pan: -1),
(degree: 8, dur: noteDur, pan: 0),
(degree: 10, dur: noteDur, pan: 1),
nil
], "Pseq");
b = Pbind(\db, Pseed(5, Pwhite(-24, 0)));
results = PtimeChain(a, b).asStream.nextN(degs.size+1, ());
this.assert(results == [
(degree: 0, dur: noteDur, db: -21),
(degree: 2, dur: noteDur, db: -20),
(degree: 4, dur: noteDur, db: -3),
(degree: 6, dur: noteDur, db: -20),
(degree: 8, dur: noteDur, db: -10),
(degree: 10, dur: noteDur, db: -23),
nil
], "Pwhite");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment