Last active
April 29, 2020 02:37
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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